diff --git a/.gitignore b/.gitignore index a41f66b9..4a7ff0af 100644 --- a/.gitignore +++ b/.gitignore @@ -100,4 +100,6 @@ typings/ .store # Ignore OSX crap-files -*.DS_Store \ No newline at end of file +*.DS_Store +app/dist/ +dist/ diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..790c3e05 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "jest.rootPath": "app" +} \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 4fe413db..c25f950e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -19,14 +19,23 @@ RUN mkdir /store # Add useful stuff RUN apk add --no-cache tzdata openssl curl git jq bash -# Dependencies stage -FROM base AS dependencies +# Dependencies stage (Build) +FROM base AS build # Copy app package.json COPY app/package* ./ -# Install dependencies -RUN npm ci --omit=dev --omit=optional --no-audit --no-fund --no-update-notifier +# Install dependencies (including dev) +RUN npm ci --include=dev --omit=optional --no-audit --no-fund --no-update-notifier + +# Copy app source +COPY app/ ./ + +# Build +RUN npm run build + +# Remove dev dependencies +RUN npm prune --omit=dev # Release stage FROM base AS release @@ -35,13 +44,14 @@ FROM base AS release COPY Docker.entrypoint.sh /usr/bin/entrypoint.sh RUN chmod +x /usr/bin/entrypoint.sh ENTRYPOINT ["/usr/bin/entrypoint.sh"] -CMD ["node", "index"] +CMD ["node", "dist/index"] ## Copy node_modules -COPY --from=dependencies /home/node/app/node_modules ./node_modules +COPY --from=build /home/node/app/node_modules ./node_modules -# Copy app -COPY app/ ./ +# Copy app (dist) +COPY --from=build /home/node/app/dist ./dist +COPY --from=build /home/node/app/package.json ./package.json # Copy ui COPY ui/dist/ ./ui diff --git a/app/.babelrc b/app/.babelrc deleted file mode 100644 index 14db08ca..00000000 --- a/app/.babelrc +++ /dev/null @@ -1,3 +0,0 @@ -{ - "plugins": ["babel-plugin-rewire"] -} diff --git a/app/api/api.test.js b/app/api/api.test.ts similarity index 71% rename from app/api/api.test.js rename to app/api/api.test.ts index e0e0b940..000dc9c3 100644 --- a/app/api/api.test.js +++ b/app/api/api.test.ts @@ -1,3 +1,4 @@ +// @ts-nocheck // Mock all the router modules jest.mock('express', () => ({ Router: jest.fn(() => ({ @@ -42,30 +43,30 @@ jest.mock('passport', () => ({ authenticate: jest.fn(() => (req, res, next) => next()), })); -const api = require('./api'); +import * as api from './api'; describe('API Router', () => { let router; - beforeEach(() => { + beforeEach(async () => { jest.clearAllMocks(); router = api.init(); }); - test('should initialize and return a router', () => { + test('should initialize and return a router', async () => { expect(router).toBeDefined(); }); - test('should mount all sub-routers', () => { - const appRouter = require('./app'); - const containerRouter = require('./container'); - const watcherRouter = require('./watcher'); - const triggerRouter = require('./trigger'); - const registryRouter = require('./registry'); - const authenticationRouter = require('./authentication'); - const logRouter = require('./log'); - const storeRouter = require('./store'); - const serverRouter = require('./server'); + test('should mount all sub-routers', async () => { + const appRouter = await import('./app'); + const containerRouter = await import('./container'); + const watcherRouter = await import('./watcher'); + const triggerRouter = await import('./trigger'); + const registryRouter = await import('./registry'); + const authenticationRouter = await import('./authentication'); + const logRouter = await import('./log'); + const storeRouter = await import('./store'); + const serverRouter = await import('./server'); expect(appRouter.init).toHaveBeenCalled(); expect(containerRouter.init).toHaveBeenCalled(); @@ -78,8 +79,8 @@ describe('API Router', () => { expect(serverRouter.init).toHaveBeenCalled(); }); - test('should use passport authentication middleware', () => { - const passport = require('passport'); + test('should use passport authentication middleware', async () => { + const passport = await import('passport'); expect(passport.authenticate).toHaveBeenCalledWith([ 'basic', 'anonymous', diff --git a/app/api/api.js b/app/api/api.ts similarity index 64% rename from app/api/api.js rename to app/api/api.ts index 6da173c3..88f14de5 100644 --- a/app/api/api.js +++ b/app/api/api.ts @@ -1,21 +1,22 @@ -const express = require('express'); -const passport = require('passport'); -const appRouter = require('./app'); -const containerRouter = require('./container'); -const watcherRouter = require('./watcher'); -const triggerRouter = require('./trigger'); -const registryRouter = require('./registry'); -const authenticationRouter = require('./authentication'); -const logRouter = require('./log'); -const storeRouter = require('./store'); -const serverRouter = require('./server'); -const auth = require('./auth'); +// @ts-nocheck +import express from 'express'; +import passport from 'passport'; +import * as appRouter from './app'; +import * as containerRouter from './container'; +import * as watcherRouter from './watcher'; +import * as triggerRouter from './trigger'; +import * as registryRouter from './registry'; +import * as authenticationRouter from './authentication'; +import * as logRouter from './log'; +import * as storeRouter from './store'; +import * as serverRouter from './server'; +import * as auth from './auth'; /** * Init the API router. * @returns {*|Router} */ -function init() { +export function init() { const router = express.Router(); // Mount app router @@ -53,7 +54,3 @@ function init() { return router; } - -module.exports = { - init, -}; diff --git a/app/api/app.test.js b/app/api/app.test.ts similarity index 85% rename from app/api/app.test.js rename to app/api/app.test.ts index 5d69242b..15f8938c 100644 --- a/app/api/app.test.js +++ b/app/api/app.test.ts @@ -1,3 +1,4 @@ +// @ts-nocheck // Mock the store module jest.mock('../store/app', () => ({ getAppInfos: jest.fn(() => ({ @@ -17,14 +18,14 @@ jest.mock('express', () => ({ jest.mock('nocache', () => jest.fn()); -const appRouter = require('./app'); +import * as appRouter from './app'; describe('App Router', () => { - beforeEach(() => { + beforeEach(async () => { jest.clearAllMocks(); }); - test('should initialize router with nocache and route', () => { + test('should initialize router with nocache and route', async () => { const router = appRouter.init(); expect(router).toBeDefined(); @@ -32,8 +33,8 @@ describe('App Router', () => { expect(router.get).toHaveBeenCalledWith('/', expect.any(Function)); }); - test('should call getAppInfos when route handler is called', () => { - const storeApp = require('../store/app'); + test('should call getAppInfos when route handler is called', async () => { + const storeApp = await import('../store/app'); const router = appRouter.init(); // Get the route handler function diff --git a/app/api/app.js b/app/api/app.ts similarity index 69% rename from app/api/app.js rename to app/api/app.ts index 98ac0e6b..92f0f2de 100644 --- a/app/api/app.js +++ b/app/api/app.ts @@ -1,6 +1,7 @@ -const express = require('express'); -const nocache = require('nocache'); -const storeApp = require('../store/app'); +// @ts-nocheck +import express from 'express'; +import nocache from 'nocache'; +import * as storeApp from '../store/app'; /** * App infos router. @@ -20,12 +21,8 @@ function getAppInfos(req, res) { * Init Router. * @returns {*} */ -function init() { +export function init() { router.use(nocache()); router.get('/', getAppInfos); return router; } - -module.exports = { - init, -}; diff --git a/app/api/auth.js b/app/api/auth.ts similarity index 88% rename from app/api/auth.js rename to app/api/auth.ts index 049910f8..4d7b2cdc 100644 --- a/app/api/auth.js +++ b/app/api/auth.ts @@ -1,13 +1,15 @@ -const express = require('express'); -const session = require('express-session'); -const LokiStore = require('connect-loki')(session); -const passport = require('passport'); -const { v5: uuidV5 } = require('uuid'); -const getmac = require('getmac').default; -const store = require('../store'); -const registry = require('../registry'); -const log = require('../log'); -const { getVersion } = require('../configuration'); +// @ts-nocheck +import express from 'express'; +import session from 'express-session'; +import ConnectLoki from 'connect-loki'; +const LokiStore = ConnectLoki(session); +import passport from 'passport'; +import { v5 as uuidV5 } from 'uuid'; +import getmac from 'getmac'; +import * as store from '../store'; +import * as registry from '../registry'; +import log from '../log'; +import { getVersion } from '../configuration'; const router = express.Router(); @@ -21,7 +23,7 @@ const WUD_NAMESPACE = 'dee41e92-5fc4-460e-beec-528c9ea7d760'; * Get all strategies id. * @returns {[]} */ -function getAllIds() { +export function getAllIds() { return STRATEGY_IDS; } @@ -132,7 +134,7 @@ function logout(req, res) { * Init auth (passport.js). * @returns {*} */ -function init(app) { +export function init(app) { // Init express session app.use( session({ @@ -182,8 +184,3 @@ function init(app) { app.use('/auth', router); } - -module.exports = { - init, - getAllIds, -}; diff --git a/app/api/authentication.js b/app/api/authentication.js deleted file mode 100644 index b8cb027e..00000000 --- a/app/api/authentication.js +++ /dev/null @@ -1,13 +0,0 @@ -const component = require('./component'); - -/** - * Init Router. - * @returns {*} - */ -function init() { - return component.init('authentication'); -} - -module.exports = { - init, -}; diff --git a/app/api/authentication.ts b/app/api/authentication.ts new file mode 100644 index 00000000..9821b976 --- /dev/null +++ b/app/api/authentication.ts @@ -0,0 +1,10 @@ +// @ts-nocheck +import * as component from './component'; + +/** + * Init Router. + * @returns {*} + */ +export function init() { + return component.init('authentication'); +} diff --git a/app/api/component.js b/app/api/component.ts similarity index 81% rename from app/api/component.js rename to app/api/component.ts index 6fd44e64..6567d398 100644 --- a/app/api/component.js +++ b/app/api/component.ts @@ -1,8 +1,9 @@ -const { byValues, byString } = require('sort-es'); +// @ts-nocheck +import { byValues, byString } from 'sort-es'; -const express = require('express'); -const nocache = require('nocache'); -const registry = require('../registry'); +import express from 'express'; +import nocache from 'nocache'; +import * as registry from '../registry'; /** * Map a Component to a displayable (api/ui) item. @@ -24,7 +25,7 @@ function mapComponentToItem(key, component) { * @param listFunction * @returns {{id: string}[]} */ -function mapComponentsToList(components) { +export function mapComponentsToList(components) { return Object.keys(components) .map((key) => mapComponentToItem(key, components[key])) .sort( @@ -50,7 +51,7 @@ function getAll(req, res, kind) { * @param res * @param listFunction */ -function getById(req, res, kind) { +export function getById(req, res, kind) { const { type, name } = req.params; const id = `${type}.${name}`; const component = registry.getState()[kind][id]; @@ -66,16 +67,10 @@ function getById(req, res, kind) { * @param kind * @returns {*|Router} */ -function init(kind) { +export function init(kind) { const router = express.Router(); router.use(nocache()); router.get('/', (req, res) => getAll(req, res, kind)); router.get('/:type/:name', (req, res) => getById(req, res, kind)); return router; } - -module.exports = { - init, - mapComponentsToList, - getById, -}; diff --git a/app/api/container.js b/app/api/container.ts similarity index 92% rename from app/api/container.js rename to app/api/container.ts index 11537abc..713655af 100644 --- a/app/api/container.js +++ b/app/api/container.ts @@ -1,11 +1,13 @@ -const express = require('express'); -const nocache = require('nocache'); -const storeContainer = require('../store/container'); -const registry = require('../registry'); -const { getServerConfiguration } = require('../configuration'); -const { mapComponentsToList } = require('./component'); -const Trigger = require('../triggers/providers/Trigger'); -const log = require('../log').child({ component: 'container' }); +// @ts-nocheck +import express from 'express'; +import nocache from 'nocache'; +import * as storeContainer from '../store/container'; +import * as registry from '../registry'; +import { getServerConfiguration } from '../configuration'; +import { mapComponentsToList } from './component'; +import Trigger from '../triggers/providers/Trigger'; +import logger from '../log'; +const log = logger.child({ component: 'container' }); const router = express.Router(); @@ -32,7 +34,7 @@ function getTriggers() { * @param query * @returns {*} */ -function getContainersFromStore(query) { +export function getContainersFromStore(query) { return storeContainer.getContainers(query); } @@ -243,7 +245,7 @@ async function watchContainer(req, res) { * Init Router. * @returns {*} */ -function init() { +export function init() { router.use(nocache()); router.get('/', getContainers); router.post('/watch', watchContainers); @@ -254,8 +256,3 @@ function init() { router.post('/:id/watch', watchContainer); return router; } - -module.exports = { - init, - getContainersFromStore, -}; diff --git a/app/api/health.test.js b/app/api/health.test.ts similarity index 74% rename from app/api/health.test.js rename to app/api/health.test.ts index 2fb5b631..0f2ed8ad 100644 --- a/app/api/health.test.js +++ b/app/api/health.test.ts @@ -1,3 +1,4 @@ +// @ts-nocheck // Mock express modules jest.mock('express', () => ({ Router: jest.fn(() => ({ @@ -9,14 +10,14 @@ jest.mock('express', () => ({ jest.mock('nocache', () => jest.fn()); jest.mock('express-healthcheck', () => jest.fn(() => 'healthcheck-middleware')); -const healthRouter = require('./health'); +import * as healthRouter from './health'; describe('Health Router', () => { - beforeEach(() => { + beforeEach(async () => { jest.clearAllMocks(); }); - test('should initialize router with nocache and healthcheck', () => { + test('should initialize router with nocache and healthcheck', async () => { const router = healthRouter.init(); expect(router).toBeDefined(); @@ -24,8 +25,8 @@ describe('Health Router', () => { expect(router.get).toHaveBeenCalledWith('/', 'healthcheck-middleware'); }); - test('should use express-healthcheck middleware', () => { - const expressHealthcheck = require('express-healthcheck'); + test('should use express-healthcheck middleware', async () => { + const expressHealthcheck = await import('express-healthcheck'); healthRouter.init(); expect(expressHealthcheck).toHaveBeenCalled(); diff --git a/app/api/health.js b/app/api/health.ts similarity index 54% rename from app/api/health.js rename to app/api/health.ts index 2c65b623..53e11263 100644 --- a/app/api/health.js +++ b/app/api/health.ts @@ -1,6 +1,7 @@ -const express = require('express'); -const nocache = require('nocache'); -const healthcheck = require('express-healthcheck'); +// @ts-nocheck +import express from 'express'; +import nocache from 'nocache'; +import healthcheck from 'express-healthcheck'; /** * Healthcheck router. @@ -12,12 +13,8 @@ const router = express.Router(); * Init Router. * @returns {*} */ -function init() { +export function init() { router.use(nocache()); router.get('/', healthcheck()); return router; } - -module.exports = { - init, -}; diff --git a/app/api/index.js b/app/api/index.ts similarity index 84% rename from app/api/index.js rename to app/api/index.ts index b2720e4b..0051893b 100644 --- a/app/api/index.js +++ b/app/api/index.ts @@ -1,15 +1,17 @@ -const fs = require('fs'); -const https = require('https'); -const express = require('express'); -const cors = require('cors'); -const bodyParser = require('body-parser'); -const log = require('../log').child({ component: 'api' }); -const auth = require('./auth'); -const apiRouter = require('./api'); -const uiRouter = require('./ui'); -const prometheusRouter = require('./prometheus'); -const healthRouter = require('./health'); -const { getServerConfiguration } = require('../configuration'); +// @ts-nocheck +import fs from 'fs'; +import https from 'https'; +import express from 'express'; +import cors from 'cors'; +import bodyParser from 'body-parser'; +import logger from '../log'; +const log = logger.child({ component: 'api' }); +import * as auth from './auth'; +import * as apiRouter from './api'; +import * as uiRouter from './ui'; +import * as prometheusRouter from './prometheus'; +import * as healthRouter from './health'; +import { getServerConfiguration } from '../configuration'; const configuration = getServerConfiguration(); @@ -17,7 +19,7 @@ const configuration = getServerConfiguration(); * Init Http API. * @returns {Promise} */ -async function init() { +export async function init() { // Start API if enabled if (configuration.enabled) { log.debug( @@ -103,7 +105,3 @@ async function init() { log.debug('API/UI disabled'); } } - -module.exports = { - init, -}; diff --git a/app/api/log.js b/app/api/log.ts similarity index 63% rename from app/api/log.js rename to app/api/log.ts index 49910854..d958de50 100644 --- a/app/api/log.js +++ b/app/api/log.ts @@ -1,6 +1,7 @@ -const express = require('express'); -const nocache = require('nocache'); -const { getLogLevel } = require('../configuration'); +// @ts-nocheck +import express from 'express'; +import nocache from 'nocache'; +import { getLogLevel } from '../configuration'; const router = express.Router(); @@ -19,12 +20,8 @@ function getLog(req, res) { * Init Router. * @returns {*} */ -function init() { +export function init() { router.use(nocache()); router.get('/', getLog); return router; } - -module.exports = { - init, -}; diff --git a/app/api/prometheus.js b/app/api/prometheus.ts similarity index 68% rename from app/api/prometheus.js rename to app/api/prometheus.ts index aef286bd..14d92f7b 100644 --- a/app/api/prometheus.js +++ b/app/api/prometheus.ts @@ -1,8 +1,9 @@ -const express = require('express'); -const passport = require('passport'); -const nocache = require('nocache'); -const { output } = require('../prometheus'); -const auth = require('./auth'); +// @ts-nocheck +import express from 'express'; +import passport from 'passport'; +import nocache from 'nocache'; +import { output } from '../prometheus'; +import * as auth from './auth'; /** * Prometheus Metrics router. @@ -25,7 +26,7 @@ async function outputMetrics(req, res) { * Init Router. * @returns {*} */ -function init() { +export function init() { router.use(nocache()); // Routes to protect after this line @@ -34,7 +35,3 @@ function init() { router.get('/', outputMetrics); return router; } - -module.exports = { - init, -}; diff --git a/app/api/registry.js b/app/api/registry.ts similarity index 53% rename from app/api/registry.js rename to app/api/registry.ts index e3983849..8639e5c9 100644 --- a/app/api/registry.js +++ b/app/api/registry.ts @@ -1,14 +1,11 @@ -const component = require('./component'); +// @ts-nocheck +import * as component from './component'; /** * Init Router. * @returns {*} */ -function init() { +export function init() { const router = component.init('registry'); return router; } - -module.exports = { - init, -}; diff --git a/app/api/server.test.js b/app/api/server.test.ts similarity index 84% rename from app/api/server.test.js rename to app/api/server.test.ts index 0d41324e..e7ac1815 100644 --- a/app/api/server.test.js +++ b/app/api/server.test.ts @@ -1,3 +1,4 @@ +// @ts-nocheck // Mock the configuration module jest.mock('../configuration', () => ({ getServerConfiguration: jest.fn(() => ({ @@ -19,14 +20,14 @@ jest.mock('express', () => ({ jest.mock('nocache', () => jest.fn()); -const serverRouter = require('./server'); +import * as serverRouter from './server'; describe('Server Router', () => { - beforeEach(() => { + beforeEach(async () => { jest.clearAllMocks(); }); - test('should initialize router with nocache and route', () => { + test('should initialize router with nocache and route', async () => { const router = serverRouter.init(); expect(router).toBeDefined(); @@ -34,8 +35,8 @@ describe('Server Router', () => { expect(router.get).toHaveBeenCalledWith('/', expect.any(Function)); }); - test('should call getServerConfiguration when route handler is called', () => { - const { getServerConfiguration } = require('../configuration'); + test('should call getServerConfiguration when route handler is called', async () => { + const { getServerConfiguration } = await import('../configuration'); const router = serverRouter.init(); // Get the route handler function diff --git a/app/api/server.js b/app/api/server.ts similarity index 63% rename from app/api/server.js rename to app/api/server.ts index 834fc933..95812c0c 100644 --- a/app/api/server.js +++ b/app/api/server.ts @@ -1,6 +1,7 @@ -const express = require('express'); -const nocache = require('nocache'); -const { getServerConfiguration } = require('../configuration'); +// @ts-nocheck +import express from 'express'; +import nocache from 'nocache'; +import { getServerConfiguration } from '../configuration'; const router = express.Router(); @@ -19,12 +20,8 @@ function getServer(req, res) { * Init Router. * @returns {*} */ -function init() { +export function init() { router.use(nocache()); router.get('/', getServer); return router; } - -module.exports = { - init, -}; diff --git a/app/api/store.js b/app/api/store.ts similarity index 67% rename from app/api/store.js rename to app/api/store.ts index 22faf0c9..1f2fe567 100644 --- a/app/api/store.js +++ b/app/api/store.ts @@ -1,6 +1,7 @@ -const express = require('express'); -const nocache = require('nocache'); -const store = require('../store'); +// @ts-nocheck +import express from 'express'; +import nocache from 'nocache'; +import * as store from '../store'; const router = express.Router(); @@ -19,12 +20,8 @@ function getStore(req, res) { * Init Router. * @returns {*} */ -function init() { +export function init() { router.use(nocache()); router.get('/', getStore); return router; } - -module.exports = { - init, -}; diff --git a/app/api/trigger.js b/app/api/trigger.ts similarity index 89% rename from app/api/trigger.js rename to app/api/trigger.ts index 75a100a5..c93735f1 100644 --- a/app/api/trigger.js +++ b/app/api/trigger.ts @@ -1,6 +1,8 @@ -const component = require('./component'); -const registry = require('../registry'); -const log = require('../log').child({ component: 'trigger' }); +// @ts-nocheck +import * as component from './component'; +import * as registry from '../registry'; +import logger from '../log'; +const log = logger.child({ component: 'trigger' }); /** * Run a specific trigger on a specific container provided in the payload. @@ -52,12 +54,8 @@ async function runTrigger(req, res) { * Init Router. * @returns {*} */ -function init() { +export function init() { const router = component.init('trigger'); router.post('/:type/:name', (req, res) => runTrigger(req, res)); return router; } - -module.exports = { - init, -}; diff --git a/app/api/ui.js b/app/api/ui.js deleted file mode 100644 index 4b46de98..00000000 --- a/app/api/ui.js +++ /dev/null @@ -1,21 +0,0 @@ -const path = require('path'); -const express = require('express'); - -/** - * Init the UI router. - * @returns {*|Router} - */ -function init() { - const router = express.Router(); - router.use(express.static(path.join(__dirname, '..', 'ui'))); - - // Redirect all 404 to index.html (for vue history mode) - router.get('*', (req, res) => { - res.sendFile(path.join(__dirname, '..', 'ui', 'index.html')); - }); - return router; -} - -module.exports = { - init, -}; diff --git a/app/api/ui.ts b/app/api/ui.ts new file mode 100644 index 00000000..2af1868e --- /dev/null +++ b/app/api/ui.ts @@ -0,0 +1,18 @@ +// @ts-nocheck +import path from 'path'; +import express from 'express'; + +/** + * Init the UI router. + * @returns {*|Router} + */ +export function init() { + const router = express.Router(); + router.use(express.static(path.join(__dirname, '..', '..', 'ui'))); + + // Redirect all 404 to index.html (for vue history mode) + router.get('*', (req, res) => { + res.sendFile(path.join(__dirname, '..', '..', 'ui', 'index.html')); + }); + return router; +} diff --git a/app/api/watcher.js b/app/api/watcher.js deleted file mode 100644 index efbdef07..00000000 --- a/app/api/watcher.js +++ /dev/null @@ -1,13 +0,0 @@ -const component = require('./component'); - -/** - * Init Router. - * @returns {*} - */ -function init() { - return component.init('watcher'); -} - -module.exports = { - init, -}; diff --git a/app/api/watcher.ts b/app/api/watcher.ts new file mode 100644 index 00000000..2763d9f5 --- /dev/null +++ b/app/api/watcher.ts @@ -0,0 +1,10 @@ +// @ts-nocheck +import * as component from './component'; + +/** + * Init Router. + * @returns {*} + */ +export function init() { + return component.init('watcher'); +} diff --git a/app/authentications/providers/Authentication.test.js b/app/authentications/providers/Authentication.test.ts similarity index 81% rename from app/authentications/providers/Authentication.test.js rename to app/authentications/providers/Authentication.test.ts index 1f3e0b81..4c80d1a4 100644 --- a/app/authentications/providers/Authentication.test.js +++ b/app/authentications/providers/Authentication.test.ts @@ -1,4 +1,5 @@ -const Authentication = require('./Authentication'); +// @ts-nocheck +import Authentication from './Authentication'; describe('Authentication Base Class', () => { let authentication; @@ -10,7 +11,7 @@ describe('Authentication Base Class', () => { }); }); - test('should create instance with default values', () => { + test('should create instance with default values', async () => { expect(authentication).toBeDefined(); expect(authentication.kind).toBe('authentication'); expect(authentication.type).toBe('test'); @@ -18,17 +19,17 @@ describe('Authentication Base Class', () => { expect(authentication.configuration).toEqual({ key: 'value' }); }); - test('should get id correctly', () => { + test('should get id correctly', async () => { expect(authentication.getId()).toBe('test.test-auth'); }); - test('should throw error when getStrategy is called on base class', () => { + test('should throw error when getStrategy is called on base class', async () => { expect(() => authentication.getStrategy()).toThrow( 'getStrategy must be implemented', ); }); - test('should throw error when getStrategyDescription is called on base class', () => { + test('should throw error when getStrategyDescription is called on base class', async () => { expect(() => authentication.getStrategyDescription()).toThrow( 'getStrategyDescription must be implemented', ); diff --git a/app/authentications/providers/Authentication.js b/app/authentications/providers/Authentication.ts similarity index 78% rename from app/authentications/providers/Authentication.js rename to app/authentications/providers/Authentication.ts index 0d189864..fe008472 100644 --- a/app/authentications/providers/Authentication.js +++ b/app/authentications/providers/Authentication.ts @@ -1,4 +1,5 @@ -const Component = require('../../registry/Component'); +import Component from '../../registry/Component'; +import { Strategy } from 'passport'; class Authentication extends Component { /** @@ -18,7 +19,7 @@ class Authentication extends Component { /** * Return passport strategy. */ - getStrategy() { + getStrategy(): Strategy { throw new Error('getStrategy must be implemented'); } @@ -27,4 +28,4 @@ class Authentication extends Component { } } -module.exports = Authentication; +export default Authentication; diff --git a/app/authentications/providers/anonymous/Anonymous.test.js b/app/authentications/providers/anonymous/Anonymous.test.ts similarity index 68% rename from app/authentications/providers/anonymous/Anonymous.test.js rename to app/authentications/providers/anonymous/Anonymous.test.ts index 9bfe1260..5878a573 100644 --- a/app/authentications/providers/anonymous/Anonymous.test.js +++ b/app/authentications/providers/anonymous/Anonymous.test.ts @@ -1,24 +1,25 @@ -const Anonymous = require('./Anonymous'); +// @ts-nocheck +import Anonymous from './Anonymous'; describe('Anonymous Authentication', () => { let anonymous; - beforeEach(() => { + beforeEach(async () => { anonymous = new Anonymous(); }); - test('should create instance', () => { + test('should create instance', async () => { expect(anonymous).toBeDefined(); expect(anonymous).toBeInstanceOf(Anonymous); }); - test('should return anonymous strategy', () => { + test('should return anonymous strategy', async () => { const strategy = anonymous.getStrategy(); expect(strategy).toBeDefined(); expect(strategy.name).toBe('anonymous'); }); - test('should return strategy description', () => { + test('should return strategy description', async () => { const description = anonymous.getStrategyDescription(); expect(description).toEqual({ type: 'anonymous', diff --git a/app/authentications/providers/anonymous/Anonymous.js b/app/authentications/providers/anonymous/Anonymous.ts similarity index 71% rename from app/authentications/providers/anonymous/Anonymous.js rename to app/authentications/providers/anonymous/Anonymous.ts index da309245..c5074eb1 100644 --- a/app/authentications/providers/anonymous/Anonymous.js +++ b/app/authentications/providers/anonymous/Anonymous.ts @@ -1,6 +1,7 @@ -const AnonymousStrategy = require('passport-anonymous').Strategy; -const Authentication = require('../Authentication'); -const log = require('../../../log'); +// @ts-nocheck +import { Strategy as AnonymousStrategy } from 'passport-anonymous'; +import Authentication from '../Authentication'; +import log from '../../../log'; /** * Anonymous authentication. @@ -24,4 +25,4 @@ class Anonymous extends Authentication { } } -module.exports = Anonymous; +export default Anonymous; diff --git a/app/authentications/providers/basic/Basic.test.js b/app/authentications/providers/basic/Basic.test.ts similarity index 56% rename from app/authentications/providers/basic/Basic.test.js rename to app/authentications/providers/basic/Basic.test.ts index 1809bcfc..461b5dd7 100644 --- a/app/authentications/providers/basic/Basic.test.js +++ b/app/authentications/providers/basic/Basic.test.ts @@ -1,18 +1,19 @@ -const Basic = require('./Basic'); +// @ts-nocheck +import Basic from './Basic'; describe('Basic Authentication', () => { let basic; - beforeEach(() => { + beforeEach(async () => { basic = new Basic(); }); - test('should create instance', () => { + test('should create instance', async () => { expect(basic).toBeDefined(); expect(basic).toBeInstanceOf(Basic); }); - test('should return basic strategy', () => { + test('should return basic strategy', async () => { // Mock configuration to avoid validation errors basic.configuration = { user: 'testuser', @@ -24,7 +25,7 @@ describe('Basic Authentication', () => { expect(strategy.name).toBe('basic'); }); - test('should return strategy description', () => { + test('should return strategy description', async () => { const description = basic.getStrategyDescription(); expect(description).toEqual({ type: 'basic', @@ -32,7 +33,7 @@ describe('Basic Authentication', () => { }); }); - test('should mask configuration hash', () => { + test('should mask configuration hash', async () => { basic.configuration = { user: 'testuser', hash: '$2b$10$test.hash.value', @@ -42,8 +43,8 @@ describe('Basic Authentication', () => { expect(masked.hash).toBe('$********************e'); }); - test('should authenticate valid user', (done) => { - const passJs = require('pass'); + test('should authenticate valid user', async () => { + const { default: passJs } = await import('pass'); basic.configuration = { user: 'testuser', hash: '$2b$10$test.hash.value', @@ -53,26 +54,30 @@ describe('Basic Authentication', () => { callback(null, true); }); - basic.authenticate('testuser', 'password', (err, result) => { - expect(result).toEqual({ username: 'testuser' }); - done(); + await new Promise((resolve) => { + basic.authenticate('testuser', 'password', (err, result) => { + expect(result).toEqual({ username: 'testuser' }); + resolve(); + }); }); }); - test('should reject invalid user', (done) => { + test('should reject invalid user', async () => { basic.configuration = { user: 'testuser', hash: '$2b$10$test.hash.value', }; - basic.authenticate('wronguser', 'password', (err, result) => { - expect(result).toBe(false); - done(); + await new Promise((resolve) => { + basic.authenticate('wronguser', 'password', (err, result) => { + expect(result).toBe(false); + resolve(); + }); }); }); - test('should reject invalid password', (done) => { - const passJs = require('pass'); + test('should reject invalid password', async () => { + const { default: passJs } = await import('pass'); basic.configuration = { user: 'testuser', hash: '$2b$10$test.hash.value', @@ -82,9 +87,11 @@ describe('Basic Authentication', () => { callback(null, false); }); - basic.authenticate('testuser', 'wrongpassword', (err, result) => { - expect(result).toBe(false); - done(); + await new Promise((resolve) => { + basic.authenticate('testuser', 'wrongpassword', (err, result) => { + expect(result).toBe(false); + resolve(); + }); }); }); }); diff --git a/app/authentications/providers/basic/Basic.js b/app/authentications/providers/basic/Basic.ts similarity index 89% rename from app/authentications/providers/basic/Basic.js rename to app/authentications/providers/basic/Basic.ts index f23e1235..bc3dac33 100644 --- a/app/authentications/providers/basic/Basic.js +++ b/app/authentications/providers/basic/Basic.ts @@ -1,6 +1,7 @@ -const passJs = require('pass'); -const BasicStrategy = require('./BasicStrategy'); -const Authentication = require('../Authentication'); +// @ts-nocheck +import passJs from 'pass'; +import BasicStrategy from './BasicStrategy'; +import Authentication from '../Authentication'; /** * Htpasswd authentication. @@ -64,4 +65,4 @@ class Basic extends Authentication { } } -module.exports = Basic; +export default Basic; diff --git a/app/authentications/providers/basic/BasicStrategy.test.js b/app/authentications/providers/basic/BasicStrategy.test.ts similarity index 71% rename from app/authentications/providers/basic/BasicStrategy.test.js rename to app/authentications/providers/basic/BasicStrategy.test.ts index 187f3eef..e4e56e9d 100644 --- a/app/authentications/providers/basic/BasicStrategy.test.js +++ b/app/authentications/providers/basic/BasicStrategy.test.ts @@ -1,22 +1,23 @@ -const BasicStrategy = require('./BasicStrategy'); +// @ts-nocheck +import BasicStrategy from './BasicStrategy'; const basicStrategy = new BasicStrategy({}, () => {}); -beforeEach(() => { +beforeEach(async () => { basicStrategy.success = jest.fn(); basicStrategy.fail = jest.fn(); }); -test('_challenge should return appropriate Auth header', () => { +test('_challenge should return appropriate Auth header', async () => { expect(basicStrategy._challenge()).toEqual(401); }); -test('authenticate should return user from session if so', () => { +test('authenticate should return user from session if so', async () => { basicStrategy.authenticate({ isAuthenticated: () => true }); expect(basicStrategy.success).toHaveBeenCalled(); }); -test('authenticate should call super.authenticate when no existing session', () => { +test('authenticate should call super.authenticate when no existing session', async () => { const fail = jest.spyOn(basicStrategy, 'fail'); basicStrategy.authenticate({ isAuthenticated: () => false, diff --git a/app/authentications/providers/basic/BasicStrategy.js b/app/authentications/providers/basic/BasicStrategy.ts similarity index 74% rename from app/authentications/providers/basic/BasicStrategy.js rename to app/authentications/providers/basic/BasicStrategy.ts index a120f2bc..bfb98d6c 100644 --- a/app/authentications/providers/basic/BasicStrategy.js +++ b/app/authentications/providers/basic/BasicStrategy.ts @@ -1,10 +1,11 @@ -const { BasicStrategy: HttpBasicStrategy } = require('passport-http'); +// @ts-nocheck +import { BasicStrategy as HttpBasicStrategy } from 'passport-http'; /** * Inherit from Basic Strategy including Session support. * @type {module.MyStrategy} */ -module.exports = class BasicStrategy extends HttpBasicStrategy { +class BasicStrategy extends HttpBasicStrategy { authenticate(req) { // Already authenticated (thanks to session) => ok if (req.isAuthenticated()) { @@ -21,4 +22,6 @@ module.exports = class BasicStrategy extends HttpBasicStrategy { _challenge() { return 401; } -}; +} + +export default BasicStrategy; diff --git a/app/authentications/providers/oidc/Oidc.test.js b/app/authentications/providers/oidc/Oidc.test.ts similarity index 86% rename from app/authentications/providers/oidc/Oidc.test.js rename to app/authentications/providers/oidc/Oidc.test.ts index cba3a77f..c9bc8582 100644 --- a/app/authentications/providers/oidc/Oidc.test.js +++ b/app/authentications/providers/oidc/Oidc.test.ts @@ -1,7 +1,8 @@ -const { ValidationError } = require('joi'); -const express = require('express'); -const { Issuer } = require('openid-client'); -const Oidc = require('./Oidc'); +// @ts-nocheck +import { ValidationError } from 'joi'; +import express from 'express'; +import { Issuer } from 'openid-client'; +import Oidc from './Oidc'; const app = express(); @@ -20,29 +21,29 @@ const oidc = new Oidc(); oidc.configuration = configurationValid; oidc.client = client; -beforeEach(() => { +beforeEach(async () => { jest.resetAllMocks(); }); -test('validateConfiguration should return validated configuration when valid', () => { +test('validateConfiguration should return validated configuration when valid', async () => { const validatedConfiguration = oidc.validateConfiguration(configurationValid); expect(validatedConfiguration).toStrictEqual(configurationValid); }); -test('validateConfiguration should throw error when invalid', () => { +test('validateConfiguration should throw error when invalid', async () => { const configuration = {}; expect(() => { oidc.validateConfiguration(configuration); }).toThrowError(ValidationError); }); -test('getStrategy should return an Authentication strategy', () => { +test('getStrategy should return an Authentication strategy', async () => { const strategy = oidc.getStrategy(app); expect(strategy.name).toEqual('oidc'); }); -test('maskConfiguration should mask configuration secrets', () => { +test('maskConfiguration should mask configuration secrets', async () => { expect(oidc.maskConfiguration()).toEqual({ clientid: '1*******8', clientsecret: 's****t', @@ -52,7 +53,7 @@ test('maskConfiguration should mask configuration secrets', () => { }); }); -test('getStrategyDescription should return strategy description', () => { +test('getStrategyDescription should return strategy description', async () => { oidc.logoutUrl = 'https://idp/logout'; expect(oidc.getStrategyDescription()).toEqual({ type: 'oidc', diff --git a/app/authentications/providers/oidc/Oidc.js b/app/authentications/providers/oidc/Oidc.ts similarity index 94% rename from app/authentications/providers/oidc/Oidc.js rename to app/authentications/providers/oidc/Oidc.ts index 9078401f..b3fd576e 100644 --- a/app/authentications/providers/oidc/Oidc.js +++ b/app/authentications/providers/oidc/Oidc.ts @@ -1,8 +1,9 @@ -const { Issuer, generators, custom } = require('openid-client'); -const { v4: uuid } = require('uuid'); -const Authentication = require('../Authentication'); -const OidcStrategy = require('./OidcStrategy'); -const { getPublicUrl } = require('../../../configuration'); +// @ts-nocheck +import { Issuer, generators, custom } from 'openid-client'; +import { v4 as uuid } from 'uuid'; +import Authentication from '../Authentication'; +import OidcStrategy from './OidcStrategy'; +import { getPublicUrl } from '../../../configuration'; /** * Htpasswd authentication. @@ -171,4 +172,4 @@ class Oidc extends Authentication { } } -module.exports = Oidc; +export default Oidc; diff --git a/app/authentications/providers/oidc/OidcStrategy.test.js b/app/authentications/providers/oidc/OidcStrategy.test.ts similarity index 73% rename from app/authentications/providers/oidc/OidcStrategy.test.js rename to app/authentications/providers/oidc/OidcStrategy.test.ts index 7972cb8b..a43afd98 100644 --- a/app/authentications/providers/oidc/OidcStrategy.test.js +++ b/app/authentications/providers/oidc/OidcStrategy.test.ts @@ -1,28 +1,29 @@ -const { Issuer } = require('openid-client'); -const OidcStrategy = require('./OidcStrategy'); -const log = require('../../../log'); +// @ts-nocheck +import { Issuer } from 'openid-client'; +import OidcStrategy from './OidcStrategy'; +import log from '../../../log'; const { Client } = new Issuer({ issuer: 'issuer' }); const client = new Client({ client_id: '123456789' }); const oidcStrategy = new OidcStrategy({ client }, () => {}, log); -beforeEach(() => { +beforeEach(async () => { oidcStrategy.success = jest.fn(); oidcStrategy.fail = jest.fn(); }); -test('authenticate should return user from session if so', () => { +test('authenticate should return user from session if so', async () => { oidcStrategy.authenticate({ isAuthenticated: () => true }); expect(oidcStrategy.success).toHaveBeenCalled(); }); -test('authenticate should call super.authenticate when no existing session', () => { +test('authenticate should call super.authenticate when no existing session', async () => { const fail = jest.spyOn(oidcStrategy, 'fail'); oidcStrategy.authenticate({ isAuthenticated: () => false, headers: {} }); expect(fail).toHaveBeenCalled(); }); -test('authenticate should get & validate Bearer token', () => { +test('authenticate should get & validate Bearer token', async () => { const verify = jest.spyOn(oidcStrategy, 'verify'); oidcStrategy.authenticate({ isAuthenticated: () => false, diff --git a/app/authentications/providers/oidc/OidcStrategy.js b/app/authentications/providers/oidc/OidcStrategy.ts similarity index 92% rename from app/authentications/providers/oidc/OidcStrategy.js rename to app/authentications/providers/oidc/OidcStrategy.ts index 9fa416ed..b5bc3322 100644 --- a/app/authentications/providers/oidc/OidcStrategy.js +++ b/app/authentications/providers/oidc/OidcStrategy.ts @@ -1,6 +1,7 @@ -const { Strategy } = require('openid-client'); +// @ts-nocheck +import { Strategy } from 'openid-client'; -module.exports = class OidcStrategy extends Strategy { +class OidcStrategy extends Strategy { /** * Constructor. * @param options @@ -46,4 +47,6 @@ module.exports = class OidcStrategy extends Strategy { } } } -}; +} + +export default OidcStrategy; diff --git a/app/configuration/index.test.js b/app/configuration/index.test.ts similarity index 87% rename from app/configuration/index.test.js rename to app/configuration/index.test.ts index a3d600e2..2905af04 100644 --- a/app/configuration/index.test.js +++ b/app/configuration/index.test.ts @@ -1,21 +1,22 @@ -const configuration = require('./index'); +// @ts-nocheck +import * as configuration from './index'; -test('getVersion should return wud version', () => { +test('getVersion should return wud version', async () => { configuration.wudEnvVars.WUD_VERSION = 'x.y.z'; expect(configuration.getVersion()).toStrictEqual('x.y.z'); }); -test('getLogLevel should return info by default', () => { +test('getLogLevel should return info by default', async () => { delete configuration.wudEnvVars.WUD_LOG_LEVEL; expect(configuration.getLogLevel()).toStrictEqual('info'); }); -test('getLogLevel should return debug when overridden', () => { +test('getLogLevel should return debug when overridden', async () => { configuration.wudEnvVars.WUD_LOG_LEVEL = 'debug'; expect(configuration.getLogLevel()).toStrictEqual('debug'); }); -test('getWatcherConfiguration should return empty object by default', () => { +test('getWatcherConfiguration should return empty object by default', async () => { delete configuration.wudEnvVars.WUD_WATCHER_WATCHER1_X; delete configuration.wudEnvVars.WUD_WATCHER_WATCHER1_Y; delete configuration.wudEnvVars.WUD_WATCHER_WATCHER2_X; @@ -23,7 +24,7 @@ test('getWatcherConfiguration should return empty object by default', () => { expect(configuration.getWatcherConfigurations()).toStrictEqual({}); }); -test('getWatcherConfiguration should return configured watchers when overridden', () => { +test('getWatcherConfiguration should return configured watchers when overridden', async () => { configuration.wudEnvVars.WUD_WATCHER_WATCHER1_X = 'x'; configuration.wudEnvVars.WUD_WATCHER_WATCHER1_Y = 'y'; configuration.wudEnvVars.WUD_WATCHER_WATCHER2_X = 'x'; @@ -34,7 +35,7 @@ test('getWatcherConfiguration should return configured watchers when overridden' }); }); -test('getTriggerConfigurations should return empty object by default', () => { +test('getTriggerConfigurations should return empty object by default', async () => { delete configuration.wudEnvVars.WUD_TRIGGER_TRIGGER1_X; delete configuration.wudEnvVars.WUD_TRIGGER_TRIGGER1_Y; delete configuration.wudEnvVars.WUD_TRIGGER_TRIGGER2_X; @@ -42,7 +43,7 @@ test('getTriggerConfigurations should return empty object by default', () => { expect(configuration.getTriggerConfigurations()).toStrictEqual({}); }); -test('getTriggerConfigurations should return configured triggers when overridden', () => { +test('getTriggerConfigurations should return configured triggers when overridden', async () => { configuration.wudEnvVars.WUD_TRIGGER_TRIGGER1_X = 'x'; configuration.wudEnvVars.WUD_TRIGGER_TRIGGER1_Y = 'y'; configuration.wudEnvVars.WUD_TRIGGER_TRIGGER2_X = 'x'; @@ -53,7 +54,7 @@ test('getTriggerConfigurations should return configured triggers when overridden }); }); -test('getRegistryConfigurations should return empty object by default', () => { +test('getRegistryConfigurations should return empty object by default', async () => { delete configuration.wudEnvVars.WUD_REGISTRY_REGISTRY1_X; delete configuration.wudEnvVars.WUD_REGISTRY_REGISTRY1_Y; delete configuration.wudEnvVars.WUD_REGISTRY_REGISTRY1_X; @@ -61,7 +62,7 @@ test('getRegistryConfigurations should return empty object by default', () => { expect(configuration.getRegistryConfigurations()).toStrictEqual({}); }); -test('getRegistryConfigurations should return configured registries when overridden', () => { +test('getRegistryConfigurations should return configured registries when overridden', async () => { configuration.wudEnvVars.WUD_REGISTRY_REGISTRY1_X = 'x'; configuration.wudEnvVars.WUD_REGISTRY_REGISTRY1_Y = 'y'; configuration.wudEnvVars.WUD_REGISTRY_REGISTRY2_X = 'x'; @@ -72,7 +73,7 @@ test('getRegistryConfigurations should return configured registries when overrid }); }); -test('getStoreConfiguration should return configured store', () => { +test('getStoreConfiguration should return configured store', async () => { configuration.wudEnvVars.WUD_STORE_X = 'x'; configuration.wudEnvVars.WUD_STORE_Y = 'y'; expect(configuration.getStoreConfiguration()).toStrictEqual({ @@ -81,7 +82,7 @@ test('getStoreConfiguration should return configured store', () => { }); }); -test('getServerConfiguration should return configured api (new vars)', () => { +test('getServerConfiguration should return configured api (new vars)', async () => { configuration.wudEnvVars.WUD_SERVER_PORT = '4000'; expect(configuration.getServerConfiguration()).toStrictEqual({ cors: {}, @@ -94,7 +95,7 @@ test('getServerConfiguration should return configured api (new vars)', () => { }); }); -test('replaceSecrets must read secret in file', () => { +test('replaceSecrets must read secret in file', async () => { const vars = { WUD_SERVER_X__FILE: `${__dirname}/secret.txt`, }; diff --git a/app/configuration/index.js b/app/configuration/index.ts similarity index 83% rename from app/configuration/index.js rename to app/configuration/index.ts index 68c50516..44082090 100644 --- a/app/configuration/index.js +++ b/app/configuration/index.ts @@ -1,6 +1,7 @@ -const fs = require('fs'); -const joi = require('joi'); -const setValue = require('set-value'); +// @ts-nocheck +import fs from 'fs'; +import joi from 'joi'; +import setValue from 'set-value'; const VAR_FILE_SUFFIX = '__FILE'; @@ -9,7 +10,7 @@ const VAR_FILE_SUFFIX = '__FILE'; * @param prop * @returns {{}} */ -function get(prop, env = process.env) { +export function get(prop, env = process.env) { const object = {}; const envVarPattern = prop.replace(/\./g, '_').toUpperCase(); const matchingEnvVars = Object.keys(env).filter((envKey) => @@ -33,7 +34,7 @@ function get(prop, env = process.env) { * Lookup external secrets defined in files. * @param wudEnvVars */ -function replaceSecrets(wudEnvVars) { +export function replaceSecrets(wudEnvVars) { const secretFileEnvVars = Object.keys(wudEnvVars).filter((wudEnvVar) => wudEnvVar.toUpperCase().endsWith(VAR_FILE_SUFFIX), ); @@ -47,7 +48,7 @@ function replaceSecrets(wudEnvVars) { } // 1. Get a copy of all wud related env vars -const wudEnvVars = {}; +export const wudEnvVars = {}; Object.keys(process.env) .filter((envVar) => envVar.toUpperCase().startsWith('WUD')) .forEach((wudEnvVar) => { @@ -57,24 +58,24 @@ Object.keys(process.env) // 2. Replace all secret files referenced by their secret values replaceSecrets(wudEnvVars); -function getVersion() { +export function getVersion() { return wudEnvVars.WUD_VERSION || 'unknown'; } -function getLogLevel() { +export function getLogLevel() { return wudEnvVars.WUD_LOG_LEVEL || 'info'; } /** * Get watcher configuration. */ -function getWatcherConfigurations() { +export function getWatcherConfigurations() { return get('wud.watcher', wudEnvVars); } /** * Get trigger configurations. */ -function getTriggerConfigurations() { +export function getTriggerConfigurations() { return get('wud.trigger', wudEnvVars); } @@ -82,7 +83,7 @@ function getTriggerConfigurations() { * Get registry configurations. * @returns {*} */ -function getRegistryConfigurations() { +export function getRegistryConfigurations() { return get('wud.registry', wudEnvVars); } @@ -90,21 +91,21 @@ function getRegistryConfigurations() { * Get authentication configurations. * @returns {*} */ -function getAuthenticationConfigurations() { +export function getAuthenticationConfigurations() { return get('wud.auth', wudEnvVars); } /** * Get Input configurations. */ -function getStoreConfiguration() { +export function getStoreConfiguration() { return get('wud.store', wudEnvVars); } /** * Get Server configurations. */ -function getServerConfiguration() { +export function getServerConfiguration() { const configurationFromEnv = get('wud.server', wudEnvVars); const configurationSchema = joi.object().keys({ enabled: joi.boolean().default(true), @@ -153,7 +154,7 @@ function getServerConfiguration() { /** * Get Prometheus configurations. */ -function getPrometheusConfiguration() { +export function getPrometheusConfiguration() { const configurationFromEnv = get('wud.prometheus', wudEnvVars); const configurationSchema = joi.object().keys({ enabled: joi.boolean().default(true), @@ -169,7 +170,7 @@ function getPrometheusConfiguration() { return configurationToValidate.value; } -function getPublicUrl(req) { +export function getPublicUrl(req) { const publicUrl = wudEnvVars.WUD_PUBLIC_URL; if (publicUrl) { return publicUrl; @@ -177,19 +178,3 @@ function getPublicUrl(req) { // Try to guess from request return `${req.protocol}://${req.hostname}`; } - -module.exports = { - wudEnvVars, - get, - replaceSecrets, - getVersion, - getLogLevel, - getStoreConfiguration, - getWatcherConfigurations, - getTriggerConfigurations, - getRegistryConfigurations, - getAuthenticationConfigurations, - getServerConfiguration, - getPrometheusConfiguration, - getPublicUrl, -}; diff --git a/app/eslint.config.mjs b/app/eslint.config.mjs deleted file mode 100644 index 45d576db..00000000 --- a/app/eslint.config.mjs +++ /dev/null @@ -1,30 +0,0 @@ -import jest from 'eslint-plugin-jest'; -import path from 'node:path'; -import { fileURLToPath } from 'node:url'; -import js from '@eslint/js'; -import { FlatCompat } from '@eslint/eslintrc'; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); -const compat = new FlatCompat({ - baseDirectory: __dirname, - recommendedConfig: js.configs.recommended, - allConfig: js.configs.all, -}); - -export default [ - ...compat.extends('plugin:prettier/recommended'), - { - plugins: { - jest, - }, - languageOptions: { - globals: { - ...jest.environments.globals.globals, - }, - }, - }, - { - ignores: ['coverage/**/*'], - }, -]; diff --git a/app/eslint.config.ts b/app/eslint.config.ts new file mode 100644 index 00000000..f94a7252 --- /dev/null +++ b/app/eslint.config.ts @@ -0,0 +1,55 @@ +import { defineConfig } from 'eslint/config'; +import js from '@eslint/js'; +import tseslint from 'typescript-eslint'; +import jest from 'eslint-plugin-jest'; +import prettier from 'eslint-plugin-prettier/recommended'; + +export default defineConfig([ + // 1. Global Ignores (Must be the first object) + { + ignores: ['coverage/**/*', 'dist/**/*', 'node_modules/**/*'], + }, + + // 2. Base JS & TS Recommended + js.configs.recommended, + ...tseslint.configs.recommended, + + // 3. Prettier (Turns off conflicting rules) + prettier, + + // 4. Project-specific TypeScript settings + { + files: ['**/*.ts', '**/*.tsx'], + languageOptions: { + parser: tseslint.parser, + parserOptions: { + project: './tsconfig.json', + }, + }, + rules: { + // 1. Completely Disable + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-require-imports': 'off', + + // 2. Downgrade to Warnings (the "Info" equivalent) + '@typescript-eslint/no-unused-vars': 'warn', + '@typescript-eslint/no-empty-object-type': 'warn', + + // 3. Special handling for @ts-ignore comments + '@typescript-eslint/ban-ts-comment': 'warn', + }, + }, + + // 5. Jest configuration + { + files: ['**/*.test.ts', '**/*.spec.ts'], + plugins: { + jest, + }, + languageOptions: { + globals: { + ...jest.environments.globals.globals, + }, + }, + }, +]); diff --git a/app/event/index.test.js b/app/event/index.test.ts similarity index 96% rename from app/event/index.test.js rename to app/event/index.test.ts index 244192d3..f7e57a99 100644 --- a/app/event/index.test.js +++ b/app/event/index.test.ts @@ -1,4 +1,5 @@ -const event = require('./index'); +// @ts-nocheck +import * as event from './index'; const eventTestCases = [ { diff --git a/app/event/index.js b/app/event/index.ts similarity index 65% rename from app/event/index.js rename to app/event/index.ts index a6878bc1..ef5b99f3 100644 --- a/app/event/index.js +++ b/app/event/index.ts @@ -1,4 +1,5 @@ -const events = require('events'); +// @ts-nocheck +import events from 'events'; // Build EventEmitter const eventEmitter = new events.EventEmitter(); @@ -18,7 +19,7 @@ const WUD_WATCHER_STOP = 'wud:watcher-stop'; * Emit ContainerReports event. * @param containerReports */ -function emitContainerReports(containerReports) { +export function emitContainerReports(containerReports) { eventEmitter.emit(WUD_CONTAINER_REPORTS, containerReports); } @@ -26,7 +27,7 @@ function emitContainerReports(containerReports) { * Register to ContainersResult event. * @param handler */ -function registerContainerReports(handler) { +export function registerContainerReports(handler) { eventEmitter.on(WUD_CONTAINER_REPORTS, handler); } @@ -34,7 +35,7 @@ function registerContainerReports(handler) { * Emit ContainerReport event. * @param containerReport */ -function emitContainerReport(containerReport) { +export function emitContainerReport(containerReport) { eventEmitter.emit(WUD_CONTAINER_REPORT, containerReport); } @@ -42,7 +43,7 @@ function emitContainerReport(containerReport) { * Register to ContainerReport event. * @param handler */ -function registerContainerReport(handler) { +export function registerContainerReport(handler) { eventEmitter.on(WUD_CONTAINER_REPORT, handler); } @@ -50,7 +51,7 @@ function registerContainerReport(handler) { * Emit container added. * @param containerAdded */ -function emitContainerAdded(containerAdded) { +export function emitContainerAdded(containerAdded) { eventEmitter.emit(WUD_CONTAINER_ADDED, containerAdded); } @@ -58,7 +59,7 @@ function emitContainerAdded(containerAdded) { * Register to container added event. * @param handler */ -function registerContainerAdded(handler) { +export function registerContainerAdded(handler) { eventEmitter.on(WUD_CONTAINER_ADDED, handler); } @@ -66,7 +67,7 @@ function registerContainerAdded(handler) { * Emit container added. * @param containerUpdated */ -function emitContainerUpdated(containerUpdated) { +export function emitContainerUpdated(containerUpdated) { eventEmitter.emit(WUD_CONTAINER_UPDATED, containerUpdated); } @@ -74,7 +75,7 @@ function emitContainerUpdated(containerUpdated) { * Register to container updated event. * @param handler */ -function registerContainerUpdated(handler) { +export function registerContainerUpdated(handler) { eventEmitter.on(WUD_CONTAINER_UPDATED, handler); } @@ -82,7 +83,7 @@ function registerContainerUpdated(handler) { * Emit container removed. * @param containerRemoved */ -function emitContainerRemoved(containerRemoved) { +export function emitContainerRemoved(containerRemoved) { eventEmitter.emit(WUD_CONTAINER_REMOVED, containerRemoved); } @@ -90,38 +91,22 @@ function emitContainerRemoved(containerRemoved) { * Register to container removed event. * @param handler */ -function registerContainerRemoved(handler) { +export function registerContainerRemoved(handler) { eventEmitter.on(WUD_CONTAINER_REMOVED, handler); } -function emitWatcherStart(watcher) { +export function emitWatcherStart(watcher) { eventEmitter.emit(WUD_WATCHER_START, watcher); } -function registerWatcherStart(handler) { +export function registerWatcherStart(handler) { eventEmitter.on(WUD_WATCHER_START, handler); } -function emitWatcherStop(watcher) { +export function emitWatcherStop(watcher) { eventEmitter.emit(WUD_WATCHER_STOP, watcher); } -function registerWatcherStop(handler) { +export function registerWatcherStop(handler) { eventEmitter.on(WUD_WATCHER_STOP, handler); } -module.exports = { - emitContainerReports, - registerContainerReports, - emitContainerReport, - registerContainerReport, - emitContainerAdded, - registerContainerAdded, - emitContainerUpdated, - registerContainerUpdated, - emitContainerRemoved, - registerContainerRemoved, - emitWatcherStart, - registerWatcherStart, - emitWatcherStop, - registerWatcherStop, -}; diff --git a/app/index.test.js b/app/index.test.ts similarity index 76% rename from app/index.test.js rename to app/index.test.ts index 165b7403..90ef35a0 100644 --- a/app/index.test.js +++ b/app/index.test.ts @@ -1,3 +1,4 @@ +// @ts-nocheck // Mock all dependencies jest.mock('./configuration', () => ({ getVersion: jest.fn(() => '1.0.0'), @@ -24,22 +25,22 @@ jest.mock('./prometheus', () => ({ })); describe('Main Application', () => { - beforeEach(() => { + beforeEach(async () => { jest.clearAllMocks(); // Clear the module cache to ensure fresh imports jest.resetModules(); }); test('should initialize all components in correct order', async () => { - const log = require('./log'); - const store = require('./store'); - const registry = require('./registry'); - const api = require('./api'); - const prometheus = require('./prometheus'); - const { getVersion } = require('./configuration'); + const { default: log } = await import('./log'); + const store = await import('./store'); + const registry = await import('./registry'); + const api = await import('./api'); + const prometheus = await import('./prometheus'); + const { getVersion } = await import('./configuration'); // Import and run the main module - require('./index'); + await import('./index'); // Wait for async operations to complete await new Promise((resolve) => setImmediate(resolve)); diff --git a/app/index.js b/app/index.ts similarity index 54% rename from app/index.js rename to app/index.ts index 26ec2d15..8cff7f8a 100644 --- a/app/index.js +++ b/app/index.ts @@ -1,9 +1,10 @@ -const { getVersion } = require('./configuration'); -const log = require('./log'); -const store = require('./store'); -const registry = require('./registry'); -const api = require('./api'); -const prometheus = require('./prometheus'); +// @ts-nocheck +import { getVersion } from './configuration'; +import log from './log'; +import * as store from './store'; +import * as registry from './registry'; +import * as api from './api'; +import * as prometheus from './prometheus'; async function main() { log.info(`WUD is starting (version = ${getVersion()})`); diff --git a/app/jest.config.cjs b/app/jest.config.cjs new file mode 100644 index 00000000..0e705dfb --- /dev/null +++ b/app/jest.config.cjs @@ -0,0 +1,28 @@ +/** @type {import('ts-jest').JestConfigWithTsJest} */ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', + transform: { + // '^.+\\.[tj]sx?$' to process js/ts with `ts-jest` + // '^.+\\.m?[tj]sx?$' to process js/ts/mjs/mts with `ts-jest` + '^.+\\.tsx?$': [ + 'ts-jest', + { + useESM: true, + }, + ], + }, + moduleNameMapper: { + '^(\\.{1,2}/.*)\\.js$': '$1', + }, + extensionsToTreatAsEsm: ['.ts'], + coverageDirectory: 'coverage', + collectCoverageFrom: [ + '**/*.{js,ts}', + '!**/node_modules/**', + '!**/dist/**', + '!**/coverage/**', + '!jest.config.cjs', + ], + testPathIgnorePatterns: ['/dist/', '/node_modules/'], +}; diff --git a/app/log/index.js b/app/log/index.js deleted file mode 100644 index dafb6dc1..00000000 --- a/app/log/index.js +++ /dev/null @@ -1,8 +0,0 @@ -const bunyan = require('bunyan'); -const { getLogLevel } = require('../configuration'); - -// Init Bunyan logger -module.exports = bunyan.createLogger({ - name: 'whats-up-docker', - level: getLogLevel(), -}); diff --git a/app/log/index.test.js b/app/log/index.test.ts similarity index 70% rename from app/log/index.test.js rename to app/log/index.test.ts index 821c6759..287cad5f 100644 --- a/app/log/index.test.js +++ b/app/log/index.test.ts @@ -1,4 +1,5 @@ -const log = require('./index'); +// @ts-nocheck +import log from './index'; // Mock the configuration module jest.mock('../configuration', () => ({ @@ -6,7 +7,7 @@ jest.mock('../configuration', () => ({ })); describe('Logger', () => { - test('should export a bunyan logger instance', () => { + test('should export a bunyan logger instance', async () => { expect(log).toBeDefined(); expect(typeof log.info).toBe('function'); expect(typeof log.warn).toBe('function'); @@ -14,11 +15,11 @@ describe('Logger', () => { expect(typeof log.debug).toBe('function'); }); - test('should have correct logger name', () => { + test('should have correct logger name', async () => { expect(log.fields.name).toBe('whats-up-docker'); }); - test('should have correct log level', () => { + test('should have correct log level', async () => { expect(log.level()).toBe(30); // INFO level in bunyan }); }); diff --git a/app/log/index.ts b/app/log/index.ts new file mode 100644 index 00000000..33ef4b67 --- /dev/null +++ b/app/log/index.ts @@ -0,0 +1,11 @@ +// @ts-nocheck +import bunyan from 'bunyan'; +import { getLogLevel } from '../configuration'; + +// Init Bunyan logger +const logger = bunyan.createLogger({ + name: 'whats-up-docker', + level: getLogLevel(), +}); + +export default logger; diff --git a/app/model/container.test.js b/app/model/container.test.ts similarity index 88% rename from app/model/container.test.js rename to app/model/container.test.ts index 3b0df99d..9aa0801b 100644 --- a/app/model/container.test.js +++ b/app/model/container.test.ts @@ -1,6 +1,7 @@ -const container = require('./container'); +// @ts-nocheck +import * as container from './container'; -test('model should be validated when compliant', () => { +test('model should be validated when compliant', async () => { const containerValidated = container.validate({ id: 'container-123456789', name: 'test', @@ -79,13 +80,13 @@ test('model should be validated when compliant', () => { }); }); -test('model should not be validated when invalid', () => { +test('model should not be validated when invalid', async () => { expect(() => { container.validate({}); }).toThrow(); }); -test('model should flag updateAvailable when tag is different', () => { +test('model should flag updateAvailable when tag is different', async () => { const containerValidated = container.validate({ id: 'container-123456789', name: 'test', @@ -116,7 +117,7 @@ test('model should flag updateAvailable when tag is different', () => { expect(containerValidated.updateAvailable).toBeTruthy(); }); -test('model should not flag updateAvailable when tag is equal', () => { +test('model should not flag updateAvailable when tag is equal', async () => { const containerValidated = container.validate({ id: 'container-123456789', name: 'test', @@ -147,7 +148,7 @@ test('model should not flag updateAvailable when tag is equal', () => { expect(containerValidated.updateAvailable).toBeFalsy(); }); -test('model should flag updateAvailable when digest is different', () => { +test('model should flag updateAvailable when digest is different', async () => { const containerValidated = container.validate({ id: 'container-123456789', name: 'test', @@ -180,7 +181,7 @@ test('model should flag updateAvailable when digest is different', () => { expect(containerValidated.updateAvailable).toBeTruthy(); }); -test('model should flag updateAvailable when created is different', () => { +test('model should flag updateAvailable when created is different', async () => { const containerValidated = container.validate({ id: 'container-123456789', name: 'test', @@ -220,7 +221,7 @@ test('model should flag updateAvailable when created is different', () => { expect(containerValidated.resultChanged(containerDifferent)).toBeTruthy(); }); -test('model should support transforms for links', () => { +test('model should support transforms for links', async () => { const containerValidated = container.validate({ id: 'container-123456789', name: 'test', @@ -256,7 +257,7 @@ test('model should support transforms for links', () => { }); }); -test('flatten should be flatten the nested properties with underscores when called', () => { +test('flatten should be flatten the nested properties with underscores when called', async () => { const containerValidated = container.validate({ id: 'container-123456789', name: 'test', @@ -317,7 +318,7 @@ test('flatten should be flatten the nested properties with underscores when call }); }); -test('fullName should build an id with watcher name & container name when called', () => { +test('fullName should build an id with watcher name & container name when called', async () => { expect( container.fullName({ watcher: 'watcher', @@ -326,8 +327,8 @@ test('fullName should build an id with watcher name & container name when called ).toEqual('watcher_container_name'); }); -test('getLink should render link templates when called', () => { - const getLink = container.__get__('getLink'); +test('getLink should render link templates when called', async () => { + const { testable_getLink: getLink } = container; expect( getLink( { @@ -344,13 +345,13 @@ test('getLink should render link templates when called', () => { ).toEqual('https://test-10.5.2.acme.com'); }); -test('getLink should render undefined when template is missing', () => { - const getLink = container.__get__('getLink'); +test('getLink should render undefined when template is missing', async () => { + const { testable_getLink: getLink } = container; expect(getLink(undefined)).toBeUndefined(); }); -test('addUpdateKindProperty should detect major update', () => { - const addUpdateKindProperty = container.__get__('addUpdateKindProperty'); +test('addUpdateKindProperty should detect major update', async () => { + const { testable_addUpdateKindProperty: addUpdateKindProperty } = container; const containerObject = { updateAvailable: true, image: { @@ -372,8 +373,8 @@ test('addUpdateKindProperty should detect major update', () => { }); }); -test('addUpdateKindProperty should detect minor update', () => { - const addUpdateKindProperty = container.__get__('addUpdateKindProperty'); +test('addUpdateKindProperty should detect minor update', async () => { + const { testable_addUpdateKindProperty: addUpdateKindProperty } = container; const containerObject = { updateAvailable: true, image: { @@ -395,8 +396,8 @@ test('addUpdateKindProperty should detect minor update', () => { }); }); -test('addUpdateKindProperty should detect patch update', () => { - const addUpdateKindProperty = container.__get__('addUpdateKindProperty'); +test('addUpdateKindProperty should detect patch update', async () => { + const { testable_addUpdateKindProperty: addUpdateKindProperty } = container; const containerObject = { updateAvailable: true, image: { @@ -418,8 +419,8 @@ test('addUpdateKindProperty should detect patch update', () => { }); }); -test('addUpdateKindProperty should support transforms', () => { - const addUpdateKindProperty = container.__get__('addUpdateKindProperty'); +test('addUpdateKindProperty should support transforms', async () => { + const { testable_addUpdateKindProperty: addUpdateKindProperty } = container; const containerObject = { transformTags: '^(\\d+\\.\\d+)-.*-(\\d+) => $1.$2', updateAvailable: true, @@ -442,8 +443,8 @@ test('addUpdateKindProperty should support transforms', () => { }); }); -test('addUpdateKindProperty should detect prerelease semver update', () => { - const addUpdateKindProperty = container.__get__('addUpdateKindProperty'); +test('addUpdateKindProperty should detect prerelease semver update', async () => { + const { testable_addUpdateKindProperty: addUpdateKindProperty } = container; const containerObject = { updateAvailable: true, image: { @@ -465,8 +466,8 @@ test('addUpdateKindProperty should detect prerelease semver update', () => { }); }); -test('addUpdateKindProperty should detect digest update', () => { - const addUpdateKindProperty = container.__get__('addUpdateKindProperty'); +test('addUpdateKindProperty should detect digest update', async () => { + const { testable_addUpdateKindProperty: addUpdateKindProperty } = container; const containerObject = { updateAvailable: true, image: { @@ -491,8 +492,8 @@ test('addUpdateKindProperty should detect digest update', () => { }); }); -test('addUpdateKindProperty should return unknown when no image or result', () => { - const addUpdateKindProperty = container.__get__('addUpdateKindProperty'); +test('addUpdateKindProperty should return unknown when no image or result', async () => { + const { testable_addUpdateKindProperty: addUpdateKindProperty } = container; const containerObject = {}; addUpdateKindProperty(containerObject); expect(containerObject.updateKind).toEqual({ @@ -500,8 +501,8 @@ test('addUpdateKindProperty should return unknown when no image or result', () = }); }); -test('addUpdateKindProperty should return unknown when no update available', () => { - const addUpdateKindProperty = container.__get__('addUpdateKindProperty'); +test('addUpdateKindProperty should return unknown when no update available', async () => { + const { testable_addUpdateKindProperty: addUpdateKindProperty } = container; const containerObject = { image: 'image', result: {}, diff --git a/app/model/container.js b/app/model/container.ts similarity index 72% rename from app/model/container.js rename to app/model/container.ts index eb9f4679..a8e2944f 100644 --- a/app/model/container.js +++ b/app/model/container.ts @@ -1,8 +1,69 @@ -const joi = require('joi'); -const flat = require('flat'); -const { snakeCase } = require('snake-case'); -const { parse: parseSemver, diff: diffSemver } = require('../tag'); -const { transform: transformTag } = require('../tag'); +import joi from 'joi'; +import flat from 'flat'; +import { snakeCase } from 'snake-case'; +import * as tag from '../tag'; +const { parse: parseSemver, diff: diffSemver, transform: transformTag } = tag; + +export interface ContainerImage { + id: string; + registry: { + name: string; + url: string; + }; + name: string; + tag: { + value: string; + semver: boolean; + }; + digest: { + watch: boolean; + value?: string; + repo?: string; + }; + architecture: string; + os: string; + variant?: string; + created?: string; +} + +export interface ContainerResult { + tag?: string; + digest?: string; + created?: string; + link?: string; +} + +export interface ContainerUpdateKind { + kind: 'tag' | 'digest' | 'unknown'; + localValue?: string; + remoteValue?: string; + semverDiff?: 'major' | 'minor' | 'patch' | 'prerelease' | 'unknown'; +} + +export interface Container { + id: string; + name: string; + displayName: string; + displayIcon: string; + status: string; + watcher: string; + includeTags?: string; + excludeTags?: string; + transformTags?: string; + linkTemplate?: string; + link?: string; + triggerInclude?: string; + triggerExclude?: string; + image: ContainerImage; + result?: ContainerResult; + error?: { + message: string; + }; + updateAvailable: boolean; + updateKind: ContainerUpdateKind; + labels?: Record; + resultChanged?: (otherContainer: Container | undefined) => boolean; +} // Container data schema const schema = joi.object({ @@ -77,13 +138,15 @@ const schema = joi.object({ * @param container * @returns {undefined|*} */ -function getLink(container, originalTagValue) { +function getLink(container: Container, originalTagValue: string) { if (!container || !container.linkTemplate) { return undefined; } // Export vars for dynamic template interpolation + // eslint-disable-next-line @typescript-eslint/no-unused-vars const raw = originalTagValue; // deprecated, kept for backward compatibility + // eslint-disable-next-line @typescript-eslint/no-unused-vars const original = originalTagValue; const transformed = container.transformTags ? transformTag(container.transformTags, originalTagValue) @@ -95,14 +158,21 @@ function getLink(container, originalTagValue) { if (container.image.tag.semver) { const versionSemver = parseSemver(transformed); - major = versionSemver.major; - minor = versionSemver.minor; - patch = versionSemver.patch; - prerelease = - versionSemver.prerelease && versionSemver.prerelease.length > 0 - ? versionSemver.prerelease[0] - : ''; + if (versionSemver) { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + major = String(versionSemver.major); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + minor = String(versionSemver.minor); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + patch = String(versionSemver.patch); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + prerelease = + versionSemver.prerelease && versionSemver.prerelease.length > 0 + ? String(versionSemver.prerelease[0]) + : ''; + } } + // eslint-disable-next-line no-eval return eval('`' + container.linkTemplate + '`'); } @@ -111,10 +181,10 @@ function getLink(container, originalTagValue) { * @param container * @returns {boolean} */ -function addUpdateAvailableProperty(container) { +function addUpdateAvailableProperty(container: Container) { Object.defineProperty(container, 'updateAvailable', { enumerable: true, - get() { + get(this: Container) { if (this.image === undefined || this.result === undefined) { return false; } @@ -147,7 +217,7 @@ function addUpdateAvailableProperty(container) { ) { const createdDate = new Date(this.image.created).getTime(); const createdDateResult = new Date( - this.result.created, + this.result.created!, ).getTime(); updateAvailable = @@ -163,11 +233,11 @@ function addUpdateAvailableProperty(container) { * @param container * @returns {undefined|*} */ -function addLinkProperty(container) { +function addLinkProperty(container: Container) { if (container.linkTemplate) { Object.defineProperty(container, 'link', { enumerable: true, - get() { + get(this: Container) { return getLink(container, container.image.tag.value); }, }); @@ -176,7 +246,7 @@ function addLinkProperty(container) { Object.defineProperty(container.result, 'link', { enumerable: true, get() { - return getLink(container, container.result.tag); + return getLink(container, container.result.tag ?? ''); }, }); } @@ -188,11 +258,11 @@ function addLinkProperty(container) { * @param container * @returns {{semverDiff: undefined, kind: string, remoteValue: undefined, localValue: undefined}} */ -function addUpdateKindProperty(container) { +function addUpdateKindProperty(container: Container) { Object.defineProperty(container, 'updateKind', { enumerable: true, - get() { - const updateKind = { + get(this: Container) { + const updateKind: ContainerUpdateKind = { kind: 'unknown', localValue: undefined, remoteValue: undefined, @@ -215,7 +285,8 @@ function addUpdateKindProperty(container) { ) { if (container.image.tag.value !== container.result.tag) { updateKind.kind = 'tag'; - let semverDiffWud = 'unknown'; + let semverDiffWud: ContainerUpdateKind['semverDiff'] = + 'unknown'; const isSemver = container.image.tag.semver; if (isSemver) { const semverDiff = diffSemver( @@ -270,12 +341,15 @@ function addUpdateKindProperty(container) { * @param otherContainer * @returns {boolean} */ -function resultChangedFunction(otherContainer) { +function resultChangedFunction( + this: Container, + otherContainer: Container | undefined, +) { return ( otherContainer === undefined || - this.result.tag !== otherContainer.result.tag || - this.result.digest !== otherContainer.result.digest || - this.result.created !== otherContainer.result.created + this.result?.tag !== otherContainer.result?.tag || + this.result?.digest !== otherContainer.result?.digest || + this.result?.created !== otherContainer.result?.created ); } @@ -284,7 +358,7 @@ function resultChangedFunction(otherContainer) { * @param container * @returns {*} */ -function addResultChangedFunction(container) { +function addResultChangedFunction(container: Container) { const containerWithResultChanged = container; containerWithResultChanged.resultChanged = resultChangedFunction; return containerWithResultChanged; @@ -295,14 +369,14 @@ function addResultChangedFunction(container) { * @param container * @returns {*} */ -function validate(container) { +export function validate(container: any): Container { const validation = schema.validate(container); if (validation.error) { throw new Error( `Error when validating container properties ${validation.error}`, ); } - const containerValidated = validation.value; + const containerValidated = validation.value as Container; // Add computed properties addUpdateAvailableProperty(containerValidated); @@ -319,10 +393,10 @@ function validate(container) { * @param container * @returns {*} */ -function flatten(container) { - const containerFlatten = flat(container, { +export function flatten(container: Container) { + const containerFlatten: any = flat(container, { delimiter: '_', - transformKey: (key) => snakeCase(key), + transformKey: (key: string) => snakeCase(key), }); delete containerFlatten.result_changed; return containerFlatten; @@ -333,12 +407,12 @@ function flatten(container) { * @param container * @returns {string} */ -function fullName(container) { +export function fullName(container: Container) { return `${container.watcher}_${container.name}`; } -module.exports = { - validate, - flatten, - fullName, +// The following exports are meant for testing only +export { + getLink as testable_getLink, + addUpdateKindProperty as testable_addUpdateKindProperty, }; diff --git a/app/package-lock.json b/app/package-lock.json index 0a1ae8ec..655ed0bb 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -24,6 +24,7 @@ "flat": "5.0.2", "getmac": "6.6.0", "gotify-client": "0.4.2", + "jiti": "^2.6.1", "joi": "17.13.3", "joi-cron-expression": "1.0.1", "just-debounce": "1.1.0", @@ -50,8 +51,20 @@ "yaml": "2.8.1" }, "devDependencies": { + "@types/bunyan": "^1.8.11", + "@types/cors": "^2.8.19", + "@types/dockerode": "^3.3.47", + "@types/express": "^5.0.6", + "@types/express-session": "^1.18.2", + "@types/jest": "^30.0.0", + "@types/node": "^25.0.3", + "@types/node-cron": "^3.0.11", + "@types/nodemailer": "^7.0.4", + "@types/passport": "^1.0.17", + "@types/semver": "^7.7.1", + "@types/uuid": "^10.0.0", + "@types/yaml": "^1.9.6", "babel-jest": "29.7.0", - "babel-plugin-rewire": "1.2.0", "eslint": "9.39.1", "eslint-config-prettier": "10.1.8", "eslint-plugin-import": "2.32.0", @@ -59,1667 +72,3423 @@ "eslint-plugin-prettier": "5.5.4", "jest": "29.7.0", "nodemon": "3.1.10", - "prettier": "3.6.2" + "prettier": "3.6.2", + "ts-jest": "^29.4.6", + "ts-node": "^10.9.2", + "typescript": "^5.9.3", + "typescript-eslint": "^8.53.1" } }, - "node_modules/@babel/code-frame": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", - "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "node_modules/@aws-crypto/sha256-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-validator-identifier": "^7.27.1", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, - "engines": { - "node": ">=6.9.0" + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" } }, - "node_modules/@babel/compat-data": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", - "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, "engines": { - "node": ">=6.9.0" + "node": ">=14.0.0" } }, - "node_modules/@babel/core": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", - "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", "dev": true, - "license": "MIT", - "peer": true, + "license": "Apache-2.0", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.5", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-module-transforms": "^7.28.3", - "@babel/helpers": "^7.28.4", - "@babel/parser": "^7.28.5", - "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.5", - "@babel/types": "^7.28.5", - "@jridgewell/remapping": "^2.3.5", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" + "node": ">=14.0.0" } }, - "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" } }, - "node_modules/@babel/generator": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", - "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", + "node_modules/@aws-crypto/sha256-js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@babel/parser": "^7.28.5", - "@babel/types": "^7.28.5", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=16.0.0" } }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", - "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "node_modules/@aws-crypto/supports-web-crypto": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@babel/compat-data": "^7.27.2", - "@babel/helper-validator-option": "^7.27.1", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" + "tslib": "^2.6.2" } }, - "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "node_modules/@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" } }, - "node_modules/@babel/helper-globals": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "node_modules/@aws-crypto/util/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, "engines": { - "node": ">=6.9.0" + "node": ">=14.0.0" } }, - "node_modules/@babel/helper-module-imports": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", - "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=14.0.0" } }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", - "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.28.3" + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "node": ">=14.0.0" } }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", - "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "node_modules/@aws-sdk/client-sesv2": { + "version": "3.966.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sesv2/-/client-sesv2-3.966.0.tgz", + "integrity": "sha512-M1xu5gcGmaE1gGYHydODnlWz1YWgnzjfClrpzgCaLpWqGriH1dqFyGw0cyCV93jli0UbzyPrNVgb7aTphEjHvg==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.966.0", + "@aws-sdk/credential-provider-node": "3.966.0", + "@aws-sdk/middleware-host-header": "3.965.0", + "@aws-sdk/middleware-logger": "3.965.0", + "@aws-sdk/middleware-recursion-detection": "3.965.0", + "@aws-sdk/middleware-user-agent": "3.966.0", + "@aws-sdk/region-config-resolver": "3.965.0", + "@aws-sdk/signature-v4-multi-region": "3.966.0", + "@aws-sdk/types": "3.965.0", + "@aws-sdk/util-endpoints": "3.965.0", + "@aws-sdk/util-user-agent-browser": "3.965.0", + "@aws-sdk/util-user-agent-node": "3.966.0", + "@smithy/config-resolver": "^4.4.5", + "@smithy/core": "^3.20.1", + "@smithy/fetch-http-handler": "^5.3.8", + "@smithy/hash-node": "^4.2.7", + "@smithy/invalid-dependency": "^4.2.7", + "@smithy/middleware-content-length": "^4.2.7", + "@smithy/middleware-endpoint": "^4.4.2", + "@smithy/middleware-retry": "^4.4.18", + "@smithy/middleware-serde": "^4.2.8", + "@smithy/middleware-stack": "^4.2.7", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/node-http-handler": "^4.4.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/smithy-client": "^4.10.3", + "@smithy/types": "^4.11.0", + "@smithy/url-parser": "^4.2.7", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.17", + "@smithy/util-defaults-mode-node": "^4.2.20", + "@smithy/util-endpoints": "^3.2.7", + "@smithy/util-middleware": "^4.2.7", + "@smithy/util-retry": "^4.2.7", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso": { + "version": "3.966.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.966.0.tgz", + "integrity": "sha512-hQZDQgqRJclALDo9wK+bb5O+VpO8JcjImp52w9KPSz9XveNRgE9AYfklRJd8qT2Bwhxe6IbnqYEino2wqUMA1w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.966.0", + "@aws-sdk/middleware-host-header": "3.965.0", + "@aws-sdk/middleware-logger": "3.965.0", + "@aws-sdk/middleware-recursion-detection": "3.965.0", + "@aws-sdk/middleware-user-agent": "3.966.0", + "@aws-sdk/region-config-resolver": "3.965.0", + "@aws-sdk/types": "3.965.0", + "@aws-sdk/util-endpoints": "3.965.0", + "@aws-sdk/util-user-agent-browser": "3.965.0", + "@aws-sdk/util-user-agent-node": "3.966.0", + "@smithy/config-resolver": "^4.4.5", + "@smithy/core": "^3.20.1", + "@smithy/fetch-http-handler": "^5.3.8", + "@smithy/hash-node": "^4.2.7", + "@smithy/invalid-dependency": "^4.2.7", + "@smithy/middleware-content-length": "^4.2.7", + "@smithy/middleware-endpoint": "^4.4.2", + "@smithy/middleware-retry": "^4.4.18", + "@smithy/middleware-serde": "^4.2.8", + "@smithy/middleware-stack": "^4.2.7", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/node-http-handler": "^4.4.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/smithy-client": "^4.10.3", + "@smithy/types": "^4.11.0", + "@smithy/url-parser": "^4.2.7", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.17", + "@smithy/util-defaults-mode-node": "^4.2.20", + "@smithy/util-endpoints": "^3.2.7", + "@smithy/util-middleware": "^4.2.7", + "@smithy/util-retry": "^4.2.7", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/core": { + "version": "3.966.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.966.0.tgz", + "integrity": "sha512-QaRVBHD1prdrFXIeFAY/1w4b4S0EFyo/ytzU+rCklEjMRT7DKGXGoHXTWLGz+HD7ovlS5u+9cf8a/LeSOEMzww==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.965.0", + "@aws-sdk/xml-builder": "3.965.0", + "@smithy/core": "^3.20.1", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/property-provider": "^4.2.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/signature-v4": "^5.3.7", + "@smithy/smithy-client": "^4.10.3", + "@smithy/types": "^4.11.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-middleware": "^4.2.7", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=6.9.0" + "node": ">=18.0.0" } }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.966.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.966.0.tgz", + "integrity": "sha512-sxVKc9PY0SH7jgN/8WxhbKQ7MWDIgaJv1AoAKJkhJ+GM5r09G5Vb2Vl8ALYpsy+r8b+iYpq5dGJj8k2VqxoQMg==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.966.0", + "@aws-sdk/types": "3.965.0", + "@smithy/property-provider": "^4.2.7", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=6.9.0" + "node": ">=18.0.0" } }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.966.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.966.0.tgz", + "integrity": "sha512-VTJDP1jOibVtc5pn5TNE12rhqOO/n10IjkoJi8fFp9BMfmh3iqo70Ppvphz/Pe/R9LcK5Z3h0Z4EB9IXDR6kag==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.966.0", + "@aws-sdk/types": "3.965.0", + "@smithy/fetch-http-handler": "^5.3.8", + "@smithy/node-http-handler": "^4.4.7", + "@smithy/property-provider": "^4.2.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/smithy-client": "^4.10.3", + "@smithy/types": "^4.11.0", + "@smithy/util-stream": "^4.5.8", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=6.9.0" + "node": ">=18.0.0" } }, - "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.966.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.966.0.tgz", + "integrity": "sha512-4oQKkYMCUx0mffKuH8LQag1M4Fo5daKVmsLAnjrIqKh91xmCrcWlAFNMgeEYvI1Yy125XeNSaFMfir6oNc2ODA==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.966.0", + "@aws-sdk/credential-provider-env": "3.966.0", + "@aws-sdk/credential-provider-http": "3.966.0", + "@aws-sdk/credential-provider-login": "3.966.0", + "@aws-sdk/credential-provider-process": "3.966.0", + "@aws-sdk/credential-provider-sso": "3.966.0", + "@aws-sdk/credential-provider-web-identity": "3.966.0", + "@aws-sdk/nested-clients": "3.966.0", + "@aws-sdk/types": "3.965.0", + "@smithy/credential-provider-imds": "^4.2.7", + "@smithy/property-provider": "^4.2.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=6.9.0" + "node": ">=18.0.0" } }, - "node_modules/@babel/helpers": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", - "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "node_modules/@aws-sdk/credential-provider-login": { + "version": "3.966.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.966.0.tgz", + "integrity": "sha512-wD1KlqLyh23Xfns/ZAPxebwXixoJJCuDbeJHFrLDpP4D4h3vA2S8nSFgBSFR15q9FhgRfHleClycf6g5K4Ww6w==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.4" + "@aws-sdk/core": "3.966.0", + "@aws-sdk/nested-clients": "3.966.0", + "@aws-sdk/types": "3.965.0", + "@smithy/property-provider": "^4.2.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=18.0.0" } }, - "node_modules/@babel/parser": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", - "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.966.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.966.0.tgz", + "integrity": "sha512-7QCOERGddMw7QbjE+LSAFgwOBpPv4px2ty0GCK7ZiPJGsni2EYmM4TtYnQb9u1WNHmHqIPWMbZR0pKDbyRyHlQ==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@babel/types": "^7.28.5" - }, - "bin": { - "parser": "bin/babel-parser.js" + "@aws-sdk/credential-provider-env": "3.966.0", + "@aws-sdk/credential-provider-http": "3.966.0", + "@aws-sdk/credential-provider-ini": "3.966.0", + "@aws-sdk/credential-provider-process": "3.966.0", + "@aws-sdk/credential-provider-sso": "3.966.0", + "@aws-sdk/credential-provider-web-identity": "3.966.0", + "@aws-sdk/types": "3.965.0", + "@smithy/credential-provider-imds": "^4.2.7", + "@smithy/property-provider": "^4.2.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.0.0" + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.966.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.966.0.tgz", + "integrity": "sha512-q5kCo+xHXisNbbPAh/DiCd+LZX4wdby77t7GLk0b2U0/mrel4lgy6o79CApe+0emakpOS1nPZS7voXA7vGPz4w==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@aws-sdk/core": "3.966.0", + "@aws-sdk/types": "3.965.0", + "@smithy/property-provider": "^4.2.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.966.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.966.0.tgz", + "integrity": "sha512-Rv5aEfbpqsQZzxpX2x+FbSyVFOE3Dngome+exNA8jGzc00rrMZEUnm3J3yAsLp/I2l7wnTfI0r2zMe+T9/nZAQ==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@aws-sdk/client-sso": "3.966.0", + "@aws-sdk/core": "3.966.0", + "@aws-sdk/token-providers": "3.966.0", + "@aws-sdk/types": "3.965.0", + "@smithy/property-provider": "^4.2.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.966.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.966.0.tgz", + "integrity": "sha512-Yv1lc9iic9xg3ywMmIAeXN1YwuvfcClLVdiF2y71LqUgIOupW8B8my84XJr6pmOQuKzZa++c2znNhC9lGsbKyw==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" + "@aws-sdk/core": "3.966.0", + "@aws-sdk/nested-clients": "3.966.0", + "@aws-sdk/types": "3.965.0", + "@smithy/property-provider": "^4.2.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "node_modules/@aws-sdk/middleware-host-header": { + "version": "3.965.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.965.0.tgz", + "integrity": "sha512-SfpSYqoPOAmdb3DBsnNsZ0vix+1VAtkUkzXM79JL3R5IfacpyKE2zytOgVAQx/FjhhlpSTwuXd+LRhUEVb3MaA==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" + "@aws-sdk/types": "3.965.0", + "@smithy/protocol-http": "^5.3.7", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", - "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", + "node_modules/@aws-sdk/middleware-logger": { + "version": "3.965.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.965.0.tgz", + "integrity": "sha512-gjUvJRZT1bUABKewnvkj51LAynFrfz2h5DYAg5/2F4Utx6UOGByTSr9Rq8JCLbURvvzAbCtcMkkIJRxw+8Zuzw==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@aws-sdk/types": "3.965.0", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.965.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.965.0.tgz", + "integrity": "sha512-6dvD+18Ni14KCRu+tfEoNxq1sIGVp9tvoZDZ7aMvpnA7mDXuRLrOjRQ/TAZqXwr9ENKVGyxcPl0cRK8jk1YWjA==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" + "@aws-sdk/types": "3.965.0", + "@aws/lambda-invoke-store": "^0.2.2", + "@smithy/protocol-http": "^5.3.7", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "node_modules/@aws-sdk/middleware-sdk-s3": { + "version": "3.966.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.966.0.tgz", + "integrity": "sha512-9N9zncsY5ydDCRatKdrPZcdCwNWt7TdHmqgwQM52PuA5gs1HXWwLLNDy/51H+9RTHi7v6oly+x9utJ/qypCh2g==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@aws-sdk/core": "3.966.0", + "@aws-sdk/types": "3.965.0", + "@aws-sdk/util-arn-parser": "3.966.0", + "@smithy/core": "^3.20.1", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/signature-v4": "^5.3.7", + "@smithy/smithy-client": "^4.10.3", + "@smithy/types": "^4.11.0", + "@smithy/util-config-provider": "^4.2.0", + "@smithy/util-middleware": "^4.2.7", + "@smithy/util-stream": "^4.5.8", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", - "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", + "node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.966.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.966.0.tgz", + "integrity": "sha512-MvGoy0vhMluVpSB5GaGJbYLqwbZfZjwEZhneDHdPhgCgQqmCtugnYIIjpUw7kKqWGsmaMQmNEgSFf1zYYmwOyg==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@aws-sdk/core": "3.966.0", + "@aws-sdk/types": "3.965.0", + "@aws-sdk/util-endpoints": "3.965.0", + "@smithy/core": "^3.20.1", + "@smithy/protocol-http": "^5.3.7", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients": { + "version": "3.966.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.966.0.tgz", + "integrity": "sha512-FRzAWwLNoKiaEWbYhnpnfartIdOgiaBLnPcd3uG1Io+vvxQUeRPhQIy4EfKnT3AuA+g7gzSCjMG2JKoJOplDtQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.966.0", + "@aws-sdk/middleware-host-header": "3.965.0", + "@aws-sdk/middleware-logger": "3.965.0", + "@aws-sdk/middleware-recursion-detection": "3.965.0", + "@aws-sdk/middleware-user-agent": "3.966.0", + "@aws-sdk/region-config-resolver": "3.965.0", + "@aws-sdk/types": "3.965.0", + "@aws-sdk/util-endpoints": "3.965.0", + "@aws-sdk/util-user-agent-browser": "3.965.0", + "@aws-sdk/util-user-agent-node": "3.966.0", + "@smithy/config-resolver": "^4.4.5", + "@smithy/core": "^3.20.1", + "@smithy/fetch-http-handler": "^5.3.8", + "@smithy/hash-node": "^4.2.7", + "@smithy/invalid-dependency": "^4.2.7", + "@smithy/middleware-content-length": "^4.2.7", + "@smithy/middleware-endpoint": "^4.4.2", + "@smithy/middleware-retry": "^4.4.18", + "@smithy/middleware-serde": "^4.2.8", + "@smithy/middleware-stack": "^4.2.7", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/node-http-handler": "^4.4.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/smithy-client": "^4.10.3", + "@smithy/types": "^4.11.0", + "@smithy/url-parser": "^4.2.7", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.17", + "@smithy/util-defaults-mode-node": "^4.2.20", + "@smithy/util-endpoints": "^3.2.7", + "@smithy/util-middleware": "^4.2.7", + "@smithy/util-retry": "^4.2.7", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/region-config-resolver": { + "version": "3.965.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.965.0.tgz", + "integrity": "sha512-RoMhu9ly2B0coxn8ctXosPP2WmDD0MkQlZGLjoYHQUOCBmty5qmCxOqBmBDa6wbWbB8xKtMQ/4VXloQOgzjHXg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.965.0", + "@smithy/config-resolver": "^4.4.5", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "node_modules/@aws-sdk/signature-v4-multi-region": { + "version": "3.966.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.966.0.tgz", + "integrity": "sha512-VNSpyfKtDiBg/nPwSXDvnjISaDE9mI8zhOK3C4/obqh8lK1V6j04xDlwyIWbbIM0f6VgV1FVixlghtJB79eBqA==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" + "@aws-sdk/middleware-sdk-s3": "3.966.0", + "@aws-sdk/types": "3.965.0", + "@smithy/protocol-http": "^5.3.7", + "@smithy/signature-v4": "^5.3.7", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "node_modules/@aws-sdk/token-providers": { + "version": "3.966.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.966.0.tgz", + "integrity": "sha512-8k5cBTicTGYJHhKaweO4gL4fud1KDnLS5fByT6/Xbiu59AxYM4E/h3ds+3jxDMnniCE3gIWpEnyfM9khtmw2lA==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@aws-sdk/core": "3.966.0", + "@aws-sdk/nested-clients": "3.966.0", + "@aws-sdk/types": "3.965.0", + "@smithy/property-provider": "^4.2.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "node_modules/@aws-sdk/types": { + "version": "3.965.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.965.0.tgz", + "integrity": "sha512-jvodoJdMavvg8faN7co58vVJRO5MVep4JFPRzUNCzpJ98BDqWDk/ad045aMJcmxkLzYLS2UAnUmqjJ/tUPNlzQ==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "node_modules/@aws-sdk/util-arn-parser": { + "version": "3.966.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.966.0.tgz", + "integrity": "sha512-WcCLdKBK2nHhtOPE8du5XjOXaOToxGF3Ge8rgK2jaRpjkzjS0/mO+Jp2H4+25hOne3sP2twBu5BrvD9KoXQ5LQ==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "tslib": "^2.6.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "node_modules/@aws-sdk/util-endpoints": { + "version": "3.965.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.965.0.tgz", + "integrity": "sha512-WqSCB0XIsGUwZWvrYkuoofi2vzoVHqyeJ2kN+WyoOsxPLTiQSBIoqm/01R/qJvoxwK/gOOF7su9i84Vw2NQQpQ==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@aws-sdk/types": "3.965.0", + "@smithy/types": "^4.11.0", + "@smithy/url-parser": "^4.2.7", + "@smithy/util-endpoints": "^3.2.7", + "tslib": "^2.6.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "node_modules/@aws-sdk/util-locate-window": { + "version": "3.965.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.965.0.tgz", + "integrity": "sha512-9LJFand4bIoOjOF4x3wx0UZYiFZRo4oUauxQSiEX2dVg+5qeBOJSjp2SeWykIE6+6frCZ5wvWm2fGLK8D32aJw==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "tslib": "^2.6.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.965.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.965.0.tgz", + "integrity": "sha512-Xiza/zMntQGpkd2dETQeAK8So1pg5+STTzpcdGWxj5q0jGO5ayjqT/q1Q7BrsX5KIr6PvRkl9/V7lLCv04wGjQ==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" + "@aws-sdk/types": "3.965.0", + "@smithy/types": "^4.11.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.966.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.966.0.tgz", + "integrity": "sha512-vPPe8V0GLj+jVS5EqFz2NUBgWH35favqxliUOvhp8xBdNRkEjiZm5TqitVtFlxS4RrLY3HOndrWbrP5ejbwl1Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-user-agent": "3.966.0", + "@aws-sdk/types": "3.965.0", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=18.0.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } } }, - "node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "node_modules/@aws-sdk/xml-builder": { + "version": "3.965.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.965.0.tgz", + "integrity": "sha512-Tcod25/BTupraQwtb+Q+GX8bmEZfxIFjjJ/AvkhUZsZlkPeVluzq1uu3Oeqf145DCdMjzLIN6vab5MrykbDP+g==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" + "@smithy/types": "^4.11.0", + "fast-xml-parser": "5.2.5", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-syntax-typescript": { + "node_modules/@aws/lambda-invoke-store": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.3.tgz", + "integrity": "sha512-oLvsaPMTBejkkmHhjf09xTgk71mOqyr/409NKhRIL08If7AhVfUsJhVsx386uJaqNd42v9kWamQ9lFbkoC2dYw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@babel/code-frame": { "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", - "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/runtime": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", - "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", + "node_modules/@babel/compat-data": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", + "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/template": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", - "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "node_modules/@babel/core": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", + "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", - "@babel/parser": "^7.27.2", - "@babel/types": "^7.27.1" + "@babel/generator": "^7.28.5", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" }, "engines": { "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" } }, - "node_modules/@babel/traverse": { + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", - "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", + "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.5", - "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", - "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", - "debug": "^4.3.1" + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/types": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", - "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@balena/dockerignore": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@balena/dockerignore/-/dockerignore-1.0.2.tgz", - "integrity": "sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q==", - "license": "Apache-2.0" + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } }, - "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==", + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } }, - "node_modules/@cypress/request": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.9.tgz", - "integrity": "sha512-I3l7FdGRXluAS44/0NguwWlO83J18p0vlr2FYHrJkWdNYhgVoiYo61IXPqaOsL+vNxU1ZqMACzItGK3/KKDsdw==", - "license": "Apache-2.0", - "dependencies": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~4.0.4", - "http-signature": "~1.4.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "performance-now": "^2.1.0", - "qs": "6.14.0", - "safe-buffer": "^5.1.2", - "tough-cookie": "^5.0.0", - "tunnel-agent": "^0.6.0", - "uuid": "^8.3.2" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/@cypress/request-promise": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@cypress/request-promise/-/request-promise-5.0.0.tgz", - "integrity": "sha512-eKdYVpa9cBEw2kTBlHeu1PP16Blwtum6QHg/u9s/MoHkZfuo1pRGka1VlUHXF5kdew82BvOJVVGk0x8X0nbp+w==", - "license": "ISC", - "dependencies": { - "bluebird": "^3.5.0", - "request-promise-core": "1.1.3", - "stealthy-require": "^1.1.1", - "tough-cookie": "^4.1.3" - }, - "engines": { - "node": ">=0.10.0" - }, - "peerDependencies": { - "@cypress/request": "^3.0.0" - } - }, - "node_modules/@cypress/request-promise/node_modules/tough-cookie": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", - "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", - "license": "BSD-3-Clause", + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, + "license": "MIT", "dependencies": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.2.0", - "url-parse": "^1.5.3" + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" }, "engines": { - "node": ">=6" + "node": ">=6.9.0" } }, - "node_modules/@cypress/request/node_modules/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", - "license": "BSD-3-Clause", + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "dev": true, + "license": "MIT", "dependencies": { - "side-channel": "^1.1.0" + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" }, "engines": { - "node": ">=0.6" + "node": ">=6.9.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/@cypress/request/node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", - "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "dev": true, "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^3.4.3" - }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + "node": ">=6.9.0" } }, - "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": ">=6.9.0" } }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", - "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", "dev": true, "license": "MIT", "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + "node": ">=6.9.0" } }, - "node_modules/@eslint/config-array": { - "version": "0.21.1", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", - "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "node_modules/@babel/helpers": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@eslint/object-schema": "^2.1.7", - "debug": "^4.3.1", - "minimatch": "^3.1.2" + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=6.9.0" } }, - "node_modules/@eslint/config-helpers": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", - "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "node_modules/@babel/parser": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@eslint/core": "^0.17.0" + "@babel/types": "^7.28.5" + }, + "bin": { + "parser": "bin/babel-parser.js" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=6.0.0" } }, - "node_modules/@eslint/core": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", - "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@types/json-schema": "^7.0.15" + "@babel/helper-plugin-utils": "^7.8.0" }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@eslint/eslintrc": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", - "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", "dev": true, "license": "MIT", "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^10.0.1", - "globals": "^14.0.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "@babel/helper-plugin-utils": "^7.8.0" }, - "funding": { - "url": "https://opencollective.com/eslint" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@eslint/eslintrc/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, - "license": "Python-2.0" - }, - "node_modules/@eslint/eslintrc/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==", + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", "dev": true, "license": "MIT", "dependencies": { - "argparse": "^2.0.1" + "@babel/helper-plugin-utils": "^7.12.13" }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@eslint/js": { - "version": "9.39.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.1.tgz", - "integrity": "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==", + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", "dev": true, "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=6.9.0" }, - "funding": { - "url": "https://eslint.org/donate" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@eslint/object-schema": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", - "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", + "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", + "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", + "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", + "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", + "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.5", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@balena/dockerignore": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@balena/dockerignore/-/dockerignore-1.0.2.tgz", + "integrity": "sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q==", + "license": "Apache-2.0" + }, + "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, + "license": "MIT" + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@cypress/request": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.9.tgz", + "integrity": "sha512-I3l7FdGRXluAS44/0NguwWlO83J18p0vlr2FYHrJkWdNYhgVoiYo61IXPqaOsL+vNxU1ZqMACzItGK3/KKDsdw==", + "license": "Apache-2.0", + "dependencies": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~4.0.4", + "http-signature": "~1.4.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "performance-now": "^2.1.0", + "qs": "6.14.0", + "safe-buffer": "^5.1.2", + "tough-cookie": "^5.0.0", + "tunnel-agent": "^0.6.0", + "uuid": "^8.3.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@cypress/request-promise": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@cypress/request-promise/-/request-promise-5.0.0.tgz", + "integrity": "sha512-eKdYVpa9cBEw2kTBlHeu1PP16Blwtum6QHg/u9s/MoHkZfuo1pRGka1VlUHXF5kdew82BvOJVVGk0x8X0nbp+w==", + "license": "ISC", + "dependencies": { + "bluebird": "^3.5.0", + "request-promise-core": "1.1.3", + "stealthy-require": "^1.1.1", + "tough-cookie": "^4.1.3" + }, + "engines": { + "node": ">=0.10.0" + }, + "peerDependencies": { + "@cypress/request": "^3.0.0" + } + }, + "node_modules/@cypress/request-promise/node_modules/tough-cookie": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", + "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", + "license": "BSD-3-Clause", + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@cypress/request/node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/@cypress/request/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/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, + "license": "Python-2.0" + }, + "node_modules/@eslint/eslintrc/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, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.1.tgz", + "integrity": "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@grpc/grpc-js": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.0.tgz", + "integrity": "sha512-N8Jx6PaYzcTRNzirReJCtADVoq4z7+1KQ4E70jTg/koQiMoUSN1kbNjPOqpPbhMFhfU1/l7ixspPl8dNY+FoUg==", + "license": "Apache-2.0", + "dependencies": { + "@grpc/proto-loader": "^0.8.0", + "@js-sdsl/ordered-map": "^4.4.2" + }, + "engines": { + "node": ">=12.10.0" + } + }, + "node_modules/@grpc/grpc-js/node_modules/@grpc/proto-loader": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.8.0.tgz", + "integrity": "sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ==", + "license": "Apache-2.0", + "dependencies": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.5.3", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@grpc/proto-loader": { + "version": "0.7.15", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.15.tgz", + "integrity": "sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ==", + "license": "Apache-2.0", + "dependencies": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.2.5", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@hapi/topo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", + "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/diff-sequences": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz", + "integrity": "sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/get-type": { + "version": "30.1.0", + "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.1.0.tgz", + "integrity": "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/pattern": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", + "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-regex-util": "30.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/pattern/node_modules/jest-regex-util": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", + "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/reporters/node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "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, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@js-sdsl/ordered-map": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", + "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, + "node_modules/@opentelemetry/api": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", + "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", + "license": "Apache-2.0", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@panva/asn1.js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@panva/asn1.js/-/asn1.js-1.0.0.tgz", + "integrity": "sha512-UdkG3mLEqXgnlKsWanWcgb6dOjUzJ+XC5f+aWw30qrtjxeNUSfKX1cd5FBzOaXQumoe9nIqeZUvrRJS03HCCtw==", + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/@pkgr/core": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", + "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/pkgr" + } + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", + "license": "BSD-3-Clause" + }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sideway/address": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", + "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@sideway/formula": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", + "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==", + "license": "BSD-3-Clause" + }, + "node_modules/@sideway/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@slack/logger": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@slack/logger/-/logger-4.0.0.tgz", + "integrity": "sha512-Wz7QYfPAlG/DR+DfABddUZeNgoeY7d1J39OCR2jR+v7VBsB8ezulDK5szTnDDPDwLH5IWhLvXIHlCFZV7MSKgA==", + "license": "MIT", + "dependencies": { + "@types/node": ">=18.0.0" + }, + "engines": { + "node": ">= 18", + "npm": ">= 8.6.0" + } + }, + "node_modules/@slack/types": { + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/@slack/types/-/types-2.18.0.tgz", + "integrity": "sha512-ZKrdeoppbM+3l2KKOi4/3oFYKCEwiW3dQfdHZDcecJ9rAmEqWPnARYmac9taZNitb0xnSgu6GOpHgwaKI8se2g==", + "license": "MIT", + "engines": { + "node": ">= 12.13.0", + "npm": ">= 6.12.0" + } + }, + "node_modules/@slack/web-api": { + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/@slack/web-api/-/web-api-7.12.0.tgz", + "integrity": "sha512-LrDxjYyqjeYYQGVdVZ6EYHunFmzveOr2pFpShr6TzW4KNFpdNNnpKekjtMg0PJlOsMibSySLGQqiBZQDasmRCA==", + "license": "MIT", + "dependencies": { + "@slack/logger": "^4.0.0", + "@slack/types": "^2.18.0", + "@types/node": ">=18.0.0", + "@types/retry": "0.12.0", + "axios": "^1.11.0", + "eventemitter3": "^5.0.1", + "form-data": "^4.0.4", + "is-electron": "2.2.2", + "is-stream": "^2", + "p-queue": "^6", + "p-retry": "^4", + "retry": "^0.13.1" + }, + "engines": { + "node": ">= 18", + "npm": ">= 8.6.0" + } + }, + "node_modules/@smithy/abort-controller": { + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.7.tgz", + "integrity": "sha512-rzMY6CaKx2qxrbYbqjXWS0plqEy7LOdKHS0bg4ixJ6aoGDPNUcLWk/FRNuCILh7GKLG9TFUXYYeQQldMBBwuyw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/config-resolver": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.5.tgz", + "integrity": "sha512-HAGoUAFYsUkoSckuKbCPayECeMim8pOu+yLy1zOxt1sifzEbrsRpYa+mKcMdiHKMeiqOibyPG0sFJnmaV/OGEg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.7", + "@smithy/types": "^4.11.0", + "@smithy/util-config-provider": "^4.2.0", + "@smithy/util-endpoints": "^3.2.7", + "@smithy/util-middleware": "^4.2.7", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/core": { + "version": "3.20.2", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.20.2.tgz", + "integrity": "sha512-nc99TseyTwL1bg+T21cyEA5oItNy1XN4aUeyOlXJnvyRW5VSK1oRKRoSM/Iq0KFPuqZMxjBemSZHZCOZbSyBMw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/middleware-serde": "^4.2.8", + "@smithy/protocol-http": "^5.3.7", + "@smithy/types": "^4.11.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-middleware": "^4.2.7", + "@smithy/util-stream": "^4.5.8", + "@smithy/util-utf8": "^4.2.0", + "@smithy/uuid": "^1.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds": { + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.7.tgz", + "integrity": "sha512-CmduWdCiILCRNbQWFR0OcZlUPVtyE49Sr8yYL0rZQ4D/wKxiNzBNS/YHemvnbkIWj623fplgkexUd/c9CAKdoA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.7", + "@smithy/property-provider": "^4.2.7", + "@smithy/types": "^4.11.0", + "@smithy/url-parser": "^4.2.7", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/fetch-http-handler": { + "version": "5.3.8", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.8.tgz", + "integrity": "sha512-h/Fi+o7mti4n8wx1SR6UHWLaakwHRx29sizvp8OOm7iqwKGFneT06GCSFhml6Bha5BT6ot5pj3CYZnCHhGC2Rg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.7", + "@smithy/querystring-builder": "^4.2.7", + "@smithy/types": "^4.11.0", + "@smithy/util-base64": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-node": { + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.7.tgz", + "integrity": "sha512-PU/JWLTBCV1c8FtB8tEFnY4eV1tSfBc7bDBADHfn1K+uRbPgSJ9jnJp0hyjiFN2PMdPzxsf1Fdu0eo9fJ760Xw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.11.0", + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/invalid-dependency": { + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.7.tgz", + "integrity": "sha512-ncvgCr9a15nPlkhIUx3CU4d7E7WEuVJOV7fS7nnK2hLtPK9tYRBkMHQbhXU1VvvKeBm/O0x26OEoBq+ngFpOEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/is-array-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.0.tgz", + "integrity": "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==", "dev": true, "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=18.0.0" } }, - "node_modules/@eslint/plugin-kit": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", - "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "node_modules/@smithy/middleware-content-length": { + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.7.tgz", + "integrity": "sha512-GszfBfCcvt7kIbJ41LuNa5f0wvQCHhnGx/aDaZJCCT05Ld6x6U2s0xsc/0mBFONBZjQJp2U/0uSJ178OXOwbhg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.17.0", - "levn": "^0.4.1" + "@smithy/protocol-http": "^5.3.7", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=18.0.0" } }, - "node_modules/@grpc/grpc-js": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.0.tgz", - "integrity": "sha512-N8Jx6PaYzcTRNzirReJCtADVoq4z7+1KQ4E70jTg/koQiMoUSN1kbNjPOqpPbhMFhfU1/l7ixspPl8dNY+FoUg==", + "node_modules/@smithy/middleware-endpoint": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.3.tgz", + "integrity": "sha512-Zb8R35hjBhp1oFhiaAZ9QhClpPHdEDmNDC2UrrB2fqV0oNDUUPH12ovZHB5xi/Rd+pg/BJHOR1q+SfsieSKPQg==", + "dev": true, "license": "Apache-2.0", "dependencies": { - "@grpc/proto-loader": "^0.8.0", - "@js-sdsl/ordered-map": "^4.4.2" + "@smithy/core": "^3.20.2", + "@smithy/middleware-serde": "^4.2.8", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", + "@smithy/url-parser": "^4.2.7", + "@smithy/util-middleware": "^4.2.7", + "tslib": "^2.6.2" }, "engines": { - "node": ">=12.10.0" + "node": ">=18.0.0" } }, - "node_modules/@grpc/grpc-js/node_modules/@grpc/proto-loader": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.8.0.tgz", - "integrity": "sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ==", + "node_modules/@smithy/middleware-retry": { + "version": "4.4.19", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.19.tgz", + "integrity": "sha512-QtisFIjIw2tjMm/ESatjWFVIQb5Xd093z8xhxq/SijLg7Mgo2C2wod47Ib/AHpBLFhwYXPzd7Hp2+JVXfeZyMQ==", + "dev": true, "license": "Apache-2.0", "dependencies": { - "lodash.camelcase": "^4.3.0", - "long": "^5.0.0", - "protobufjs": "^7.5.3", - "yargs": "^17.7.2" + "@smithy/node-config-provider": "^4.3.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/service-error-classification": "^4.2.7", + "@smithy/smithy-client": "^4.10.4", + "@smithy/types": "^4.11.0", + "@smithy/util-middleware": "^4.2.7", + "@smithy/util-retry": "^4.2.7", + "@smithy/uuid": "^1.1.0", + "tslib": "^2.6.2" }, - "bin": { - "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-serde": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.8.tgz", + "integrity": "sha512-8rDGYen5m5+NV9eHv9ry0sqm2gI6W7mc1VSFMtn6Igo25S507/HaOX9LTHAS2/J32VXD0xSzrY0H5FJtOMS4/w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.7", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6" + "node": ">=18.0.0" } }, - "node_modules/@grpc/proto-loader": { - "version": "0.7.15", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.15.tgz", - "integrity": "sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ==", + "node_modules/@smithy/middleware-stack": { + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.7.tgz", + "integrity": "sha512-bsOT0rJ+HHlZd9crHoS37mt8qRRN/h9jRve1SXUhVbkRzu0QaNYZp1i1jha4n098tsvROjcwfLlfvcFuJSXEsw==", + "dev": true, "license": "Apache-2.0", "dependencies": { - "lodash.camelcase": "^4.3.0", - "long": "^5.0.0", - "protobufjs": "^7.2.5", - "yargs": "^17.7.2" + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" }, - "bin": { - "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-config-provider": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.7.tgz", + "integrity": "sha512-7r58wq8sdOcrwWe+klL9y3bc4GW1gnlfnFOuL7CXa7UzfhzhxKuzNdtqgzmTV+53lEp9NXh5hY/S4UgjLOzPfw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.2.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6" + "node": ">=18.0.0" } }, - "node_modules/@hapi/hoek": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", - "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", - "license": "BSD-3-Clause" + "node_modules/@smithy/node-http-handler": { + "version": "4.4.7", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.7.tgz", + "integrity": "sha512-NELpdmBOO6EpZtWgQiHjoShs1kmweaiNuETUpuup+cmm/xJYjT4eUjfhrXRP4jCOaAsS3c3yPsP3B+K+/fyPCQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^4.2.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/querystring-builder": "^4.2.7", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@hapi/topo": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", - "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", - "license": "BSD-3-Clause", + "node_modules/@smithy/property-provider": { + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.7.tgz", + "integrity": "sha512-jmNYKe9MGGPoSl/D7JDDs1C8b3dC8f/w78LbaVfoTtWy4xAd5dfjaFG9c9PWPihY4ggMQNQSMtzU77CNgAJwmA==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "@hapi/hoek": "^9.0.0" + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "node_modules/@smithy/protocol-http": { + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.7.tgz", + "integrity": "sha512-1r07pb994I20dD/c2seaZhoCuNYm0rWrvBxhCQ70brNh11M5Ml2ew6qJVo0lclB3jMIXirD4s2XRXRe7QEi0xA==", "dev": true, "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18.18.0" + "node": ">=18.0.0" } }, - "node_modules/@humanfs/node": { - "version": "0.16.7", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", - "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "node_modules/@smithy/querystring-builder": { + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.7.tgz", + "integrity": "sha512-eKONSywHZxK4tBxe2lXEysh8wbBdvDWiA+RIuaxZSgCMmA0zMgoDpGLJhnyj+c0leOQprVnXOmcB4m+W9Rw7sg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.4.0" + "@smithy/types": "^4.11.0", + "@smithy/util-uri-escape": "^4.2.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=18.18.0" + "node": ">=18.0.0" } }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "node_modules/@smithy/querystring-parser": { + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.7.tgz", + "integrity": "sha512-3X5ZvzUHmlSTHAXFlswrS6EGt8fMSIxX/c3Rm1Pni3+wYWB6cjGocmRIoqcQF9nU5OgGmL0u7l9m44tSUpfj9w==", "dev": true, "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=12.22" + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/service-error-classification": { + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.7.tgz", + "integrity": "sha512-YB7oCbukqEb2Dlh3340/8g8vNGbs/QsNNRms+gv3N2AtZz9/1vSBx6/6tpwQpZMEJFs7Uq8h4mmOn48ZZ72MkA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.11.0" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@humanwhocodes/retry": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", - "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "node_modules/@smithy/shared-ini-file-loader": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.2.tgz", + "integrity": "sha512-M7iUUff/KwfNunmrgtqBfvZSzh3bmFgv/j/t1Y1dQ+8dNo34br1cqVEqy6v0mYEgi0DkGO7Xig0AnuOaEGVlcg==", "dev": true, "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18.18" + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/signature-v4": { + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.7.tgz", + "integrity": "sha512-9oNUlqBlFZFOSdxgImA6X5GFuzE7V2H7VG/7E70cdLhidFbdtvxxt81EHgykGK5vq5D3FafH//X+Oy31j3CKOg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.2.0", + "@smithy/protocol-http": "^5.3.7", + "@smithy/types": "^4.11.0", + "@smithy/util-hex-encoding": "^4.2.0", + "@smithy/util-middleware": "^4.2.7", + "@smithy/util-uri-escape": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "node_modules/@smithy/smithy-client": { + "version": "4.10.4", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.10.4.tgz", + "integrity": "sha512-rHig+BWjhjlHlah67ryaW9DECYixiJo5pQCTEwsJyarRBAwHMMC3iYz5MXXAHXe64ZAMn1NhTUSTFIu1T6n6jg==", "dev": true, - "license": "ISC", + "license": "Apache-2.0", "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" + "@smithy/core": "^3.20.2", + "@smithy/middleware-endpoint": "^4.4.3", + "@smithy/middleware-stack": "^4.2.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/types": "^4.11.0", + "@smithy/util-stream": "^4.5.8", + "tslib": "^2.6.2" }, "engines": { - "node": ">=8" + "node": ">=18.0.0" } }, - "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==", + "node_modules/@smithy/types": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.11.0.tgz", + "integrity": "sha512-mlrmL0DRDVe3mNrjTcVcZEgkFmufITfUAPBEA+AHYiIeYyJebso/He1qLbP3PssRe22KUzLRpQSdBPbXdgZ2VA==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, "engines": { - "node": ">=8" + "node": ">=18.0.0" } }, - "node_modules/@jest/console": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", - "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "node_modules/@smithy/url-parser": { + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.7.tgz", + "integrity": "sha512-/RLtVsRV4uY3qPWhBDsjwahAtt3x2IsMGnP5W1b2VZIe+qgCqkLxI1UOHDZp1Q1QSOrdOR32MF3Ph2JfWT1VHg==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "slash": "^3.0.0" + "@smithy/querystring-parser": "^4.2.7", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=18.0.0" } }, - "node_modules/@jest/core": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", - "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "node_modules/@smithy/util-base64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.0.tgz", + "integrity": "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@jest/console": "^29.7.0", - "@jest/reporters": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-changed-files": "^29.7.0", - "jest-config": "^29.7.0", - "jest-haste-map": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-resolve-dependencies": "^29.7.0", - "jest-runner": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "jest-watcher": "^29.7.0", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-browser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.0.tgz", + "integrity": "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-node": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.1.tgz", + "integrity": "sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@jest/environment": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", - "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "node_modules/@smithy/util-buffer-from": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.0.tgz", + "integrity": "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-mock": "^29.7.0" + "@smithy/is-array-buffer": "^4.2.0", + "tslib": "^2.6.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=18.0.0" } }, - "node_modules/@jest/expect": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", - "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "node_modules/@smithy/util-config-provider": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.2.0.tgz", + "integrity": "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "expect": "^29.7.0", - "jest-snapshot": "^29.7.0" + "tslib": "^2.6.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=18.0.0" } }, - "node_modules/@jest/expect-utils": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", - "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "node_modules/@smithy/util-defaults-mode-browser": { + "version": "4.3.18", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.18.tgz", + "integrity": "sha512-Ao1oLH37YmLyHnKdteMp6l4KMCGBeZEAN68YYe00KAaKFijFELDbRQRm3CNplz7bez1HifuBV0l5uR6eVJLhIg==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.2.7", + "@smithy/smithy-client": "^4.10.4", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-node": { + "version": "4.2.21", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.21.tgz", + "integrity": "sha512-e21ASJDirE96kKXZLcYcnn4Zt0WGOvMYc1P8EK0gQeQ3I8PbJWqBKx9AUr/YeFpDkpYwEu1RsPe4UXk2+QL7IA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/config-resolver": "^4.4.5", + "@smithy/credential-provider-imds": "^4.2.7", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/property-provider": "^4.2.7", + "@smithy/smithy-client": "^4.10.4", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-endpoints": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.2.7.tgz", + "integrity": "sha512-s4ILhyAvVqhMDYREeTS68R43B1V5aenV5q/V1QpRQJkCXib5BPRo4s7uNdzGtIKxaPHCfU/8YkvPAEvTpxgspg==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "jest-get-type": "^29.6.3" + "@smithy/node-config-provider": "^4.3.7", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=18.0.0" } }, - "node_modules/@jest/fake-timers": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", - "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "node_modules/@smithy/util-hex-encoding": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.0.tgz", + "integrity": "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@jest/types": "^29.6.3", - "@sinonjs/fake-timers": "^10.0.2", - "@types/node": "*", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" + "tslib": "^2.6.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=18.0.0" } }, - "node_modules/@jest/globals": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", - "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "node_modules/@smithy/util-middleware": { + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.7.tgz", + "integrity": "sha512-i1IkpbOae6NvIKsEeLLM9/2q4X+M90KV3oCFgWQI4q0Qz+yUZvsr+gZPdAEAtFhWQhAHpTsJO8DRJPuwVyln+w==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/expect": "^29.7.0", - "@jest/types": "^29.6.3", - "jest-mock": "^29.7.0" + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=18.0.0" } }, - "node_modules/@jest/reporters": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", - "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "node_modules/@smithy/util-retry": { + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.7.tgz", + "integrity": "sha512-SvDdsQyF5CIASa4EYVT02LukPHVzAgUA4kMAuZ97QJc2BpAqZfA4PINB8/KOoCXEw9tsuv/jQjMeaHFvxdLNGg==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "@types/node": "*", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^6.0.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.1.3", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "slash": "^3.0.0", - "string-length": "^4.0.1", - "strip-ansi": "^6.0.0", - "v8-to-istanbul": "^9.0.1" + "@smithy/service-error-classification": "^4.2.7", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } + "node": ">=18.0.0" } }, - "node_modules/@jest/reporters/node_modules/istanbul-lib-instrument": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", - "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "node_modules/@smithy/util-stream": { + "version": "4.5.8", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.8.tgz", + "integrity": "sha512-ZnnBhTapjM0YPGUSmOs0Mcg/Gg87k503qG4zU2v/+Js2Gu+daKOJMeqcQns8ajepY8tgzzfYxl6kQyZKml6O2w==", "dev": true, - "license": "BSD-3-Clause", + "license": "Apache-2.0", "dependencies": { - "@babel/core": "^7.23.9", - "@babel/parser": "^7.23.9", - "@istanbuljs/schema": "^0.1.3", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^7.5.4" + "@smithy/fetch-http-handler": "^5.3.8", + "@smithy/node-http-handler": "^4.4.7", + "@smithy/types": "^4.11.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-hex-encoding": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=10" + "node": ">=18.0.0" } }, - "node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "node_modules/@smithy/util-uri-escape": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.0.tgz", + "integrity": "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@sinclair/typebox": "^0.27.8" + "tslib": "^2.6.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=18.0.0" } }, - "node_modules/@jest/source-map": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", - "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "node_modules/@smithy/util-utf8": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz", + "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@jridgewell/trace-mapping": "^0.3.18", - "callsites": "^3.0.0", - "graceful-fs": "^4.2.9" + "@smithy/util-buffer-from": "^4.2.0", + "tslib": "^2.6.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=18.0.0" } }, - "node_modules/@jest/test-result": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", - "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "node_modules/@smithy/uuid": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@smithy/uuid/-/uuid-1.1.0.tgz", + "integrity": "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@jest/console": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" + "tslib": "^2.6.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=18.0.0" } }, - "node_modules/@jest/test-sequencer": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", - "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", - "dev": true, + "node_modules/@szmarczak/http-timer": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", + "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", "license": "MIT", "dependencies": { - "@jest/test-result": "^29.7.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "slash": "^3.0.0" + "defer-to-connect": "^2.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=10" } }, - "node_modules/@jest/transform": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", - "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "node_modules/@tsconfig/node10": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", + "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/core": "^7.11.6", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.2" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" } }, - "node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "@babel/types": "^7.0.0" } }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" } }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" + "@babel/types": "^7.28.2" } }, - "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==", + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", "dev": true, "license": "MIT", - "engines": { - "node": ">=6.0.0" + "dependencies": { + "@types/connect": "*", + "@types/node": "*" } }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "node_modules/@types/bunyan": { + "version": "1.8.11", + "resolved": "https://registry.npmjs.org/@types/bunyan/-/bunyan-1.8.11.tgz", + "integrity": "sha512-758fRH7umIMk5qt5ELmRMff4mLDlN+xyYzC+dkPTdKwbSkJFvz6xwyScrytPU0QIBbRRwbiE8/BIg8bpajerNQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "@types/node": "*" + } }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, + "node_modules/@types/cacheable-request": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", + "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", "license": "MIT", "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" + "@types/http-cache-semantics": "*", + "@types/keyv": "^3.1.4", + "@types/node": "*", + "@types/responselike": "^1.0.0" } }, - "node_modules/@js-sdsl/ordered-map": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", - "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/js-sdsl" + "dependencies": { + "@types/node": "*" } }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "node_modules/@types/cors": { + "version": "2.8.19", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", + "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", "dev": true, "license": "MIT", "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" + "@types/node": "*" } }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "node_modules/@types/docker-modem": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/docker-modem/-/docker-modem-3.0.6.tgz", + "integrity": "sha512-yKpAGEuKRSS8wwx0joknWxsmLha78wNMe9R2S3UNsVOkZded8UqOrV8KoeDXoXsjndxwyF3eIhyClGbO1SEhEg==", "dev": true, "license": "MIT", - "engines": { - "node": ">= 8" + "dependencies": { + "@types/node": "*", + "@types/ssh2": "*" } }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "node_modules/@types/dockerode": { + "version": "3.3.47", + "resolved": "https://registry.npmjs.org/@types/dockerode/-/dockerode-3.3.47.tgz", + "integrity": "sha512-ShM1mz7rCjdssXt7Xz0u1/R2BJC7piWa3SJpUBiVjCf2A3XNn4cP6pUVaD8bLanpPVVn4IKzJuw3dOvkJ8IbYw==", "dev": true, "license": "MIT", "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" + "@types/docker-modem": "*", + "@types/node": "*", + "@types/ssh2": "*" } }, - "node_modules/@opentelemetry/api": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", - "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", - "license": "Apache-2.0", - "engines": { - "node": ">=8.0.0" - } + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" }, - "node_modules/@panva/asn1.js": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@panva/asn1.js/-/asn1.js-1.0.0.tgz", - "integrity": "sha512-UdkG3mLEqXgnlKsWanWcgb6dOjUzJ+XC5f+aWw30qrtjxeNUSfKX1cd5FBzOaXQumoe9nIqeZUvrRJS03HCCtw==", + "node_modules/@types/express": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz", + "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", + "dev": true, "license": "MIT", - "engines": { - "node": ">=10.13.0" + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/serve-static": "^2" } }, - "node_modules/@pkgr/core": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", - "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", + "node_modules/@types/express-serve-static-core": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.0.tgz", + "integrity": "sha512-jnHMsrd0Mwa9Cf4IdOzbz543y4XJepXrbia2T4b6+spXC2We3t1y6K44D3mR8XMFSXMCf3/l7rCgddfx7UNVBA==", "dev": true, "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/pkgr" + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" } }, - "node_modules/@protobufjs/aspromise": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/base64": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/codegen": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", - "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/eventemitter": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/fetch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", - "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", - "license": "BSD-3-Clause", + "node_modules/@types/express-session": { + "version": "1.18.2", + "resolved": "https://registry.npmjs.org/@types/express-session/-/express-session-1.18.2.tgz", + "integrity": "sha512-k+I0BxwVXsnEU2hV77cCobC08kIsn4y44C3gC0b46uxZVMaXA04lSPgRLR/bSL2w0t0ShJiG8o4jPzRG/nscFg==", + "dev": true, + "license": "MIT", "dependencies": { - "@protobufjs/aspromise": "^1.1.1", - "@protobufjs/inquire": "^1.1.0" + "@types/express": "*" } }, - "node_modules/@protobufjs/float": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/inquire": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", - "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/path": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/pool": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/utf8": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", - "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", - "license": "BSD-3-Clause" - }, - "node_modules/@rtsao/scc": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", - "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", "dev": true, - "license": "MIT" - }, - "node_modules/@sideway/address": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", - "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", - "license": "BSD-3-Clause", + "license": "MIT", "dependencies": { - "@hapi/hoek": "^9.0.0" + "@types/node": "*" } }, - "node_modules/@sideway/formula": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", - "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==", - "license": "BSD-3-Clause" + "node_modules/@types/http-cache-semantics": { + "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==", + "license": "MIT" }, - "node_modules/@sideway/pinpoint": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", - "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", - "license": "BSD-3-Clause" + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "dev": true, + "license": "MIT" }, - "node_modules/@sinclair/typebox": { - "version": "0.27.8", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "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, "license": "MIT" }, - "node_modules/@sindresorhus/is": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", - "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/is?sponsor=1" + "dependencies": { + "@types/istanbul-lib-coverage": "*" } }, - "node_modules/@sinonjs/commons": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", - "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", "dev": true, - "license": "BSD-3-Clause", + "license": "MIT", "dependencies": { - "type-detect": "4.0.8" + "@types/istanbul-lib-report": "*" } }, - "node_modules/@sinonjs/fake-timers": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", - "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "node_modules/@types/jest": { + "version": "30.0.0", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-30.0.0.tgz", + "integrity": "sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA==", "dev": true, - "license": "BSD-3-Clause", + "license": "MIT", "dependencies": { - "@sinonjs/commons": "^3.0.0" + "expect": "^30.0.0", + "pretty-format": "^30.0.0" } }, - "node_modules/@slack/logger": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@slack/logger/-/logger-4.0.0.tgz", - "integrity": "sha512-Wz7QYfPAlG/DR+DfABddUZeNgoeY7d1J39OCR2jR+v7VBsB8ezulDK5szTnDDPDwLH5IWhLvXIHlCFZV7MSKgA==", + "node_modules/@types/jest/node_modules/@jest/expect-utils": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.2.0.tgz", + "integrity": "sha512-1JnRfhqpD8HGpOmQp180Fo9Zt69zNtC+9lR+kT7NVL05tNXIi+QC8Csz7lfidMoVLPD3FnOtcmp0CEFnxExGEA==", + "dev": true, "license": "MIT", "dependencies": { - "@types/node": ">=18.0.0" + "@jest/get-type": "30.1.0" }, "engines": { - "node": ">= 18", - "npm": ">= 8.6.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@slack/types": { - "version": "2.18.0", - "resolved": "https://registry.npmjs.org/@slack/types/-/types-2.18.0.tgz", - "integrity": "sha512-ZKrdeoppbM+3l2KKOi4/3oFYKCEwiW3dQfdHZDcecJ9rAmEqWPnARYmac9taZNitb0xnSgu6GOpHgwaKI8se2g==", + "node_modules/@types/jest/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dev": true, "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, "engines": { - "node": ">= 12.13.0", - "npm": ">= 6.12.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@slack/web-api": { - "version": "7.12.0", - "resolved": "https://registry.npmjs.org/@slack/web-api/-/web-api-7.12.0.tgz", - "integrity": "sha512-LrDxjYyqjeYYQGVdVZ6EYHunFmzveOr2pFpShr6TzW4KNFpdNNnpKekjtMg0PJlOsMibSySLGQqiBZQDasmRCA==", + "node_modules/@types/jest/node_modules/@jest/types": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.2.0.tgz", + "integrity": "sha512-H9xg1/sfVvyfU7o3zMfBEjQ1gcsdeTMgqHoYdN79tuLqfTtuu7WckRA1R5whDwOzxaZAeMKTYWqP+WCAi0CHsg==", + "dev": true, "license": "MIT", "dependencies": { - "@slack/logger": "^4.0.0", - "@slack/types": "^2.18.0", - "@types/node": ">=18.0.0", - "@types/retry": "0.12.0", - "axios": "^1.11.0", - "eventemitter3": "^5.0.1", - "form-data": "^4.0.4", - "is-electron": "2.2.2", - "is-stream": "^2", - "p-queue": "^6", - "p-retry": "^4", - "retry": "^0.13.1" + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" }, "engines": { - "node": ">= 18", - "npm": ">= 8.6.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@szmarczak/http-timer": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", - "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "node_modules/@types/jest/node_modules/@sinclair/typebox": { + "version": "0.34.47", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.47.tgz", + "integrity": "sha512-ZGIBQ+XDvO5JQku9wmwtabcVTHJsgSWAHYtVuM9pBNNR5E88v6Jcj/llpmsjivig5X8A8HHOb4/mbEKPS5EvAw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/jest/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@types/jest/node_modules/ci-info": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.1.tgz", + "integrity": "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], "license": "MIT", - "dependencies": { - "defer-to-connect": "^2.0.0" - }, "engines": { - "node": ">=10" + "node": ">=8" } }, - "node_modules/@types/babel__core": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", - "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "node_modules/@types/jest/node_modules/expect": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-30.2.0.tgz", + "integrity": "sha512-u/feCi0GPsI+988gU2FLcsHyAHTU0MX1Wg68NhAnN7z/+C5wqG+CY8J53N9ioe8RXgaoz0nBR/TYMf3AycUuPw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" + "@jest/expect-utils": "30.2.0", + "@jest/get-type": "30.1.0", + "jest-matcher-utils": "30.2.0", + "jest-message-util": "30.2.0", + "jest-mock": "30.2.0", + "jest-util": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@types/babel__generator": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", - "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "node_modules/@types/jest/node_modules/jest-diff": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.2.0.tgz", + "integrity": "sha512-dQHFo3Pt4/NLlG5z4PxZ/3yZTZ1C7s9hveiOj+GCN+uT109NC2QgsoVZsVOAvbJ3RgKkvyLGXZV9+piDpWbm6A==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.0.0" + "@jest/diff-sequences": "30.0.1", + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "pretty-format": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@types/babel__template": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", - "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "node_modules/@types/jest/node_modules/jest-matcher-utils": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.2.0.tgz", + "integrity": "sha512-dQ94Nq4dbzmUWkQ0ANAWS9tBRfqCrn0bV9AMYdOi/MHW726xn7eQmMeRTpX2ViC00bpNaWXq+7o4lIQ3AX13Hg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "jest-diff": "30.2.0", + "pretty-format": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@types/babel__traverse": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", - "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "node_modules/@types/jest/node_modules/jest-message-util": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.2.0.tgz", + "integrity": "sha512-y4DKFLZ2y6DxTWD4cDe07RglV88ZiNEdlRfGtqahfbIjfsw1nMCPx49Uev4IA/hWn3sDKyAnSPwoYSsAEdcimw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.28.2" + "@babel/code-frame": "^7.27.1", + "@jest/types": "30.2.0", + "@types/stack-utils": "^2.0.3", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "micromatch": "^4.0.8", + "pretty-format": "30.2.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.6" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@types/cacheable-request": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", - "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", + "node_modules/@types/jest/node_modules/jest-mock": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.2.0.tgz", + "integrity": "sha512-JNNNl2rj4b5ICpmAcq+WbLH83XswjPbjH4T7yvGzfAGCPh1rw+xVNbtk+FnRslvt9lkCcdn9i1oAoKUuFsOxRw==", + "dev": true, "license": "MIT", "dependencies": { - "@types/http-cache-semantics": "*", - "@types/keyv": "^3.1.4", + "@jest/types": "30.2.0", "@types/node": "*", - "@types/responselike": "^1.0.0" + "jest-util": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/graceful-fs": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", - "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "node_modules/@types/jest/node_modules/jest-util": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.2.0.tgz", + "integrity": "sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA==", "dev": true, "license": "MIT", "dependencies": { - "@types/node": "*" + "@jest/types": "30.2.0", + "@types/node": "*", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@types/http-cache-semantics": { - "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==", - "license": "MIT" - }, - "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, - "license": "MIT" - }, - "node_modules/@types/istanbul-lib-report": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", - "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "node_modules/@types/jest/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "dependencies": { - "@types/istanbul-lib-coverage": "*" + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/@types/istanbul-reports": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", - "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "node_modules/@types/jest/node_modules/pretty-format": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.2.0.tgz", + "integrity": "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==", "dev": true, "license": "MIT", "dependencies": { - "@types/istanbul-lib-report": "*" + "@jest/schemas": "30.0.5", + "ansi-styles": "^5.2.0", + "react-is": "^18.3.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@types/json-schema": { @@ -1746,14 +3515,56 @@ } }, "node_modules/@types/node": { - "version": "24.10.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.0.tgz", - "integrity": "sha512-qzQZRBqkFsYyaSWXuEHc2WR9c0a0CXwiE5FWUvn7ZM+vdy1uZLfCunD38UzhuB7YN/J11ndbDBcTmOdxJo9Q7A==", + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.3.tgz", + "integrity": "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA==", "license": "MIT", "dependencies": { "undici-types": "~7.16.0" } }, + "node_modules/@types/node-cron": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@types/node-cron/-/node-cron-3.0.11.tgz", + "integrity": "sha512-0ikrnug3/IyneSHqCBeslAhlK2aBfYek1fGo4bP4QnZPmiqSGRK+Oy7ZMisLWkesffJvQ1cqAcBnJC+8+nxIAg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/nodemailer": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-7.0.4.tgz", + "integrity": "sha512-ee8fxWqOchH+Hv6MDDNNy028kwvVnLplrStm4Zf/3uHWw5zzo8FoYYeffpJtGs2wWysEumMH0ZIdMGMY1eMAow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@aws-sdk/client-sesv2": "^3.839.0", + "@types/node": "*" + } + }, + "node_modules/@types/passport": { + "version": "1.0.17", + "resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.17.tgz", + "integrity": "sha512-aciLyx+wDwT2t2/kJGJR2AEeBz0nJU4WuRX04Wu9Dqc5lSUtwu0WERPHYsLhF9PtseiAMPBGNUOtFjxZ56prsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/readable-stream": { "version": "4.0.22", "resolved": "https://registry.npmjs.org/@types/readable-stream/-/readable-stream-4.0.22.tgz", @@ -1778,6 +3589,61 @@ "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", "license": "MIT" }, + "node_modules/@types/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*" + } + }, + "node_modules/@types/ssh2": { + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/@types/ssh2/-/ssh2-1.15.5.tgz", + "integrity": "sha512-N1ASjp/nXH3ovBHddRJpli4ozpk6UdDYIX4RJWFa9L1YKnzdhTlVmiGHm4DZnj/jLbqZpes4aeR30EFGQtvhQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "^18.11.18" + } + }, + "node_modules/@types/ssh2/node_modules/@types/node": { + "version": "18.19.130", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz", + "integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/ssh2/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==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/stack-utils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", @@ -1785,6 +3651,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/ws": { "version": "8.18.1", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", @@ -1794,6 +3667,16 @@ "@types/node": "*" } }, + "node_modules/@types/yaml": { + "version": "1.9.6", + "resolved": "https://registry.npmjs.org/@types/yaml/-/yaml-1.9.6.tgz", + "integrity": "sha512-VKOCuDN57wngmyQnRqcn4vuGWCXViISHv+UCCjrKcf1yt4zyfMmOGlZDI2ucTHK72V8ki+sd7h21OZL6O5S52A==", + "dev": true, + "license": "MIT", + "dependencies": { + "yaml": "*" + } + }, "node_modules/@types/yargs": { "version": "17.0.34", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.34.tgz", @@ -1811,16 +3694,80 @@ "dev": true, "license": "MIT" }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.53.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.53.1.tgz", + "integrity": "sha512-cFYYFZ+oQFi6hUnBTbLRXfTJiaQtYE3t4O692agbBl+2Zy+eqSKWtPjhPXJu1G7j4RLjKgeJPDdq3EqOwmX5Ag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.53.1", + "@typescript-eslint/type-utils": "8.53.1", + "@typescript-eslint/utils": "8.53.1", + "@typescript-eslint/visitor-keys": "8.53.1", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.53.1", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.53.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.53.1.tgz", + "integrity": "sha512-nm3cvFN9SqZGXjmw5bZ6cGmvJSyJPn0wU9gHAZZHDnZl2wF9PhHv78Xf06E0MaNk4zLVHL8hb2/c32XvyJOLQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.53.1", + "@typescript-eslint/types": "8.53.1", + "@typescript-eslint/typescript-estree": "8.53.1", + "@typescript-eslint/visitor-keys": "8.53.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, "node_modules/@typescript-eslint/project-service": { - "version": "8.46.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.46.3.tgz", - "integrity": "sha512-Fz8yFXsp2wDFeUElO88S9n4w1I4CWDTXDqDr9gYvZgUpwXQqmZBr9+NTTql5R3J7+hrJZPdpiWaB9VNhAKYLuQ==", + "version": "8.53.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.53.1.tgz", + "integrity": "sha512-WYC4FB5Ra0xidsmlPb+1SsnaSKPmS3gsjIARwbEkHkoWloQmuzcfypljaJcR78uyLA1h8sHdWWPHSLDI+MtNog==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.46.3", - "@typescript-eslint/types": "^8.46.3", - "debug": "^4.3.4" + "@typescript-eslint/tsconfig-utils": "^8.53.1", + "@typescript-eslint/types": "^8.53.1", + "debug": "^4.4.3" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1834,14 +3781,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.46.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.46.3.tgz", - "integrity": "sha512-FCi7Y1zgrmxp3DfWfr+3m9ansUUFoy8dkEdeQSgA9gbm8DaHYvZCdkFRQrtKiedFf3Ha6VmoqoAaP68+i+22kg==", + "version": "8.53.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.53.1.tgz", + "integrity": "sha512-Lu23yw1uJMFY8cUeq7JlrizAgeQvWugNQzJp8C3x8Eo5Jw5Q2ykMdiiTB9vBVOOUBysMzmRRmUfwFrZuI2C4SQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.46.3", - "@typescript-eslint/visitor-keys": "8.46.3" + "@typescript-eslint/types": "8.53.1", + "@typescript-eslint/visitor-keys": "8.53.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1852,11 +3799,35 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.46.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.46.3.tgz", - "integrity": "sha512-GLupljMniHNIROP0zE7nCcybptolcH8QZfXOpCfhQDAdwJ/ZTlcaBOYebSOZotpti/3HrHSw7D3PZm75gYFsOA==", + "version": "8.53.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.53.1.tgz", + "integrity": "sha512-qfvLXS6F6b1y43pnf0pPbXJ+YoXIC7HKg0UGZ27uMIemKMKA6XH2DTxsEDdpdN29D+vHV07x/pnlPNVLhdhWiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.53.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.53.1.tgz", + "integrity": "sha512-MOrdtNvyhy0rHyv0ENzub1d4wQYKb2NmIqG7qEqPWFW7Mpy2jzFC3pQ2yKDvirZB7jypm5uGjF2Qqs6OIqu47w==", "dev": true, "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.53.1", + "@typescript-eslint/typescript-estree": "8.53.1", + "@typescript-eslint/utils": "8.53.1", + "debug": "^4.4.3", + "ts-api-utils": "^2.4.0" + }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -1865,13 +3836,14 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/types": { - "version": "8.46.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.46.3.tgz", - "integrity": "sha512-G7Ok9WN/ggW7e/tOf8TQYMaxgID3Iujn231hfi0Pc7ZheztIJVpO44ekY00b7akqc6nZcvregk0Jpah3kep6hA==", + "version": "8.53.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.53.1.tgz", + "integrity": "sha512-jr/swrr2aRmUAUjW5/zQHbMaui//vQlsZcJKijZf3M26bnmLj8LyZUpj8/Rd6uzaek06OWsqdofN/Thenm5O8A==", "dev": true, "license": "MIT", "engines": { @@ -1883,22 +3855,21 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.46.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.46.3.tgz", - "integrity": "sha512-f/NvtRjOm80BtNM5OQtlaBdM5BRFUv7gf381j9wygDNL+qOYSNOgtQ/DCndiYi80iIOv76QqaTmp4fa9hwI0OA==", + "version": "8.53.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.53.1.tgz", + "integrity": "sha512-RGlVipGhQAG4GxV1s34O91cxQ/vWiHJTDHbXRr0li2q/BGg3RR/7NM8QDWgkEgrwQYCvmJV9ichIwyoKCQ+DTg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.46.3", - "@typescript-eslint/tsconfig-utils": "8.46.3", - "@typescript-eslint/types": "8.46.3", - "@typescript-eslint/visitor-keys": "8.46.3", - "debug": "^4.3.4", - "fast-glob": "^3.3.2", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^2.1.0" + "@typescript-eslint/project-service": "8.53.1", + "@typescript-eslint/tsconfig-utils": "8.53.1", + "@typescript-eslint/types": "8.53.1", + "@typescript-eslint/visitor-keys": "8.53.1", + "debug": "^4.4.3", + "minimatch": "^9.0.5", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.4.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1938,16 +3909,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.46.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.46.3.tgz", - "integrity": "sha512-VXw7qmdkucEx9WkmR3ld/u6VhRyKeiF1uxWwCy/iuNfokjJ7VhsgLSOTjsol8BunSw190zABzpwdNsze2Kpo4g==", + "version": "8.53.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.53.1.tgz", + "integrity": "sha512-c4bMvGVWW4hv6JmDUEG7fSYlWOl3II2I4ylt0NM+seinYQlZMQIaKaXIIVJWt9Ofh6whrpM+EdDQXKXjNovvrg==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.46.3", - "@typescript-eslint/types": "8.46.3", - "@typescript-eslint/typescript-estree": "8.46.3" + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.53.1", + "@typescript-eslint/types": "8.53.1", + "@typescript-eslint/typescript-estree": "8.53.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1962,13 +3933,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.46.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.46.3.tgz", - "integrity": "sha512-uk574k8IU0rOF/AjniX8qbLSGURJVUCeM5e4MIMKBFFi8weeiLrG1fyQejyLXQpRZbU/1BuQasleV/RfHC3hHg==", + "version": "8.53.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.53.1.tgz", + "integrity": "sha512-oy+wV7xDKFPRyNggmXuZQSBzvoLnpmJs+GhzRhPjrxl2b/jIlyjVokzm47CZCDUdXKr2zd7ZLodPfOBpOPyPlg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.46.3", + "@typescript-eslint/types": "8.53.1", "eslint-visitor-keys": "^4.2.1" }, "engines": { @@ -2010,7 +3981,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2028,6 +3998,19 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/aggregate-error": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", @@ -2111,6 +4094,13 @@ "node": ">= 8" } }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "license": "MIT" + }, "node_modules/argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -2424,13 +4414,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/babel-plugin-rewire": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/babel-plugin-rewire/-/babel-plugin-rewire-1.2.0.tgz", - "integrity": "sha512-JBZxczHw3tScS+djy6JPLMjblchGhLI89ep15H3SyjujIzlxo5nr6Yjo7AXotdeVczeBmWs0tF8PgJWDdgzAkQ==", - "dev": true, - "license": "ISC" - }, "node_modules/babel-preset-current-node-syntax": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz", @@ -2666,6 +4649,13 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, + "node_modules/bowser": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.13.1.tgz", + "integrity": "sha512-OHawaAbjwx6rqICCKgSG0SAnT05bzd7ppyKLVUITZpANBaaMFBAsaNkto3LoQ31tyFP5kNujE8Cdx85G9VzOkw==", + "dev": true, + "license": "MIT" + }, "node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", @@ -2722,7 +4712,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.19", "caniuse-lite": "^1.0.30001751", @@ -2737,6 +4726,19 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/bser": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", @@ -3264,6 +5266,13 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "license": "MIT" + }, "node_modules/cron-parser": { "version": "2.18.0", "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-2.18.0.tgz", @@ -3509,7 +5518,17 @@ "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=8" + } + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" } }, "node_modules/diff-sequences": { @@ -3865,7 +5884,6 @@ "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -3926,7 +5944,6 @@ "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", "dev": true, "license": "MIT", - "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -4482,36 +6499,6 @@ "dev": true, "license": "Apache-2.0" }, - "node_modules/fast-glob": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -4538,14 +6525,23 @@ "node": ">=18.2.0" } }, - "node_modules/fastq": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", - "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "node_modules/fast-xml-parser": { + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.2.5.tgz", + "integrity": "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==", "dev": true, - "license": "ISC", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", "dependencies": { - "reusify": "^1.0.4" + "strnum": "^2.1.0" + }, + "bin": { + "fxparser": "src/cli/cli.js" } }, "node_modules/fb-watchman": { @@ -5053,11 +7049,34 @@ "dev": true, "license": "ISC" }, + "node_modules/handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, "node_modules/har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", "license": "ISC", + "peer": true, "engines": { "node": ">=4" } @@ -5068,6 +7087,7 @@ "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", "deprecated": "this library is no longer supported", "license": "MIT", + "peer": true, "dependencies": { "ajv": "^6.12.3", "har-schema": "^2.0.0" @@ -5989,7 +8009,6 @@ "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/core": "^29.7.0", "@jest/types": "^29.6.3", @@ -6567,6 +8586,15 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, "node_modules/jmespath": { "version": "0.16.0", "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.16.0.tgz", @@ -6819,6 +8847,13 @@ "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", "license": "MIT" }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true, + "license": "MIT" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -6932,16 +8967,6 @@ "dev": true, "license": "MIT" }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, "node_modules/methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", @@ -7251,6 +9276,13 @@ "node": ">= 0.6" } }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true, + "license": "MIT" + }, "node_modules/no-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", @@ -7479,6 +9511,7 @@ "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", "license": "Apache-2.0", + "peer": true, "engines": { "node": "*" } @@ -8066,7 +10099,6 @@ "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", "dev": true, "license": "MIT", - "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -8296,27 +10328,6 @@ "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", "license": "MIT" }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, "node_modules/quick-lru": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", @@ -8444,6 +10455,7 @@ "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", "license": "Apache-2.0", + "peer": true, "dependencies": { "aws-sign2": "~0.7.0", "aws4": "^1.8.0", @@ -8490,6 +10502,7 @@ "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", "license": "MIT", + "peer": true, "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.6", @@ -8504,6 +10517,7 @@ "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", "license": "MIT", + "peer": true, "dependencies": { "assert-plus": "^1.0.0", "jsprim": "^1.2.2", @@ -8519,6 +10533,7 @@ "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", "license": "MIT", + "peer": true, "dependencies": { "assert-plus": "1.0.0", "extsprintf": "1.3.0", @@ -8534,6 +10549,7 @@ "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", "license": "BSD-3-Clause", + "peer": true, "engines": { "node": ">=0.6" } @@ -8543,6 +10559,7 @@ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", "license": "BSD-3-Clause", + "peer": true, "dependencies": { "psl": "^1.1.28", "punycode": "^2.1.1" @@ -8557,6 +10574,7 @@ "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", "license": "MIT", + "peer": true, "bin": { "uuid": "bin/uuid" } @@ -8657,17 +10675,6 @@ "node": ">= 4" } }, - "node_modules/reusify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", - "dev": true, - "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, "node_modules/rfdc": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", @@ -8706,30 +10713,6 @@ "node": "*" } }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, "node_modules/safe-array-concat": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", @@ -9427,6 +11410,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/strnum": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.2.tgz", + "integrity": "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -9566,6 +11562,54 @@ "node": ">=8" } }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/tldts": { "version": "6.1.86", "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", @@ -9636,9 +11680,9 @@ } }, "node_modules/ts-api-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", - "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", + "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", "dev": true, "license": "MIT", "engines": { @@ -9648,6 +11692,116 @@ "typescript": ">=4.8.4" } }, + "node_modules/ts-jest": { + "version": "29.4.6", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.6.tgz", + "integrity": "sha512-fSpWtOO/1AjSNQguk43hb/JCo16oJDnMJf3CdEGNkqsEX3t0KX96xvyX1D7PfLCpVoKu4MfVrqUkFyblYoY4lA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bs-logger": "^0.2.6", + "fast-json-stable-stringify": "^2.1.0", + "handlebars": "^4.7.8", + "json5": "^2.2.3", + "lodash.memoize": "^4.1.2", + "make-error": "^1.3.6", + "semver": "^7.7.3", + "type-fest": "^4.41.0", + "yargs-parser": "^21.1.1" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/transform": "^29.0.0 || ^30.0.0", + "@jest/types": "^29.0.0 || ^30.0.0", + "babel-jest": "^29.0.0 || ^30.0.0", + "jest": "^29.0.0 || ^30.0.0", + "jest-util": "^29.0.0 || ^30.0.0", + "typescript": ">=4.3 <6" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/transform": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jest-util": { + "optional": true + } + } + }, + "node_modules/ts-jest/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, "node_modules/tsconfig-paths": { "version": "3.15.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", @@ -9843,7 +11997,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -9852,6 +12005,44 @@ "node": ">=14.17" } }, + "node_modules/typescript-eslint": { + "version": "8.53.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.53.1.tgz", + "integrity": "sha512-gB+EVQfP5RDElh9ittfXlhZJdjSU4jUSTyE2+ia8CYyNvet4ElfaLlAIqDvQV9JPknKx0jQH1racTYe/4LaLSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.53.1", + "@typescript-eslint/parser": "8.53.1", + "@typescript-eslint/typescript-estree": "8.53.1", + "@typescript-eslint/utils": "8.53.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/uglify-js": { + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/uid-safe": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", @@ -10020,6 +12211,13 @@ "uuid": "dist/esm/bin/uuid" } }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "license": "MIT" + }, "node_modules/v8-to-istanbul": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", @@ -10185,6 +12383,13 @@ "node": ">=0.10.0" } }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true, + "license": "MIT" + }, "node_modules/worker-factory": { "version": "7.0.46", "resolved": "https://registry.npmjs.org/worker-factory/-/worker-factory-7.0.46.tgz", @@ -10367,6 +12572,16 @@ "node": ">=12" } }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/app/package.json b/app/package.json index b9b19513..4f81ea63 100644 --- a/app/package.json +++ b/app/package.json @@ -2,13 +2,14 @@ "name": "wud-app", "version": "1.0.0", "description": "What'up Docker? the app", - "main": "index.js", + "main": "dist/index.js", "scripts": { - "start": "nodemon index.js | bunyan -L -o short", + "build": "tsc", + "start": "nodemon --exec ts-node index.ts | bunyan -L -o short", "doc": ".docsify serve ./docs", "format": "prettier --write .", - "lint:fix": "prettier --write . && eslint '**/*.js'", - "lint": "eslint '**/*.js'", + "lint:fix": "prettier --write . && eslint '**/*.ts'", + "lint": "eslint '**/*.ts'", "test": "jest --coverage" }, "author": "fmartinou", @@ -17,6 +18,7 @@ "dependencies": { "@slack/web-api": "7.12.0", "aws-sdk": "2.1692.0", + "axios": "1.13.2", "body-parser": "1.20.3", "bunyan": "1.8.15", "capitalize": "2.0.4", @@ -29,6 +31,7 @@ "flat": "5.0.2", "getmac": "6.6.0", "gotify-client": "0.4.2", + "jiti": "^2.6.1", "joi": "17.13.3", "joi-cron-expression": "1.0.1", "just-debounce": "1.1.0", @@ -47,7 +50,6 @@ "passport-http": "0.3.0", "prom-client": "15.1.3", "pushover-notifications": "1.2.3", - "axios": "1.13.2", "semver": "7.7.3", "set-value": "4.1.0", "snake-case": "3.0.4", @@ -56,8 +58,20 @@ "yaml": "2.8.1" }, "devDependencies": { + "@types/bunyan": "^1.8.11", + "@types/cors": "^2.8.19", + "@types/dockerode": "^3.3.47", + "@types/express": "^5.0.6", + "@types/express-session": "^1.18.2", + "@types/jest": "^30.0.0", + "@types/node": "^25.0.3", + "@types/node-cron": "^3.0.11", + "@types/nodemailer": "^7.0.4", + "@types/passport": "^1.0.17", + "@types/semver": "^7.7.1", + "@types/uuid": "^10.0.0", + "@types/yaml": "^1.9.6", "babel-jest": "29.7.0", - "babel-plugin-rewire": "1.2.0", "eslint": "9.39.1", "eslint-config-prettier": "10.1.8", "eslint-plugin-import": "2.32.0", @@ -65,6 +79,10 @@ "eslint-plugin-prettier": "5.5.4", "jest": "29.7.0", "nodemon": "3.1.10", - "prettier": "3.6.2" + "prettier": "3.6.2", + "ts-jest": "^29.4.6", + "ts-node": "^10.9.2", + "typescript": "^5.9.3", + "typescript-eslint": "^8.53.1" } } diff --git a/app/prometheus/container.test.js b/app/prometheus/container.test.ts similarity index 89% rename from app/prometheus/container.test.js rename to app/prometheus/container.test.ts index 7fc1a133..70853fe9 100644 --- a/app/prometheus/container.test.js +++ b/app/prometheus/container.test.ts @@ -1,11 +1,12 @@ +// @ts-nocheck jest.mock('../store/container'); jest.mock('../log'); -const store = require('../store/container'); -const container = require('./container'); -const log = require('../log'); +import * as store from '../store/container'; +import * as container from './container'; +import log from '../log'; -test('gauge must be populated when containers are in the store', () => { +test('gauge must be populated when containers are in the store', async () => { jest.useFakeTimers(); store.getContainers = () => [ { @@ -61,7 +62,7 @@ test('gauge must be populated when containers are in the store', () => { ); }); -test("gauge must warn when data don't match expected labels", () => { +test("gauge must warn when data don't match expected labels", async () => { store.getContainers = () => [ { extra: 'extra', diff --git a/app/prometheus/container.js b/app/prometheus/container.ts similarity index 90% rename from app/prometheus/container.js rename to app/prometheus/container.ts index 1badf12e..a1b58a2b 100644 --- a/app/prometheus/container.js +++ b/app/prometheus/container.ts @@ -1,7 +1,8 @@ -const { Gauge, register } = require('prom-client'); -const storeContainer = require('../store/container'); -const log = require('../log'); -const { flatten } = require('../model/container'); +// @ts-nocheck +import { Gauge, register } from 'prom-client'; +import * as storeContainer from '../store/container'; +import log from '../log'; +import { flatten } from '../model/container'; let gaugeContainer; @@ -33,7 +34,7 @@ function populateGauge() { * Init Container prometheus gauge. * @returns {Gauge} */ -function init() { +export function init() { // Replace gauge if init is called more than once if (gaugeContainer) { register.removeSingleMetric(gaugeContainer.name); @@ -86,7 +87,3 @@ function init() { populateGauge(); return gaugeContainer; } - -module.exports = { - init, -}; diff --git a/app/prometheus/index.test.js b/app/prometheus/index.test.ts similarity index 69% rename from app/prometheus/index.test.js rename to app/prometheus/index.test.ts index 53ca545d..a972d348 100644 --- a/app/prometheus/index.test.js +++ b/app/prometheus/index.test.ts @@ -1,5 +1,6 @@ -const prometheus = require('./index'); -const configuration = require('../configuration'); +// @ts-nocheck +import * as prometheus from './index'; +import * as configuration from '../configuration'; // Mock configuration jest.mock('../configuration', () => ({ @@ -46,12 +47,12 @@ describe('Prometheus Module', () => { }); }); - test('should initialize all prometheus components when enabled', () => { - const { collectDefaultMetrics } = require('prom-client'); - const container = require('./container'); - const trigger = require('./trigger'); - const watcher = require('./watcher'); - const registry = require('./registry'); + test('should initialize all prometheus components when enabled', async () => { + const { collectDefaultMetrics } = await import('prom-client'); + const container = await import('./container'); + const trigger = await import('./trigger'); + const watcher = await import('./watcher'); + const registry = await import('./registry'); prometheus.init(); @@ -62,15 +63,15 @@ describe('Prometheus Module', () => { expect(watcher.init).toHaveBeenCalled(); }); - test('should NOT initialize metrics when disabled', () => { + test('should NOT initialize metrics when disabled', async () => { configuration.getPrometheusConfiguration.mockReturnValue({ enabled: false, }); - const { collectDefaultMetrics } = require('prom-client'); - const container = require('./container'); - const trigger = require('./trigger'); - const watcher = require('./watcher'); - const registry = require('./registry'); + const { collectDefaultMetrics } = await import('prom-client'); + const container = await import('./container'); + const trigger = await import('./trigger'); + const watcher = await import('./watcher'); + const registry = await import('./registry'); prometheus.init(); @@ -82,7 +83,7 @@ describe('Prometheus Module', () => { }); test('should return metrics output', async () => { - const { register } = require('prom-client'); + const { register } = await import('prom-client'); const output = await prometheus.output(); diff --git a/app/prometheus/index.js b/app/prometheus/index.ts similarity index 54% rename from app/prometheus/index.js rename to app/prometheus/index.ts index 554c4fa7..1c5086d7 100644 --- a/app/prometheus/index.js +++ b/app/prometheus/index.ts @@ -1,16 +1,18 @@ -const { collectDefaultMetrics, register } = require('prom-client'); +// @ts-nocheck +import { collectDefaultMetrics, register } from 'prom-client'; -const log = require('../log').child({ component: 'prometheus' }); -const configuration = require('../configuration'); -const container = require('./container'); -const trigger = require('./trigger'); -const watcher = require('./watcher'); -const registry = require('./registry'); +import logger from '../log'; +const log = logger.child({ component: 'prometheus' }); +import * as configuration from '../configuration'; +import * as container from './container'; +import * as trigger from './trigger'; +import * as watcher from './watcher'; +import * as registry from './registry'; /** * Start the Prometheus registry. */ -function init() { +export function init() { const prometheusConfiguration = configuration.getPrometheusConfiguration(); if (!prometheusConfiguration.enabled) { log.info('Prometheus monitoring disabled'); @@ -28,11 +30,6 @@ function init() { * Return all metrics as string for Prometheus scrapping. * @returns {string} */ -async function output() { +export async function output() { return register.metrics(); } - -module.exports = { - init, - output, -}; diff --git a/app/prometheus/registry.test.js b/app/prometheus/registry.test.ts similarity index 61% rename from app/prometheus/registry.test.js rename to app/prometheus/registry.test.ts index 316ed54c..8a5214fa 100644 --- a/app/prometheus/registry.test.js +++ b/app/prometheus/registry.test.ts @@ -1,6 +1,7 @@ -const registry = require('./registry'); +// @ts-nocheck +import * as registry from './registry'; -test('registry histogram should be properly configured', () => { +test('registry histogram should be properly configured', async () => { registry.init(); const summary = registry.getSummaryTags(); expect(summary.name).toStrictEqual('wud_registry_response'); diff --git a/app/prometheus/registry.js b/app/prometheus/registry.ts similarity index 71% rename from app/prometheus/registry.js rename to app/prometheus/registry.ts index c1303461..701fc262 100644 --- a/app/prometheus/registry.js +++ b/app/prometheus/registry.ts @@ -1,8 +1,9 @@ -const { Summary, register } = require('prom-client'); +// @ts-nocheck +import { Summary, register } from 'prom-client'; let summaryGetTags; -function init() { +export function init() { // Replace summary if init is called more than once if (summaryGetTags) { register.removeSingleMetric(summaryGetTags.name); @@ -14,11 +15,6 @@ function init() { }); } -function getSummaryTags() { +export function getSummaryTags() { return summaryGetTags; } - -module.exports = { - init, - getSummaryTags, -}; diff --git a/app/prometheus/trigger.test.js b/app/prometheus/trigger.test.ts similarity index 63% rename from app/prometheus/trigger.test.js rename to app/prometheus/trigger.test.ts index 19f3f7c5..960c48c1 100644 --- a/app/prometheus/trigger.test.js +++ b/app/prometheus/trigger.test.ts @@ -1,6 +1,7 @@ -const trigger = require('./trigger'); +// @ts-nocheck +import * as trigger from './trigger'; -test('trigger counter should be properly configured', () => { +test('trigger counter should be properly configured', async () => { trigger.init(); const summary = trigger.getTriggerCounter(); expect(summary.name).toStrictEqual('wud_trigger_count'); diff --git a/app/prometheus/trigger.js b/app/prometheus/trigger.ts similarity index 70% rename from app/prometheus/trigger.js rename to app/prometheus/trigger.ts index f8437c8d..3209af90 100644 --- a/app/prometheus/trigger.js +++ b/app/prometheus/trigger.ts @@ -1,8 +1,9 @@ -const { Counter, register } = require('prom-client'); +// @ts-nocheck +import { Counter, register } from 'prom-client'; let triggerCounter; -function init() { +export function init() { // Replace counter if init is called more than once if (triggerCounter) { register.removeSingleMetric(triggerCounter.name); @@ -14,11 +15,6 @@ function init() { }); } -function getTriggerCounter() { +export function getTriggerCounter() { return triggerCounter; } - -module.exports = { - init, - getTriggerCounter, -}; diff --git a/app/prometheus/watcher.test.js b/app/prometheus/watcher.test.ts similarity index 62% rename from app/prometheus/watcher.test.js rename to app/prometheus/watcher.test.ts index 8879fd23..27cc0461 100644 --- a/app/prometheus/watcher.test.js +++ b/app/prometheus/watcher.test.ts @@ -1,6 +1,7 @@ -const watcher = require('./watcher'); +// @ts-nocheck +import * as watcher from './watcher'; -test('watcher counter should be properly configured', () => { +test('watcher counter should be properly configured', async () => { watcher.init(); const gauge = watcher.getWatchContainerGauge(); expect(gauge.name).toStrictEqual('wud_watcher_total'); diff --git a/app/prometheus/watcher.js b/app/prometheus/watcher.ts similarity index 69% rename from app/prometheus/watcher.js rename to app/prometheus/watcher.ts index 2ffa2d87..782f8862 100644 --- a/app/prometheus/watcher.js +++ b/app/prometheus/watcher.ts @@ -1,8 +1,9 @@ -const { Gauge, register } = require('prom-client'); +// @ts-nocheck +import { Gauge, register } from 'prom-client'; let watchContainerGauge; -function init() { +export function init() { // Replace gauge if init is called more than once if (watchContainerGauge) { register.removeSingleMetric(watchContainerGauge.name); @@ -14,11 +15,6 @@ function init() { }); } -function getWatchContainerGauge() { +export function getWatchContainerGauge() { return watchContainerGauge; } - -module.exports = { - init, - getWatchContainerGauge, -}; diff --git a/app/registries/BaseRegistry.js b/app/registries/BaseRegistry.ts similarity index 97% rename from app/registries/BaseRegistry.js rename to app/registries/BaseRegistry.ts index 5b29284d..7ddb1c2a 100644 --- a/app/registries/BaseRegistry.js +++ b/app/registries/BaseRegistry.ts @@ -1,4 +1,5 @@ -const Registry = require('./Registry'); +// @ts-nocheck +import Registry from './Registry'; /** * Base Registry with common patterns @@ -95,4 +96,4 @@ class BaseRegistry extends Registry { } } -module.exports = BaseRegistry; +export default BaseRegistry; diff --git a/app/registries/Registry.test.js b/app/registries/Registry.test.ts similarity index 93% rename from app/registries/Registry.test.js rename to app/registries/Registry.test.ts index 134a7b26..28e5e29a 100644 --- a/app/registries/Registry.test.js +++ b/app/registries/Registry.test.ts @@ -1,4 +1,5 @@ -const log = require('../log'); +// @ts-nocheck +import log from '../log'; jest.mock('axios'); jest.mock('../prometheus/registry', () => ({ @@ -7,36 +8,36 @@ jest.mock('../prometheus/registry', () => ({ }), })); -const Registry = require('./Registry'); +import Registry from './Registry'; const registry = new Registry(); registry.register('registry', 'hub', 'test', {}); -test('base64Encode should decode credentials', () => { +test('base64Encode should decode credentials', async () => { expect(Registry.base64Encode('username', 'password')).toEqual( 'dXNlcm5hbWU6cGFzc3dvcmQ=', ); }); -test('getId should return registry type only', () => { +test('getId should return registry type only', async () => { expect(registry.getId()).toStrictEqual('hub.test'); }); -test('match should return false when not overridden', () => { +test('match should return false when not overridden', async () => { expect(registry.match({})).toBeFalsy(); }); -test('normalizeImage should return same image when not overridden', () => { +test('normalizeImage should return same image when not overridden', async () => { expect(registry.normalizeImage({ x: 'x' })).toStrictEqual({ x: 'x' }); }); -test('authenticate should return same request options when not overridden', () => { +test('authenticate should return same request options when not overridden', async () => { expect(registry.authenticate({}, { x: 'x' })).resolves.toStrictEqual({ x: 'x', }); }); -test('getTags should sort tags z -> a', () => { +test('getTags should sort tags z -> a', async () => { const registryMocked = new Registry(); registryMocked.log = log; registryMocked.callRegistry = () => ({ @@ -48,7 +49,7 @@ test('getTags should sort tags z -> a', () => { ).resolves.toStrictEqual(['v3', 'v2', 'v1']); }); -test('getImageManifestDigest should return digest for application/vnd.docker.distribution.manifest.list.v2+json then application/vnd.docker.distribution.manifest.v2+json', () => { +test('getImageManifestDigest should return digest for application/vnd.docker.distribution.manifest.list.v2+json then application/vnd.docker.distribution.manifest.v2+json', async () => { const registryMocked = new Registry(); registryMocked.log = log; registryMocked.callRegistry = (options) => { @@ -111,7 +112,7 @@ test('getImageManifestDigest should return digest for application/vnd.docker.dis }); }); -test('getImageManifestDigest should return digest for application/vnd.docker.distribution.manifest.list.v2+json then application/vnd.docker.container.image.v1+json', () => { +test('getImageManifestDigest should return digest for application/vnd.docker.distribution.manifest.list.v2+json then application/vnd.docker.container.image.v1+json', async () => { const registryMocked = new Registry(); registryMocked.log = log; registryMocked.callRegistry = (options) => { @@ -164,7 +165,7 @@ test('getImageManifestDigest should return digest for application/vnd.docker.dis }); }); -test('getImageManifestDigest should return digest for application/vnd.docker.distribution.manifest.v2+json', () => { +test('getImageManifestDigest should return digest for application/vnd.docker.distribution.manifest.v2+json', async () => { const registryMocked = new Registry(); registryMocked.log = log; registryMocked.callRegistry = (options) => { @@ -213,7 +214,7 @@ test('getImageManifestDigest should return digest for application/vnd.docker.dis }); }); -test('getImageManifestDigest should return digest for application/vnd.docker.container.image.v1+json', () => { +test('getImageManifestDigest should return digest for application/vnd.docker.container.image.v1+json', async () => { const registryMocked = new Registry(); registryMocked.log = log; registryMocked.callRegistry = (options) => { @@ -255,7 +256,7 @@ test('getImageManifestDigest should return digest for application/vnd.docker.con }); }); -test('getImageManifestDigest should throw when no digest found', () => { +test('getImageManifestDigest should throw when no digest found', async () => { const registryMocked = new Registry(); registryMocked.log = log; registryMocked.callRegistry = () => ({}); @@ -275,7 +276,7 @@ test('getImageManifestDigest should throw when no digest found', () => { }); test('callRegistry should call authenticate', async () => { - const axios = require('axios'); + const { default: axios } = await import('axios'); axios.mockResolvedValue({ data: {} }); const registryMocked = new Registry(); registryMocked.log = log; diff --git a/app/registries/Registry.js b/app/registries/Registry.ts similarity index 70% rename from app/registries/Registry.js rename to app/registries/Registry.ts index 0b4fad09..29bdd293 100644 --- a/app/registries/Registry.js +++ b/app/registries/Registry.ts @@ -1,7 +1,44 @@ -const axios = require('axios'); -const log = require('../log'); -const Component = require('../registry/Component'); -const { getSummaryTags } = require('../prometheus/registry'); +import axios, { AxiosRequestConfig, Method, AxiosResponse } from 'axios'; +import log from '../log'; +import Component from '../registry/Component'; +import { getSummaryTags } from '../prometheus/registry'; +import { ContainerImage } from '../model/container'; + +export interface RegistryImage extends ContainerImage { + // Add any registry specific properties if needed +} + +export interface RegistryManifest { + digest?: string; + version?: number; + created?: string; +} + +export interface RegistryTagsList { + name: string; + tags: string[]; +} + +export interface RegistryManifestResponse { + schemaVersion: number; + mediaType?: string; + manifests?: { + digest: string; + mediaType: string; + platform: { + architecture: string; + os: string; + variant?: string; + }; + }[]; + config?: { + digest: string; + mediaType: string; + }; + history?: { + v1Compatibility: string; + }[]; +} /** * Docker Registry Abstract class. @@ -13,34 +50,16 @@ class Registry extends Component { * @param token * @returns {string} */ - static base64Encode(login, token) { + static base64Encode(login: string, token: string) { return Buffer.from(`${login}:${token}`, 'utf-8').toString('base64'); } - /** - * Override to apply custom format to the logger. - */ - async register(kind, type, name, configuration) { - this.log = log.child({ component: `${kind}.${type}.${name}` }); - this.kind = kind; - this.type = type; - this.name = name; - - this.configuration = this.validateConfiguration(configuration); - this.log.info( - `Register with configuration ${JSON.stringify(this.maskConfiguration(configuration))}`, - ); - await this.init(); - return this; - } - /** * If this registry is responsible for the image (to be overridden). * @param image the image * @returns {boolean} */ - - match(image) { + match(_image: ContainerImage): boolean { return false; } @@ -49,8 +68,7 @@ class Registry extends Component { * @param image * @returns {*} */ - - normalizeImage(image) { + normalizeImage(image: ContainerImage): ContainerImage { return image; } @@ -60,8 +78,10 @@ class Registry extends Component { * @param requestOptions * @returns {*} */ - - async authenticate(image, requestOptions) { + async authenticate( + _image: ContainerImage, + requestOptions: AxiosRequestConfig, + ): Promise { return requestOptions; } @@ -70,12 +90,12 @@ class Registry extends Component { * @param image * @returns {*} */ - async getTags(image) { + async getTags(image: ContainerImage): Promise { this.log.debug(`Get ${image.name} tags`); - const tags = []; - let page; + const tags: string[] = []; + let page: AxiosResponse | undefined = undefined; let hasNext = true; - let link; + let link: string | undefined = undefined; while (hasNext) { const lastItem = page && page.data && page.data.tags @@ -102,11 +122,15 @@ class Registry extends Component { * @param lastItem * @returns {Promise<*>} */ - getTagsPage(image, lastItem = undefined) { + getTagsPage( + image: ContainerImage, + lastItem: string | undefined = undefined, + _link: string | undefined = undefined, + ) { // Default items per page (not honoured by all registries) const itemsPerPage = 1000; const last = lastItem ? `&last=${lastItem}` : ''; - return this.callRegistry({ + return this.callRegistry({ image, url: `${image.registry.url}/${image.name}/tags/list?n=${itemsPerPage}${last}`, resolveWithFullResponse: true, @@ -119,20 +143,24 @@ class Registry extends Component { * @param digest (optional) * @returns {Promise} */ - async getImageManifestDigest(image, digest) { + async getImageManifestDigest( + image: ContainerImage, + digest?: string, + ): Promise { const tagOrDigest = digest || image.tag.value; let manifestDigestFound; let manifestMediaType; this.log.debug( `${this.getId()} - Get ${image.name}:${tagOrDigest} manifest`, ); - const responseManifests = await this.callRegistry({ - image, - url: `${image.registry.url}/${image.name}/manifests/${tagOrDigest}`, - headers: { - Accept: 'application/vnd.docker.distribution.manifest.list.v2+json, application/vnd.oci.image.index.v1+json, application/vnd.docker.distribution.manifest.v2+json, application/vnd.oci.image.manifest.v1+json', - }, - }); + const responseManifests = + await this.callRegistry({ + image, + url: `${image.registry.url}/${image.name}/manifests/${tagOrDigest}`, + headers: { + Accept: 'application/vnd.docker.distribution.manifest.list.v2+json, application/vnd.oci.image.index.v1+json, application/vnd.docker.distribution.manifest.v2+json, application/vnd.oci.image.manifest.v1+json', + }, + }); if (responseManifests) { log.debug(`Found manifests [${JSON.stringify(responseManifests)}]`); if (responseManifests.schemaVersion === 2) { @@ -151,7 +179,7 @@ class Registry extends Component { ); let manifestFound; const manifestFounds = responseManifests.manifests.filter( - (manifest) => + (manifest: any) => manifest.platform.architecture === image.architecture && manifest.platform.os === image.os, @@ -166,7 +194,7 @@ class Registry extends Component { if (manifestFounds.length > 1) { const manifestFoundFilteredOnVariant = manifestFounds.find( - (manifest) => + (manifest: any) => manifest.platform.variant === image.variant, ); @@ -221,15 +249,16 @@ class Registry extends Component { log.debug( 'Calling registry to get docker-content-digest header', ); - const responseManifest = await this.callRegistry({ - image, - method: 'head', - url: `${image.registry.url}/${image.name}/manifests/${manifestDigestFound}`, - headers: { - Accept: manifestMediaType, - }, - resolveWithFullResponse: true, - }); + const responseManifest = + await this.callRegistry({ + image, + method: 'head', + url: `${image.registry.url}/${image.name}/manifests/${manifestDigestFound}`, + headers: { + Accept: manifestMediaType, + }, + resolveWithFullResponse: true, + }); const manifestFound = { digest: responseManifest.headers['docker-content-digest'], version: 2, @@ -261,7 +290,23 @@ class Registry extends Component { throw new Error('Unexpected error; no manifest found'); } - async callRegistry({ + async callRegistry(options: { + image: ContainerImage; + url: string; + method?: Method; + headers?: any; + resolveWithFullResponse: true; + }): Promise>; + + async callRegistry(options: { + image: ContainerImage; + url: string; + method?: Method; + headers?: any; + resolveWithFullResponse?: false; + }): Promise; + + async callRegistry({ image, url, method = 'get', @@ -269,11 +314,17 @@ class Registry extends Component { Accept: 'application/json', }, resolveWithFullResponse = false, - }) { + }: { + image: ContainerImage; + url: string; + method?: Method; + headers?: any; + resolveWithFullResponse?: boolean; + }): Promise> { const start = new Date().getTime(); // Request options - const axiosOptions = { + const axiosOptions: AxiosRequestConfig = { url, method, headers, @@ -286,7 +337,9 @@ class Registry extends Component { ); try { - const response = await axios(axiosOptionsWithAuth); + const response = (await axios( + axiosOptionsWithAuth, + )) as AxiosResponse; const end = new Date().getTime(); getSummaryTags().observe( { type: this.type, name: this.name }, @@ -303,7 +356,7 @@ class Registry extends Component { } } - getImageFullName(image, tagOrDigest) { + getImageFullName(image: ContainerImage, tagOrDigest: string) { // digests are separated with @ whereas tags are separated with : const tagOrDigestWithSeparator = tagOrDigest.indexOf(':') !== -1 @@ -321,9 +374,9 @@ class Registry extends Component { * @returns {} */ - async getAuthPull() { + async getAuthPull(): Promise<{ username?: string, password?: string } | undefined> { return undefined; } } -module.exports = Registry; +export default Registry; diff --git a/app/registries/providers/acr/Acr.test.js b/app/registries/providers/acr/Acr.test.ts similarity index 82% rename from app/registries/providers/acr/Acr.test.js rename to app/registries/providers/acr/Acr.test.ts index fc85e7dd..56bb5140 100644 --- a/app/registries/providers/acr/Acr.test.js +++ b/app/registries/providers/acr/Acr.test.ts @@ -1,4 +1,5 @@ -const Acr = require('./Acr'); +// @ts-nocheck +import Acr from './Acr'; const acr = new Acr(); acr.configuration = { @@ -6,7 +7,7 @@ acr.configuration = { clientsecret: 'clientsecret', }; -test('validatedConfiguration should initialize when configuration is valid', () => { +test('validatedConfiguration should initialize when configuration is valid', async () => { expect( acr.validateConfiguration({ clientid: 'clientid', @@ -18,20 +19,20 @@ test('validatedConfiguration should initialize when configuration is valid', () }); }); -test('validatedConfiguration should throw error when configuration item is missing', () => { +test('validatedConfiguration should throw error when configuration item is missing', async () => { expect(() => { acr.validateConfiguration({}); }).toThrow('"clientid" is required'); }); -test('maskConfiguration should mask configuration secrets', () => { +test('maskConfiguration should mask configuration secrets', async () => { expect(acr.maskConfiguration()).toEqual({ clientid: 'clientid', clientsecret: 'c**********t', }); }); -test('match should return true when registry url is from acr', () => { +test('match should return true when registry url is from acr', async () => { expect( acr.match({ registry: { @@ -41,7 +42,7 @@ test('match should return true when registry url is from acr', () => { ).toBeTruthy(); }); -test('match should return false when registry url is not from acr', () => { +test('match should return false when registry url is not from acr', async () => { expect( acr.match({ registry: { @@ -51,7 +52,7 @@ test('match should return false when registry url is not from acr', () => { ).toBeFalsy(); }); -test('normalizeImage should return the proper registry v2 endpoint', () => { +test('normalizeImage should return the proper registry v2 endpoint', async () => { expect( acr.normalizeImage({ name: 'test/image', @@ -67,7 +68,7 @@ test('normalizeImage should return the proper registry v2 endpoint', () => { }); }); -test('authenticate should add basic auth', () => { +test('authenticate should add basic auth', async () => { expect(acr.authenticate(undefined, { headers: {} })).resolves.toEqual({ headers: { Authorization: 'Basic Y2xpZW50aWQ6Y2xpZW50c2VjcmV0', diff --git a/app/registries/providers/acr/Acr.js b/app/registries/providers/acr/Acr.ts similarity index 95% rename from app/registries/providers/acr/Acr.js rename to app/registries/providers/acr/Acr.ts index 34a785e3..ee4b233b 100644 --- a/app/registries/providers/acr/Acr.js +++ b/app/registries/providers/acr/Acr.ts @@ -1,4 +1,5 @@ -const Registry = require('../../Registry'); +// @ts-nocheck +import Registry from '../../Registry'; /** * Azure Container Registry integration. @@ -61,4 +62,4 @@ class Acr extends Registry { } } -module.exports = Acr; +export default Acr; diff --git a/app/registries/providers/custom/Custom.test.js b/app/registries/providers/custom/Custom.test.ts similarity index 86% rename from app/registries/providers/custom/Custom.test.js rename to app/registries/providers/custom/Custom.test.ts index ab79ef31..2d80eab0 100644 --- a/app/registries/providers/custom/Custom.test.js +++ b/app/registries/providers/custom/Custom.test.ts @@ -1,4 +1,5 @@ -const Custom = require('./Custom'); +// @ts-nocheck +import Custom from './Custom'; const custom = new Custom(); custom.configuration = { @@ -7,7 +8,7 @@ custom.configuration = { url: 'http://localhost:5000', }; -test('validatedConfiguration should initialize when configuration is valid', () => { +test('validatedConfiguration should initialize when configuration is valid', async () => { expect( custom.validateConfiguration({ url: 'http://localhost:5000', @@ -21,7 +22,7 @@ test('validatedConfiguration should initialize when configuration is valid', () }); }); -test('validatedConfiguration should throw error when auth is not base64', () => { +test('validatedConfiguration should throw error when auth is not base64', async () => { expect(() => { custom.validateConfiguration({ url: 'http://localhost:5000', @@ -30,7 +31,7 @@ test('validatedConfiguration should throw error when auth is not base64', () => }).toThrow('"auth" must be a valid base64 string'); }); -test('maskConfiguration should mask configuration secrets', () => { +test('maskConfiguration should mask configuration secrets', async () => { expect(custom.maskConfiguration()).toEqual({ auth: undefined, login: 'login', @@ -39,7 +40,7 @@ test('maskConfiguration should mask configuration secrets', () => { }); }); -test('match should return true when registry url is from custom', () => { +test('match should return true when registry url is from custom', async () => { expect( custom.match({ registry: { @@ -49,7 +50,7 @@ test('match should return true when registry url is from custom', () => { ).toBeTruthy(); }); -test('match should return false when registry url is not from custom', () => { +test('match should return false when registry url is not from custom', async () => { expect( custom.match({ registry: { @@ -59,7 +60,7 @@ test('match should return false when registry url is not from custom', () => { ).toBeFalsy(); }); -test('normalizeImage should return the proper registry v2 endpoint', () => { +test('normalizeImage should return the proper registry v2 endpoint', async () => { expect( custom.normalizeImage({ name: 'test/image', @@ -75,7 +76,7 @@ test('normalizeImage should return the proper registry v2 endpoint', () => { }); }); -test('authenticate should add basic auth', () => { +test('authenticate should add basic auth', async () => { expect(custom.authenticate(undefined, { headers: {} })).resolves.toEqual({ headers: { Authorization: 'Basic bG9naW46cGFzc3dvcmQ=', @@ -83,18 +84,18 @@ test('authenticate should add basic auth', () => { }); }); -test('getAuthCredentials should return base64 creds when set in configuration', () => { +test('getAuthCredentials should return base64 creds when set in configuration', async () => { custom.configuration.auth = 'dXNlcm5hbWU6cGFzc3dvcmQ='; expect(custom.getAuthCredentials()).toEqual('dXNlcm5hbWU6cGFzc3dvcmQ='); }); -test('getAuthCredentials should return base64 creds when login/token set in configuration', () => { +test('getAuthCredentials should return base64 creds when login/token set in configuration', async () => { custom.configuration.login = 'username'; custom.configuration.token = 'password'; expect(custom.getAuthCredentials()).toEqual('dXNlcm5hbWU6cGFzc3dvcmQ='); }); -test('getAuthCredentials should return undefined when no login/token/auth set in configuration', () => { +test('getAuthCredentials should return undefined when no login/token/auth set in configuration', async () => { custom.configuration = {}; expect(custom.getAuthCredentials()).toBe(undefined); }); diff --git a/app/registries/providers/custom/Custom.js b/app/registries/providers/custom/Custom.ts similarity index 95% rename from app/registries/providers/custom/Custom.js rename to app/registries/providers/custom/Custom.ts index e20be56e..965494b3 100644 --- a/app/registries/providers/custom/Custom.js +++ b/app/registries/providers/custom/Custom.ts @@ -1,4 +1,5 @@ -const BaseRegistry = require('../../BaseRegistry'); +// @ts-nocheck +import BaseRegistry from '../../BaseRegistry'; /** * Docker Custom Registry V2 integration. @@ -65,4 +66,4 @@ class Custom extends BaseRegistry { } } -module.exports = Custom; +export default Custom; diff --git a/app/registries/providers/ecr/Ecr.test.js b/app/registries/providers/ecr/Ecr.test.ts similarity index 88% rename from app/registries/providers/ecr/Ecr.test.js rename to app/registries/providers/ecr/Ecr.test.ts index 52fa126f..9957c297 100644 --- a/app/registries/providers/ecr/Ecr.test.js +++ b/app/registries/providers/ecr/Ecr.test.ts @@ -1,4 +1,5 @@ -const Ecr = require('./Ecr'); +// @ts-nocheck +import Ecr from './Ecr'; jest.mock('aws-sdk/clients/ecr', () => jest.fn().mockImplementation(() => ({ @@ -20,7 +21,7 @@ ecr.configuration = { jest.mock('axios'); -test('validatedConfiguration should initialize when configuration is valid', () => { +test('validatedConfiguration should initialize when configuration is valid', async () => { expect( ecr.validateConfiguration({ accesskeyid: 'accesskeyid', @@ -34,7 +35,7 @@ test('validatedConfiguration should initialize when configuration is valid', () }); }); -test('validatedConfiguration should throw error when accessKey is missing', () => { +test('validatedConfiguration should throw error when accessKey is missing', async () => { expect(() => { ecr.validateConfiguration({ secretaccesskey: 'secretaccesskey', @@ -43,7 +44,7 @@ test('validatedConfiguration should throw error when accessKey is missing', () = }).toThrow('"accesskeyid" is required'); }); -test('validatedConfiguration should throw error when secretaccesskey is missing', () => { +test('validatedConfiguration should throw error when secretaccesskey is missing', async () => { expect(() => { ecr.validateConfiguration({ accesskeyid: 'accesskeyid', @@ -52,7 +53,7 @@ test('validatedConfiguration should throw error when secretaccesskey is missing' }).toThrow('"secretaccesskey" is required'); }); -test('validatedConfiguration should throw error when secretaccesskey is missing', () => { +test('validatedConfiguration should throw error when secretaccesskey is missing', async () => { expect(() => { ecr.validateConfiguration({ accesskeyid: 'accesskeyid', @@ -61,7 +62,7 @@ test('validatedConfiguration should throw error when secretaccesskey is missing' }).toThrow('"region" is required'); }); -test('match should return true when registry url is from ecr', () => { +test('match should return true when registry url is from ecr', async () => { expect( ecr.match({ registry: { @@ -71,7 +72,7 @@ test('match should return true when registry url is from ecr', () => { ).toBeTruthy(); }); -test('match should return false when registry url is not from ecr', () => { +test('match should return false when registry url is not from ecr', async () => { expect( ecr.match({ registry: { @@ -81,7 +82,7 @@ test('match should return false when registry url is not from ecr', () => { ).toBeFalsy(); }); -test('maskConfiguration should mask configuration secrets', () => { +test('maskConfiguration should mask configuration secrets', async () => { expect(ecr.maskConfiguration()).toEqual({ accesskeyid: 'a*********d', region: 'region', @@ -89,7 +90,7 @@ test('maskConfiguration should mask configuration secrets', () => { }); }); -test('normalizeImage should return the proper registry v2 endpoint', () => { +test('normalizeImage should return the proper registry v2 endpoint', async () => { expect( ecr.normalizeImage({ name: 'test/image', @@ -105,7 +106,7 @@ test('normalizeImage should return the proper registry v2 endpoint', () => { }); }); -test('authenticate should call ecr auth endpoint', () => { +test('authenticate should call ecr auth endpoint', async () => { expect(ecr.authenticate(undefined, { headers: {} })).resolves.toEqual({ headers: { Authorization: 'Basic QVdTOnh4eHg=', diff --git a/app/registries/providers/ecr/Ecr.js b/app/registries/providers/ecr/Ecr.ts similarity index 91% rename from app/registries/providers/ecr/Ecr.js rename to app/registries/providers/ecr/Ecr.ts index 5f6f5cdb..751c3f5e 100644 --- a/app/registries/providers/ecr/Ecr.js +++ b/app/registries/providers/ecr/Ecr.ts @@ -1,7 +1,10 @@ -const ECR = require('aws-sdk/clients/ecr'); -require('aws-sdk/lib/maintenance_mode_message').suppress = true; // Disable aws sdk maintenance mode message at startup -const axios = require('axios'); -const Registry = require('../../Registry'); +// @ts-nocheck +import ECR from 'aws-sdk/clients/ecr'; +import maintenanceMode from 'aws-sdk/lib/maintenance_mode_message'; +// @ts-ignore +maintenanceMode.suppress = true; // Disable aws sdk maintenance mode message at startup +import axios from 'axios'; +import Registry from '../../Registry'; const ECR_PUBLIC_GALLERY_HOSTNAME = 'public.ecr.aws'; @@ -110,4 +113,4 @@ class Ecr extends Registry { } } -module.exports = Ecr; +export default Ecr; diff --git a/app/registries/providers/forgejo/Forgejo.test.js b/app/registries/providers/forgejo/Forgejo.test.ts similarity index 88% rename from app/registries/providers/forgejo/Forgejo.test.js rename to app/registries/providers/forgejo/Forgejo.test.ts index 316a4a15..04ef67a0 100644 --- a/app/registries/providers/forgejo/Forgejo.test.js +++ b/app/registries/providers/forgejo/Forgejo.test.ts @@ -1,4 +1,5 @@ -const Forgejo = require('./Forgejo'); +// @ts-nocheck +import Forgejo from './Forgejo'; const forgejo = new Forgejo(); forgejo.configuration = { @@ -7,7 +8,7 @@ forgejo.configuration = { url: 'https://forgejo.acme.com', }; -test('normalizeImage should return the proper registry v2 endpoint', () => { +test('normalizeImage should return the proper registry v2 endpoint', async () => { expect( forgejo.normalizeImage({ name: 'test/image', diff --git a/app/registries/providers/forgejo/Forgejo.js b/app/registries/providers/forgejo/Forgejo.ts similarity index 52% rename from app/registries/providers/forgejo/Forgejo.js rename to app/registries/providers/forgejo/Forgejo.ts index 6ddec6e3..c4920c65 100644 --- a/app/registries/providers/forgejo/Forgejo.js +++ b/app/registries/providers/forgejo/Forgejo.ts @@ -1,8 +1,9 @@ -const Gitea = require('../gitea/Gitea'); +// @ts-nocheck +import Gitea from '../gitea/Gitea'; /** * Forgejo Container Registry integration. */ class Forgejo extends Gitea {} -module.exports = Forgejo; +export default Forgejo; diff --git a/app/registries/providers/gcr/Gcr.test.js b/app/registries/providers/gcr/Gcr.test.ts similarity index 86% rename from app/registries/providers/gcr/Gcr.test.js rename to app/registries/providers/gcr/Gcr.test.ts index c4918d62..21a5715e 100644 --- a/app/registries/providers/gcr/Gcr.test.js +++ b/app/registries/providers/gcr/Gcr.test.ts @@ -1,4 +1,5 @@ -const Gcr = require('./Gcr'); +// @ts-nocheck +import Gcr from './Gcr'; jest.mock('axios', () => jest.fn().mockImplementation(() => ({ @@ -14,7 +15,7 @@ gcr.configuration = { jest.mock('axios'); -test('validatedConfiguration should initialize when configuration is valid', () => { +test('validatedConfiguration should initialize when configuration is valid', async () => { expect( gcr.validateConfiguration({ clientemail: 'accesskeyid', @@ -26,20 +27,20 @@ test('validatedConfiguration should initialize when configuration is valid', () }); }); -test('validatedConfiguration should throw error when configuration is missing', () => { +test('validatedConfiguration should throw error when configuration is missing', async () => { expect(() => { gcr.validateConfiguration({}); }).toThrow('"clientemail" is required'); }); -test('maskConfiguration should mask configuration secrets', () => { +test('maskConfiguration should mask configuration secrets', async () => { expect(gcr.maskConfiguration()).toEqual({ clientemail: 'accesskeyid', privatekey: 's*************y', }); }); -test('match should return true when registry url is from gcr', () => { +test('match should return true when registry url is from gcr', async () => { expect( gcr.match({ registry: { @@ -70,7 +71,7 @@ test('match should return true when registry url is from gcr', () => { ).toBeTruthy(); }); -test('match should return false when registry url is not from gcr', () => { +test('match should return false when registry url is not from gcr', async () => { expect( gcr.match({ registry: { @@ -80,7 +81,7 @@ test('match should return false when registry url is not from gcr', () => { ).toBeFalsy(); }); -test('normalizeImage should return the proper registry v2 endpoint', () => { +test('normalizeImage should return the proper registry v2 endpoint', async () => { expect( gcr.normalizeImage({ name: 'test/image', @@ -96,7 +97,7 @@ test('normalizeImage should return the proper registry v2 endpoint', () => { }); }); -test('authenticate should call ecr auth endpoint', () => { +test('authenticate should call ecr auth endpoint', async () => { expect(gcr.authenticate({}, { headers: {} })).resolves.toEqual({ headers: { Authorization: 'Bearer xxxxx', diff --git a/app/registries/providers/gcr/Gcr.js b/app/registries/providers/gcr/Gcr.ts similarity index 94% rename from app/registries/providers/gcr/Gcr.js rename to app/registries/providers/gcr/Gcr.ts index e0336dd1..5b617944 100644 --- a/app/registries/providers/gcr/Gcr.js +++ b/app/registries/providers/gcr/Gcr.ts @@ -1,5 +1,6 @@ -const axios = require('axios'); -const BaseRegistry = require('../../BaseRegistry'); +// @ts-nocheck +import axios from 'axios'; +import BaseRegistry from '../../BaseRegistry'; /** * Google Container Registry integration. @@ -60,4 +61,4 @@ class Gcr extends BaseRegistry { } } -module.exports = Gcr; +export default Gcr; diff --git a/app/registries/providers/ghcr/Ghcr.test.js b/app/registries/providers/ghcr/Ghcr.test.ts similarity index 89% rename from app/registries/providers/ghcr/Ghcr.test.js rename to app/registries/providers/ghcr/Ghcr.test.ts index 71609b04..107eaff1 100644 --- a/app/registries/providers/ghcr/Ghcr.test.js +++ b/app/registries/providers/ghcr/Ghcr.test.ts @@ -1,4 +1,5 @@ -const Ghcr = require('./Ghcr'); +// @ts-nocheck +import Ghcr from './Ghcr'; describe('GitHub Container Registry', () => { let ghcr; @@ -11,24 +12,24 @@ describe('GitHub Container Registry', () => { }); }); - test('should create instance', () => { + test('should create instance', async () => { expect(ghcr).toBeDefined(); expect(ghcr).toBeInstanceOf(Ghcr); }); - test('should match registry', () => { + test('should match registry', async () => { expect(ghcr.match({ registry: { url: 'ghcr.io' } })).toBe(true); expect(ghcr.match({ registry: { url: 'docker.io' } })).toBe(false); }); - test('should normalize image name', () => { + test('should normalize image name', async () => { const image = { name: 'user/repo', registry: { url: 'ghcr.io' } }; const normalized = ghcr.normalizeImage(image); expect(normalized.name).toBe('user/repo'); expect(normalized.registry.url).toBe('https://ghcr.io/v2'); }); - test('should not modify URL if already starts with https', () => { + test('should not modify URL if already starts with https', async () => { const image = { name: 'user/repo', registry: { url: 'https://ghcr.io/v2' }, @@ -37,7 +38,7 @@ describe('GitHub Container Registry', () => { expect(normalized.registry.url).toBe('https://ghcr.io/v2'); }); - test('should mask configuration token', () => { + test('should mask configuration token', async () => { ghcr.configuration = { username: 'testuser', token: 'secret_token' }; const masked = ghcr.maskConfiguration(); expect(masked.username).toBe('testuser'); @@ -83,7 +84,7 @@ describe('GitHub Container Registry', () => { expect(result.headers.Authorization).toBe(`Bearer ${expectedBearer}`); }); - test('should validate string configuration', () => { + test('should validate string configuration', async () => { expect(() => ghcr.validateConfiguration('')).not.toThrow(); expect(() => ghcr.validateConfiguration('some-string')).not.toThrow(); }); diff --git a/app/registries/providers/ghcr/Ghcr.js b/app/registries/providers/ghcr/Ghcr.ts similarity index 91% rename from app/registries/providers/ghcr/Ghcr.js rename to app/registries/providers/ghcr/Ghcr.ts index 2752215b..9531892a 100644 --- a/app/registries/providers/ghcr/Ghcr.js +++ b/app/registries/providers/ghcr/Ghcr.ts @@ -1,4 +1,5 @@ -const BaseRegistry = require('../../BaseRegistry'); +// @ts-nocheck +import BaseRegistry from '../../BaseRegistry'; /** * Github Container Registry integration. @@ -35,4 +36,4 @@ class Ghcr extends BaseRegistry { } } -module.exports = Ghcr; +export default Ghcr; diff --git a/app/registries/providers/gitea/Gitea.test.js b/app/registries/providers/gitea/Gitea.test.ts similarity index 87% rename from app/registries/providers/gitea/Gitea.test.js rename to app/registries/providers/gitea/Gitea.test.ts index 3de97fc7..6f1a81b3 100644 --- a/app/registries/providers/gitea/Gitea.test.js +++ b/app/registries/providers/gitea/Gitea.test.ts @@ -1,4 +1,5 @@ -const Gitea = require('./Gitea'); +// @ts-nocheck +import Gitea from './Gitea'; const gitea = new Gitea(); gitea.configuration = { @@ -7,7 +8,7 @@ gitea.configuration = { url: 'https://gitea.acme.com', }; -test('validatedConfiguration should initialize when configuration is valid', () => { +test('validatedConfiguration should initialize when configuration is valid', async () => { expect( gitea.validateConfiguration({ url: 'https://gitea.acme.com', @@ -21,7 +22,7 @@ test('validatedConfiguration should initialize when configuration is valid', () }); }); -test('validatedConfiguration should throw error when auth is not base64', () => { +test('validatedConfiguration should throw error when auth is not base64', async () => { expect(() => { gitea.validateConfiguration({ url: 'https://gitea.acme.com', @@ -30,7 +31,7 @@ test('validatedConfiguration should throw error when auth is not base64', () => }).toThrow('"auth" must be a valid base64 string'); }); -test('match should return true when registry url is from gitea', () => { +test('match should return true when registry url is from gitea', async () => { expect( gitea.match({ registry: { @@ -40,7 +41,7 @@ test('match should return true when registry url is from gitea', () => { ).toBeTruthy(); }); -test('match should return false when registry url is not from custom', () => { +test('match should return false when registry url is not from custom', async () => { expect( gitea.match({ registry: { @@ -50,7 +51,7 @@ test('match should return false when registry url is not from custom', () => { ).toBeFalsy(); }); -test('normalizeImage should return the proper registry v2 endpoint', () => { +test('normalizeImage should return the proper registry v2 endpoint', async () => { expect( gitea.normalizeImage({ name: 'test/image', @@ -66,7 +67,7 @@ test('normalizeImage should return the proper registry v2 endpoint', () => { }); }); -test('should initialize and prepend https to URL without protocol', () => { +test('should initialize and prepend https to URL without protocol', async () => { const giteaInstance = new Gitea(); giteaInstance.configuration = { url: 'gitea.example.com', @@ -78,7 +79,7 @@ test('should initialize and prepend https to URL without protocol', () => { expect(giteaInstance.configuration.url).toBe('https://gitea.example.com'); }); -test('should not modify URL that already has protocol', () => { +test('should not modify URL that already has protocol', async () => { const giteaInstance = new Gitea(); giteaInstance.configuration = { url: 'http://gitea.example.com', @@ -90,7 +91,7 @@ test('should not modify URL that already has protocol', () => { expect(giteaInstance.configuration.url).toBe('http://gitea.example.com'); }); -test('should validate configuration with auth instead of login/password', () => { +test('should validate configuration with auth instead of login/password', async () => { const config = { url: 'https://gitea.example.com', auth: Buffer.from('user:pass').toString('base64'), @@ -99,7 +100,7 @@ test('should validate configuration with auth instead of login/password', () => expect(() => gitea.validateConfiguration(config)).not.toThrow(); }); -test('should validate configuration with empty auth', () => { +test('should validate configuration with empty auth', async () => { const config = { url: 'https://gitea.example.com', auth: '', @@ -108,7 +109,7 @@ test('should validate configuration with empty auth', () => { expect(() => gitea.validateConfiguration(config)).not.toThrow(); }); -test('match should handle URLs with different protocols', () => { +test('match should handle URLs with different protocols', async () => { const giteaWithHttp = new Gitea(); giteaWithHttp.configuration = { url: 'http://gitea.acme.com' }; @@ -119,7 +120,7 @@ test('match should handle URLs with different protocols', () => { ).toBeTruthy(); }); -test('match should be case insensitive', () => { +test('match should be case insensitive', async () => { const giteaUpper = new Gitea(); giteaUpper.configuration = { url: 'https://GITEA.ACME.COM' }; diff --git a/app/registries/providers/gitea/Gitea.js b/app/registries/providers/gitea/Gitea.ts similarity index 96% rename from app/registries/providers/gitea/Gitea.js rename to app/registries/providers/gitea/Gitea.ts index 565b94c8..31f8febc 100644 --- a/app/registries/providers/gitea/Gitea.js +++ b/app/registries/providers/gitea/Gitea.ts @@ -1,4 +1,5 @@ -const Custom = require('../custom/Custom'); +// @ts-nocheck +import Custom from '../custom/Custom'; /** * Gitea Container Registry integration. @@ -67,4 +68,4 @@ class Gitea extends Custom { } } -module.exports = Gitea; +export default Gitea; diff --git a/app/registries/providers/gitlab/Gitlab.test.js b/app/registries/providers/gitlab/Gitlab.test.ts similarity index 86% rename from app/registries/providers/gitlab/Gitlab.test.js rename to app/registries/providers/gitlab/Gitlab.test.ts index 7f91cbed..ac853e86 100644 --- a/app/registries/providers/gitlab/Gitlab.test.js +++ b/app/registries/providers/gitlab/Gitlab.test.ts @@ -1,5 +1,6 @@ -const axios = require('axios'); -const Gitlab = require('./Gitlab'); +// @ts-nocheck +import axios from 'axios'; +import Gitlab from './Gitlab'; const gitlab = new Gitlab(); gitlab.configuration = { @@ -10,7 +11,7 @@ gitlab.configuration = { jest.mock('axios'); -test('validatedConfiguration should initialize when configuration is valid', () => { +test('validatedConfiguration should initialize when configuration is valid', async () => { expect( gitlab.validateConfiguration({ token: 'abcdef', @@ -33,13 +34,13 @@ test('validatedConfiguration should initialize when configuration is valid', () }); }); -test('validatedConfiguration should throw error when no pam', () => { +test('validatedConfiguration should throw error when no pam', async () => { expect(() => { gitlab.validateConfiguration({}); }).toThrow('"token" is required'); }); -test('maskConfiguration should mask configuration secrets', () => { +test('maskConfiguration should mask configuration secrets', async () => { expect(gitlab.maskConfiguration()).toEqual({ url: 'https://registry.gitlab.com', authurl: 'https://gitlab.com', @@ -47,7 +48,7 @@ test('maskConfiguration should mask configuration secrets', () => { }); }); -test('match should return true when registry url is from gitlab.com', () => { +test('match should return true when registry url is from gitlab.com', async () => { expect( gitlab.match({ registry: { @@ -57,7 +58,7 @@ test('match should return true when registry url is from gitlab.com', () => { ).toBeTruthy(); }); -test('match should return true when registry url is from custom gitlab', () => { +test('match should return true when registry url is from custom gitlab', async () => { const gitlabCustom = new Gitlab(); gitlabCustom.configuration = { url: 'https://registry.custom.com', @@ -73,7 +74,7 @@ test('match should return true when registry url is from custom gitlab', () => { ).toBeTruthy(); }); -test('authenticate should perform authenticate request', () => { +test('authenticate should perform authenticate request', async () => { axios.mockImplementation(() => ({ data: { token: 'token', @@ -89,7 +90,7 @@ test('authenticate should perform authenticate request', () => { ).resolves.toEqual({ headers: { Authorization: 'Bearer token' } }); }); -test('normalizeImage should return the proper registry v2 endpoint', () => { +test('normalizeImage should return the proper registry v2 endpoint', async () => { expect( gitlab.normalizeImage({ name: 'test/image', diff --git a/app/registries/providers/gitlab/Gitlab.js b/app/registries/providers/gitlab/Gitlab.ts similarity index 95% rename from app/registries/providers/gitlab/Gitlab.js rename to app/registries/providers/gitlab/Gitlab.ts index 741af23b..0482c019 100644 --- a/app/registries/providers/gitlab/Gitlab.js +++ b/app/registries/providers/gitlab/Gitlab.ts @@ -1,5 +1,6 @@ -const axios = require('axios'); -const Registry = require('../../Registry'); +// @ts-nocheck +import axios from 'axios'; +import Registry from '../../Registry'; /** * Docker Gitlab integration. @@ -86,4 +87,4 @@ class Gitlab extends Registry { } } -module.exports = Gitlab; +export default Gitlab; diff --git a/app/registries/providers/hub/Hub.test.js b/app/registries/providers/hub/Hub.test.ts similarity index 84% rename from app/registries/providers/hub/Hub.test.js rename to app/registries/providers/hub/Hub.test.ts index 1d5cea41..9384055b 100644 --- a/app/registries/providers/hub/Hub.test.js +++ b/app/registries/providers/hub/Hub.test.ts @@ -1,4 +1,5 @@ -const Hub = require('./Hub'); +// @ts-nocheck +import Hub from './Hub'; // Mock axios jest.mock('axios', () => jest.fn()); @@ -12,16 +13,16 @@ describe('Docker Hub Registry', () => { jest.clearAllMocks(); }); - test('should create instance', () => { + test('should create instance', async () => { expect(hub).toBeDefined(); expect(hub).toBeInstanceOf(Hub); }); - test('should have correct registry url after init', () => { + test('should have correct registry url after init', async () => { expect(hub.configuration.url).toBe('https://registry-1.docker.io'); }); - test('should match registry', () => { + test('should match registry', async () => { expect(hub.match({ registry: { url: 'registry-1.docker.io' } })).toBe( true, ); @@ -32,28 +33,28 @@ describe('Docker Hub Registry', () => { ); }); - test('should normalize image name for official images', () => { + test('should normalize image name for official images', async () => { const image = { name: 'nginx', registry: {} }; const normalized = hub.normalizeImage(image); expect(normalized.name).toBe('library/nginx'); expect(normalized.registry.url).toBe('https://registry-1.docker.io/v2'); }); - test('should not normalize image name for user images', () => { + test('should not normalize image name for user images', async () => { const image = { name: 'user/nginx', registry: {} }; const normalized = hub.normalizeImage(image); expect(normalized.name).toBe('user/nginx'); expect(normalized.registry.url).toBe('https://registry-1.docker.io/v2'); }); - test('should mask configuration with token', () => { + test('should mask configuration with token', async () => { hub.configuration = { login: 'testuser', token: 'secret_token' }; const masked = hub.maskConfiguration(); expect(masked.login).toBe('testuser'); expect(masked.token).toBe('s**********n'); }); - test('should get image full name without registry prefix', () => { + test('should get image full name without registry prefix', async () => { const image = { name: 'library/nginx', registry: { url: 'https://registry-1.docker.io/v2' }, @@ -62,7 +63,7 @@ describe('Docker Hub Registry', () => { expect(fullName).toBe('nginx:1.0.0'); }); - test('should get image full name for user images', () => { + test('should get image full name for user images', async () => { const image = { name: 'user/nginx', registry: { url: 'https://registry-1.docker.io/v2' }, @@ -80,7 +81,7 @@ describe('Docker Hub Registry', () => { }); test('should authenticate with credentials', async () => { - const axios = require('axios'); + const { default: axios } = await import('axios'); axios.mockResolvedValue({ data: { token: 'auth-token' } }); hub.getAuthCredentials = jest.fn().mockReturnValue('base64credentials'); @@ -102,7 +103,7 @@ describe('Docker Hub Registry', () => { }); test('should authenticate without credentials', async () => { - const axios = require('axios'); + const { default: axios } = await import('axios'); axios.mockResolvedValue({ data: { token: 'public-token' } }); hub.getAuthCredentials = jest.fn().mockReturnValue(null); @@ -122,12 +123,12 @@ describe('Docker Hub Registry', () => { expect(result.headers.Authorization).toBe('Bearer public-token'); }); - test('should validate string configuration', () => { + test('should validate string configuration', async () => { expect(() => hub.validateConfiguration('')).not.toThrow(); expect(() => hub.validateConfiguration('some-string')).not.toThrow(); }); - test('should validate object configuration with auth', () => { + test('should validate object configuration with auth', async () => { const config = { login: 'user', password: 'pass', @@ -136,7 +137,7 @@ describe('Docker Hub Registry', () => { expect(() => hub.validateConfiguration(config)).not.toThrow(); }); - test('should mask all configuration fields', () => { + test('should mask all configuration fields', async () => { hub.configuration = { url: 'https://registry-1.docker.io', login: 'testuser', diff --git a/app/registries/providers/hub/Hub.js b/app/registries/providers/hub/Hub.ts similarity index 96% rename from app/registries/providers/hub/Hub.js rename to app/registries/providers/hub/Hub.ts index 4902e157..e9c8fb7b 100644 --- a/app/registries/providers/hub/Hub.js +++ b/app/registries/providers/hub/Hub.ts @@ -1,5 +1,6 @@ -const axios = require('axios'); -const Custom = require('../custom/Custom'); +// @ts-nocheck +import axios from 'axios'; +import Custom from '../custom/Custom'; /** * Docker Hub integration. @@ -105,4 +106,4 @@ class Hub extends Custom { } } -module.exports = Hub; +export default Hub; diff --git a/app/registries/providers/lscr/Lscr.test.js b/app/registries/providers/lscr/Lscr.test.ts similarity index 90% rename from app/registries/providers/lscr/Lscr.test.js rename to app/registries/providers/lscr/Lscr.test.ts index c34fab11..74135c6d 100644 --- a/app/registries/providers/lscr/Lscr.test.js +++ b/app/registries/providers/lscr/Lscr.test.ts @@ -1,4 +1,5 @@ -const Lscr = require('./Lscr'); +// @ts-nocheck +import Lscr from './Lscr'; jest.mock('axios', () => jest.fn().mockImplementation(() => ({ @@ -14,7 +15,7 @@ lscr.configuration = { jest.mock('axios'); -test('validatedConfiguration should initialize when configuration is valid', () => { +test('validatedConfiguration should initialize when configuration is valid', async () => { expect( lscr.validateConfiguration({ username: 'user', @@ -26,13 +27,13 @@ test('validatedConfiguration should initialize when configuration is valid', () }); }); -test('validatedConfiguration should throw error when configuration is missing', () => { +test('validatedConfiguration should throw error when configuration is missing', async () => { expect(() => { lscr.validateConfiguration({}); }).toThrow('"username" is required'); }); -test('match should return true when registry url is from lscr', () => { +test('match should return true when registry url is from lscr', async () => { expect( lscr.match({ registry: { @@ -42,7 +43,7 @@ test('match should return true when registry url is from lscr', () => { ).toBeTruthy(); }); -test('match should return false when registry url is not from lscr', () => { +test('match should return false when registry url is not from lscr', async () => { expect( lscr.match({ registry: { @@ -52,7 +53,7 @@ test('match should return false when registry url is not from lscr', () => { ).toBeFalsy(); }); -test('normalizeImage should return the proper registry v2 endpoint', () => { +test('normalizeImage should return the proper registry v2 endpoint', async () => { expect( lscr.normalizeImage({ name: 'test/image', diff --git a/app/registries/providers/lscr/Lscr.js b/app/registries/providers/lscr/Lscr.ts similarity index 92% rename from app/registries/providers/lscr/Lscr.js rename to app/registries/providers/lscr/Lscr.ts index 056519a7..9083be43 100644 --- a/app/registries/providers/lscr/Lscr.js +++ b/app/registries/providers/lscr/Lscr.ts @@ -1,4 +1,5 @@ -const Ghcr = require('../ghcr/Ghcr'); +// @ts-nocheck +import Ghcr from '../ghcr/Ghcr'; /** * Linux-Server Container Registry integration. @@ -36,4 +37,4 @@ class Lscr extends Ghcr { } } -module.exports = Lscr; +export default Lscr; diff --git a/app/registries/providers/quay/Quay.test.js b/app/registries/providers/quay/Quay.test.ts similarity index 88% rename from app/registries/providers/quay/Quay.test.js rename to app/registries/providers/quay/Quay.test.ts index 15550558..692833e7 100644 --- a/app/registries/providers/quay/Quay.test.js +++ b/app/registries/providers/quay/Quay.test.ts @@ -1,6 +1,7 @@ -const axios = require('axios'); -const Quay = require('./Quay'); -const log = require('../../../log'); +// @ts-nocheck +import axios from 'axios'; +import Quay from './Quay'; +import log from '../../../log'; jest.mock('axios'); axios.mockImplementation(() => ({ @@ -15,12 +16,12 @@ quay.configuration = { }; quay.log = log; -test('validatedConfiguration should initialize when anonymous configuration is valid', () => { +test('validatedConfiguration should initialize when anonymous configuration is valid', async () => { expect(quay.validateConfiguration('')).toStrictEqual({}); expect(quay.validateConfiguration(undefined)).toStrictEqual({}); }); -test('validatedConfiguration should initialize when auth configuration is valid', () => { +test('validatedConfiguration should initialize when auth configuration is valid', async () => { expect( quay.validateConfiguration({ namespace: 'namespace', @@ -34,19 +35,19 @@ test('validatedConfiguration should initialize when auth configuration is valid' }); }); -test('validatedConfiguration should throw error when configuration is missing', () => { +test('validatedConfiguration should throw error when configuration is missing', async () => { expect(() => { quay.validateConfiguration({}); }).toThrow('"namespace" is required'); }); -test('maskConfiguration should mask anonymous configuration secrets', () => { +test('maskConfiguration should mask anonymous configuration secrets', async () => { const quayInstance = new Quay(); quayInstance.configuration = ''; expect(quayInstance.maskConfiguration()).toEqual({}); }); -test('maskConfiguration should mask authentication configuration secrets', () => { +test('maskConfiguration should mask authentication configuration secrets', async () => { expect(quay.maskConfiguration()).toEqual({ account: 'account', namespace: 'namespace', @@ -54,7 +55,7 @@ test('maskConfiguration should mask authentication configuration secrets', () => }); }); -test('match should return true when registry url is from quay.io', () => { +test('match should return true when registry url is from quay.io', async () => { expect( quay.match({ registry: { @@ -64,7 +65,7 @@ test('match should return true when registry url is from quay.io', () => { ).toBeTruthy(); }); -test('match should return false when registry url is not from quay.io', () => { +test('match should return false when registry url is not from quay.io', async () => { expect( quay.match({ registry: { @@ -74,7 +75,7 @@ test('match should return false when registry url is not from quay.io', () => { ).toBeFalsy(); }); -test('normalizeImage should return the proper registry v2 endpoint', () => { +test('normalizeImage should return the proper registry v2 endpoint', async () => { expect( quay.normalizeImage({ name: 'test/image', @@ -90,13 +91,13 @@ test('normalizeImage should return the proper registry v2 endpoint', () => { }); }); -test('getAuthCredentials should return undefined when anonymous configuration', () => { +test('getAuthCredentials should return undefined when anonymous configuration', async () => { const quayInstance = new Quay(); quayInstance.configuration = {}; expect(quayInstance.getAuthCredentials()).toEqual(undefined); }); -test('getAuthCredentials should return base64 encode credentials when auth configuration', () => { +test('getAuthCredentials should return base64 encode credentials when auth configuration', async () => { const quayInstance = new Quay(); quayInstance.configuration = { namespace: 'namespace', @@ -127,7 +128,7 @@ test('getAuthPull should return credentials when auth configuration', async () = }); }); -test('authenticate should populate header with base64 bearer', () => { +test('authenticate should populate header with base64 bearer', async () => { expect(quay.authenticate({}, { headers: {} })).resolves.toEqual({ headers: { Authorization: 'Bearer token', @@ -135,7 +136,7 @@ test('authenticate should populate header with base64 bearer', () => { }); }); -test('authenticate should not populate header with base64 bearer when anonymous', () => { +test('authenticate should not populate header with base64 bearer when anonymous', async () => { const quayInstance = new Quay(); quayInstance.configuration = {}; expect(quayInstance.authenticate({}, { headers: {} })).resolves.toEqual({ diff --git a/app/registries/providers/quay/Quay.js b/app/registries/providers/quay/Quay.ts similarity index 97% rename from app/registries/providers/quay/Quay.js rename to app/registries/providers/quay/Quay.ts index ca1a0288..06e3f86e 100644 --- a/app/registries/providers/quay/Quay.js +++ b/app/registries/providers/quay/Quay.ts @@ -1,5 +1,6 @@ -const axios = require('axios'); -const Registry = require('../../Registry'); +// @ts-nocheck +import axios from 'axios'; +import Registry from '../../Registry'; /** * Quay.io Registry integration. @@ -137,4 +138,4 @@ class Quay extends Registry { } } -module.exports = Quay; +export default Quay; diff --git a/app/registries/providers/trueforge/trueforge.test.js b/app/registries/providers/trueforge/trueforge.test.ts similarity index 89% rename from app/registries/providers/trueforge/trueforge.test.js rename to app/registries/providers/trueforge/trueforge.test.ts index 8ff0c87a..176ab533 100644 --- a/app/registries/providers/trueforge/trueforge.test.js +++ b/app/registries/providers/trueforge/trueforge.test.ts @@ -1,4 +1,5 @@ -const Trueforge = require('./trueforge'); +// @ts-nocheck +import Trueforge from './trueforge'; jest.mock('axios', () => jest.fn().mockImplementation(() => ({ @@ -14,7 +15,7 @@ trueforge.configuration = { jest.mock('axios'); -test('validatedConfiguration should initialize when configuration is valid', () => { +test('validatedConfiguration should initialize when configuration is valid', async () => { expect( trueforge.validateConfiguration({ username: 'user', @@ -26,13 +27,13 @@ test('validatedConfiguration should initialize when configuration is valid', () }); }); -test('validatedConfiguration should throw error when configuration is missing', () => { +test('validatedConfiguration should throw error when configuration is missing', async () => { expect(() => { trueforge.validateConfiguration({}); }).toThrow('"username" is required'); }); -test('match should return true when registry url is from trueforge', () => { +test('match should return true when registry url is from trueforge', async () => { expect( trueforge.match({ registry: { @@ -42,7 +43,7 @@ test('match should return true when registry url is from trueforge', () => { ).toBeTruthy(); }); -test('match should return false when registry url is not from trueforge', () => { +test('match should return false when registry url is not from trueforge', async () => { expect( trueforge.match({ registry: { @@ -52,7 +53,7 @@ test('match should return false when registry url is not from trueforge', () => ).toBeFalsy(); }); -test('normalizeImage should return the proper registry v2 endpoint', () => { +test('normalizeImage should return the proper registry v2 endpoint', async () => { expect( trueforge.normalizeImage({ name: 'test/image', diff --git a/app/registries/providers/trueforge/trueforge.js b/app/registries/providers/trueforge/trueforge.ts similarity index 92% rename from app/registries/providers/trueforge/trueforge.js rename to app/registries/providers/trueforge/trueforge.ts index 239e7c2a..746c9a4e 100644 --- a/app/registries/providers/trueforge/trueforge.js +++ b/app/registries/providers/trueforge/trueforge.ts @@ -1,4 +1,5 @@ -const Ghcr = require('../ghcr/Ghcr'); +// @ts-nocheck +import Ghcr from '../ghcr/Ghcr'; /** * Linux-Server Container Registry integration. @@ -36,4 +37,4 @@ class Trueforge extends Ghcr { } } -module.exports = Trueforge; +export default Trueforge; diff --git a/app/registry/Component.test.js b/app/registry/Component.test.ts similarity index 78% rename from app/registry/Component.test.js rename to app/registry/Component.test.ts index 2d796938..f301e34c 100644 --- a/app/registry/Component.test.js +++ b/app/registry/Component.test.ts @@ -1,36 +1,37 @@ -const Component = require('./Component'); +// @ts-nocheck +import Component from './Component'; -beforeEach(() => { +beforeEach(async () => { jest.resetAllMocks(); }); -test('mask should mask with * when called with defaults', () => { +test('mask should mask with * when called with defaults', async () => { expect(Component.mask('abcdefgh')).toStrictEqual('a******h'); }); -test('mask should mask with ยง when called with ยง masking char', () => { +test('mask should mask with ยง when called with ยง masking char', async () => { expect(Component.mask('abcdefgh', 1, 'ยง')).toStrictEqual('aยงยงยงยงยงยงh'); }); -test('mask should mask with ยง and keep 3 chars when called with ยง masking char and a number of 3', () => { +test('mask should mask with ยง and keep 3 chars when called with ยง masking char and a number of 3', async () => { expect(Component.mask('abcdefgh', 3, 'ยง')).toStrictEqual('abcยงยงfgh'); }); -test('mask should return undefined when value is undefined', () => { +test('mask should return undefined when value is undefined', async () => { expect(Component.mask(undefined)).toStrictEqual(undefined); }); -test('mask should not fail when mask is longer than original string', () => { +test('mask should not fail when mask is longer than original string', async () => { expect(Component.mask('abc', 5)).toStrictEqual('***'); }); -test('getId should return the concatenation $type.$name', () => { +test('getId should return the concatenation $type.$name', async () => { const component = new Component(); component.register('kind', 'type', 'name', { x: 'x' }); expect(component.getId()).toEqual('type.name'); }); -test('register should call validateConfiguration and init methods of the component', () => { +test('register should call validateConfiguration and init methods of the component', async () => { const component = new Component(); const spyValidateConsiguration = jest.spyOn( component, @@ -42,7 +43,7 @@ test('register should call validateConfiguration and init methods of the compone expect(spyInit).toHaveBeenCalledTimes(1); }); -test('register should not call init when validateConfiguration fails', () => { +test('register should not call init when validateConfiguration fails', async () => { const component = new Component(); component.validateConfiguration = () => { throw new Error('validation failed'); @@ -54,7 +55,7 @@ test('register should not call init when validateConfiguration fails', () => { expect(spyInit).toHaveBeenCalledTimes(0); }); -test('register should throw when init fails', () => { +test('register should throw when init fails', async () => { const component = new Component(); component.init = () => { throw new Error('init failed'); diff --git a/app/registry/Component.js b/app/registry/Component.ts similarity index 69% rename from app/registry/Component.js rename to app/registry/Component.ts index de6c92b8..f9471cd4 100644 --- a/app/registry/Component.js +++ b/app/registry/Component.ts @@ -1,24 +1,43 @@ -const joi = require('joi'); -const log = require('../log'); +import joi from 'joi'; +import log from '../log'; +import Logger from 'bunyan'; + +export interface ComponentConfiguration { + [key: string]: any; +} /** * Base Component Class. */ class Component { + public joi: typeof joi; + public log: Logger; + public kind: string = ''; + public type: string = ''; + public name: string = ''; + public configuration: ComponentConfiguration = {}; + /** * Constructor. */ constructor() { this.joi = joi; + this.log = log; } /** * Register the component. + * @param kind the kind of the component * @param type the type of the component * @param name the name of the component * @param configuration the configuration of the component */ - async register(kind, type, name, configuration) { + async register( + kind: string, + type: string, + name: string, + configuration: ComponentConfiguration, + ): Promise { // Child log for the component this.log = log.child({ component: `${kind}.${type}.${name}` }); this.kind = kind; @@ -37,7 +56,7 @@ class Component { * Deregister the component. * @returns {Promise} */ - async deregister() { + async deregister(): Promise { this.log.info('Deregister component'); await this.deregisterComponent(); return this; @@ -48,7 +67,7 @@ class Component { * @returns {Promise} */ - async deregisterComponent() { + async deregisterComponent(): Promise { // Do nothing by default } @@ -58,7 +77,9 @@ class Component { * @param configuration the configuration * @returns {*} or throw a validation error */ - validateConfiguration(configuration) { + validateConfiguration( + configuration: ComponentConfiguration, + ): ComponentConfiguration { const schema = this.getConfigurationSchema(); const schemaValidated = schema.validate(configuration); if (schemaValidated.error) { @@ -72,7 +93,7 @@ class Component { * Can be overridden by the component implementation class * @returns {*} */ - getConfigurationSchema() { + getConfigurationSchema(): joi.ObjectSchema { return this.joi.object(); } @@ -81,21 +102,23 @@ class Component { * Can be overridden by the component implementation class */ - init() {} + async init(): Promise {} /** * Sanitize sensitive data * @returns {*} */ - maskConfiguration() { - return this.configuration; + maskConfiguration( + configuration?: ComponentConfiguration, + ): ComponentConfiguration { + return configuration || this.configuration; } /** * Get Component ID. * @returns {string} */ - getId() { + getId(): string { return `${this.type}.${this.name}`; } @@ -106,7 +129,11 @@ class Component { * @param char the replacement char * @returns {string|undefined} the masked string */ - static mask(value, nb = 1, char = '*') { + static mask( + value: string | undefined, + nb = 1, + char = '*', + ): string | undefined { if (!value) { return undefined; } @@ -119,4 +146,4 @@ class Component { } } -module.exports = Component; +export default Component; diff --git a/app/registry/index.test.js b/app/registry/index.test.ts similarity index 70% rename from app/registry/index.test.js rename to app/registry/index.test.ts index 95172b71..8aefbc26 100644 --- a/app/registry/index.test.js +++ b/app/registry/index.test.ts @@ -1,63 +1,84 @@ -const configuration = require('../configuration'); -const Component = require('./Component'); -const prometheusWatcher = require('../prometheus/watcher'); +// @ts-nocheck +import * as configuration from '../configuration'; +import Component from './Component'; +import * as prometheusWatcher from '../prometheus/watcher'; -jest.mock('../configuration'); - -configuration.getLogLevel = () => 'info'; +jest.mock('../configuration', () => ({ + getLogLevel: jest.fn(() => 'info'), + getRegistryConfigurations: jest.fn(), + getTriggerConfigurations: jest.fn(), + getWatcherConfigurations: jest.fn(), + getAuthenticationConfigurations: jest.fn(), +})); let registries = {}; let triggers = {}; let watchers = {}; let authentications = {}; -configuration.getRegistryConfigurations = () => registries; -configuration.getTriggerConfigurations = () => triggers; -configuration.getWatcherConfigurations = () => watchers; -configuration.getAuthenticationConfigurations = () => authentications; +// Override the mocked functions +// We need to cast to jest.Mock or assume they are mocks because of the factory above +const mockGetRegistryConfigurations = configuration.getRegistryConfigurations; +const mockGetTriggerConfigurations = configuration.getTriggerConfigurations; +const mockGetWatcherConfigurations = configuration.getWatcherConfigurations; +const mockGetAuthenticationConfigurations = + configuration.getAuthenticationConfigurations; + +mockGetRegistryConfigurations.mockImplementation(() => registries); +mockGetTriggerConfigurations.mockImplementation(() => triggers); +mockGetWatcherConfigurations.mockImplementation(() => watchers); +mockGetAuthenticationConfigurations.mockImplementation(() => authentications); -const registry = require('./index'); +import * as registry from './index'; -beforeEach(() => { - jest.resetAllMocks(); +beforeEach(async () => { + jest.clearAllMocks(); prometheusWatcher.init(); registries = {}; triggers = {}; watchers = {}; authentications = {}; + + // Ensure default implementations return the variables + mockGetRegistryConfigurations.mockImplementation(() => registries); + mockGetTriggerConfigurations.mockImplementation(() => triggers); + mockGetWatcherConfigurations.mockImplementation(() => watchers); + mockGetAuthenticationConfigurations.mockImplementation( + () => authentications, + ); }); afterEach(async () => { try { - await registry.__get__('deregisterRegistries')(); - await registry.__get__('deregisterTriggers')(); - await registry.__get__('deregisterWatchers')(); - await registry.__get__('deregisterAuthentications')(); + await registry.testable_deregisterRegistries(); + await registry.testable_deregisterTriggers(); + await registry.testable_deregisterWatchers(); + await registry.testable_deregisterAuthentications(); } catch (e) { // ignore error } }); -test('registerComponent should warn when component does not exist', () => { - const registerComponent = registry.__get__('registerComponent'); +test('registerComponent should warn when component does not exist', async () => { + const registerComponent = registry.testable_registerComponent; expect( registerComponent('kind', 'provider', 'name', {}, 'path'), ).rejects.toThrow(/Unknown kind provider/); }); -test('registerComponents should return empty array if not components', () => { - const registerComponents = registry.__get__('registerComponents'); +test('registerComponents should return empty array if not components', async () => { + const registerComponents = registry.testable_registerComponents; expect(registerComponents('kind', undefined, 'path')).resolves.toEqual([]); }); -test('deregisterComponent should throw when component fails to deregister', () => { - const deregisterComponent = registry.__get__('deregisterComponent'); +test('deregisterComponent should throw when component fails to deregister', async () => { + const deregisterComponent = registry.testable_deregisterComponent; const component = new Component(); component.deregister = () => { throw new Error('Error x'); }; expect(deregisterComponent(component)).rejects.toThrowError( - 'Error when deregistering component undefined.undefined', + 'Error when deregistering component .', ); }); @@ -77,7 +98,7 @@ test('registerRegistries should register all registries', async () => { }, }, }; - await registry.__get__('registerRegistries')(); + await registry.testable_registerRegistries(); expect(Object.keys(registry.getState().registry).sort()).toEqual([ 'codeberg.public', 'ecr.private', @@ -89,7 +110,7 @@ test('registerRegistries should register all registries', async () => { }); test('registerRegistries should register all anonymous registries by default', async () => { - await registry.__get__('registerRegistries')(); + await registry.testable_registerRegistries(); expect(Object.keys(registry.getState().registry).sort()).toEqual([ 'codeberg.public', 'ecr.public', @@ -101,7 +122,7 @@ test('registerRegistries should register all anonymous registries by default', a }); test('registerRegistries should warn when registration errors occur', async () => { - const spyLog = jest.spyOn(registry.__get__('log'), 'warn'); + const spyLog = jest.spyOn(registry.testable_log, 'warn'); registries = { hub: { private: { @@ -109,7 +130,7 @@ test('registerRegistries should warn when registration errors occur', async () = }, }, }; - await registry.__get__('registerRegistries')(); + await registry.testable_registerRegistries(); expect(spyLog).toHaveBeenCalledWith( 'Some registries failed to register (Error when registering component hub ("login" must be a string))', ); @@ -122,7 +143,7 @@ test('registerTriggers should register all triggers', async () => { mock2: {}, }, }; - await registry.__get__('registerTriggers')(); + await registry.testable_registerTriggers(); expect(Object.keys(registry.getState().trigger)).toEqual([ 'mock.mock1', 'mock.mock2', @@ -130,13 +151,13 @@ test('registerTriggers should register all triggers', async () => { }); test('registerTriggers should warn when registration errors occur', async () => { - const spyLog = jest.spyOn(registry.__get__('log'), 'warn'); + const spyLog = jest.spyOn(registry.testable_log, 'warn'); triggers = { trigger1: { fail: true, }, }; - await registry.__get__('registerTriggers')(); + await registry.testable_registerTriggers(); expect(spyLog).toHaveBeenCalledWith( expect.stringContaining( "Some triggers failed to register (Unknown trigger provider: 'trigger1'", @@ -153,7 +174,7 @@ test('registerWatchers should register all watchers', async () => { host: 'host2', }, }; - await registry.__get__('registerWatchers')(); + await registry.testable_registerWatchers(); expect(Object.keys(registry.getState().watcher)).toEqual([ 'docker.watcher1', 'docker.watcher2', @@ -161,18 +182,18 @@ test('registerWatchers should register all watchers', async () => { }); test('registerWatchers should register local docker watcher by default', async () => { - await registry.__get__('registerWatchers')(); + await registry.testable_registerWatchers(); expect(Object.keys(registry.getState().watcher)).toEqual(['docker.local']); }); test('registerWatchers should warn when registration errors occur', async () => { - const spyLog = jest.spyOn(registry.__get__('log'), 'warn'); + const spyLog = jest.spyOn(registry.testable_log, 'warn'); watchers = { watcher1: { fail: true, }, }; - await registry.__get__('registerWatchers')(); + await registry.testable_registerWatchers(); expect(spyLog).toHaveBeenCalledWith( 'Some watchers failed to register (Error when registering component docker ("fail" is not allowed))', ); @@ -191,7 +212,7 @@ test('registerAuthentications should register all auth strategies', async () => }, }, }; - await registry.__get__('registerAuthentications')(); + await registry.testable_registerAuthentications(); expect(Object.keys(registry.getState().authentication)).toEqual([ 'basic.john', 'basic.jane', @@ -199,7 +220,7 @@ test('registerAuthentications should register all auth strategies', async () => }); test('registerAuthentications should warn when registration errors occur', async () => { - const spyLog = jest.spyOn(registry.__get__('log'), 'warn'); + const spyLog = jest.spyOn(registry.testable_log, 'warn'); authentications = { basic: { john: { @@ -207,14 +228,14 @@ test('registerAuthentications should warn when registration errors occur', async }, }, }; - await registry.__get__('registerAuthentications')(); + await registry.testable_registerAuthentications(); expect(spyLog).toHaveBeenCalledWith( 'Some authentications failed to register (Error when registering component basic ("user" is required))', ); }); test('registerAuthentications should register anonymous auth by default', async () => { - await registry.__get__('registerAuthentications')(); + await registry.testable_registerAuthentications(); expect(Object.keys(registry.getState().authentication)).toEqual([ 'anonymous.anonymous', ]); @@ -324,14 +345,14 @@ test('deregisterAll should deregister all components', async () => { }, }; await registry.init(); - await registry.__get__('deregisterAll')(); + await registry.testable_deregisterAll(); expect(Object.keys(registry.getState().registry).length).toEqual(0); expect(Object.keys(registry.getState().trigger).length).toEqual(0); expect(Object.keys(registry.getState().watcher).length).toEqual(0); expect(Object.keys(registry.getState().authentication).length).toEqual(0); }); -test('deregisterAll should throw an error when any component fails to deregister', () => { +test('deregisterAll should throw an error when any component fails to deregister', async () => { const component = new Component(); component.deregister = () => { throw new Error('Fail!!!'); @@ -339,8 +360,8 @@ test('deregisterAll should throw an error when any component fails to deregister registry.getState().trigger = { trigger1: component, }; - expect(registry.__get__('deregisterAll')()).rejects.toThrowError( - 'Error when deregistering component undefined.undefined', + expect(registry.testable_deregisterAll()).rejects.toThrowError( + 'Error when deregistering component .', ); }); @@ -352,8 +373,8 @@ test('deregisterRegistries should throw when errors occurred', async () => { registry.getState().registry = { registry1: component, }; - expect(registry.__get__('deregisterRegistries')()).rejects.toThrowError( - 'Error when deregistering component undefined.undefined', + expect(registry.testable_deregisterRegistries()).rejects.toThrowError( + 'Error when deregistering component .', ); }); @@ -365,8 +386,8 @@ test('deregisterTriggers should throw when errors occurred', async () => { registry.getState().trigger = { trigger1: component, }; - expect(registry.__get__('deregisterTriggers')()).rejects.toThrowError( - 'Error when deregistering component undefined.undefined', + expect(registry.testable_deregisterTriggers()).rejects.toThrowError( + 'Error when deregistering component .', ); }); @@ -378,7 +399,7 @@ test('deregisterWatchers should throw when errors occurred', async () => { registry.getState().watcher = { watcher1: component, }; - expect(registry.__get__('deregisterWatchers')()).rejects.toThrowError( - 'Error when deregistering component undefined.undefined', + expect(registry.testable_deregisterWatchers()).rejects.toThrowError( + 'Error when deregistering component .', ); }); diff --git a/app/registry/index.js b/app/registry/index.ts similarity index 77% rename from app/registry/index.js rename to app/registry/index.ts index effe1e9d..2db6c8d3 100644 --- a/app/registry/index.js +++ b/app/registry/index.ts @@ -1,28 +1,43 @@ /** * Registry handling all components (registries, triggers, watchers). */ -const capitalize = require('capitalize'); -const fs = require('fs'); -const path = require('path'); -const log = require('../log').child({ component: 'registry' }); -const { +import capitalize from 'capitalize'; +import fs from 'fs'; +import path from 'path'; +import logger from '../log'; +const log = logger.child({ component: 'registry' }); +import { getWatcherConfigurations, getTriggerConfigurations, getRegistryConfigurations, getAuthenticationConfigurations, -} = require('../configuration'); +} from '../configuration'; +import Component, { ComponentConfiguration } from './Component'; +import Trigger from '../triggers/providers/Trigger'; +import Watcher from '../watchers/Watcher'; +import Registry from '../registries/Registry'; +import Authentication from '../authentications/providers/Authentication'; + +export interface RegistryState { + trigger: { [key: string]: Trigger }; + watcher: { [key: string]: Watcher }; + registry: { [key: string]: Registry }; + authentication: { [key: string]: Authentication }; +} + +type ComponentKind = keyof RegistryState; /** * Registry state. */ -const state = { +const state: RegistryState = { trigger: {}, watcher: {}, registry: {}, authentication: {}, }; -function getState() { +export function getState() { return state; } @@ -31,7 +46,7 @@ function getState() { * @param {string} basePath relative path to the providers directory * @returns {string[]} sorted list of available provider names */ -function getAvailableProviders(basePath) { +function getAvailableProviders(basePath: string) { try { const resolvedPath = path.resolve(__dirname, basePath); const providers = fs @@ -52,8 +67,8 @@ function getAvailableProviders(basePath) { * @param {string} kind component kind (trigger, watcher, etc.) * @returns {string} documentation path */ -function getDocumentationLink(kind) { - const docLinks = { +function getDocumentationLink(kind: ComponentKind) { + const docLinks: Record = { trigger: 'https://github.com/getwud/wud/tree/main/docs/configuration/triggers', watcher: @@ -77,7 +92,12 @@ function getDocumentationLink(kind) { * @param {string[]} availableProviders list of available providers * @returns {string} formatted error message */ -function getHelpfulErrorMessage(kind, provider, error, availableProviders) { +function getHelpfulErrorMessage( + kind: ComponentKind, + provider: string, + error: string, + availableProviders: string[], +) { let message = `Error when registering component ${provider} (${error})`; if (error.includes('Cannot find module')) { @@ -107,27 +127,31 @@ function getHelpfulErrorMessage(kind, provider, error, availableProviders) { * @param {*} componentPath */ async function registerComponent( - kind, - provider, - name, - configuration, - componentPath, -) { + kind: ComponentKind, + provider: string, + name: string, + configuration: ComponentConfiguration, + componentPath: string, +): Promise { const providerLowercase = provider.toLowerCase(); const nameLowercase = name.toLowerCase(); const componentFile = `${componentPath}/${providerLowercase.toLowerCase()}/${capitalize(provider)}`; try { - const Component = require(componentFile); - const component = new Component(); + // eslint-disable-next-line @typescript-eslint/no-var-requires + const ComponentClass = (await import(componentFile)).default; + const component: Component = new ComponentClass(); const componentRegistered = await component.register( kind, providerLowercase, nameLowercase, configuration, ); - state[kind][component.getId()] = component; + + // Type assertion is safe here because we know the kind matches the expected type + // if the file structure and inheritance are correct + (state[kind] as any)[component.getId()] = component; return componentRegistered; - } catch (e) { + } catch (e: any) { const availableProviders = getAvailableProviders(componentPath); const helpfulMessage = getHelpfulErrorMessage( kind, @@ -146,7 +170,11 @@ async function registerComponent( * @param path * @returns {*[]} */ -async function registerComponents(kind, configurations, path) { +async function registerComponents( + kind: ComponentKind, + configurations: Record, + path: string, +) { if (configurations) { const providers = Object.keys(configurations); const providerPromises = providers @@ -178,7 +206,7 @@ async function registerComponents(kind, configurations, path) { */ async function registerWatchers() { const configurations = getWatcherConfigurations(); - let watchersToRegister = []; + let watchersToRegister: Promise[] = []; try { if (Object.keys(configurations).length === 0) { log.info( @@ -208,7 +236,7 @@ async function registerWatchers() { ); } await Promise.all(watchersToRegister); - } catch (e) { + } catch (e: any) { log.warn(`Some watchers failed to register (${e.message})`); log.debug(e); } @@ -225,7 +253,7 @@ async function registerTriggers() { configurations, '../triggers/providers', ); - } catch (e) { + } catch (e: any) { log.warn(`Some triggers failed to register (${e.message})`); log.debug(e); } @@ -255,7 +283,7 @@ async function registerRegistries() { registriesToRegister, '../registries/providers', ); - } catch (e) { + } catch (e: any) { log.warn(`Some registries failed to register (${e.message})`); log.debug(e); } @@ -282,7 +310,7 @@ async function registerAuthentications() { configurations, '../authentications/providers', ); - } catch (e) { + } catch (e: any) { log.warn(`Some authentications failed to register (${e.message})`); log.debug(e); } @@ -294,10 +322,10 @@ async function registerAuthentications() { * @param kind * @returns {Promise} */ -async function deregisterComponent(component, kind) { +async function deregisterComponent(component: Component, kind: ComponentKind) { try { await component.deregister(); - } catch (e) { + } catch (e: any) { throw new Error( `Error when deregistering component ${component.getId()} (${e.message})`, ); @@ -315,7 +343,10 @@ async function deregisterComponent(component, kind) { * @param kind * @returns {Promise} */ -async function deregisterComponents(components, kind) { +async function deregisterComponents( + components: Component[], + kind: ComponentKind, +) { const deregisterPromises = components.map(async (component) => deregisterComponent(component, kind), ); @@ -367,12 +398,12 @@ async function deregisterAll() { await deregisterTriggers(); await deregisterRegistries(); await deregisterAuthentications(); - } catch (e) { + } catch (e: any) { throw new Error(`Error when trying to deregister ${e.message}`); } } -async function init() { +export async function init() { // Register triggers await registerTriggers(); @@ -390,7 +421,19 @@ async function init() { process.on('SIGTERM', deregisterAll); } -module.exports = { - init, - getState, +// The following exports are meant for testing only +export { + registerComponent as testable_registerComponent, + registerComponents as testable_registerComponents, + registerRegistries as testable_registerRegistries, + registerTriggers as testable_registerTriggers, + registerWatchers as testable_registerWatchers, + registerAuthentications as testable_registerAuthentications, + deregisterComponent as testable_deregisterComponent, + deregisterRegistries as testable_deregisterRegistries, + deregisterTriggers as testable_deregisterTriggers, + deregisterWatchers as testable_deregisterWatchers, + deregisterAuthentications as testable_deregisterAuthentications, + deregisterAll as testable_deregisterAll, + log as testable_log, }; diff --git a/app/store/app.test.js b/app/store/app.test.ts similarity index 88% rename from app/store/app.test.js rename to app/store/app.test.ts index d9bae091..226b6161 100644 --- a/app/store/app.test.js +++ b/app/store/app.test.ts @@ -1,5 +1,6 @@ -const app = require('./app'); -const migrate = require('./migrate'); +// @ts-nocheck +import * as app from './app'; +import * as migrate from './migrate'; jest.mock('../configuration', () => ({ getVersion: () => '2.0.0', @@ -7,11 +8,11 @@ jest.mock('../configuration', () => ({ })); jest.mock('./migrate'); -beforeEach(() => { +beforeEach(async () => { jest.resetAllMocks(); }); -test('createCollections should create collection app when not exist', () => { +test('createCollections should create collection app when not exist', async () => { const db = { getCollection: () => null, addCollection: () => ({ @@ -24,7 +25,7 @@ test('createCollections should create collection app when not exist', () => { expect(spy).toHaveBeenCalledWith('app'); }); -test('createCollections should not create collection app when already exist', () => { +test('createCollections should not create collection app when already exist', async () => { const db = { getCollection: () => ({ findOne: () => {}, @@ -37,7 +38,7 @@ test('createCollections should not create collection app when already exist', () expect(spy).not.toHaveBeenCalled(); }); -test('createCollections should call migrate when versions are different', () => { +test('createCollections should call migrate when versions are different', async () => { const db = { getCollection: () => ({ findOne: () => ({ @@ -54,7 +55,7 @@ test('createCollections should call migrate when versions are different', () => expect(spy).toHaveBeenCalledWith('1.0.0', '2.0.0'); }); -test('createCollections should not call migrate when versions are identical', () => { +test('createCollections should not call migrate when versions are identical', async () => { const db = { getCollection: () => ({ findOne: () => ({ @@ -71,7 +72,7 @@ test('createCollections should not call migrate when versions are identical', () expect(spy).not.toHaveBeenCalledWith(); }); -test('getAppInfos should return collection content', () => { +test('getAppInfos should return collection content', async () => { const db = { getCollection: () => ({ findOne: () => ({ diff --git a/app/store/app.js b/app/store/app.ts similarity index 66% rename from app/store/app.js rename to app/store/app.ts index 0bafaa66..d6816dec 100644 --- a/app/store/app.js +++ b/app/store/app.ts @@ -1,9 +1,12 @@ +// @ts-nocheck /** * App store. */ -const log = require('../log').child({ component: 'store' }); -const { migrate } = require('./migrate'); -const { getVersion } = require('../configuration'); +import logger from '../log'; +const log = logger.child({ component: 'store' }); +import * as migrate from './migrate'; +const { migrate: migrateData } = migrate; +import { getVersion } from '../configuration'; let app; @@ -16,7 +19,7 @@ function saveAppInfosAndMigrate() { const versionFromStore = appInfosSaved ? appInfosSaved.version : undefined; const currentVersion = appInfosCurrent.version; if (currentVersion !== versionFromStore) { - migrate(versionFromStore, currentVersion); + migrateData(versionFromStore, currentVersion); } if (appInfosSaved) { app.remove(appInfosSaved); @@ -24,7 +27,7 @@ function saveAppInfosAndMigrate() { app.insert(appInfosCurrent); } -function createCollections(db) { +export function createCollections(db) { app = db.getCollection('app'); if (app === null) { log.info('Create Collection app'); @@ -33,11 +36,6 @@ function createCollections(db) { saveAppInfosAndMigrate(); } -function getAppInfos() { +export function getAppInfos() { return app.findOne({}); } - -module.exports = { - createCollections, - getAppInfos, -}; diff --git a/app/store/container.test.js b/app/store/container.test.ts similarity index 93% rename from app/store/container.test.js rename to app/store/container.test.ts index d23b755f..fdf0df51 100644 --- a/app/store/container.test.js +++ b/app/store/container.test.ts @@ -1,14 +1,15 @@ -const container = require('./container'); -const event = require('../event'); +// @ts-nocheck +import * as container from './container'; +import * as event from '../event'; jest.mock('./migrate'); jest.mock('../event'); -beforeEach(() => { +beforeEach(async () => { jest.resetAllMocks(); }); -test('createCollections should create collection containers when not exist', () => { +test('createCollections should create collection containers when not exist', async () => { const db = { getCollection: () => null, addCollection: () => ({ @@ -21,7 +22,7 @@ test('createCollections should create collection containers when not exist', () expect(spy).toHaveBeenCalledWith('containers'); }); -test('createCollections should not create collection containers when already exist', () => { +test('createCollections should not create collection containers when already exist', async () => { const db = { getCollection: () => ({ findOne: () => {}, @@ -34,7 +35,7 @@ test('createCollections should not create collection containers when already exi expect(spy).not.toHaveBeenCalled(); }); -test('insertContainer should insert doc and emit an event', () => { +test('insertContainer should insert doc and emit an event', async () => { const collection = { findOne: () => {}, insert: () => {}, @@ -78,7 +79,7 @@ test('insertContainer should insert doc and emit an event', () => { expect(spyEvent).toHaveBeenCalled(); }); -test('updateContainer should update doc and emit an event', () => { +test('updateContainer should update doc and emit an event', async () => { const collection = { insert: () => {}, chain: () => ({ @@ -126,7 +127,7 @@ test('updateContainer should update doc and emit an event', () => { expect(spyEvent).toHaveBeenCalled(); }); -test('getContainers should return all containers sorted by name', () => { +test('getContainers should return all containers sorted by name', async () => { const containerExample = { id: 'container-123456789', name: 'test', @@ -191,7 +192,7 @@ test('getContainers should return all containers sorted by name', () => { expect(results[2].name).toEqual('container3'); }); -test('getContainer should return 1 container by id', () => { +test('getContainer should return 1 container by id', async () => { const containerExample = { data: { id: 'container-123456789', @@ -232,7 +233,7 @@ test('getContainer should return 1 container by id', () => { expect(result.name).toEqual(containerExample.data.name); }); -test('getContainer should return undefined when not found', () => { +test('getContainer should return undefined when not found', async () => { const collection = { findOne: () => null, }; @@ -244,7 +245,7 @@ test('getContainer should return undefined when not found', () => { expect(result).toEqual(undefined); }); -test('deleteContainer should delete doc and emit an event', () => { +test('deleteContainer should delete doc and emit an event', async () => { const containerExample = { data: { id: 'container-123456789', diff --git a/app/store/container.js b/app/store/container.ts similarity index 79% rename from app/store/container.js rename to app/store/container.ts index 37cb539d..cc6994b0 100644 --- a/app/store/container.js +++ b/app/store/container.ts @@ -1,14 +1,17 @@ +// @ts-nocheck /** * Container store. */ -const { byString, byValues } = require('sort-es'); -const log = require('../log').child({ component: 'store' }); -const { validate: validateContainer } = require('../model/container'); -const { +import { byString, byValues } from 'sort-es'; +import logger from '../log'; +const log = logger.child({ component: 'store' }); +import * as container from '../model/container'; +const { validate: validateContainer } = container; +import { emitContainerAdded, emitContainerUpdated, emitContainerRemoved, -} = require('../event'); +} from '../event'; let containers; @@ -16,7 +19,7 @@ let containers; * Create container collections. * @param db */ -function createCollections(db) { +export function createCollections(db) { containers = db.getCollection('containers'); if (containers === null) { log.info('Create Collection containers'); @@ -28,7 +31,7 @@ function createCollections(db) { * Insert new Container. * @param container */ -function insertContainer(container) { +export function insertContainer(container) { const containerToSave = validateContainer(container); containers.insert({ data: containerToSave, @@ -41,7 +44,7 @@ function insertContainer(container) { * Update existing container. * @param container */ -function updateContainer(container) { +export function updateContainer(container) { const containerToReturn = validateContainer(container); // Remove existing container @@ -65,7 +68,7 @@ function updateContainer(container) { * @param query * @returns {*} */ -function getContainers(query = {}) { +export function getContainers(query = {}) { const filter = {}; Object.keys(query).forEach((key) => { filter[`data.${key}`] = query[key]; @@ -90,7 +93,7 @@ function getContainers(query = {}) { * @param id * @returns {null|Image} */ -function getContainer(id) { +export function getContainer(id) { const container = containers.findOne({ 'data.id': id, }); @@ -105,7 +108,7 @@ function getContainer(id) { * Delete container by id. * @param id */ -function deleteContainer(id) { +export function deleteContainer(id) { const container = getContainer(id); if (container) { containers @@ -117,12 +120,3 @@ function deleteContainer(id) { emitContainerRemoved(container); } } - -module.exports = { - createCollections, - insertContainer, - updateContainer, - getContainers, - getContainer, - deleteContainer, -}; diff --git a/app/store/index.test.js b/app/store/index.test.ts similarity index 86% rename from app/store/index.test.js rename to app/store/index.test.ts index 43bd4c07..2e37edc2 100644 --- a/app/store/index.test.js +++ b/app/store/index.test.ts @@ -1,5 +1,6 @@ -const fs = require('fs'); -const store = require('./index'); +// @ts-nocheck +import fs from 'fs'; +import * as store from './index'; // Mock dependencies jest.mock('lokijs', () => { @@ -38,7 +39,7 @@ jest.mock('../log', () => ({ })); describe('Store Module', () => { - beforeEach(() => { + beforeEach(async () => { jest.clearAllMocks(); }); @@ -47,8 +48,8 @@ describe('Store Module', () => { await store.init(); - const app = require('./app'); - const container = require('./container'); + const app = await import('./app'); + const container = await import('./container'); expect(app.createCollections).toHaveBeenCalled(); expect(container.createCollections).toHaveBeenCalled(); @@ -62,7 +63,7 @@ describe('Store Module', () => { expect(fs.mkdirSync).toHaveBeenCalledWith('/test/store'); }); - test('should return configuration', () => { + test('should return configuration', async () => { const config = store.getConfiguration(); expect(config).toEqual({ @@ -84,7 +85,7 @@ describe('Store Module', () => { })); }); - const storeWithError = require('./index'); + const storeWithError = await import('./index'); await expect(storeWithError.init()).rejects.toThrow( 'Database load failed', ); diff --git a/app/store/index.js b/app/store/index.ts similarity index 78% rename from app/store/index.js rename to app/store/index.ts index 96079b40..8538ab30 100644 --- a/app/store/index.js +++ b/app/store/index.ts @@ -1,11 +1,13 @@ -const joi = require('joi'); -const Loki = require('lokijs'); -const fs = require('fs'); -const log = require('../log').child({ component: 'store' }); -const { getStoreConfiguration } = require('../configuration'); +// @ts-nocheck +import joi from 'joi'; +import Loki from 'lokijs'; +import fs from 'fs'; +import logger from '../log'; +const log = logger.child({ component: 'store' }); +import { getStoreConfiguration } from '../configuration'; -const app = require('./app'); -const container = require('./container'); +import * as app from './app'; +import * as container from './container'; // Store Configuration Schema const configurationSchema = joi.object().keys({ @@ -53,7 +55,7 @@ async function loadDb(err, resolve, reject) { * Init DB. * @returns {Promise} */ -async function init() { +export async function init() { log.info(`Load store from (${configuration.path}/${configuration.file})`); if (!fs.existsSync(configuration.path)) { log.info(`Create folder ${configuration.path}`); @@ -68,11 +70,6 @@ async function init() { * Get configuration. * @returns {*} */ -function getConfiguration() { +export function getConfiguration() { return configuration; } - -module.exports = { - init, - getConfiguration, -}; diff --git a/app/store/migrate.test.js b/app/store/migrate.test.ts similarity index 74% rename from app/store/migrate.test.js rename to app/store/migrate.test.ts index 6bdedae3..d892d363 100644 --- a/app/store/migrate.test.js +++ b/app/store/migrate.test.ts @@ -1,4 +1,5 @@ -const container = require('./container'); +// @ts-nocheck +import * as container from './container'; jest.mock('./container'); @@ -11,19 +12,19 @@ container.getContainers = () => [ }, ]; -const migrate = require('./migrate'); +import * as migrate from './migrate'; -beforeEach(() => { +beforeEach(async () => { jest.resetAllMocks(); }); -test('migrate should delete all containers when from is lower than 8 and to is grater than 8', () => { +test('migrate should delete all containers when from is lower than 8 and to is grater than 8', async () => { const spy = jest.spyOn(container, 'deleteContainer'); migrate.migrate('7.0.0', '8.0.0'); expect(spy).toHaveBeenCalledTimes(2); }); -test('migrate should not delete all containers when from is from and to are 8 versions', () => { +test('migrate should not delete all containers when from is from and to are 8 versions', async () => { const spy = jest.spyOn(container, 'deleteContainer'); migrate.migrate('8.1.0', '8.2.0'); expect(spy).not.toHaveBeenCalled(); diff --git a/app/store/migrate.js b/app/store/migrate.ts similarity index 68% rename from app/store/migrate.js rename to app/store/migrate.ts index 8e485546..0dd457a1 100644 --- a/app/store/migrate.js +++ b/app/store/migrate.ts @@ -1,5 +1,8 @@ -const log = require('../log').child({ component: 'store' }); -const { getContainers, deleteContainer } = require('./container'); +// @ts-nocheck +import logger from '../log'; +const log = logger.child({ component: 'store' }); +import * as container from './container'; +const { getContainers, deleteContainer } = container; /** * Delete all containers from state. @@ -14,13 +17,9 @@ function deleteAllContainersFromState() { * @param from version * @param to version */ -function migrate(from, to) { +export function migrate(from, to) { log.info(`Migrate data from version ${from} to version ${to}`); if (from && !from.startsWith('8') && to && to.startsWith('8')) { deleteAllContainersFromState(); } } - -module.exports = { - migrate, -}; diff --git a/app/tag/index.test.js b/app/tag/index.test.ts similarity index 94% rename from app/tag/index.test.js rename to app/tag/index.test.ts index d909b582..91e44985 100644 --- a/app/tag/index.test.js +++ b/app/tag/index.test.ts @@ -1,4 +1,5 @@ -const semver = require('./index'); +// @ts-nocheck +import * as semver from './index'; describe('parse', () => { const validVersions = [ @@ -50,11 +51,11 @@ describe('parse', () => { }, ); - test('should handle null input gracefully', () => { + test('should handle null input gracefully', async () => { expect(() => semver.parse(null)).toThrow(); }); - test('should handle undefined input gracefully', () => { + test('should handle undefined input gracefully', async () => { expect(() => semver.parse(undefined)).toThrow(); }); }); @@ -266,23 +267,23 @@ describe('transform', () => { }); describe('edge cases', () => { - test('should return original tag when formula is undefined', () => { + test('should return original tag when formula is undefined', async () => { expect(semver.transform(undefined, '1.2.3')).toBe('1.2.3'); }); - test('should return original tag when formula is empty string', () => { + test('should return original tag when formula is empty string', async () => { expect(semver.transform('', '1.2.3')).toBe('1.2.3'); }); - test('should return original tag when formula is invalid', () => { + test('should return original tag when formula is invalid', async () => { expect(semver.transform('invalid-formula', '1.2.3')).toBe('1.2.3'); }); - test('should handle formula with no matches', () => { + test('should handle formula with no matches', async () => { expect(semver.transform('^nomatch$ => $1', '1.2.3')).toBe('1.2.3'); }); - test('should handle malformed regex', () => { + test('should handle malformed regex', async () => { expect(semver.transform('[invalid-regex => $1', '1.2.3')).toBe( '1.2.3', ); @@ -291,7 +292,7 @@ describe('transform', () => { }); describe('integration tests', () => { - test('should handle complete semver workflow', () => { + test('should handle complete semver workflow', async () => { const versions = ['1.0.0', '1.1.0', '2.0.0-alpha', '2.0.0', '2.1.0']; const parsed = versions.map((v) => semver.parse(v)).filter(Boolean); @@ -300,7 +301,7 @@ describe('integration tests', () => { expect(semver.diff('1.0.0', '2.0.0')).toBe('major'); }); - test('should handle Docker-style tags', () => { + test('should handle Docker-style tags', async () => { const dockerTags = [ 'nginx:1.21', 'nginx:1.21.6', diff --git a/app/tag/index.js b/app/tag/index.ts similarity index 89% rename from app/tag/index.js rename to app/tag/index.ts index 2b7add8f..cecc7f4c 100644 --- a/app/tag/index.js +++ b/app/tag/index.ts @@ -1,15 +1,16 @@ +// @ts-nocheck /** * Semver utils. */ -const semver = require('semver'); -const log = require('../log'); +import semver from 'semver'; +import log from '../log'; /** * Parse a string to a semver (return null is it cannot be parsed as a valid semver). * @param rawVersion * @returns {*|SemVer} */ -function parse(rawVersion) { +export function parse(rawVersion) { const rawVersionCleaned = semver.clean(rawVersion, { loose: true }); const rawVersionSemver = semver.parse( rawVersionCleaned !== null ? rawVersionCleaned : rawVersion, @@ -28,7 +29,7 @@ function parse(rawVersion) { * @param version1 * @param version2 */ -function isGreater(version1, version2) { +export function isGreater(version1, version2) { const version1Semver = parse(version1); const version2Semver = parse(version2); @@ -45,7 +46,7 @@ function isGreater(version1, version2) { * @param version2 * @returns {*|string|null} */ -function diff(version1, version2) { +export function diff(version1, version2) { const version1Semver = parse(version1); const version2Semver = parse(version2); @@ -62,7 +63,7 @@ function diff(version1, version2) { * @param originalTag * @return {*} */ -function transform(transformFormula, originalTag) { +export function transform(transformFormula, originalTag) { // No formula ? return original tag value if (!transformFormula || transformFormula === '') { return originalTag; @@ -94,10 +95,3 @@ function transform(transformFormula, originalTag) { return originalTag; } } - -module.exports = { - parse, - isGreater, - diff, - transform, -}; diff --git a/app/triggers/providers/Trigger.test.js b/app/triggers/providers/Trigger.test.ts similarity index 97% rename from app/triggers/providers/Trigger.test.js rename to app/triggers/providers/Trigger.test.ts index 9d763b68..ac7378e7 100644 --- a/app/triggers/providers/Trigger.test.js +++ b/app/triggers/providers/Trigger.test.ts @@ -1,7 +1,8 @@ -const { ValidationError } = require('joi'); -const event = require('../../event'); -const log = require('../../log'); -const Trigger = require('./Trigger'); +// @ts-nocheck +import { ValidationError } from 'joi'; +import * as event from '../../event'; +import log from '../../log'; +import Trigger from './Trigger'; jest.mock('../../log'); jest.mock('../../event'); @@ -27,20 +28,20 @@ const configurationValid = { batchtitle: '${containers.length} updates available', }; -beforeEach(() => { +beforeEach(async () => { jest.resetAllMocks(); trigger = new Trigger(); trigger.log = log; trigger.configuration = { ...configurationValid }; }); -test('validateConfiguration should return validated configuration when valid', () => { +test('validateConfiguration should return validated configuration when valid', async () => { const validatedConfiguration = trigger.validateConfiguration(configurationValid); expect(validatedConfiguration).toStrictEqual(configurationValid); }); -test('validateConfiguration should throw error when invalid', () => { +test('validateConfiguration should throw error when invalid', async () => { const configuration = { url: 'git://xxx.com', }; diff --git a/app/triggers/providers/Trigger.js b/app/triggers/providers/Trigger.ts similarity index 78% rename from app/triggers/providers/Trigger.js rename to app/triggers/providers/Trigger.ts index 38cff17a..d33b0336 100644 --- a/app/triggers/providers/Trigger.js +++ b/app/triggers/providers/Trigger.ts @@ -1,7 +1,22 @@ -const Component = require('../../registry/Component'); -const event = require('../../event'); -const { getTriggerCounter } = require('../../prometheus/trigger'); -const { fullName } = require('../../model/container'); +import Component, { ComponentConfiguration } from '../../registry/Component'; +import * as event from '../../event'; +import { getTriggerCounter } from '../../prometheus/trigger'; +import { fullName, Container } from '../../model/container'; + +export interface TriggerConfiguration extends ComponentConfiguration { + auto?: boolean; + threshold?: string; + mode?: string; + once?: boolean; + simpletitle?: string; + simplebody?: string; + batchtitle?: string; +} + +export interface ContainerReport { + container: Container; + changed: boolean; +} /** * Render body or title simple template. @@ -9,35 +24,46 @@ const { fullName } = require('../../model/container'); * @param container * @returns {*} */ -function renderSimple(template, container) { +function renderSimple(template: string, container: Container) { // Set deprecated vars for backward compatibility + // eslint-disable-next-line @typescript-eslint/no-unused-vars const id = container.id; + // eslint-disable-next-line @typescript-eslint/no-unused-vars const name = container.name; + // eslint-disable-next-line @typescript-eslint/no-unused-vars const watcher = container.watcher; + // eslint-disable-next-line @typescript-eslint/no-unused-vars const kind = container.updateKind && container.updateKind.kind ? container.updateKind.kind : ''; + // eslint-disable-next-line @typescript-eslint/no-unused-vars const semver = container.updateKind && container.updateKind.semverDiff ? container.updateKind.semverDiff : ''; + // eslint-disable-next-line @typescript-eslint/no-unused-vars const local = container.updateKind && container.updateKind.localValue ? container.updateKind.localValue : ''; + // eslint-disable-next-line @typescript-eslint/no-unused-vars const remote = container.updateKind && container.updateKind.remoteValue ? container.updateKind.remoteValue : ''; + // eslint-disable-next-line @typescript-eslint/no-unused-vars const link = container.result && container.result.link ? container.result.link : ''; + // eslint-disable-next-line no-eval return eval('`' + template + '`'); } -function renderBatch(template, containers) { +function renderBatch(template: string, containers: Container[]) { // Set deprecated vars for backward compatibility + // eslint-disable-next-line @typescript-eslint/no-unused-vars const count = containers ? containers.length : 0; + // eslint-disable-next-line no-eval return eval('`' + template + '`'); } @@ -45,13 +71,15 @@ function renderBatch(template, containers) { * Trigger base component. */ class Trigger extends Component { + public configuration: TriggerConfiguration = {}; + /** * Return true if update reaches trigger threshold. * @param containerResult * @param threshold * @returns {boolean} */ - static isThresholdReached(containerResult, threshold) { + static isThresholdReached(containerResult: Container, threshold: string) { let thresholdPassing = true; if ( threshold.toLowerCase() !== 'all' && @@ -90,7 +118,9 @@ class Trigger extends Component { * @param {*} includeOrExcludeTriggerString * @returns */ - static parseIncludeOrIncludeTriggerString(includeOrExcludeTriggerString) { + static parseIncludeOrIncludeTriggerString( + includeOrExcludeTriggerString: string, + ) { const includeOrExcludeTriggerSplit = includeOrExcludeTriggerString.split(/\s*:\s*/); const includeOrExcludeTrigger = { @@ -126,7 +156,7 @@ class Trigger extends Component { * @param containerReport * @returns {Promise} */ - async handleContainerReport(containerReport) { + async handleContainerReport(containerReport: ContainerReport) { // Filter on changed containers with update available and passing trigger threshold if ( (containerReport.changed || !this.configuration.once) && @@ -141,7 +171,7 @@ class Trigger extends Component { if ( !Trigger.isThresholdReached( containerReport.container, - this.configuration.threshold.toLowerCase(), + (this.configuration.threshold || 'all').toLowerCase(), ) ) { logContainer.debug('Threshold not reached => ignore'); @@ -152,7 +182,7 @@ class Trigger extends Component { await this.trigger(containerReport.container); } status = 'success'; - } catch (e) { + } catch (e: any) { logContainer.warn(`Error (${e.message})`); logContainer.debug(e); } finally { @@ -170,7 +200,7 @@ class Trigger extends Component { * @param containerReports * @returns {Promise} */ - async handleContainerReports(containerReports) { + async handleContainerReports(containerReports: ContainerReport[]) { // Filter on containers with update available and passing trigger threshold try { const containerReportsFiltered = containerReports @@ -188,7 +218,7 @@ class Trigger extends Component { .filter((containerReport) => Trigger.isThresholdReached( containerReport.container, - this.configuration.threshold.toLowerCase(), + (this.configuration.threshold || 'all').toLowerCase(), ), ); const containersFiltered = containerReportsFiltered.map( @@ -198,13 +228,13 @@ class Trigger extends Component { this.log.debug('Run batch'); await this.triggerBatch(containersFiltered); } - } catch (e) { + } catch (e: any) { this.log.warn(`Error (${e.message})`); this.log.debug(e); } } - isTriggerIncludedOrExcluded(containerResult, trigger) { + isTriggerIncludedOrExcluded(containerResult: Container, trigger: string) { const triggers = trigger .split(/\s*,\s*/) .map((triggerToMatch) => @@ -223,7 +253,10 @@ class Trigger extends Component { ); } - isTriggerIncluded(containerResult, triggerInclude) { + isTriggerIncluded( + containerResult: Container, + triggerInclude: string | undefined, + ) { if (!triggerInclude) { return true; } @@ -233,7 +266,10 @@ class Trigger extends Component { ); } - isTriggerExcluded(containerResult, triggerExclude) { + isTriggerExcluded( + containerResult: Container, + triggerExclude: string | undefined, + ) { if (!triggerExclude) { return false; } @@ -248,7 +284,7 @@ class Trigger extends Component { * @param containerResult * @returns {boolean} */ - mustTrigger(containerResult) { + mustTrigger(containerResult: Container) { const { triggerInclude, triggerExclude } = containerResult; return ( this.isTriggerIncluded(containerResult, triggerInclude) && @@ -263,12 +299,18 @@ class Trigger extends Component { await this.initTrigger(); if (this.configuration.auto) { this.log.info(`Registering for auto execution`); - if (this.configuration.mode.toLowerCase() === 'simple') { + if ( + this.configuration.mode && + this.configuration.mode.toLowerCase() === 'simple' + ) { event.registerContainerReport(async (containerReport) => this.handleContainerReport(containerReport), ); } - if (this.configuration.mode.toLowerCase() === 'batch') { + if ( + this.configuration.mode && + this.configuration.mode.toLowerCase() === 'batch' + ) { event.registerContainerReports(async (containersReports) => this.handleContainerReports(containersReports), ); @@ -283,7 +325,9 @@ class Trigger extends Component { * @param configuration * @returns {*} */ - validateConfiguration(configuration) { + validateConfiguration( + configuration: TriggerConfiguration, + ): TriggerConfiguration { const schema = this.getConfigurationSchema(); const schemaWithDefaultOptions = schema.append({ auto: this.joi.bool().default(true), @@ -341,7 +385,8 @@ class Trigger extends Component { /** * Trigger method. Must be overridden in trigger implementation class. */ - trigger(containerWithResult) { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + trigger(containerWithResult: Container) { // do nothing by default this.log.warn( 'Cannot trigger container result; this trigger does not implement "simple" mode', @@ -354,7 +399,7 @@ class Trigger extends Component { * @param containersWithResult * @returns {*} */ - triggerBatch(containersWithResult) { + triggerBatch(containersWithResult: Container[]) { // do nothing by default this.log.warn( 'Cannot trigger container results; this trigger does not implement "batch" mode', @@ -367,8 +412,8 @@ class Trigger extends Component { * @param container * @returns {*} */ - renderSimpleTitle(container) { - return renderSimple(this.configuration.simpletitle, container); + renderSimpleTitle(container: Container) { + return renderSimple(this.configuration.simpletitle!, container); } /** @@ -376,8 +421,8 @@ class Trigger extends Component { * @param container * @returns {*} */ - renderSimpleBody(container) { - return renderSimple(this.configuration.simplebody, container); + renderSimpleBody(container: Container) { + return renderSimple(this.configuration.simplebody!, container); } /** @@ -385,8 +430,8 @@ class Trigger extends Component { * @param containers * @returns {*} */ - renderBatchTitle(containers) { - return renderBatch(this.configuration.batchtitle, containers); + renderBatchTitle(containers: Container[]) { + return renderBatch(this.configuration.batchtitle!, containers); } /** @@ -394,11 +439,11 @@ class Trigger extends Component { * @param containers * @returns {*} */ - renderBatchBody(containers) { + renderBatchBody(containers: Container[]) { return containers .map((container) => `- ${this.renderSimpleBody(container)}\n`) .join('\n'); } } -module.exports = Trigger; +export default Trigger; diff --git a/app/triggers/providers/apprise/Apprise.test.js b/app/triggers/providers/apprise/Apprise.test.ts similarity index 93% rename from app/triggers/providers/apprise/Apprise.test.js rename to app/triggers/providers/apprise/Apprise.test.ts index 9d6ba7f1..a6ffb2c8 100644 --- a/app/triggers/providers/apprise/Apprise.test.js +++ b/app/triggers/providers/apprise/Apprise.test.ts @@ -1,8 +1,9 @@ -const { ValidationError } = require('joi'); -const axios = require('axios'); +// @ts-nocheck +import { ValidationError } from 'joi'; +import axios from 'axios'; jest.mock('axios'); -const Apprise = require('./Apprise'); +import Apprise from './Apprise'; const apprise = new Apprise(); @@ -23,17 +24,17 @@ const configurationValid = { batchtitle: '${containers.length} updates available', }; -beforeEach(() => { +beforeEach(async () => { jest.resetAllMocks(); }); -test('validateConfiguration should return validated configuration when valid', () => { +test('validateConfiguration should return validated configuration when valid', async () => { const validatedConfiguration = apprise.validateConfiguration(configurationValid); expect(validatedConfiguration).toStrictEqual(configurationValid); }); -test('validateConfiguration should throw error when invalid', () => { +test('validateConfiguration should throw error when invalid', async () => { const configuration = { url: 'git://xxx.com', }; @@ -42,7 +43,7 @@ test('validateConfiguration should throw error when invalid', () => { }).toThrowError(ValidationError); }); -test('validateConfiguration should throw error when urls and config are set at the same time', () => { +test('validateConfiguration should throw error when urls and config are set at the same time', async () => { const configuration = { ...configurationValid, config: 'config', @@ -176,7 +177,7 @@ test('triggerBatch should send batch notification', async () => { }); }); -test('maskConfiguration should mask urls', () => { +test('maskConfiguration should mask urls', async () => { apprise.configuration = { url: 'http://xxx.com', urls: 'mailto://secret@example.com', diff --git a/app/triggers/providers/apprise/Apprise.js b/app/triggers/providers/apprise/Apprise.ts similarity index 96% rename from app/triggers/providers/apprise/Apprise.js rename to app/triggers/providers/apprise/Apprise.ts index 3bff9e22..63fcc391 100644 --- a/app/triggers/providers/apprise/Apprise.js +++ b/app/triggers/providers/apprise/Apprise.ts @@ -1,6 +1,7 @@ -const axios = require('axios'); +// @ts-nocheck +import axios from 'axios'; -const Trigger = require('../Trigger'); +import Trigger from '../Trigger'; /** * Apprise Trigger implementation @@ -92,4 +93,4 @@ class Apprise extends Trigger { } } -module.exports = Apprise; +export default Apprise; diff --git a/app/triggers/providers/command/Command.test.js b/app/triggers/providers/command/Command.test.ts similarity index 92% rename from app/triggers/providers/command/Command.test.js rename to app/triggers/providers/command/Command.test.ts index e966b720..6ad22bda 100644 --- a/app/triggers/providers/command/Command.test.js +++ b/app/triggers/providers/command/Command.test.ts @@ -1,6 +1,7 @@ -const { ValidationError } = require('joi'); +// @ts-nocheck +import { ValidationError } from 'joi'; -const Command = require('./Command'); +import Command from './Command'; const command = new Command(); @@ -19,24 +20,24 @@ const configurationValid = { batchtitle: '${containers.length} updates available', }; -beforeEach(() => { +beforeEach(async () => { jest.resetAllMocks(); }); -test('validateConfiguration should return validated configuration when valid', () => { +test('validateConfiguration should return validated configuration when valid', async () => { const validatedConfiguration = command.validateConfiguration(configurationValid); expect(validatedConfiguration).toStrictEqual(configurationValid); }); -test('validateConfiguration should apply_default_configuration', () => { +test('validateConfiguration should apply_default_configuration', async () => { const validatedConfiguration = command.validateConfiguration({ cmd: configurationValid.cmd, }); expect(validatedConfiguration).toStrictEqual(configurationValid); }); -test('validateConfiguration should throw error when invalid', () => { +test('validateConfiguration should throw error when invalid', async () => { const configuration = { command: 123456789, }; diff --git a/app/triggers/providers/command/Command.js b/app/triggers/providers/command/Command.ts similarity index 88% rename from app/triggers/providers/command/Command.js rename to app/triggers/providers/command/Command.ts index bc54a82b..6ea58ca3 100644 --- a/app/triggers/providers/command/Command.js +++ b/app/triggers/providers/command/Command.ts @@ -1,8 +1,10 @@ -const util = require('node:util'); -const exec = util.promisify(require('node:child_process').exec); -const Trigger = require('../Trigger'); +// @ts-nocheck +import util from 'node:util'; +import child_process from 'node:child_process'; +const exec = util.promisify(child_process.exec); +import Trigger from '../Trigger'; -const { flatten } = require('../../../model/container'); +import { flatten } from '../../../model/container'; /** * Command Trigger implementation */ @@ -79,4 +81,4 @@ class Command extends Trigger { } } -module.exports = Command; +export default Command; diff --git a/app/triggers/providers/discord/Discord.test.js b/app/triggers/providers/discord/Discord.test.ts similarity index 84% rename from app/triggers/providers/discord/Discord.test.js rename to app/triggers/providers/discord/Discord.test.ts index f446ab40..6a14f801 100644 --- a/app/triggers/providers/discord/Discord.test.js +++ b/app/triggers/providers/discord/Discord.test.ts @@ -1,4 +1,5 @@ -const Discord = require('./Discord'); +// @ts-nocheck +import Discord from './Discord'; // Mock axios jest.mock('axios', () => jest.fn().mockResolvedValue({ data: {} })); @@ -6,22 +7,22 @@ jest.mock('axios', () => jest.fn().mockResolvedValue({ data: {} })); describe('Discord Trigger', () => { let discord; - beforeEach(() => { + beforeEach(async () => { discord = new Discord(); jest.clearAllMocks(); }); - test('should create instance', () => { + test('should create instance', async () => { expect(discord).toBeDefined(); expect(discord).toBeInstanceOf(Discord); }); - test('should have correct configuration schema', () => { + test('should have correct configuration schema', async () => { const schema = discord.getConfigurationSchema(); expect(schema).toBeDefined(); }); - test('should validate configuration with webhook URL', () => { + test('should validate configuration with webhook URL', async () => { const config = { url: 'https://discord.com/api/webhooks/123/abc', }; @@ -29,13 +30,13 @@ describe('Discord Trigger', () => { expect(() => discord.validateConfiguration(config)).not.toThrow(); }); - test('should throw error when webhook URL is missing', () => { + test('should throw error when webhook URL is missing', async () => { const config = {}; expect(() => discord.validateConfiguration(config)).toThrow(); }); - test('should mask configuration URL', () => { + test('should mask configuration URL', async () => { discord.configuration = { url: 'https://discord.com/api/webhooks/123/secret', }; @@ -44,7 +45,7 @@ describe('Discord Trigger', () => { }); test('should trigger with container', async () => { - const axios = require('axios'); + const { default: axios } = await import('axios'); discord.configuration = { url: 'https://discord.com/api/webhooks/123/abc', }; @@ -58,7 +59,7 @@ describe('Discord Trigger', () => { }); test('should trigger batch with containers', async () => { - const axios = require('axios'); + const { default: axios } = await import('axios'); discord.configuration = { url: 'https://discord.com/api/webhooks/123/abc', }; @@ -72,7 +73,7 @@ describe('Discord Trigger', () => { }); test('should send message with custom configuration', async () => { - const axios = require('axios'); + const { default: axios } = await import('axios'); discord.configuration = { url: 'https://discord.com/api/webhooks/123/abc', botusername: 'CustomBot', diff --git a/app/triggers/providers/discord/Discord.js b/app/triggers/providers/discord/Discord.ts similarity index 95% rename from app/triggers/providers/discord/Discord.js rename to app/triggers/providers/discord/Discord.ts index 82bce14d..cb3e9f30 100644 --- a/app/triggers/providers/discord/Discord.js +++ b/app/triggers/providers/discord/Discord.ts @@ -1,6 +1,7 @@ -const axios = require('axios'); +// @ts-nocheck +import axios from 'axios'; -const Trigger = require('../Trigger'); +import Trigger from '../Trigger'; /** * Discord Trigger implementation @@ -93,4 +94,4 @@ class Discord extends Trigger { } } -module.exports = Discord; +export default Discord; diff --git a/app/triggers/providers/docker/Docker.test.js b/app/triggers/providers/docker/Docker.test.ts similarity index 97% rename from app/triggers/providers/docker/Docker.test.js rename to app/triggers/providers/docker/Docker.test.ts index bd3f513f..e508a0b9 100644 --- a/app/triggers/providers/docker/Docker.test.js +++ b/app/triggers/providers/docker/Docker.test.ts @@ -1,6 +1,7 @@ -const { ValidationError } = require('joi'); -const Docker = require('./Docker'); -const log = require('../../../log'); +// @ts-nocheck +import { ValidationError } from 'joi'; +import Docker from './Docker'; +import log from '../../../log'; const configurationValid = { prune: false, @@ -108,17 +109,17 @@ jest.mock('../../../registry', () => ({ }, })); -beforeEach(() => { +beforeEach(async () => { jest.resetAllMocks(); }); -test('validateConfiguration should return validated configuration when valid', () => { +test('validateConfiguration should return validated configuration when valid', async () => { const validatedConfiguration = docker.validateConfiguration(configurationValid); expect(validatedConfiguration).toStrictEqual(configurationValid); }); -test('validateConfiguration should throw error when invalid', () => { +test('validateConfiguration should throw error when invalid', async () => { const configuration = { url: 'git://xxx.com', }; @@ -127,7 +128,7 @@ test('validateConfiguration should throw error when invalid', () => { }).toThrowError(ValidationError); }); -test('getWatcher should return watcher responsible for a container', () => { +test('getWatcher should return watcher responsible for a container', async () => { expect( docker .getWatcher({ @@ -351,7 +352,7 @@ test('removeImage should throw error when error occurs', async () => { ).rejects.toThrowError('Error when removing image'); }); -test('clone should clone an existing container spec', () => { +test('clone should clone an existing container spec', async () => { const clone = docker.cloneContainer( { Name: '/test', diff --git a/app/triggers/providers/docker/Docker.js b/app/triggers/providers/docker/Docker.ts similarity index 98% rename from app/triggers/providers/docker/Docker.js rename to app/triggers/providers/docker/Docker.ts index 60b11ed1..9df0818b 100644 --- a/app/triggers/providers/docker/Docker.js +++ b/app/triggers/providers/docker/Docker.ts @@ -1,7 +1,8 @@ -const parse = require('parse-docker-image-name'); -const Trigger = require('../Trigger'); -const { getState } = require('../../../registry'); -const { fullName } = require('../../../model/container'); +// @ts-nocheck +import parse from 'parse-docker-image-name'; +import Trigger from '../Trigger'; +import { getState } from '../../../registry'; +import { fullName } from '../../../model/container'; /** * Replace a Docker container with an updated one. @@ -528,4 +529,4 @@ class Docker extends Trigger { } } -module.exports = Docker; +export default Docker; diff --git a/app/triggers/providers/dockercompose/Dockercompose.js b/app/triggers/providers/dockercompose/Dockercompose.ts similarity index 97% rename from app/triggers/providers/dockercompose/Dockercompose.js rename to app/triggers/providers/dockercompose/Dockercompose.ts index 42a7e5d7..41ee5290 100644 --- a/app/triggers/providers/dockercompose/Dockercompose.js +++ b/app/triggers/providers/dockercompose/Dockercompose.ts @@ -1,8 +1,9 @@ -const fs = require('fs/promises'); -const path = require('path'); -const yaml = require('yaml'); -const Docker = require('../docker/Docker'); -const { getState } = require('../../../registry'); +// @ts-nocheck +import fs from 'fs/promises'; +import path from 'path'; +import yaml from 'yaml'; +import Docker from '../docker/Docker'; +import { getState } from '../../../registry'; /** * Return true if the container belongs to the compose file. @@ -312,4 +313,4 @@ class Dockercompose extends Docker { } } -module.exports = Dockercompose; +export default Dockercompose; diff --git a/app/triggers/providers/gotify/Gotify.test.js b/app/triggers/providers/gotify/Gotify.test.ts similarity index 91% rename from app/triggers/providers/gotify/Gotify.test.js rename to app/triggers/providers/gotify/Gotify.test.ts index a5cff4a8..ce192b7f 100644 --- a/app/triggers/providers/gotify/Gotify.test.js +++ b/app/triggers/providers/gotify/Gotify.test.ts @@ -1,8 +1,9 @@ -const { ValidationError } = require('joi'); -const axios = require('axios'); +// @ts-nocheck +import { ValidationError } from 'joi'; +import axios from 'axios'; jest.mock('axios'); -const Gotify = require('./Gotify'); +import Gotify from './Gotify'; const gotify = new Gotify(); @@ -22,17 +23,17 @@ const configurationValid = { batchtitle: '${containers.length} updates available', }; -beforeEach(() => { +beforeEach(async () => { jest.resetAllMocks(); }); -test('validateConfiguration should return validated configuration when valid', () => { +test('validateConfiguration should return validated configuration when valid', async () => { const validatedConfiguration = gotify.validateConfiguration(configurationValid); expect(validatedConfiguration).toStrictEqual(configurationValid); }); -test('validateConfiguration should apply default configuration', () => { +test('validateConfiguration should apply default configuration', async () => { const validatedConfiguration = gotify.validateConfiguration({ url: configurationValid.url, token: configurationValid.token, @@ -41,7 +42,7 @@ test('validateConfiguration should apply default configuration', () => { expect(validatedConfiguration).toStrictEqual(expectedWithoutPriority); }); -test('validateConfiguration should throw error when invalid', () => { +test('validateConfiguration should throw error when invalid', async () => { const configuration = { url: 'git://xxx.com', }; @@ -50,7 +51,7 @@ test('validateConfiguration should throw error when invalid', () => { }).toThrowError(ValidationError); }); -test('maskConfiguration should mask sensitive data', () => { +test('maskConfiguration should mask sensitive data', async () => { gotify.configuration = configurationValid; expect(gotify.maskConfiguration()).toEqual({ url: configurationValid.url, diff --git a/app/triggers/providers/gotify/Gotify.js b/app/triggers/providers/gotify/Gotify.ts similarity index 93% rename from app/triggers/providers/gotify/Gotify.js rename to app/triggers/providers/gotify/Gotify.ts index 212a2a1a..6fac14a9 100644 --- a/app/triggers/providers/gotify/Gotify.js +++ b/app/triggers/providers/gotify/Gotify.ts @@ -1,5 +1,6 @@ -const { GotifyClient } = require('gotify-client'); -const Trigger = require('../Trigger'); +// @ts-nocheck +import { GotifyClient } from 'gotify-client'; +import Trigger from '../Trigger'; /** * Gotify Trigger implementation @@ -67,4 +68,4 @@ class Gotify extends Trigger { } } -module.exports = Gotify; +export default Gotify; diff --git a/app/triggers/providers/http/Http.test.js b/app/triggers/providers/http/Http.test.ts similarity index 85% rename from app/triggers/providers/http/Http.test.js rename to app/triggers/providers/http/Http.test.ts index eba7935e..f4e3dbbf 100644 --- a/app/triggers/providers/http/Http.test.js +++ b/app/triggers/providers/http/Http.test.ts @@ -1,4 +1,5 @@ -const Http = require('./Http'); +// @ts-nocheck +import Http from './Http'; // Mock axios jest.mock('axios', () => jest.fn()); @@ -6,22 +7,22 @@ jest.mock('axios', () => jest.fn()); describe('HTTP Trigger', () => { let http; - beforeEach(() => { + beforeEach(async () => { http = new Http(); jest.clearAllMocks(); }); - test('should create instance', () => { + test('should create instance', async () => { expect(http).toBeDefined(); expect(http).toBeInstanceOf(Http); }); - test('should have correct configuration schema', () => { + test('should have correct configuration schema', async () => { const schema = http.getConfigurationSchema(); expect(schema).toBeDefined(); }); - test('should validate configuration with URL', () => { + test('should validate configuration with URL', async () => { const config = { url: 'https://example.com/webhook', }; @@ -29,14 +30,14 @@ describe('HTTP Trigger', () => { expect(() => http.validateConfiguration(config)).not.toThrow(); }); - test('should throw error when URL is missing', () => { + test('should throw error when URL is missing', async () => { const config = {}; expect(() => http.validateConfiguration(config)).toThrow(); }); test('should trigger with container', async () => { - const axios = require('axios'); + const { default: axios } = await import('axios'); axios.mockResolvedValue({ data: {} }); await http.register('trigger', 'http', 'test', { url: 'https://example.com/webhook', @@ -52,7 +53,7 @@ describe('HTTP Trigger', () => { }); test('should trigger batch with containers', async () => { - const axios = require('axios'); + const { default: axios } = await import('axios'); axios.mockResolvedValue({ data: {} }); await http.register('trigger', 'http', 'test', { url: 'https://example.com/webhook', @@ -68,7 +69,7 @@ describe('HTTP Trigger', () => { }); test('should use GET method with query string', async () => { - const axios = require('axios'); + const { default: axios } = await import('axios'); axios.mockResolvedValue({ data: {} }); await http.register('trigger', 'http', 'test', { url: 'https://example.com/webhook', @@ -85,7 +86,7 @@ describe('HTTP Trigger', () => { }); test('should use BASIC auth', async () => { - const axios = require('axios'); + const { default: axios } = await import('axios'); axios.mockResolvedValue({ data: {} }); await http.register('trigger', 'http', 'test', { url: 'https://example.com/webhook', @@ -103,7 +104,7 @@ describe('HTTP Trigger', () => { }); test('should use BEARER auth', async () => { - const axios = require('axios'); + const { default: axios } = await import('axios'); axios.mockResolvedValue({ data: {} }); await http.register('trigger', 'http', 'test', { url: 'https://example.com/webhook', @@ -121,7 +122,7 @@ describe('HTTP Trigger', () => { }); test('should use proxy', async () => { - const axios = require('axios'); + const { default: axios } = await import('axios'); axios.mockResolvedValue({ data: {} }); await http.register('trigger', 'http', 'test', { url: 'https://example.com/webhook', diff --git a/app/triggers/providers/http/Http.js b/app/triggers/providers/http/Http.ts similarity index 96% rename from app/triggers/providers/http/Http.js rename to app/triggers/providers/http/Http.ts index 7eb57162..d23ea822 100644 --- a/app/triggers/providers/http/Http.js +++ b/app/triggers/providers/http/Http.ts @@ -1,6 +1,7 @@ -const axios = require('axios'); +// @ts-nocheck +import axios from 'axios'; -const Trigger = require('../Trigger'); +import Trigger from '../Trigger'; /** * HTTP Trigger implementation @@ -90,4 +91,4 @@ class Http extends Trigger { } } -module.exports = Http; +export default Http; diff --git a/app/triggers/providers/ifttt/Ifttt.test.js b/app/triggers/providers/ifttt/Ifttt.test.ts similarity index 87% rename from app/triggers/providers/ifttt/Ifttt.test.js rename to app/triggers/providers/ifttt/Ifttt.test.ts index 580a1c2e..f8a35185 100644 --- a/app/triggers/providers/ifttt/Ifttt.test.js +++ b/app/triggers/providers/ifttt/Ifttt.test.ts @@ -1,9 +1,10 @@ -const { ValidationError } = require('joi'); -const axios = require('axios'); +// @ts-nocheck +import { ValidationError } from 'joi'; +import axios from 'axios'; jest.mock('axios'); -const Ifttt = require('./Ifttt'); +import Ifttt from './Ifttt'; const ifttt = new Ifttt(); @@ -23,31 +24,31 @@ const configurationValid = { batchtitle: '${containers.length} updates available', }; -beforeEach(() => { +beforeEach(async () => { jest.resetAllMocks(); }); -test('validateConfiguration should return validated configuration when valid', () => { +test('validateConfiguration should return validated configuration when valid', async () => { const validatedConfiguration = ifttt.validateConfiguration(configurationValid); expect(validatedConfiguration).toStrictEqual(configurationValid); }); -test('validateConfiguration should apply_default_configuration', () => { +test('validateConfiguration should apply_default_configuration', async () => { const validatedConfiguration = ifttt.validateConfiguration({ key: configurationValid.key, }); expect(validatedConfiguration).toStrictEqual(configurationValid); }); -test('validateConfiguration should throw error when invalid', () => { +test('validateConfiguration should throw error when invalid', async () => { const configuration = {}; expect(() => { ifttt.validateConfiguration(configuration); }).toThrowError(ValidationError); }); -test('maskConfiguration should mask sensitive data', () => { +test('maskConfiguration should mask sensitive data', async () => { ifttt.configuration = { key: 'key', event: 'event', diff --git a/app/triggers/providers/ifttt/Ifttt.js b/app/triggers/providers/ifttt/Ifttt.ts similarity index 94% rename from app/triggers/providers/ifttt/Ifttt.js rename to app/triggers/providers/ifttt/Ifttt.ts index 2f977ddb..514c4f1a 100644 --- a/app/triggers/providers/ifttt/Ifttt.js +++ b/app/triggers/providers/ifttt/Ifttt.ts @@ -1,6 +1,7 @@ -const axios = require('axios'); +// @ts-nocheck +import axios from 'axios'; -const Trigger = require('../Trigger'); +import Trigger from '../Trigger'; /** * Ifttt Trigger implementation @@ -73,4 +74,4 @@ class Ifttt extends Trigger { } } -module.exports = Ifttt; +export default Ifttt; diff --git a/app/triggers/providers/kafka/Kafka.test.js b/app/triggers/providers/kafka/Kafka.test.ts similarity index 91% rename from app/triggers/providers/kafka/Kafka.test.js rename to app/triggers/providers/kafka/Kafka.test.ts index bc471eb2..5058ba2a 100644 --- a/app/triggers/providers/kafka/Kafka.test.js +++ b/app/triggers/providers/kafka/Kafka.test.ts @@ -1,9 +1,10 @@ -const { ValidationError } = require('joi'); +// @ts-nocheck +import { ValidationError } from 'joi'; const { Kafka: KafkaClient } = require('kafkajs'); jest.mock('kafkajs'); -const Kafka = require('./Kafka'); +import Kafka from './Kafka'; const kafka = new Kafka(); @@ -25,24 +26,24 @@ const configurationValid = { batchtitle: '${containers.length} updates available', }; -beforeEach(() => { +beforeEach(async () => { jest.resetAllMocks(); }); -test('validateConfiguration should return validated configuration when valid', () => { +test('validateConfiguration should return validated configuration when valid', async () => { const validatedConfiguration = kafka.validateConfiguration(configurationValid); expect(validatedConfiguration).toStrictEqual(configurationValid); }); -test('validateConfiguration should apply_default_configuration', () => { +test('validateConfiguration should apply_default_configuration', async () => { const validatedConfiguration = kafka.validateConfiguration({ brokers: 'broker1:9000, broker2:9000', }); expect(validatedConfiguration).toStrictEqual(configurationValid); }); -test('validateConfiguration should validate_optional_authentication', () => { +test('validateConfiguration should validate_optional_authentication', async () => { const validatedConfiguration = kafka.validateConfiguration({ ...configurationValid, authentication: { @@ -60,7 +61,7 @@ test('validateConfiguration should validate_optional_authentication', () => { }); }); -test('validateConfiguration should throw error when invalid', () => { +test('validateConfiguration should throw error when invalid', async () => { const configuration = { ssl: 'whynot', }; @@ -69,7 +70,7 @@ test('validateConfiguration should throw error when invalid', () => { }).toThrowError(ValidationError); }); -test('maskConfiguration should mask sensitive data', () => { +test('maskConfiguration should mask sensitive data', async () => { kafka.configuration = { brokers: 'broker1:9000, broker2:9000', topic: 'wud-image', @@ -94,7 +95,7 @@ test('maskConfiguration should mask sensitive data', () => { }); }); -test('maskConfiguration should not fail if no auth provided', () => { +test('maskConfiguration should not fail if no auth provided', async () => { kafka.configuration = { brokers: 'broker1:9000, broker2:9000', topic: 'wud-image', diff --git a/app/triggers/providers/kafka/Kafka.js b/app/triggers/providers/kafka/Kafka.ts similarity index 96% rename from app/triggers/providers/kafka/Kafka.js rename to app/triggers/providers/kafka/Kafka.ts index 155830ad..a36cf0fc 100644 --- a/app/triggers/providers/kafka/Kafka.js +++ b/app/triggers/providers/kafka/Kafka.ts @@ -1,5 +1,6 @@ -const { Kafka: KafkaClient } = require('kafkajs'); -const Trigger = require('../Trigger'); +// @ts-nocheck +import { Kafka as KafkaClient } from 'kafkajs'; +import Trigger from '../Trigger'; /** * Kafka Trigger implementation @@ -105,4 +106,4 @@ class Kafka extends Trigger { } } -module.exports = Kafka; +export default Kafka; diff --git a/app/triggers/providers/mock/Mock.test.js b/app/triggers/providers/mock/Mock.test.ts similarity index 92% rename from app/triggers/providers/mock/Mock.test.js rename to app/triggers/providers/mock/Mock.test.ts index abb7e2a9..0956050a 100644 --- a/app/triggers/providers/mock/Mock.test.js +++ b/app/triggers/providers/mock/Mock.test.ts @@ -1,4 +1,5 @@ -const Mock = require('./Mock'); +// @ts-nocheck +import Mock from './Mock'; describe('Mock Trigger', () => { let mock; @@ -8,24 +9,24 @@ describe('Mock Trigger', () => { await mock.register('trigger', 'mock', 'test', {}); }); - test('should create instance', () => { + test('should create instance', async () => { expect(mock).toBeDefined(); expect(mock).toBeInstanceOf(Mock); }); - test('should have correct configuration schema', () => { + test('should have correct configuration schema', async () => { const schema = mock.getConfigurationSchema(); expect(schema).toBeDefined(); }); - test('should validate configuration with default mock value', () => { + test('should validate configuration with default mock value', async () => { const config = {}; expect(() => mock.validateConfiguration(config)).not.toThrow(); const validated = mock.validateConfiguration(config); expect(validated.mock).toBe('mock'); }); - test('should validate configuration with custom mock value', () => { + test('should validate configuration with custom mock value', async () => { const config = { mock: 'custom' }; const validated = mock.validateConfiguration(config); expect(validated.mock).toBe('custom'); diff --git a/app/triggers/providers/mock/Mock.js b/app/triggers/providers/mock/Mock.ts similarity index 93% rename from app/triggers/providers/mock/Mock.js rename to app/triggers/providers/mock/Mock.ts index 025ad979..eb8268b5 100644 --- a/app/triggers/providers/mock/Mock.js +++ b/app/triggers/providers/mock/Mock.ts @@ -1,4 +1,5 @@ -const Trigger = require('../Trigger'); +// @ts-nocheck +import Trigger from '../Trigger'; /** * Mock Trigger implementation (for tests) @@ -43,4 +44,4 @@ class Mock extends Trigger { } } -module.exports = Mock; +export default Mock; diff --git a/app/triggers/providers/mqtt/Hass.test.js b/app/triggers/providers/mqtt/Hass.test.ts similarity index 99% rename from app/triggers/providers/mqtt/Hass.test.js rename to app/triggers/providers/mqtt/Hass.test.ts index 48cf9089..e2a0f8dd 100644 --- a/app/triggers/providers/mqtt/Hass.test.js +++ b/app/triggers/providers/mqtt/Hass.test.ts @@ -1,5 +1,6 @@ -const log = require('../../../log'); -const Hass = require('./Hass'); +// @ts-nocheck +import log from '../../../log'; +import Hass from './Hass'; const containerData = [ { @@ -29,7 +30,7 @@ const containerData = [ let hass; let mqttClientMock; -beforeEach(() => { +beforeEach(async () => { jest.resetAllMocks(); mqttClientMock = { publish: jest.fn(() => {}), diff --git a/app/triggers/providers/mqtt/Hass.js b/app/triggers/providers/mqtt/Hass.ts similarity index 98% rename from app/triggers/providers/mqtt/Hass.js rename to app/triggers/providers/mqtt/Hass.ts index 5c46711f..e60c260f 100644 --- a/app/triggers/providers/mqtt/Hass.js +++ b/app/triggers/providers/mqtt/Hass.ts @@ -1,12 +1,13 @@ -const { getVersion } = require('../../../configuration'); -const { +// @ts-nocheck +import { getVersion } from '../../../configuration'; +import { registerContainerAdded, registerContainerUpdated, registerContainerRemoved, registerWatcherStart, registerWatcherStop, -} = require('../../../event'); -const containerStore = require('../../../store/container'); +} from '../../../event'; +import * as containerStore from '../../../store/container'; const HASS_DEVICE_ID = 'wud'; const HASS_DEVICE_NAME = 'wud'; @@ -408,4 +409,4 @@ class Hass { } } -module.exports = Hass; +export default Hass; diff --git a/app/triggers/providers/mqtt/Mqtt.test.js b/app/triggers/providers/mqtt/Mqtt.test.ts similarity index 91% rename from app/triggers/providers/mqtt/Mqtt.test.js rename to app/triggers/providers/mqtt/Mqtt.test.ts index ed9a68af..24a8ec8a 100644 --- a/app/triggers/providers/mqtt/Mqtt.test.js +++ b/app/triggers/providers/mqtt/Mqtt.test.ts @@ -1,10 +1,11 @@ -const { ValidationError } = require('joi'); -const mqttClient = require('mqtt'); -const log = require('../../../log'); -const { flatten } = require('../../../model/container'); +// @ts-nocheck +import { ValidationError } from 'joi'; +import mqttClient from 'mqtt'; +import log from '../../../log'; +import { flatten } from '../../../model/container'; jest.mock('mqtt'); -const Mqtt = require('./Mqtt'); +import Mqtt from './Mqtt'; const mqtt = new Mqtt(); mqtt.log = log; @@ -54,20 +55,20 @@ const containerData = [ }, ]; -beforeEach(() => { +beforeEach(async () => { jest.resetAllMocks(); mqtt.client = { publish: jest.fn(() => {}), }; }); -test('validateConfiguration should return validated configuration when valid', () => { +test('validateConfiguration should return validated configuration when valid', async () => { const validatedConfiguration = mqtt.validateConfiguration(configurationValid); expect(validatedConfiguration).toStrictEqual(configurationValid); }); -test('validateConfiguration should apply_default_configuration', () => { +test('validateConfiguration should apply_default_configuration', async () => { const validatedConfiguration = mqtt.validateConfiguration({ url: configurationValid.url, clientid: 'wud', @@ -75,7 +76,7 @@ test('validateConfiguration should apply_default_configuration', () => { expect(validatedConfiguration).toStrictEqual(configurationValid); }); -test('validateConfiguration should throw error when invalid', () => { +test('validateConfiguration should throw error when invalid', async () => { const configuration = { url: 'http://invalid', }; @@ -84,7 +85,7 @@ test('validateConfiguration should throw error when invalid', () => { }).toThrowError(ValidationError); }); -test('maskConfiguration should mask sensitive data', () => { +test('maskConfiguration should mask sensitive data', async () => { mqtt.configuration = { password: 'password', url: 'mqtt://host:1883', diff --git a/app/triggers/providers/mqtt/Mqtt.js b/app/triggers/providers/mqtt/Mqtt.ts similarity index 94% rename from app/triggers/providers/mqtt/Mqtt.js rename to app/triggers/providers/mqtt/Mqtt.ts index ff23dbff..732c67e4 100644 --- a/app/triggers/providers/mqtt/Mqtt.js +++ b/app/triggers/providers/mqtt/Mqtt.ts @@ -1,12 +1,13 @@ -const fs = require('fs').promises; -const mqtt = require('mqtt'); -const Trigger = require('../Trigger'); -const Hass = require('./Hass'); -const { +// @ts-nocheck +import fs from 'fs/promises'; +import mqtt from 'mqtt'; +import Trigger from '../Trigger'; +import Hass from './Hass'; +import { registerContainerAdded, registerContainerUpdated, -} = require('../../../event'); -const { flatten } = require('../../../model/container'); +} from '../../../event'; +import { flatten } from '../../../model/container'; const containerDefaultTopic = 'wud/container'; const hassDefaultPrefix = 'homeassistant'; @@ -158,4 +159,4 @@ class Mqtt extends Trigger { } } -module.exports = Mqtt; +export default Mqtt; diff --git a/app/triggers/providers/ntfy/Ntfy.test.js b/app/triggers/providers/ntfy/Ntfy.test.ts similarity index 93% rename from app/triggers/providers/ntfy/Ntfy.test.js rename to app/triggers/providers/ntfy/Ntfy.test.ts index 351bdcb2..1efc63f7 100644 --- a/app/triggers/providers/ntfy/Ntfy.test.js +++ b/app/triggers/providers/ntfy/Ntfy.test.ts @@ -1,6 +1,7 @@ -const { ValidationError } = require('joi'); -const axios = require('axios'); -const Ntfy = require('./Ntfy'); +// @ts-nocheck +import { ValidationError } from 'joi'; +import axios from 'axios'; +import Ntfy from './Ntfy'; jest.mock('axios'); @@ -23,17 +24,17 @@ const configurationValid = { batchtitle: '${containers.length} updates available', }; -beforeEach(() => { +beforeEach(async () => { jest.resetAllMocks(); }); -test('validateConfiguration should return validated configuration when valid', () => { +test('validateConfiguration should return validated configuration when valid', async () => { const validatedConfiguration = ntfy.validateConfiguration(configurationValid); expect(validatedConfiguration).toStrictEqual(configurationValid); }); -test('validateConfiguration should throw error when invalid', () => { +test('validateConfiguration should throw error when invalid', async () => { const configuration = { url: 'git://xxx.com', }; @@ -42,7 +43,7 @@ test('validateConfiguration should throw error when invalid', () => { }).toThrowError(ValidationError); }); -test('maskConfiguration should mask sensitive data', () => { +test('maskConfiguration should mask sensitive data', async () => { ntfy.configuration = { auth: { user: 'user', diff --git a/app/triggers/providers/ntfy/Ntfy.js b/app/triggers/providers/ntfy/Ntfy.ts similarity index 96% rename from app/triggers/providers/ntfy/Ntfy.js rename to app/triggers/providers/ntfy/Ntfy.ts index bcaaed41..29bbd31b 100644 --- a/app/triggers/providers/ntfy/Ntfy.js +++ b/app/triggers/providers/ntfy/Ntfy.ts @@ -1,5 +1,6 @@ -const axios = require('axios'); -const Trigger = require('../Trigger'); +// @ts-nocheck +import axios from 'axios'; +import Trigger from '../Trigger'; /** * Ntfy Trigger implementation @@ -106,4 +107,4 @@ class Ntfy extends Trigger { } } -module.exports = Ntfy; +export default Ntfy; diff --git a/app/triggers/providers/pushover/Pushover.test.js b/app/triggers/providers/pushover/Pushover.test.ts similarity index 93% rename from app/triggers/providers/pushover/Pushover.test.js rename to app/triggers/providers/pushover/Pushover.test.ts index 840dca87..e4687118 100644 --- a/app/triggers/providers/pushover/Pushover.test.js +++ b/app/triggers/providers/pushover/Pushover.test.ts @@ -1,4 +1,5 @@ -const { ValidationError } = require('joi'); +// @ts-nocheck +import { ValidationError } from 'joi'; jest.mock( 'pushover-notifications', @@ -10,7 +11,7 @@ jest.mock( }, ); -const Pushover = require('./Pushover'); +import Pushover from './Pushover'; const pushover = new Pushover(); @@ -33,13 +34,13 @@ const configurationValid = { batchtitle: '${containers.length} updates available', }; -test('validateConfiguration should return validated configuration when valid', () => { +test('validateConfiguration should return validated configuration when valid', async () => { const validatedConfiguration = pushover.validateConfiguration(configurationValid); expect(validatedConfiguration).toStrictEqual(configurationValid); }); -test('validateConfiguration should fail when priority is 2 and no retry set', () => { +test('validateConfiguration should fail when priority is 2 and no retry set', async () => { expect(() => { pushover.validateConfiguration({ ...configurationValid, @@ -48,7 +49,7 @@ test('validateConfiguration should fail when priority is 2 and no retry set', () }).toThrowError(ValidationError); }); -test('validateConfiguration should fail when priority is 2 and retry too small', () => { +test('validateConfiguration should fail when priority is 2 and retry too small', async () => { expect(() => { pushover.validateConfiguration({ ...configurationValid, @@ -58,7 +59,7 @@ test('validateConfiguration should fail when priority is 2 and retry too small', }).toThrowError(ValidationError); }); -test('validateConfiguration should fail when priority is 2 and no expire', () => { +test('validateConfiguration should fail when priority is 2 and no expire', async () => { expect(() => { pushover.validateConfiguration({ ...configurationValid, @@ -68,7 +69,7 @@ test('validateConfiguration should fail when priority is 2 and no expire', () => }).toThrowError(ValidationError); }); -test('validateConfiguration should succeed when priority is 2 and expire and retry set', () => { +test('validateConfiguration should succeed when priority is 2 and expire and retry set', async () => { expect({ ...configurationValid, priority: 2, @@ -82,7 +83,7 @@ test('validateConfiguration should succeed when priority is 2 and expire and ret }); }); -test('validateConfiguration should apply_default_configuration', () => { +test('validateConfiguration should apply_default_configuration', async () => { const validatedConfiguration = pushover.validateConfiguration({ user: configurationValid.user, token: configurationValid.token, @@ -90,14 +91,14 @@ test('validateConfiguration should apply_default_configuration', () => { expect(validatedConfiguration).toStrictEqual(configurationValid); }); -test('validateConfiguration should throw error when invalid', () => { +test('validateConfiguration should throw error when invalid', async () => { const configuration = {}; expect(() => { pushover.validateConfiguration(configuration); }).toThrowError(ValidationError); }); -test('maskConfiguration should mask sensitive data', () => { +test('maskConfiguration should mask sensitive data', async () => { pushover.configuration = configurationValid; expect(pushover.maskConfiguration()).toEqual({ mode: 'simple', diff --git a/app/triggers/providers/pushover/Pushover.js b/app/triggers/providers/pushover/Pushover.ts similarity index 97% rename from app/triggers/providers/pushover/Pushover.js rename to app/triggers/providers/pushover/Pushover.ts index 61d53040..e917d14b 100644 --- a/app/triggers/providers/pushover/Pushover.js +++ b/app/triggers/providers/pushover/Pushover.ts @@ -1,5 +1,6 @@ -const Push = require('pushover-notifications'); -const Trigger = require('../Trigger'); +// @ts-nocheck +import Push from 'pushover-notifications'; +import Trigger from '../Trigger'; /** * Ifttt Trigger implementation @@ -149,4 +150,4 @@ class Pushover extends Trigger { } } -module.exports = Pushover; +export default Pushover; diff --git a/app/triggers/providers/rocketchat/Rocketchat.test.js b/app/triggers/providers/rocketchat/Rocketchat.test.ts similarity index 85% rename from app/triggers/providers/rocketchat/Rocketchat.test.js rename to app/triggers/providers/rocketchat/Rocketchat.test.ts index 2b7fda3f..4526ad2b 100644 --- a/app/triggers/providers/rocketchat/Rocketchat.test.js +++ b/app/triggers/providers/rocketchat/Rocketchat.test.ts @@ -1,4 +1,5 @@ -const Rocketchat = require('./Rocketchat'); +// @ts-nocheck +import Rocketchat from './Rocketchat'; // Mock axios jest.mock('axios', () => ({ @@ -8,22 +9,22 @@ jest.mock('axios', () => ({ describe('Rocketchat Trigger', () => { let rocketchat; - beforeEach(() => { + beforeEach(async () => { rocketchat = new Rocketchat(); jest.clearAllMocks(); }); - test('should create instance', () => { + test('should create instance', async () => { expect(rocketchat).toBeDefined(); expect(rocketchat).toBeInstanceOf(Rocketchat); }); - test('should have correct configuration schema', () => { + test('should have correct configuration schema', async () => { const schema = rocketchat.getConfigurationSchema(); expect(schema).toBeDefined(); }); - test('should validate configuration with required fields', () => { + test('should validate configuration with required fields', async () => { const config = { url: 'https://open.rocket.chat', user: { id: 'jDdn8oh9BfJKnWdDY' }, @@ -34,7 +35,7 @@ describe('Rocketchat Trigger', () => { expect(() => rocketchat.validateConfiguration(config)).not.toThrow(); }); - test('should throw error when URL is missing', () => { + test('should throw error when URL is missing', async () => { const config = { user: { id: 'test' }, auth: { token: 'test' }, @@ -44,7 +45,7 @@ describe('Rocketchat Trigger', () => { expect(() => rocketchat.validateConfiguration(config)).toThrow(); }); - test('should throw error when user id is missing', () => { + test('should throw error when user id is missing', async () => { const config = { url: 'https://open.rocket.chat', user: {}, @@ -55,7 +56,7 @@ describe('Rocketchat Trigger', () => { expect(() => rocketchat.validateConfiguration(config)).toThrow(); }); - test('should throw error when auth token is missing', () => { + test('should throw error when auth token is missing', async () => { const config = { url: 'https://open.rocket.chat', user: { id: 'test' }, @@ -66,7 +67,7 @@ describe('Rocketchat Trigger', () => { expect(() => rocketchat.validateConfiguration(config)).toThrow(); }); - test('should throw error when channel is missing', () => { + test('should throw error when channel is missing', async () => { const config = { url: 'https://open.rocket.chat', user: { id: 'test' }, @@ -76,7 +77,7 @@ describe('Rocketchat Trigger', () => { expect(() => rocketchat.validateConfiguration(config)).toThrow(); }); - test('should mask configuration sensitive data', () => { + test('should mask configuration sensitive data', async () => { rocketchat.configuration = { auth: { token: 'token' }, user: { id: 'some_user_id' }, @@ -89,7 +90,7 @@ describe('Rocketchat Trigger', () => { }); test('should trigger with container', async () => { - const axios = require('axios'); + const { default: axios } = await import('axios'); rocketchat.configuration = { url: 'https://open.rocket.chat', user: { id: 'jDdn8oh9BfJKnWdDY' }, @@ -106,7 +107,7 @@ describe('Rocketchat Trigger', () => { }); test('should trigger batch with containers', async () => { - const axios = require('axios'); + const { default: axios } = await import('axios'); rocketchat.configuration = { url: 'https://open.rocket.chat', user: { id: 'jDdn8oh9BfJKnWdDY' }, @@ -123,7 +124,7 @@ describe('Rocketchat Trigger', () => { }); test('should send message with correct data', async () => { - const axios = require('axios'); + const { default: axios } = await import('axios'); rocketchat.configuration = { url: 'https://open.rocket.chat', user: { id: 'jDdn8oh9BfJKnWdDY' }, diff --git a/app/triggers/providers/rocketchat/Rocketchat.js b/app/triggers/providers/rocketchat/Rocketchat.ts similarity index 97% rename from app/triggers/providers/rocketchat/Rocketchat.js rename to app/triggers/providers/rocketchat/Rocketchat.ts index b1fb5599..6ce6eb60 100644 --- a/app/triggers/providers/rocketchat/Rocketchat.js +++ b/app/triggers/providers/rocketchat/Rocketchat.ts @@ -1,5 +1,6 @@ -const axios = require('axios'); -const Trigger = require('../Trigger'); +// @ts-nocheck +import axios from 'axios'; +import Trigger from '../Trigger'; /** * Rocket Chat Trigger implementation @@ -155,4 +156,4 @@ class Rocketchat extends Trigger { } } -module.exports = Rocketchat; +export default Rocketchat; diff --git a/app/triggers/providers/slack/Slack.test.js b/app/triggers/providers/slack/Slack.test.ts similarity index 93% rename from app/triggers/providers/slack/Slack.test.js rename to app/triggers/providers/slack/Slack.test.ts index 718036e1..9a734201 100644 --- a/app/triggers/providers/slack/Slack.test.js +++ b/app/triggers/providers/slack/Slack.test.ts @@ -1,8 +1,9 @@ -const { ValidationError } = require('joi'); -const { WebClient } = require('@slack/web-api'); +// @ts-nocheck +import { ValidationError } from 'joi'; +import { WebClient } from '@slack/web-api'; jest.mock('@slack/web-api'); -const Slack = require('./Slack'); +import Slack from './Slack'; const slack = new Slack(); @@ -23,19 +24,19 @@ const configurationValid = { disabletitle: false, }; -test('validateConfiguration should return validated configuration when valid', () => { +test('validateConfiguration should return validated configuration when valid', async () => { const validatedConfiguration = slack.validateConfiguration(configurationValid); expect(validatedConfiguration).toStrictEqual(configurationValid); }); -test('validateConfiguration should throw error when invalid', () => { +test('validateConfiguration should throw error when invalid', async () => { expect(() => { slack.validateConfiguration({}); }).toThrowError(ValidationError); }); -test('maskConfiguration should mask sensitive data', () => { +test('maskConfiguration should mask sensitive data', async () => { slack.configuration = { token: 'token', channel: 'channel', diff --git a/app/triggers/providers/slack/Slack.js b/app/triggers/providers/slack/Slack.ts similarity index 94% rename from app/triggers/providers/slack/Slack.js rename to app/triggers/providers/slack/Slack.ts index 81597d46..07997694 100644 --- a/app/triggers/providers/slack/Slack.js +++ b/app/triggers/providers/slack/Slack.ts @@ -1,5 +1,6 @@ -const { WebClient } = require('@slack/web-api'); -const Trigger = require('../Trigger'); +// @ts-nocheck +import { WebClient } from '@slack/web-api'; +import Trigger from '../Trigger'; /* * Slack Trigger implementation @@ -76,4 +77,4 @@ class Slack extends Trigger { } } -module.exports = Slack; +export default Slack; diff --git a/app/triggers/providers/smtp/Smtp.test.js b/app/triggers/providers/smtp/Smtp.test.ts similarity index 95% rename from app/triggers/providers/smtp/Smtp.test.js rename to app/triggers/providers/smtp/Smtp.test.ts index 4eb5e44e..bb4fac26 100644 --- a/app/triggers/providers/smtp/Smtp.test.js +++ b/app/triggers/providers/smtp/Smtp.test.ts @@ -1,9 +1,10 @@ -const { ValidationError } = require('joi'); -const Smtp = require('./Smtp'); -var bunyan = require('bunyan'); +// @ts-nocheck +import { ValidationError } from 'joi'; +import Smtp from './Smtp'; +import bunyan from 'bunyan'; -var loggerBuffer = new bunyan.RingBuffer({ limit: 5 }); -var log = bunyan.createLogger({ +const loggerBuffer = new bunyan.RingBuffer({ limit: 5 }); +const log = bunyan.createLogger({ name: 'Smtp.Tests', streams: [{ stream: loggerBuffer }], }); @@ -39,7 +40,7 @@ const configurationValid = { batchtitle: '${containers.length} updates available', }; -test('validateConfiguration should return validated configuration when valid', () => { +test('validateConfiguration should return validated configuration when valid', async () => { const validatedConfiguration = smtp.validateConfiguration(configurationValid); @@ -131,7 +132,7 @@ test.each([ }, ); -test('validateConfiguration should throw error when invalid', () => { +test('validateConfiguration should throw error when invalid', async () => { const configuration = { host: 'smtp.gmail..com', port: 'xyz', @@ -143,7 +144,7 @@ test('validateConfiguration should throw error when invalid', () => { }).toThrow(ValidationError); }); -test('init should create a mailer transporter with expected configuration when called', () => { +test('init should create a mailer transporter with expected configuration when called', async () => { smtp.configuration = configurationValid; smtp.init(); expect(smtp.transporter.options).toEqual( @@ -161,7 +162,7 @@ test('init should create a mailer transporter with expected configuration when c ); }); -test('maskConfiguration should mask sensitive data', () => { +test('maskConfiguration should mask sensitive data', async () => { smtp.configuration = { host: configurationValid.host, port: configurationValid.port, diff --git a/app/triggers/providers/smtp/Smtp.js b/app/triggers/providers/smtp/Smtp.ts similarity index 97% rename from app/triggers/providers/smtp/Smtp.js rename to app/triggers/providers/smtp/Smtp.ts index 8c0ecc9d..544efc07 100644 --- a/app/triggers/providers/smtp/Smtp.js +++ b/app/triggers/providers/smtp/Smtp.ts @@ -1,5 +1,6 @@ -const nodemailer = require('nodemailer'); -const Trigger = require('../Trigger'); +// @ts-nocheck +import nodemailer from 'nodemailer'; +import Trigger from '../Trigger'; /** * SMTP Trigger implementation @@ -138,4 +139,4 @@ class Smtp extends Trigger { } } -module.exports = Smtp; +export default Smtp; diff --git a/app/triggers/providers/telegram/Telegram.test.js b/app/triggers/providers/telegram/Telegram.test.ts similarity index 93% rename from app/triggers/providers/telegram/Telegram.test.js rename to app/triggers/providers/telegram/Telegram.test.ts index d900aa35..910e6d14 100644 --- a/app/triggers/providers/telegram/Telegram.test.js +++ b/app/triggers/providers/telegram/Telegram.test.ts @@ -1,5 +1,6 @@ -const { ValidationError } = require('joi'); -const Telegram = require('./Telegram'); +// @ts-nocheck +import { ValidationError } from 'joi'; +import Telegram from './Telegram'; const telegram = new Telegram(); @@ -21,24 +22,24 @@ const configurationValid = { messageformat: 'Markdown', }; -beforeEach(() => { +beforeEach(async () => { jest.resetAllMocks(); }); -test('validateConfiguration should return validated configuration when valid', () => { +test('validateConfiguration should return validated configuration when valid', async () => { const validatedConfiguration = telegram.validateConfiguration(configurationValid); expect(validatedConfiguration).toStrictEqual(configurationValid); }); -test('validateConfiguration should throw error when invalid', () => { +test('validateConfiguration should throw error when invalid', async () => { const configuration = {}; expect(() => { telegram.validateConfiguration(configuration); }).toThrowError(ValidationError); }); -test('maskConfiguration should mask sensitive data', () => { +test('maskConfiguration should mask sensitive data', async () => { telegram.configuration = configurationValid; expect(telegram.maskConfiguration()).toEqual({ batchtitle: '${containers.length} updates available', diff --git a/app/triggers/providers/telegram/Telegram.js b/app/triggers/providers/telegram/Telegram.ts similarity index 94% rename from app/triggers/providers/telegram/Telegram.js rename to app/triggers/providers/telegram/Telegram.ts index dea19314..7ae92d88 100644 --- a/app/triggers/providers/telegram/Telegram.js +++ b/app/triggers/providers/telegram/Telegram.ts @@ -1,5 +1,6 @@ -const TelegramBot = require('node-telegram-bot-api'); -const Trigger = require('../Trigger'); +// @ts-nocheck +import TelegramBot from 'node-telegram-bot-api'; +import Trigger from '../Trigger'; /** * Escape special characters. @@ -86,7 +87,7 @@ class Telegram extends Trigger { * @returns {Promise<>} */ async sendMessage(text) { - let txtToSend = text; + const txtToSend = text; return this.telegramBot.sendMessage( this.configuration.chatid, txtToSend, @@ -109,4 +110,4 @@ class Telegram extends Trigger { } } -module.exports = Telegram; +export default Telegram; diff --git a/app/tsconfig.json b/app/tsconfig.json new file mode 100644 index 00000000..83b43d76 --- /dev/null +++ b/app/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "commonjs", + "moduleResolution": "node", + "outDir": "./dist", + "rootDir": "./", + "strict": false, + "allowJs": true, + "checkJs": false, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true + }, + "include": ["./**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/app/watchers/Watcher.ts b/app/watchers/Watcher.ts new file mode 100644 index 00000000..c47f0b69 --- /dev/null +++ b/app/watchers/Watcher.ts @@ -0,0 +1,22 @@ +import Component from '../registry/Component'; +import { Container } from '../model/container'; + +/** + * Watcher abstract class. + */ +abstract class Watcher extends Component { + /** + * Watch main method. + * @returns {Promise} + */ + abstract watch(): Promise; + + /** + * Watch a Container. + * @param container + * @returns {Promise} + */ + abstract watchContainer(container: Container): Promise; +} + +export default Watcher; diff --git a/app/watchers/providers/docker/Docker.test.js b/app/watchers/providers/docker/Docker.test.ts similarity index 75% rename from app/watchers/providers/docker/Docker.test.js rename to app/watchers/providers/docker/Docker.test.ts index 5db236e7..215dd520 100644 --- a/app/watchers/providers/docker/Docker.test.js +++ b/app/watchers/providers/docker/Docker.test.ts @@ -1,8 +1,9 @@ -const Docker = require('./Docker'); -const event = require('../../../event'); -const storeContainer = require('../../../store/container'); -const registry = require('../../../registry'); -const { fullName } = require('../../../model/container'); +// @ts-nocheck +import Docker from './Docker'; +import * as event from '../../../event'; +import * as storeContainer from '../../../store/container'; +import * as registry from '../../../registry'; +import { fullName } from '../../../model/container'; // Mock all dependencies jest.mock('dockerode'); @@ -17,13 +18,13 @@ jest.mock('../../../prometheus/watcher'); jest.mock('parse-docker-image-name'); jest.mock('fs'); -const mockDockerode = require('dockerode'); -const mockCron = require('node-cron'); -const mockDebounce = require('just-debounce'); -const mockFs = require('fs'); -const mockParse = require('parse-docker-image-name'); -const mockTag = require('../../../tag'); -const mockPrometheus = require('../../../prometheus/watcher'); +import mockDockerode from 'dockerode'; +import mockCron from 'node-cron'; +import mockDebounce from 'just-debounce'; +import mockFs from 'fs'; +import mockParse from 'parse-docker-image-name'; +import * as mockTag from '../../../tag'; +import * as mockPrometheus from '../../../prometheus/watcher'; describe('Docker Watcher', () => { let docker; @@ -32,7 +33,7 @@ describe('Docker Watcher', () => { let mockContainer; let mockImage; - beforeEach(() => { + beforeEach(async () => { jest.clearAllMocks(); // Setup dockerode mock @@ -104,27 +105,27 @@ describe('Docker Watcher', () => { }); describe('Configuration', () => { - test('should create instance', () => { + test('should create instance', async () => { expect(docker).toBeDefined(); expect(docker).toBeInstanceOf(Docker); }); - test('should have correct configuration schema', () => { + test('should have correct configuration schema', async () => { const schema = docker.getConfigurationSchema(); expect(schema).toBeDefined(); }); - test('should validate configuration', () => { + test('should validate configuration', async () => { const config = { socket: '/var/run/docker.sock' }; expect(() => docker.validateConfiguration(config)).not.toThrow(); }); - test('should validate configuration with watchall option', () => { + test('should validate configuration with watchall option', async () => { const config = { socket: '/var/run/docker.sock', watchall: true }; expect(() => docker.validateConfiguration(config)).not.toThrow(); }); - test('should validate configuration with custom cron', () => { + test('should validate configuration with custom cron', async () => { const config = { socket: '/var/run/docker.sock', cron: '*/5 * * * *', @@ -731,6 +732,39 @@ describe('Docker Watcher', () => { expect(mockRegistry.getTags).toHaveBeenCalled(); }); + + test('should filter tags with different number of semver parts', async () => { + const container = { + image: { + registry: { name: 'hub' }, + tag: { value: '1.2', semver: true }, + digest: { watch: false }, + }, + }; + const mockRegistry = { + getTags: jest.fn().mockResolvedValue([ + '1.2.1', // 3 parts, should be filtered out + '1.3', // 2 parts, should be kept + '1.1', // 2 parts, should be kept (but lower) + '2', // 1 part, should be filtered out + ]), + }; + registry.getState.mockReturnValue({ + registry: { hub: mockRegistry }, + }); + + // Mock isGreater to return true for 1.3 > 1.2 + mockTag.isGreater.mockImplementation((t1, t2) => { + if (t1 === '1.3' && t2 === '1.2') return true; + return false; + }); + + const mockLogChild = { error: jest.fn(), warn: jest.fn() }; + + const result = await docker.findNewVersion(container, mockLogChild); + + expect(result).toEqual({ tag: '1.3' }); + }); }); describe('Container Details', () => { @@ -777,9 +811,9 @@ describe('Docker Watcher', () => { }); // Mock the validateContainer function to return the container - const { - validate: validateContainer, - } = require('../../../model/container'); + const containerModule = await import('../../../model/container'); + const validateContainer = containerModule.validate; + // @ts-ignore validateContainer.mockReturnValue({ id: '123', name: 'test-container', @@ -792,6 +826,56 @@ describe('Docker Watcher', () => { expect(result).toBeDefined(); }); + test('should handle container with implicit docker hub image (no domain)', async () => { + await docker.register('watcher', 'docker', 'test', {}); + const container = { + Id: '123', + Image: 'prom/prometheus:v3.8.0', + Names: ['/prometheus'], + State: 'running', + Labels: {}, + }; + const imageDetails = { + RepoTags: ['prom/prometheus:v3.8.0'], + Architecture: 'amd64', + Os: 'linux', + Created: '2023-01-01', + Id: 'image123', + }; + mockImage.inspect.mockResolvedValue(imageDetails); + // Mock parse to return undefined domain (simulating parse-docker-image-name behavior) + mockParse.mockReturnValue({ + domain: undefined, + path: 'prom/prometheus', + tag: 'v3.8.0', + }); + + // Mock registry to handle unknown/docker hub + const mockRegistry = { + normalizeImage: jest.fn((img) => img), + getId: () => 'hub', + match: () => true, + }; + registry.getState.mockReturnValue({ + registry: { hub: mockRegistry }, + }); + + const containerModule = await import('../../../model/container'); + const validateContainer = containerModule.validate; + // @ts-ignore + validateContainer.mockReturnValue({ + id: '123', + name: 'prometheus', + image: { architecture: 'amd64' }, + }); + + const result = await docker.addImageDetailsToContainer(container); + + expect(result).toBeDefined(); + // Verify parse was called + expect(mockParse).toHaveBeenCalledWith('prom/prometheus:v3.8.0'); + }); + test('should handle container with SHA256 image', async () => { await docker.register('watcher', 'docker', 'test', {}); const container = { @@ -819,9 +903,9 @@ describe('Docker Watcher', () => { }); // Mock the validateContainer function to return the container - const { - validate: validateContainer, - } = require('../../../model/container'); + const containerModule = await import('../../../model/container'); + const validateContainer = containerModule.validate; + // @ts-ignore validateContainer.mockReturnValue({ id: '123', name: 'test', @@ -884,9 +968,9 @@ describe('Docker Watcher', () => { }); // Mock the validateContainer function to return the container - const { - validate: validateContainer, - } = require('../../../model/container'); + const containerModule = await import('../../../model/container'); + const validateContainer = containerModule.validate; + // @ts-ignore validateContainer.mockReturnValue({ id: '123', name: 'test', @@ -900,7 +984,7 @@ describe('Docker Watcher', () => { }); describe('Container Reporting', () => { - test('should map container to report for new container', () => { + test('should map container to report for new container', async () => { const container = { id: '123', name: 'test' }; const mockLogChild = { debug: jest.fn() }; const mockLog = { child: jest.fn().mockReturnValue(mockLogChild) }; @@ -916,7 +1000,7 @@ describe('Docker Watcher', () => { ); }); - test('should map container to report for existing container', () => { + test('should map container to report for existing container', async () => { const container = { id: '123', name: 'test', @@ -939,7 +1023,7 @@ describe('Docker Watcher', () => { ); }); - test('should not mark as changed when no update available', () => { + test('should not mark as changed when no update available', async () => { const container = { id: '123', name: 'test', @@ -961,61 +1045,30 @@ describe('Docker Watcher', () => { }); describe('Utility Functions', () => { - test('should get tag candidates with include filter', () => { + test('should get tag candidates with include filter', async () => { const tags = ['v1.0.0', 'latest', 'v2.0.0', 'beta']; const filtered = tags.filter((tag) => /^v\d+/.test(tag)); expect(filtered).toEqual(['v1.0.0', 'v2.0.0']); }); - test('should get container name and strip slash', () => { + test('should get container name and strip slash', async () => { const container = { Names: ['/test-container'] }; const name = container.Names[0].replace(/\//, ''); expect(name).toBe('test-container'); }); - test('should get repo digest from image', () => { + test('should get repo digest from image', async () => { const image = { RepoDigests: ['nginx@sha256:abc123def456'] }; const digest = image.RepoDigests[0].split('@')[1]; expect(digest).toBe('sha256:abc123def456'); }); - test('should handle empty repo digests', () => { + test('should handle empty repo digests', async () => { const image = { RepoDigests: [] }; expect(image.RepoDigests.length).toBe(0); }); - test('should determine if container should be watched', () => { - expect('true'.toLowerCase() === 'true').toBe(true); - expect('false'.toLowerCase() === 'true').toBe(false); - expect(undefined !== undefined && undefined !== '').toBe(false); - }); - - test('should determine digest watching for semver', () => { - const isSemver = true; - const watchDigestLabel = 'true'; - let result = false; - if (isSemver) { - if (watchDigestLabel !== undefined && watchDigestLabel !== '') { - result = watchDigestLabel.toLowerCase() === 'true'; - } - } - expect(result).toBe(true); - }); - - test('should determine digest watching for non-semver', () => { - const isSemver = false; - const watchDigestLabel = undefined; - let result = false; - if (!isSemver) { - result = true; - if (watchDigestLabel !== undefined && watchDigestLabel !== '') { - result = watchDigestLabel.toLowerCase() === 'true'; - } - } - expect(result).toBe(true); - }); - - test('should get old containers for pruning', () => { + test('should get old containers for pruning', async () => { const newContainers = [{ id: '1' }, { id: '2' }]; const storeContainers = [{ id: '1' }, { id: '3' }]; @@ -1029,8 +1082,186 @@ describe('Docker Watcher', () => { expect(oldContainers).toEqual([{ id: '3' }]); }); - test('should handle null inputs for old containers', () => { + test('should handle null inputs for old containers', async () => { expect([].filter(() => false)).toEqual([]); }); }); }); + +describe('isDigestToWatch Logic', () => { + let docker; + let mockImage; + + beforeEach(async () => { + // Setup dockerode mock + const mockDockerApi = { + getImage: jest.fn(), + }; + mockDockerode.mockImplementation(() => mockDockerApi); + + mockImage = { + inspect: jest.fn(), + }; + mockDockerApi.getImage.mockReturnValue(mockImage); + + // Setup store mock + storeContainer.getContainer.mockReturnValue(undefined); + storeContainer.insertContainer.mockImplementation((c) => c); + storeContainer.updateContainer.mockImplementation((c) => c); + + // Setup registry mock + registry.getState.mockReturnValue({ registry: {} }); + + // Setup event mock + event.emitContainerReport.mockImplementation(() => {}); + + // Setup prometheus mock + const mockGauge = { set: jest.fn() }; + mockPrometheus.getWatchContainerGauge.mockReturnValue(mockGauge); + + // Setup fullName mock + fullName.mockReturnValue('test_container'); + + docker = new Docker(); + docker.name = 'test'; + docker.dockerApi = mockDockerApi; + docker.ensureLogger(); + }); + + // Helper to setup the environment for addImageDetailsToContainer + const setupTest = async (labels, domain, tag, isSemver = false) => { + const container = { + Id: '123', + Image: `${domain ? domain + '/' : ''}repo/image:${tag}`, + Names: ['/test'], + State: 'running', + Labels: labels || {}, + }; + const imageDetails = { + Id: 'image123', + Architecture: 'amd64', + Os: 'linux', + Created: '2023-01-01', + RepoDigests: ['repo/image@sha256:abc'], + RepoTags: [`${domain ? domain + '/' : ''}repo/image:${tag}`], + }; + mockImage.inspect.mockResolvedValue(imageDetails); + // Mock parse to return appropriate structure + mockParse.mockReturnValue({ + domain: domain, + path: 'repo/image', + tag: tag, + }); + + // Mock semver check + if (isSemver) { + mockTag.parse.mockReturnValue({ major: 1, minor: 0, patch: 0 }); + } else { + mockTag.parse.mockReturnValue(null); + } + + const mockRegistry = { + normalizeImage: jest.fn((img) => img), + getId: () => 'registry', + match: () => true, + }; + registry.getState.mockReturnValue({ + registry: { registry: mockRegistry }, + }); + + const containerModule = await import('../../../model/container'); + const validateContainer = containerModule.validate; + // @ts-ignore + validateContainer.mockImplementation((c) => c); + + return container; + }; + + // Case 1: Explicit Label present + test('should watch digest if label is true (semver)', async () => { + const container = await setupTest( + { 'wud.watch.digest': 'true' }, + 'my.registry', + '1.0.0', + true, + ); + const result = await docker.addImageDetailsToContainer(container); + expect(result.image.digest.watch).toBe(true); + }); + + test('should watch digest if label is true (non-semver)', async () => { + const container = await setupTest( + { 'wud.watch.digest': 'true' }, + 'my.registry', + 'latest', + false, + ); + const result = await docker.addImageDetailsToContainer(container); + expect(result.image.digest.watch).toBe(true); + }); + + test('should NOT watch digest if label is false (semver)', async () => { + const container = await setupTest( + { 'wud.watch.digest': 'false' }, + 'my.registry', + '1.0.0', + true, + ); + const result = await docker.addImageDetailsToContainer(container); + expect(result.image.digest.watch).toBe(false); + }); + + test('should NOT watch digest if label is false (non-semver)', async () => { + const container = await setupTest( + { 'wud.watch.digest': 'false' }, + 'my.registry', + 'latest', + false, + ); + const result = await docker.addImageDetailsToContainer(container); + expect(result.image.digest.watch).toBe(false); + }); + + // Case 2: Semver (no label) -> default false + test('should NOT watch digest by default for semver images', async () => { + const container = await setupTest({}, 'my.registry', '1.0.0', true); + const result = await docker.addImageDetailsToContainer(container); + expect(result.image.digest.watch).toBe(false); + }); + + test('should NOT watch digest by default for semver images (Docker Hub)', async () => { + const container = await setupTest({}, 'docker.io', '1.0.0', true); + const result = await docker.addImageDetailsToContainer(container); + expect(result.image.digest.watch).toBe(false); + }); + + // Case 3: Non-Semver (no label) -> default true, EXCEPT Docker Hub + test('should watch digest by default for non-semver images (Custom Registry)', async () => { + const container = await setupTest({}, 'my.registry', 'latest', false); + const result = await docker.addImageDetailsToContainer(container); + expect(result.image.digest.watch).toBe(true); + }); + + test('should NOT watch digest by default for non-semver images (Docker Hub Explicit)', async () => { + const container = await setupTest({}, 'docker.io', 'latest', false); + const result = await docker.addImageDetailsToContainer(container); + expect(result.image.digest.watch).toBe(false); + }); + + test('should NOT watch digest by default for non-semver images (Docker Hub Registry-1)', async () => { + const container = await setupTest( + {}, + 'registry-1.docker.io', + 'latest', + false, + ); + const result = await docker.addImageDetailsToContainer(container); + expect(result.image.digest.watch).toBe(false); + }); + + test('should NOT watch digest by default for non-semver images (Docker Hub Implicit)', async () => { + const container = await setupTest({}, undefined, 'latest', false); // Implicit + const result = await docker.addImageDetailsToContainer(container); + expect(result.image.digest.watch).toBe(false); + }); +}); diff --git a/app/watchers/providers/docker/Docker.js b/app/watchers/providers/docker/Docker.ts similarity index 86% rename from app/watchers/providers/docker/Docker.js rename to app/watchers/providers/docker/Docker.ts index edf6e029..8e187da1 100644 --- a/app/watchers/providers/docker/Docker.js +++ b/app/watchers/providers/docker/Docker.ts @@ -1,16 +1,18 @@ -const fs = require('fs'); -const Dockerode = require('dockerode'); -const joi = require('joi-cron-expression')(require('joi')); -const cron = require('node-cron'); -const parse = require('parse-docker-image-name'); -const debounce = require('just-debounce'); -const { - parse: parseSemver, - isGreater: isGreaterSemver, - transform: transformTag, -} = require('../../../tag'); -const event = require('../../../event'); -const { +import fs from 'fs'; +import Dockerode from 'dockerode'; +import Joi from 'joi'; +import JoiCronExpression from 'joi-cron-expression'; +const joi = JoiCronExpression(Joi); +import cron from 'node-cron'; +import parse from 'parse-docker-image-name'; +import debounce from 'just-debounce'; +import { + parse as parseSemver, + isGreater as isGreaterSemver, + transform as transformTag, +} from '../../../tag'; +import * as event from '../../../event'; +import { wudWatch, wudTagInclude, wudTagExclude, @@ -21,16 +23,35 @@ const { wudDisplayIcon, wudTriggerInclude, wudTriggerExclude, -} = require('./label'); -const storeContainer = require('../../../store/container'); -const log = require('../../../log'); -const Component = require('../../../registry/Component'); -const { - validate: validateContainer, +} from './label'; +import * as storeContainer from '../../../store/container'; +import log from '../../../log'; +import { + validate as validateContainer, fullName, -} = require('../../../model/container'); -const registry = require('../../../registry'); -const { getWatchContainerGauge } = require('../../../prometheus/watcher'); + Container, + ContainerImage, +} from '../../../model/container'; +import * as registry from '../../../registry'; +import { getWatchContainerGauge } from '../../../prometheus/watcher'; +import Watcher from '../../Watcher'; +import { ComponentConfiguration } from '../../../registry/Component'; + +export interface DockerWatcherConfiguration extends ComponentConfiguration { + socket: string; + host?: string; + port: number; + cafile?: string; + certfile?: string; + keyfile?: string; + cron: string; + jitter: number; + watchbydefault: boolean; + watchall: boolean; + watchdigest?: any; + watchevents: boolean; + watchatstart: boolean; +} // The delay before starting the watcher when the app is started const START_WATCHER_DELAY_MS = 1000; @@ -52,7 +73,11 @@ function getRegistries() { * @param tags * @returns {*} */ -function getTagCandidates(container, tags, logContainer) { +function getTagCandidates( + container: Container, + tags: string[], + logContainer: any, +) { let filteredTags = tags; // Match include tag regex @@ -165,7 +190,7 @@ function getTagCandidates(container, tags, logContainer) { return filteredTags; } -function normalizeContainer(container) { +function normalizeContainer(container: Container) { const containerWithNormalizedImage = container; const registryProvider = Object.values(getRegistries()).find((provider) => provider.match(container.image), @@ -187,7 +212,7 @@ function normalizeContainer(container) { * Get the Docker Registry by name. * @param registryName */ -function getRegistry(registryName) { +function getRegistry(registryName: string) { const registryToReturn = getRegistries()[registryName]; if (!registryToReturn) { throw new Error(`Unsupported Registry ${registryName}`); @@ -201,7 +226,10 @@ function getRegistry(registryName) { * @param containersFromTheStore * @returns {*[]|*} */ -function getOldContainers(newContainers, containersFromTheStore) { +function getOldContainers( + newContainers: Container[], + containersFromTheStore: Container[], +) { if (!containersFromTheStore || !newContainers) { return []; } @@ -218,7 +246,10 @@ function getOldContainers(newContainers, containersFromTheStore) { * @param newContainers * @param containersFromTheStore */ -function pruneOldContainers(newContainers, containersFromTheStore) { +function pruneOldContainers( + newContainers: Container[], + containersFromTheStore: Container[], +) { const containersToRemove = getOldContainers( newContainers, containersFromTheStore, @@ -228,8 +259,8 @@ function pruneOldContainers(newContainers, containersFromTheStore) { }); } -function getContainerName(container) { - let containerName; +function getContainerName(container: any) { + let containerName = ''; const names = container.Names; if (names && names.length > 0) { [containerName] = names; @@ -244,7 +275,7 @@ function getContainerName(container) { * @param containerImage * @returns {*} digest */ -function getRepoDigest(containerImage) { +function getRepoDigest(containerImage: any) { if ( !containerImage.RepoDigests || containerImage.RepoDigests.length === 0 @@ -262,7 +293,10 @@ function getRepoDigest(containerImage) { * @param watchByDefault true if containers must be watched by default * @returns {boolean} */ -function isContainerToWatch(wudWatchLabelValue, watchByDefault) { +function isContainerToWatch( + wudWatchLabelValue: string, + watchByDefault: boolean, +) { return wudWatchLabelValue !== undefined && wudWatchLabelValue !== '' ? wudWatchLabelValue.toLowerCase() === 'true' : watchByDefault; @@ -274,28 +308,50 @@ function isContainerToWatch(wudWatchLabelValue, watchByDefault) { * @param {object} parsedImage - object containing at least `domain` property * @returns {boolean} */ -function isDigestToWatch(wudWatchDigestLabelValue, parsedImage) { - let result = true; +function isDigestToWatch( + wudWatchDigestLabelValue: string, + parsedImage: any, + isSemver: boolean, +) { + const domain = parsedImage.domain; + const isDockerHub = + !domain || + domain === '' || + domain === 'docker.io' || + domain.endsWith('.docker.io'); if ( - parsedImage.domain === 'docker.io' || - parsedImage.domain === 'registry-1.docker.io' || - parsedImage.domain === '' + wudWatchDigestLabelValue !== undefined && + wudWatchDigestLabelValue !== '' ) { - result = false; + const shouldWatch = wudWatchDigestLabelValue.toLowerCase() === 'true'; + if (shouldWatch && isDockerHub) { + log.warn( + `Watching digest for image ${parsedImage.path} with domain ${domain} may result in throttled requests`, + ); + } + return shouldWatch; } - if (wudWatchDigestLabelValue) { - result = wudWatchDigestLabelValue.toLowerCase() === 'true'; + if (isSemver) { + return false; } - return result; + return !isDockerHub; } /** * Docker Watcher Component. */ -class Docker extends Component { +class Docker extends Watcher { + public configuration: DockerWatcherConfiguration = + {} as DockerWatcherConfiguration; + public dockerApi: Dockerode; + public watchCron: any; + public watchCronTimeout: any; + public watchCronDebounced: any; + public listenDockerEventsTimeout: any; + ensureLogger() { if (!this.log) { try { @@ -305,9 +361,13 @@ class Docker extends Component { } catch (error) { // Fallback to silent logger if log module fails this.log = { + // @ts-ignore Unused implementation info: () => {}, + // @ts-ignore Unused implementation warn: () => {}, + // @ts-ignore Unused implementation error: () => {}, + // @ts-ignore Unused implementation debug: () => {}, child: () => this.log, }; @@ -336,7 +396,7 @@ class Docker extends Component { /** * Init the Watcher. */ - init() { + async init() { this.ensureLogger(); this.initWatcher(); if (this.configuration.watchdigest !== undefined) { @@ -377,7 +437,7 @@ class Docker extends Component { } initWatcher() { - const options = {}; + const options: Dockerode.DockerOptions = {}; if (this.configuration.host) { options.host = this.configuration.host; options.port = this.configuration.port; @@ -424,7 +484,7 @@ class Docker extends Component { return; } this.log.info('Listening to docker events'); - const options = { + const options: Dockerode.GetEventsOptions = { filters: { type: ['container'], event: [ @@ -448,16 +508,7 @@ class Docker extends Component { this.log.debug(err); } } else { - let chunks = []; - const collectChunks = (chunk) => { - chunks.push(chunk); - if (chunk.toString().endsWith('\n')) { - const dockerEventChunk = Buffer.concat(chunks); - this.onDockerEvent(dockerEventChunk); - chunks = []; - } - }; - stream.on('data', collectChunks); + stream.on('data', (chunk: any) => this.onDockerEvent(chunk)); } }); } @@ -467,7 +518,7 @@ class Docker extends Component { * @param dockerEventChunk * @return {Promise} */ - async onDockerEvent(dockerEventChunk) { + async onDockerEvent(dockerEventChunk: any) { this.ensureLogger(); let dockerEvent; try { @@ -506,7 +557,7 @@ class Docker extends Component { ); } } - } catch (e) { + } catch (e: any) { this.log.debug( `Unable to get container details for container id=[${containerId}] (${e.message})`, ); @@ -555,7 +606,7 @@ class Docker extends Component { */ async watch() { this.ensureLogger(); - let containers = []; + let containers: Container[] = []; // Dispatch event to notify start watching event.emitWatcherStart(this); @@ -563,7 +614,7 @@ class Docker extends Component { // List images to watch try { containers = await this.getContainers(); - } catch (e) { + } catch (e: any) { this.log.warn( `Error when trying to get the list of the containers to watch (${e.message})`, ); @@ -574,7 +625,7 @@ class Docker extends Component { ); event.emitContainerReports(containerReports); return containerReports; - } catch (e) { + } catch (e: any) { this.log.warn( `Error when processing some containers (${e.message})`, ); @@ -590,7 +641,7 @@ class Docker extends Component { * @param container * @returns {Promise<*>} */ - async watchContainer(container) { + async watchContainer(container: Container) { this.ensureLogger(); // Child logger for the container to process const logContainer = this.log.child({ container: fullName(container) }); @@ -606,7 +657,7 @@ class Docker extends Component { container, logContainer, ); - } catch (e) { + } catch (e: any) { logContainer.warn(`Error when processing (${e.message})`); logContainer.debug(e); containerWithResult.error = { @@ -624,9 +675,9 @@ class Docker extends Component { * Get all containers to watch. * @returns {Promise} */ - async getContainers() { + async getContainers(): Promise { this.ensureLogger(); - const listContainersOptions = {}; + const listContainersOptions: Dockerode.ContainerListOptions = {}; if (this.configuration.watchall) { listContainersOptions.all = true; } @@ -635,13 +686,13 @@ class Docker extends Component { ); // Filter on containers to watch - const filteredContainers = containers.filter((container) => + const filteredContainers = containers.filter((container: any) => isContainerToWatch( container.Labels[wudWatch], this.configuration.watchbydefault, ), ); - const containerPromises = filteredContainers.map((container) => + const containerPromises = filteredContainers.map((container: any) => this.addImageDetailsToContainer( container, container.Labels[wudTagInclude], @@ -674,7 +725,7 @@ class Docker extends Component { watcher: this.name, }); pruneOldContainers(containersToReturn, containersFromTheStore); - } catch (e) { + } catch (e: any) { this.log.warn( `Error when trying to prune the old containers (${e.message})`, ); @@ -694,9 +745,9 @@ class Docker extends Component { * Find new version for a Container. */ - async findNewVersion(container, logContainer) { + async findNewVersion(container: Container, logContainer: any) { const registryProvider = getRegistry(container.image.registry.name); - const result = { tag: container.image.tag.value }; + const result: any = { tag: container.image.tag.value }; if (!registryProvider) { logContainer.error( `Unsupported registry (${container.image.registry.name})`, @@ -775,15 +826,15 @@ class Docker extends Component { * @returns {Promise} */ async addImageDetailsToContainer( - container, - includeTags, - excludeTags, - transformTags, - linkTemplate, - displayName, - displayIcon, - triggerInclude, - triggerExclude, + container: any, + includeTags: string, + excludeTags: string, + transformTags: string, + linkTemplate: string, + displayName: string, + displayIcon: string, + triggerInclude: string, + triggerExclude: string, ) { const containerId = container.Id; @@ -841,6 +892,7 @@ class Docker extends Component { const watchDigest = isDigestToWatch( container.Labels[wudWatchDigest], parsedImage, + isSemver, ); if (!isSemver && !watchDigest) { this.ensureLogger(); @@ -864,6 +916,7 @@ class Docker extends Component { image: { id: imageId, registry: { + name: 'unknown', // Will be overwritten by normalizeContainer url: parsedImage.domain, }, name: parsedImage.path, @@ -884,7 +937,9 @@ class Docker extends Component { result: { tag: tagName, }, - }); + updateAvailable: false, + updateKind: { kind: 'unknown' }, + } as Container); } /** @@ -892,7 +947,7 @@ class Docker extends Component { * @param containerWithResult * @return {*} */ - mapContainerToContainerReport(containerWithResult) { + mapContainerToContainerReport(containerWithResult: Container) { this.ensureLogger(); const logContainer = this.log.child({ container: fullName(containerWithResult), @@ -926,4 +981,4 @@ class Docker extends Component { } } -module.exports = Docker; +export default Docker; diff --git a/app/watchers/providers/docker/label.js b/app/watchers/providers/docker/label.js deleted file mode 100644 index 77745450..00000000 --- a/app/watchers/providers/docker/label.js +++ /dev/null @@ -1,54 +0,0 @@ -/** - * WUD supported Docker labels. - */ -module.exports = { - /** - * Should the container be tracked? (true | false). - */ - wudWatch: 'wud.watch', - - /** - * Optional regex indicating what tags to consider. - */ - wudTagInclude: 'wud.tag.include', - - /** - * Optional regex indicating what tags to not consider. - */ - wudTagExclude: 'wud.tag.exclude', - - /** - * Optional transform function to apply to the tag. - */ - wudTagTransform: 'wud.tag.transform', - - /** - * Should container digest be tracked? (true | false). - */ - wudWatchDigest: 'wud.watch.digest', - - /** - * Optional templated string pointing to a browsable link. - */ - wudLinkTemplate: 'wud.link.template', - - /** - * Optional friendly name to display. - */ - wudDisplayName: 'wud.display.name', - - /** - * Optional friendly icon to display. - */ - wudDisplayIcon: 'wud.display.icon', - - /** - * Optional list of triggers to include - */ - wudTriggerInclude: 'wud.trigger.include', - - /** - * Optional list of triggers to exclude - */ - wudTriggerExclude: 'wud.trigger.exclude', -}; diff --git a/app/watchers/providers/docker/label.ts b/app/watchers/providers/docker/label.ts new file mode 100644 index 00000000..e2c2754d --- /dev/null +++ b/app/watchers/providers/docker/label.ts @@ -0,0 +1,54 @@ +// @ts-nocheck +/** + * WUD supported Docker labels. + */ + +/** + * Should the container be tracked? (true | false). + */ +export const wudWatch = 'wud.watch'; + +/** + * Optional regex indicating what tags to consider. + */ +export const wudTagInclude = 'wud.tag.include'; + +/** + * Optional regex indicating what tags to not consider. + */ +export const wudTagExclude = 'wud.tag.exclude'; + +/** + * Optional transform function to apply to the tag. + */ +export const wudTagTransform = 'wud.tag.transform'; + +/** + * Should container digest be tracked? (true | false). + */ +export const wudWatchDigest = 'wud.watch.digest'; + +/** + * Optional templated string pointing to a browsable link. + */ +export const wudLinkTemplate = 'wud.link.template'; + +/** + * Optional friendly name to display. + */ +export const wudDisplayName = 'wud.display.name'; + +/** + * Optional friendly icon to display. + */ +export const wudDisplayIcon = 'wud.display.icon'; + +/** + * Optional list of triggers to include + */ +export const wudTriggerInclude = 'wud.trigger.include'; + +/** + * Optional list of triggers to exclude + */ +export const wudTriggerExclude = 'wud.trigger.exclude'; diff --git a/e2e/features/api-container.feature b/e2e/features/api-container.feature index f612d0e7..1b73192c 100644 --- a/e2e/features/api-container.feature +++ b/e2e/features/api-container.feature @@ -22,29 +22,29 @@ Feature: WUD Container API Exposure Examples: | index | registry | containerName | registryUrl | imageName | tag | resultTag | updateAvailable | testCase | # Containers in alphabetical order by name - | 0 | ecr.private | ecr_sub_sub_test | https://229211676173.dkr.ecr.eu-west-1.amazonaws.com/v2 | sub/sub/test | 1.0.0 | 2.0.0 | true | ECR semver major update | - | 1 | ghcr.private | ghcr_radarr | https://ghcr.io/v2 | linuxserver/radarr | 5.14.0.9383-ls245 | 6.0.4.10291-ls292 | true | GHCR complex semver update | - | 2 | gitlab.private | gitlab_test | https://registry.gitlab.com/v2 | manfred-martin/docker-registry-test | 1.0.0 | 2.0.0 | true | GitLab semver major update | - | 3 | hub.public | hub_homeassistant_202161 | https://registry-1.docker.io/v2 | homeassistant/home-assistant | 2021.6.1 | 2026.2.2 | true | Hub date-based versioning | + # | 0 | ecr.private | ecr_sub_sub_test | https://229211676173.dkr.ecr.eu-west-1.amazonaws.com/v2 | sub/sub/test | 1.0.0 | 2.0.0 | true | ECR semver major update | + | 1 | ghcr.private | ghcr_radarr | https://ghcr.io/v2 | linuxserver/radarr | 5.14.0.9383-ls245 | 6.0.4.10291-ls290 | true | GHCR complex semver update | + | 2 | gitlab.private | gitlab_test | https://registry.gitlab.com/v2 | gitlab-org/gitlab-runner | v16.0.0 | v16.1.0 | true | GitLab semver update | + | 3 | hub.public | hub_homeassistant_202161 | https://registry-1.docker.io/v2 | homeassistant/home-assistant | 2021.6.1 | 2026.1.2 | true | Hub date-based versioning | | 4 | hub.public | hub_homeassistant_latest | https://registry-1.docker.io/v2 | homeassistant/home-assistant | latest | latest | false | Hub latest tag no update | - | 5 | hub.public | hub_nginx_120 | https://registry-1.docker.io/v2 | library/nginx | 1.20-alpine | 1.29-alpine | true | Hub alpine minor update | + | 5 | hub.public | hub_nginx_120 | https://registry-1.docker.io/v2 | library/nginx | 1.20-alpine | 1.29-alpine | true | Hub alpine minor update | | 6 | hub.public | hub_nginx_latest | https://registry-1.docker.io/v2 | library/nginx | latest | latest | true | Hub latest tag digest update| - | 7 | hub.public | hub_traefik_245 | https://registry-1.docker.io/v2 | library/traefik | 2.4.5 | 3.6.8 | true | Hub semver major update | - | 8 | lscr.private | lscr_radarr | https://lscr.io/v2 | linuxserver/radarr | 5.14.0.9383-ls245 | 6.0.4.10291-ls292 | true | LSCR complex semver update | + | 7 | hub.public | hub_traefik_245 | https://registry-1.docker.io/v2 | library/traefik | 2.4.5 | 3.6.7 | true | Hub semver major update | + | 8 | lscr.private | lscr_radarr | https://lscr.io/v2 | linuxserver/radarr | 5.14.0.9383-ls245 | 6.0.4.10291-ls290 | true | LSCR complex semver update | | 9 | quay.public | quay_prometheus | https://quay.io/v2 | prometheus/prometheus | v2.52.0 | v3.9.1 | true | Quay semver major update | # Test detailed container inspection (semver) Scenario: WUD must provide detailed container information for semver containers Given I GET /api/containers - And I store the value of body path $[0].id as containerId in scenario scope + And I store the value of body path $[2].id as containerId in scenario scope When I GET /api/containers/`containerId` Then response code should be 200 And response body should be valid json And response body path $.watcher should be local - And response body path $.name should be ecr_sub_sub_test - And response body path $.image.registry.name should be ecr.private + And response body path $.name should be gitlab_test + And response body path $.image.registry.name should be gitlab.private And response body path $.image.tag.semver should be true - And response body path $.result.tag should be 2.0.0 + And response body path $.result.tag should be v16.1.0 And response body path $.updateAvailable should be true # Test detailed container inspection (digest) @@ -58,7 +58,7 @@ Feature: WUD Container API Exposure And response body path $.name should be hub_nginx_latest And response body path $.image.tag.semver should be false And response body path $.image.digest.value should be sha256:f94d6dd9b5761f33a21bb92848a1f70ea11a1c15f3a142c19a44ea3a4c545a4d - And response body path $.result.digest should be sha256:514a9c2814250e61396ef4d6125ece1a8fbb3b0964a2ab441e9f7acf0b66b8b5 + And response body path $.result.digest should be sha256:617fef3c6adb90788d06f7ebe634fe990a6cd1e63c815facaf5fcaf3fa1ea003 And response body path $.updateAvailable should be true # Test link functionality @@ -69,13 +69,13 @@ Feature: WUD Container API Exposure Then response code should be 200 And response body should be valid json And response body path $.link should be https://github.com/home-assistant/core/releases/tag/2021.6.1 - And response body path $.result.link should be https://github.com/home-assistant/core/releases/tag/2026.2.2 + And response body path $.result.link should be https://github.com/home-assistant/core/releases/tag/2026.1.2 # Test watch trigger functionality Scenario: WUD must allow triggering container watch Given I GET /api/containers - And I store the value of body path $[0].id as containerId in scenario scope + And I store the value of body path $[2].id as containerId in scenario scope When I POST to /api/containers/`containerId`/watch Then response code should be 200 And response body should be valid json - And response body path $.result.tag should be 2.0.0 \ No newline at end of file + And response body path $.result.tag should be v16.1.0 \ No newline at end of file diff --git a/e2e/features/prometheus.feature b/e2e/features/prometheus.feature index 5c12ec90..9e72a3e7 100644 --- a/e2e/features/prometheus.feature +++ b/e2e/features/prometheus.feature @@ -22,13 +22,13 @@ Feature: Prometheus exposure And response body should contain update_available="" Examples: | containerName | registry | registryUrl | imageName | tag | resultTag | updateAvailable | - | ecr_sub_sub_test | ecr.private | https://229211676173.dkr.ecr.eu-west-1.amazonaws.com/v2 | sub/sub/test | 1.0.0 | 2.0.0 | true | - | ghcr_radarr | ghcr.private | https://ghcr.io/v2 | linuxserver/radarr | 5.14.0.9383-ls245 | 6.0.4.10291-ls292 | true | + # | ecr_sub_sub_test | ecr.private | https://229211676173.dkr.ecr.eu-west-1.amazonaws.com/v2 | sub/sub/test | 1.0.0 | 2.0.0 | true | + | ghcr_radarr | ghcr.private | https://ghcr.io/v2 | linuxserver/radarr | 5.14.0.9383-ls245 | 6.0.4.10291-ls290 | false | - | hub_homeassistant_202161 | hub.public | https://registry-1.docker.io/v2 | homeassistant/home-assistant | 2021.6.1 | 2026.2.2 | true | + | hub_homeassistant_202161 | hub.public | https://registry-1.docker.io/v2 | homeassistant/home-assistant | 2021.6.1 | 2026.1.2 | false | | hub_homeassistant_latest | hub.public | https://registry-1.docker.io/v2 | homeassistant/home-assistant | latest | latest | false | - | hub_nginx_120 | hub.public | https://registry-1.docker.io/v2 | library/nginx | 1.20-alpine | 1.29-alpine | true | + | hub_nginx_120 | hub.public | https://registry-1.docker.io/v2 | library/nginx | 1.20-alpine | 1.29-alpine | false | | hub_nginx_latest | hub.public | https://registry-1.docker.io/v2 | library/nginx | latest | latest | true | - | hub_traefik_245 | hub.public | https://registry-1.docker.io/v2 | library/traefik | 2.4.5 | 3.6.8 | true | - | lscr_radarr | lscr.private | https://lscr.io/v2 | linuxserver/radarr | 5.14.0.9383-ls245 | 6.0.4.10291-ls292 | true | + | hub_traefik_245 | hub.public | https://registry-1.docker.io/v2 | library/traefik | 2.4.5 | 3.6.7 | false | + | lscr_radarr | lscr.private | https://lscr.io/v2 | linuxserver/radarr | 5.14.0.9383-ls245 | 6.0.4.10291-ls290 | true | | quay_prometheus | quay.public | https://quay.io/v2 | prometheus/prometheus | v2.52.0 | v3.9.1 | true | diff --git a/e2e/features/support/init.js b/e2e/features/support/init.js index 57d566d1..11e49983 100644 --- a/e2e/features/support/init.js +++ b/e2e/features/support/init.js @@ -1,7 +1,9 @@ const apickli = require('apickli'); -const { Before } = require('@cucumber/cucumber'); +const { Before, setDefaultTimeout } = require('@cucumber/cucumber'); const configuration = require('../../config'); +setDefaultTimeout(20 * 1000); + Before(function initApickli() { this.apickli = new apickli.Apickli(configuration.protocol, `${configuration.host}:${configuration.port}`); this.apickli.addHttpBasicAuthorizationHeader(configuration.username, configuration.password); diff --git a/e2e/package-lock.json b/e2e/package-lock.json index d0bd9113..359a7bf4 100644 --- a/e2e/package-lock.json +++ b/e2e/package-lock.json @@ -13,6 +13,7 @@ "apickli": "3.0.3" }, "devDependencies": { + "@dotenvx/dotenvx": "^1.51.4", "eslint": "8.57.0", "eslint-config-airbnb-base": "15.0.0", "eslint-plugin-cucumber": "2.0.0", @@ -249,6 +250,81 @@ "integrity": "sha512-0oj5KTzf2DsR3DhL3hYeI9fP3nyKzs7TQdpl54uJelJ3W3Hlyyet2Hib+8LK7kNnqJsXENnJg9zahRYyrtvNEg==", "license": "MIT" }, + "node_modules/@dotenvx/dotenvx": { + "version": "1.51.4", + "resolved": "https://registry.npmjs.org/@dotenvx/dotenvx/-/dotenvx-1.51.4.tgz", + "integrity": "sha512-AoziS8lRQ3ew/lY5J4JSlzYSN9Fo0oiyMBY37L3Bwq4mOQJT5GSrdZYLFPt6pH1LApDI3ZJceNyx+rHRACZSeQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "commander": "^11.1.0", + "dotenv": "^17.2.1", + "eciesjs": "^0.4.10", + "execa": "^5.1.1", + "fdir": "^6.2.0", + "ignore": "^5.3.0", + "object-treeify": "1.1.33", + "picomatch": "^4.0.2", + "which": "^4.0.0" + }, + "bin": { + "dotenvx": "src/cli/dotenvx.js" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/@dotenvx/dotenvx/node_modules/commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/@dotenvx/dotenvx/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/@dotenvx/dotenvx/node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, + "node_modules/@ecies/ciphers": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@ecies/ciphers/-/ciphers-0.2.5.tgz", + "integrity": "sha512-GalEZH4JgOMHYYcYmVqnFirFsjZHeoGMDt9IxEnM9F7GRUUyUksJ7Ou53L83WHJq3RWKD3AcBpo0iQh0oMpf8A==", + "dev": true, + "license": "MIT", + "engines": { + "bun": ">=1", + "deno": ">=2", + "node": ">=16" + }, + "peerDependencies": { + "@noble/ciphers": "^1.0.0" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", @@ -441,6 +517,48 @@ "jsep": "^0.4.0||^1.0.0" } }, + "node_modules/@noble/ciphers": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz", + "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/curves": { + "version": "1.9.7", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.7.tgz", + "integrity": "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.8.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1427,6 +1545,19 @@ "node": ">=6.0.0" } }, + "node_modules/dotenv": { + "version": "17.2.3", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", + "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -1457,6 +1588,24 @@ "safer-buffer": "^2.1.0" } }, + "node_modules/eciesjs": { + "version": "0.4.16", + "resolved": "https://registry.npmjs.org/eciesjs/-/eciesjs-0.4.16.tgz", + "integrity": "sha512-dS5cbA9rA2VR4Ybuvhg6jvdmp46ubLn3E+px8cG/35aEDNclrqoCjg6mt0HYZ/M+OoESS3jSkCrqk1kWAEhWAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ecies/ciphers": "^0.2.4", + "@noble/ciphers": "^1.3.0", + "@noble/curves": "^1.9.7", + "@noble/hashes": "^1.8.0" + }, + "engines": { + "bun": ">=1", + "deno": ">=2", + "node": ">=16" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -1979,6 +2128,37 @@ "node": ">= 0.6" } }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/execa/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -2029,6 +2209,24 @@ "reusify": "^1.0.4" } }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, "node_modules/figures": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", @@ -2259,6 +2457,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-symbol-description": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", @@ -2597,6 +2808,16 @@ "npm": ">=1.3.7" } }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, "node_modules/iconv-lite": { "version": "0.4.19", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", @@ -3739,6 +3960,13 @@ "node": ">= 0.6" } }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, "node_modules/methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", @@ -3781,6 +4009,16 @@ "node": ">= 0.6" } }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -3920,6 +4158,19 @@ "semver": "bin/semver" } }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/oauth-sign": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", @@ -3975,6 +4226,16 @@ "node": ">= 0.4" } }, + "node_modules/object-treeify": { + "version": "1.1.33", + "resolved": "https://registry.npmjs.org/object-treeify/-/object-treeify-1.1.33.tgz", + "integrity": "sha512-EFVjAYfzWqWsBMRHPMAXLCDIJnpMhdWAqR7xG6M6a2cs6PMFpl/+Z20w9zDW4vkxOFfddegBKq9Rehd0bxWE7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, "node_modules/object.assign": { "version": "4.1.7", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", @@ -4084,6 +4345,22 @@ "wrappy": "1" } }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -4400,6 +4677,19 @@ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "license": "ISC" }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/possible-typed-array-names": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", @@ -5572,6 +5862,16 @@ "node": ">=4" } }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", diff --git a/e2e/package.json b/e2e/package.json index 381b6d7e..a3a75f33 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -6,9 +6,9 @@ "scripts": { "lint": "eslint '**/*.js'", "cucumber": "cucumber-js **/*.feature", - "test:local": "../scripts/run-e2e-tests.sh", - "test:setup": "../scripts/setup-test-containers.sh", - "test:start-wud": "../scripts/start-wud.sh", + "test:local": "dotenvx run -- ../scripts/run-e2e-tests.sh", + "test:setup": "dotenvx run -- ../scripts/setup-test-containers.sh", + "test:start-wud": "dotenvx run -- ../scripts/start-wud.sh", "test:cleanup": "../scripts/cleanup-test-containers.sh" }, "author": "fmartinou", @@ -19,6 +19,7 @@ "apickli": "3.0.3" }, "devDependencies": { + "@dotenvx/dotenvx": "^1.51.4", "eslint": "8.57.0", "eslint-config-airbnb-base": "15.0.0", "eslint-plugin-cucumber": "2.0.0", diff --git a/scripts/cleanup-test-containers.sh b/scripts/cleanup-test-containers.sh index 4901dc9d..b5bef630 100755 --- a/scripts/cleanup-test-containers.sh +++ b/scripts/cleanup-test-containers.sh @@ -3,6 +3,6 @@ echo "๐Ÿงน Cleaning up test containers..." # Stop and remove test containers -docker rm -f ecr_sub_sub_test ghcr_radarr gitlab_test hub_homeassistant_202161 hub_homeassistant_latest hub_nginx_120 hub_nginx_latest hub_traefik_245 lscr_radarr quay_prometheus wud 2>/dev/null || true +docker rm -f ecr_sub_sub_test ghcr_radarr gitlab_test hub_homeassistant_202161 hub_homeassistant_latest hub_nginx_120 hub_nginx_latest hub_traefik_245 lscr_radarr trueforge_radarr quay_prometheus wud 2>/dev/null || true echo "โœ… Test containers cleaned up" \ No newline at end of file diff --git a/scripts/run-e2e-tests.sh b/scripts/run-e2e-tests.sh index f07a3e77..be3583b2 100755 --- a/scripts/run-e2e-tests.sh +++ b/scripts/run-e2e-tests.sh @@ -2,19 +2,21 @@ set -e +SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) + echo "๐Ÿงช Running complete e2e test suite..." # Cleanup any existing containers -./scripts/cleanup-test-containers.sh +"$SCRIPT_DIR/cleanup-test-containers.sh" # Setup test containers -./scripts/setup-test-containers.sh +"$SCRIPT_DIR/setup-test-containers.sh" # Start WUD -./scripts/start-wud.sh +"$SCRIPT_DIR/start-wud.sh" # Run e2e tests echo "๐Ÿƒ Running cucumber tests..." -(cd e2e && npm run cucumber) +(cd "$SCRIPT_DIR/../e2e" && npm run cucumber) echo "โœ… E2E tests completed!" \ No newline at end of file diff --git a/scripts/setup-test-containers.sh b/scripts/setup-test-containers.sh index cc27147b..f10d7b33 100755 --- a/scripts/setup-test-containers.sh +++ b/scripts/setup-test-containers.sh @@ -38,26 +38,26 @@ echo "๐Ÿš€ Starting test containers..." docker run -d --name ecr_sub_sub_test --label 'wud.watch=true' 229211676173.dkr.ecr.eu-west-1.amazonaws.com/sub/sub/test:1.0.0 # GHCR -docker run -d --name ghcr_radarr --label 'wud.watch=true' --label 'wud.tag.include=^\\d+\\.\\d+\\.\\d+\\.\\d+-ls\\d+$' ghcr.io/linuxserver/radarr:5.14.0.9383-ls245 +docker run -d --name ghcr_radarr --label 'wud.watch=true' --label 'wud.tag.include=^\d+\.\d+\.\d+\.\d+-ls\d+$' ghcr.io/linuxserver/radarr:5.14.0.9383-ls245 # GITLAB -docker run -d --name gitlab_test --label 'wud.watch=true' --label 'wud.tag.include=^\\d+\\.\\d+\\.\\d+$' registry.gitlab.com/manfred-martin/docker-registry-test:1.0.0 +docker run -d --name gitlab_test --label 'wud.watch=true' --label 'wud.tag.include=^v16\.[01]\.0$' registry.gitlab.com/gitlab-org/gitlab-runner:v16.0.0 # HUB -docker run -d --name hub_homeassistant_202161 --label 'wud.watch=true' --label 'wud.tag.include=^\\d+\\.\\d+.\\d+$' --label 'wud.link.template=https://github.com/home-assistant/core/releases/tag/${major}.${minor}.${patch}' homeassistant/home-assistant:2021.6.1 +docker run -d --name hub_homeassistant_202161 --label 'wud.watch=true' --label 'wud.tag.include=^\d+\.\d+.\d+$' --label 'wud.link.template=https://github.com/home-assistant/core/releases/tag/${major}.${minor}.${patch}' homeassistant/home-assistant:2021.6.1 docker run -d --name hub_homeassistant_latest --label 'wud.watch=true' --label 'wud.watch.digest=true' --label 'wud.tag.include=^latest$' homeassistant/home-assistant -docker run -d --name hub_nginx_120 --label 'wud.watch=true' --label 'wud.tag.include=^\\d+\\.\\d+-alpine$' nginx:1.20-alpine +docker run -d --name hub_nginx_120 --label 'wud.watch=true' --label 'wud.tag.include=^\d+\.\d+-alpine$' nginx:1.20-alpine docker run -d --name hub_nginx_latest --label 'wud.watch=true' --label 'wud.watch.digest=true' --label 'wud.tag.include=^latest$' nginx -docker run -d --name hub_traefik_245 --label 'wud.watch=true' --label 'wud.tag.include=^\\d+\\.\\d+.\\d+$' traefik:2.4.5 +docker run -d --name hub_traefik_245 --label 'wud.watch=true' --label 'wud.tag.include=^\d+\.\d+.\d+$' traefik:2.4.5 # LSCR -docker run -d --name lscr_radarr --label 'wud.watch=true' --label 'wud.tag.include=^\\d+\\.\\d+\\.\\d+\\.\\d+-ls\\d+$' lscr.io/linuxserver/radarr:5.14.0.9383-ls245 +docker run -d --name lscr_radarr --label 'wud.watch=true' --label 'wud.tag.include=^\d+\.\d+\.\d+\.\d+-ls\d+$' lscr.io/linuxserver/radarr:5.14.0.9383-ls245 # TrueForge -docker run -d --name trueforge_radarr --label 'wud.watch=true' --label 'wud.tag.include=^v\\d+\\.\\d+\\.\\d+$' oci.trueforge.org/containerforge/radarr:6.0.4 +docker run -d --name trueforge_radarr --label 'wud.watch=true' --label 'wud.tag.include=^v\d+\.\d+\.\d+$' --memory 512m --tmpfs /config oci.trueforge.org/containerforge/radarr:6.0.4 # QUAY -docker run -d --name quay_prometheus --label 'wud.watch=true' --label 'wud.tag.include=^v\\d+\\.\\d+\\.\\d+$' quay.io/prometheus/prometheus:v2.52.0 +docker run -d --name quay_prometheus --label 'wud.watch=true' --label 'wud.tag.include=^v\d+\.\d+\.\d+$' --user root --tmpfs /prometheus:rw,mode=777 quay.io/prometheus/prometheus:v2.52.0 echo "โœ… Test containers started (10 containers)" -docker ps --format "table {{.Names}}\t{{.Image}}\t{{.Status}}" | grep -E "(ecr_|ghcr_|gitlab_|hub_|lscr_|quay_)" \ No newline at end of file +docker ps --format "table {{.Names}}\t{{.Image}}\t{{.Status}}" | grep -E "(ecr_|ghcr_|gitlab_|hub_|lscr_|quay_|trueforge_)" \ No newline at end of file diff --git a/scripts/start-wud.sh b/scripts/start-wud.sh index 7619c738..443514fc 100755 --- a/scripts/start-wud.sh +++ b/scripts/start-wud.sh @@ -2,10 +2,14 @@ set -e +export DOCKER_BUILDKIT=0 + +SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) + echo "๐Ÿš€ Starting WUD container for local e2e tests..." # Build wud docker image -docker build -t wud --build-arg WUD_VERSION=local . +docker build -t wud --build-arg WUD_VERSION=local "$SCRIPT_DIR/.." # Run wud docker image docker run -d \ @@ -22,6 +26,12 @@ docker run -d \ --env WUD_REGISTRY_GITLAB_PRIVATE_TOKEN="${GITLAB_TOKEN:-dummy}" \ --env WUD_REGISTRY_LSCR_PRIVATE_USERNAME="${GITHUB_USERNAME:-dummy}" \ --env WUD_REGISTRY_LSCR_PRIVATE_TOKEN="${GITHUB_TOKEN:-dummy}" \ + --env WUD_REGISTRY_ACR_PRIVATE_CLIENTID="${ACR_CLIENT_ID:-89dcf54b-ef99-4dc1-bebb-8e0eacafdac8}" \ + --env WUD_REGISTRY_ACR_PRIVATE_CLIENTSECRET="${ACR_CLIENT_SECRET:-dummy}" \ + --env WUD_REGISTRY_TRUEFORGE_PRIVATE_USERNAME="${TRUEFORGE_USERNAME:-dummy}" \ + --env WUD_REGISTRY_TRUEFORGE_PRIVATE_TOKEN="${TRUEFORGE_TOKEN:-dummy}" \ + --env WUD_REGISTRY_GCR_PRIVATE_CLIENTEMAIL="${GCR_CLIENT_EMAIL:-gcr@wud-test.iam.gserviceaccount.com}" \ + --env WUD_REGISTRY_GCR_PRIVATE_PRIVATEKEY="${GCR_PRIVATE_KEY:------BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDZ\n-----END PRIVATE KEY-----}" \ --env WUD_AUTH_BASIC_JOHN_USER="john" \ --env WUD_AUTH_BASIC_JOHN_HASH='$apr1$8zDVtSAY$62WBh9DspNbUKMZXYRsjS/' \ wud diff --git a/test/test.sh b/test/test.sh index aecf50cb..d5db2852 100755 --- a/test/test.sh +++ b/test/test.sh @@ -25,6 +25,8 @@ docker tag nginx:1.10-alpine wudtest.azurecr.io/test:1.0.0 docker tag nginx:1.10-alpine wudtest.azurecr.io/sub/test:1.0.0 docker tag nginx:1.10-alpine wudtest.azurecr.io/sub/sub/test:1.0.0 -docker-compose down --remove-orphans +SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) -docker-compose up -d \ No newline at end of file +docker-compose -f "$SCRIPT_DIR/docker-compose.yml" down --remove-orphans + +docker-compose -f "$SCRIPT_DIR/docker-compose.yml" up -d \ No newline at end of file