From 33df47af95b98bc4690206a6884f23fa9f02fc0c Mon Sep 17 00:00:00 2001 From: Daniel Hatas <21317298+samson84@users.noreply.github.com> Date: Thu, 14 Nov 2024 19:27:07 +0100 Subject: [PATCH 1/7] Extract owner and pet routes to plugins. --- requests.http | 5 ++ src/controller/app.ts | 77 ++----------------- src/controller/routes/owner/owner.routes.ts | 57 ++++++++++++++ .../{ => routes/owner}/owner.schemas.ts | 0 src/controller/routes/pet/pet.routes.ts | 45 +++++++++++ .../{ => routes/pet}/pet.schemas.ts | 0 6 files changed, 114 insertions(+), 70 deletions(-) create mode 100644 src/controller/routes/owner/owner.routes.ts rename src/controller/{ => routes/owner}/owner.schemas.ts (100%) create mode 100644 src/controller/routes/pet/pet.routes.ts rename src/controller/{ => routes/pet}/pet.schemas.ts (100%) diff --git a/requests.http b/requests.http index 5615710..666e2a4 100644 --- a/requests.http +++ b/requests.http @@ -6,6 +6,11 @@ GET {{host}}/api/pets ### +GET {{host}}/api/pets/3 + +### + + POST {{host}}/api/pets Content-Type: application/json diff --git a/src/controller/app.ts b/src/controller/app.ts index 49c6f64..c33a9f7 100644 --- a/src/controller/app.ts +++ b/src/controller/app.ts @@ -3,10 +3,12 @@ import { PetService } from '../service/pet.service'; import { PetRepository } from '../repository/pet.repository'; import { DbClient } from '../db'; import { JsonSchemaToTsProvider } from '@fastify/type-provider-json-schema-to-ts' -import { getPetByIdSchema, getPetsSchema, postPetsSchema, putPetsToOwnersSchema } from './pet.schemas'; +import { getPetByIdSchema, getPetsSchema, postPetsSchema, putPetsToOwnersSchema } from './routes/pet/pet.schemas'; import { OwnerRepository } from '../repository/owner.repository'; import { OwnerService } from '../service/owner.service'; -import { getOwnerByIdSchema, getOwnersSchema, postOwnerSchema } from './owner.schemas'; +import { getOwnerByIdSchema, getOwnersSchema, postOwnerSchema } from './routes/owner/owner.schemas'; +import { createPetRoutes } from './routes/pet/pet.routes'; +import { createOwnerRoutes } from './routes/owner/owner.routes'; type Dependencies = { dbClient: DbClient; @@ -20,75 +22,10 @@ export default function createApp(options = {}, dependencies: Dependencies) { const ownerRepository = new OwnerRepository(dbClient); const ownerService = new OwnerService(ownerRepository); - const app = fastify(options) - .withTypeProvider() + const app = fastify(options); - app.get( - '/api/pets', - { schema: getPetsSchema }, - async () => { - const pets = await petService.getAll(); - return pets; - }) - - app.get( - '/api/pets/:id', - { schema: getPetByIdSchema }, - async (request) => { - const { id } = request.params; - const pets = await petService.getById(id); - return pets; - }) - - - app.post( - '/api/pets', - { schema: postPetsSchema }, - async (request, reply) => { - const { body: petToCreate } = request; - - const created = await petService.create(petToCreate); - reply.status(201); - return created; - }) - - app.put( - '/api/owners/:ownerId/pets/:petId', - { schema: putPetsToOwnersSchema }, - async (request) => { - const { petId, ownerId } = request.params; - const updated = await petService.adopt(petId, ownerId); - return updated; - } - ) - - app.get( - '/api/owners', - { schema: getOwnersSchema }, - async () => { - return await ownerService.getAll(); - } - ) - - app.get( - '/api/owners/:id', - { schema: getOwnerByIdSchema }, - async (request) => { - const { id } = request.params; - return await ownerService.getById(id); - } - ) - - app.post( - '/api/owners', - { schema: postOwnerSchema }, - async (request, reply) => { - const ownerProps = request.body; - const created = await ownerService.create(ownerProps); - reply.status(201); - return created; - } - ) + app.register(createPetRoutes, { petService, prefix: '/api/pets' }) + app.register(createOwnerRoutes, { petService, ownerService }) return app; } \ No newline at end of file diff --git a/src/controller/routes/owner/owner.routes.ts b/src/controller/routes/owner/owner.routes.ts new file mode 100644 index 0000000..15b3f10 --- /dev/null +++ b/src/controller/routes/owner/owner.routes.ts @@ -0,0 +1,57 @@ +import { FastifyPluginAsync } from 'fastify' +import { PetService } from '../../../service/pet.service'; +import { OwnerService } from '../../../service/owner.service'; +import { JsonSchemaToTsProvider } from '@fastify/type-provider-json-schema-to-ts' +import { putPetsToOwnersSchema } from '../pet/pet.schemas'; +import { getOwnerByIdSchema, getOwnersSchema, postOwnerSchema } from './owner.schemas'; + +type PluginOption = { + petService: PetService, + ownerService: OwnerService +} + +export const createOwnerRoutes: FastifyPluginAsync = async ( + app, + {petService, ownerService} +) => { + const appWithProvider = app.withTypeProvider() + + appWithProvider.put( + '/api/owners/:ownerId/pets/:petId', + { schema: putPetsToOwnersSchema }, + async (request) => { + const { petId, ownerId } = request.params; + const updated = await petService.adopt(petId, ownerId); + return updated; + } + ) + + appWithProvider.get( + '/api/owners', + { schema: getOwnersSchema }, + async () => { + return await ownerService.getAll(); + } + ) + + appWithProvider.get( + '/api/owners/:id', + { schema: getOwnerByIdSchema }, + async (request) => { + const { id } = request.params; + return await ownerService.getById(id); + } + ) + + appWithProvider.post( + '/api/owners', + { schema: postOwnerSchema }, + async (request, reply) => { + const ownerProps = request.body; + const created = await ownerService.create(ownerProps); + reply.status(201); + return created; + } + ) + +} \ No newline at end of file diff --git a/src/controller/owner.schemas.ts b/src/controller/routes/owner/owner.schemas.ts similarity index 100% rename from src/controller/owner.schemas.ts rename to src/controller/routes/owner/owner.schemas.ts diff --git a/src/controller/routes/pet/pet.routes.ts b/src/controller/routes/pet/pet.routes.ts new file mode 100644 index 0000000..66e993c --- /dev/null +++ b/src/controller/routes/pet/pet.routes.ts @@ -0,0 +1,45 @@ +import { FastifyPluginAsync } from 'fastify' +import { getPetByIdSchema, getPetsSchema, postPetsSchema } from './pet.schemas'; +import { PetService } from '../../../service/pet.service'; +import { JsonSchemaToTsProvider } from '@fastify/type-provider-json-schema-to-ts' + +type PluginOption = { + petService: PetService +} + +export const createPetRoutes: FastifyPluginAsync = async ( + app, + { petService } +) => { + + const appWithProvider = app.withTypeProvider() + + appWithProvider.get( + '/', + { schema: getPetsSchema }, + async () => { + const pets = await petService.getAll(); + return pets; + }) + + appWithProvider.get( + '/:id', + { schema: getPetByIdSchema }, + async (request) => { + const { id } = request.params; + const pets = await petService.getById(id); + return pets; + }) + + appWithProvider.post( + '/', + { schema: postPetsSchema }, + async (request, reply) => { + const { body: petToCreate } = request; + + const created = await petService.create(petToCreate); + reply.status(201); + return created; + }) + +} \ No newline at end of file diff --git a/src/controller/pet.schemas.ts b/src/controller/routes/pet/pet.schemas.ts similarity index 100% rename from src/controller/pet.schemas.ts rename to src/controller/routes/pet/pet.schemas.ts From ef5a980378aa351531d775d3394e1a9ac7ec3721 Mon Sep 17 00:00:00 2001 From: Daniel Hatas <21317298+samson84@users.noreply.github.com> Date: Thu, 14 Nov 2024 19:27:38 +0100 Subject: [PATCH 2/7] Declare a plugin. --- src/controller/app.ts | 13 +++++++++++-- src/controller/routes/pet/pet.routes.ts | 14 ++++---------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/controller/app.ts b/src/controller/app.ts index c33a9f7..a3b558c 100644 --- a/src/controller/app.ts +++ b/src/controller/app.ts @@ -14,6 +14,13 @@ type Dependencies = { dbClient: DbClient; } +declare module 'fastify' { + interface FastifyInstance { + petService: PetService + } +} + + export default function createApp(options = {}, dependencies: Dependencies) { const { dbClient } = dependencies; @@ -24,8 +31,10 @@ export default function createApp(options = {}, dependencies: Dependencies) { const app = fastify(options); - app.register(createPetRoutes, { petService, prefix: '/api/pets' }) - app.register(createOwnerRoutes, { petService, ownerService }) + app.decorate('petService', petService); + + app.register(createPetRoutes, { prefix: '/api/pets' }); + app.register(createOwnerRoutes, { petService, ownerService }); return app; } \ No newline at end of file diff --git a/src/controller/routes/pet/pet.routes.ts b/src/controller/routes/pet/pet.routes.ts index 66e993c..9533d0d 100644 --- a/src/controller/routes/pet/pet.routes.ts +++ b/src/controller/routes/pet/pet.routes.ts @@ -1,15 +1,9 @@ import { FastifyPluginAsync } from 'fastify' import { getPetByIdSchema, getPetsSchema, postPetsSchema } from './pet.schemas'; -import { PetService } from '../../../service/pet.service'; import { JsonSchemaToTsProvider } from '@fastify/type-provider-json-schema-to-ts' -type PluginOption = { - petService: PetService -} - -export const createPetRoutes: FastifyPluginAsync = async ( +export const createPetRoutes: FastifyPluginAsync = async ( app, - { petService } ) => { const appWithProvider = app.withTypeProvider() @@ -18,7 +12,7 @@ export const createPetRoutes: FastifyPluginAsync = async ( '/', { schema: getPetsSchema }, async () => { - const pets = await petService.getAll(); + const pets = await app.petService.getAll(); return pets; }) @@ -27,7 +21,7 @@ export const createPetRoutes: FastifyPluginAsync = async ( { schema: getPetByIdSchema }, async (request) => { const { id } = request.params; - const pets = await petService.getById(id); + const pets = await app.petService.getById(id); return pets; }) @@ -37,7 +31,7 @@ export const createPetRoutes: FastifyPluginAsync = async ( async (request, reply) => { const { body: petToCreate } = request; - const created = await petService.create(petToCreate); + const created = await app.petService.create(petToCreate); reply.status(201); return created; }) From f28a9b7dcede5ce384d4fd46eb5d59156d5cb29f Mon Sep 17 00:00:00 2001 From: Daniel Hatas <21317298+samson84@users.noreply.github.com> Date: Thu, 14 Nov 2024 19:45:48 +0100 Subject: [PATCH 3/7] Create ownerService. --- src/controller/app.ts | 12 +++++----- src/controller/routes/owner/owner.routes.ts | 26 +++++++-------------- 2 files changed, 15 insertions(+), 23 deletions(-) diff --git a/src/controller/app.ts b/src/controller/app.ts index a3b558c..168d7c2 100644 --- a/src/controller/app.ts +++ b/src/controller/app.ts @@ -2,11 +2,8 @@ import fastify from 'fastify'; import { PetService } from '../service/pet.service'; import { PetRepository } from '../repository/pet.repository'; import { DbClient } from '../db'; -import { JsonSchemaToTsProvider } from '@fastify/type-provider-json-schema-to-ts' -import { getPetByIdSchema, getPetsSchema, postPetsSchema, putPetsToOwnersSchema } from './routes/pet/pet.schemas'; import { OwnerRepository } from '../repository/owner.repository'; import { OwnerService } from '../service/owner.service'; -import { getOwnerByIdSchema, getOwnersSchema, postOwnerSchema } from './routes/owner/owner.schemas'; import { createPetRoutes } from './routes/pet/pet.routes'; import { createOwnerRoutes } from './routes/owner/owner.routes'; @@ -16,8 +13,9 @@ type Dependencies = { declare module 'fastify' { interface FastifyInstance { - petService: PetService - } + petService: PetService, + ownerService: OwnerService + } } @@ -32,9 +30,11 @@ export default function createApp(options = {}, dependencies: Dependencies) { const app = fastify(options); app.decorate('petService', petService); + app.decorate('ownerService', ownerService) app.register(createPetRoutes, { prefix: '/api/pets' }); - app.register(createOwnerRoutes, { petService, ownerService }); + app.register(createOwnerRoutes, { prefix: '/api/owners' }); + // app.register(createMessengerPlugin, {message: 'hjksdhjkashdkja'}) return app; } \ No newline at end of file diff --git a/src/controller/routes/owner/owner.routes.ts b/src/controller/routes/owner/owner.routes.ts index 15b3f10..d1c615e 100644 --- a/src/controller/routes/owner/owner.routes.ts +++ b/src/controller/routes/owner/owner.routes.ts @@ -1,54 +1,46 @@ import { FastifyPluginAsync } from 'fastify' -import { PetService } from '../../../service/pet.service'; -import { OwnerService } from '../../../service/owner.service'; import { JsonSchemaToTsProvider } from '@fastify/type-provider-json-schema-to-ts' import { putPetsToOwnersSchema } from '../pet/pet.schemas'; import { getOwnerByIdSchema, getOwnersSchema, postOwnerSchema } from './owner.schemas'; -type PluginOption = { - petService: PetService, - ownerService: OwnerService -} - -export const createOwnerRoutes: FastifyPluginAsync = async ( +export const createOwnerRoutes: FastifyPluginAsync = async ( app, - {petService, ownerService} ) => { const appWithProvider = app.withTypeProvider() appWithProvider.put( - '/api/owners/:ownerId/pets/:petId', + '/:ownerId/pets/:petId', { schema: putPetsToOwnersSchema }, async (request) => { const { petId, ownerId } = request.params; - const updated = await petService.adopt(petId, ownerId); + const updated = await app.petService.adopt(petId, ownerId); return updated; } ) appWithProvider.get( - '/api/owners', + '/', { schema: getOwnersSchema }, async () => { - return await ownerService.getAll(); + return await app.ownerService.getAll(); } ) appWithProvider.get( - '/api/owners/:id', + '/:id', { schema: getOwnerByIdSchema }, async (request) => { const { id } = request.params; - return await ownerService.getById(id); + return await app.ownerService.getById(id); } ) appWithProvider.post( - '/api/owners', + '/', { schema: postOwnerSchema }, async (request, reply) => { const ownerProps = request.body; - const created = await ownerService.create(ownerProps); + const created = await app.ownerService.create(ownerProps); reply.status(201); return created; } From ab9eecc607758d905c6c0607af0def4698dd66f5 Mon Sep 17 00:00:00 2001 From: Daniel Hatas <21317298+samson84@users.noreply.github.com> Date: Fri, 15 Nov 2024 00:30:59 +0100 Subject: [PATCH 4/7] Add a greeter plugin. --- package-lock.json | 7 +++++++ package.json | 1 + src/controller/app.ts | 9 +++++---- src/controller/plugins/greeter.ts | 30 ++++++++++++++++++++++++++++++ src/server.ts | 20 ++++++++++++-------- 5 files changed, 55 insertions(+), 12 deletions(-) create mode 100644 src/controller/plugins/greeter.ts diff --git a/package-lock.json b/package-lock.json index 020009d..afce7e8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@fastify/type-provider-json-schema-to-ts": "^4.0.1", "dotenv": "^16.4.5", "fastify": "^5.0.0", + "fastify-plugin": "^5.0.1", "pg": "^8.13.1", "pg-format": "^1.0.4" }, @@ -2549,6 +2550,12 @@ "toad-cache": "^3.7.0" } }, + "node_modules/fastify-plugin": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/fastify-plugin/-/fastify-plugin-5.0.1.tgz", + "integrity": "sha512-HCxs+YnRaWzCl+cWRYFnHmeRFyR5GVnJTAaCJQiYzQSDwK9MgJdyAsuL3nh0EWRCYMgQ5MeziymvmAhUHYHDUQ==", + "license": "MIT" + }, "node_modules/fastq": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", diff --git a/package.json b/package.json index dfe7a81..fd1de69 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "@fastify/type-provider-json-schema-to-ts": "^4.0.1", "dotenv": "^16.4.5", "fastify": "^5.0.0", + "fastify-plugin": "^5.0.1", "pg": "^8.13.1", "pg-format": "^1.0.4" }, diff --git a/src/controller/app.ts b/src/controller/app.ts index 168d7c2..b244062 100644 --- a/src/controller/app.ts +++ b/src/controller/app.ts @@ -6,6 +6,7 @@ import { OwnerRepository } from '../repository/owner.repository'; import { OwnerService } from '../service/owner.service'; import { createPetRoutes } from './routes/pet/pet.routes'; import { createOwnerRoutes } from './routes/owner/owner.routes'; +import createGreeterPlugin from './plugins/greeter'; type Dependencies = { dbClient: DbClient; @@ -19,7 +20,7 @@ declare module 'fastify' { } -export default function createApp(options = {}, dependencies: Dependencies) { +export default async function createApp(options = {}, dependencies: Dependencies) { const { dbClient } = dependencies; const petRepository = new PetRepository(dbClient); @@ -32,9 +33,9 @@ export default function createApp(options = {}, dependencies: Dependencies) { app.decorate('petService', petService); app.decorate('ownerService', ownerService) - app.register(createPetRoutes, { prefix: '/api/pets' }); - app.register(createOwnerRoutes, { prefix: '/api/owners' }); - // app.register(createMessengerPlugin, {message: 'hjksdhjkashdkja'}) + await app.register(createGreeterPlugin, { message: 'Hi' }) + await app.register(createPetRoutes, { prefix: '/api/pets' }); + await app.register(createOwnerRoutes, { prefix: '/api/owners' }); return app; } \ No newline at end of file diff --git a/src/controller/plugins/greeter.ts b/src/controller/plugins/greeter.ts new file mode 100644 index 0000000..bc6552f --- /dev/null +++ b/src/controller/plugins/greeter.ts @@ -0,0 +1,30 @@ +import { FastifyPluginAsync } from 'fastify'; +import fastifyPlugin from 'fastify-plugin'; + +type PluginOptions = { + message: string +} + +declare module 'fastify' { + interface FastifyRequest { + message: string + } +} + +const createGreeterPlugin: FastifyPluginAsync = async ( + app, + options +) => { + app.decorateRequest('message', '') + + app.addHook('onRequest', async (request) => { + const { message } = options; + request.message = message + }) + + app.addHook('onResponse', async (request) => { + console.log(`Message: ${request.message}`) + }) +} + +export default fastifyPlugin(createGreeterPlugin) \ No newline at end of file diff --git a/src/server.ts b/src/server.ts index 2c6a37b..c27b680 100644 --- a/src/server.ts +++ b/src/server.ts @@ -16,12 +16,16 @@ const options = { }, }; -const app = createApp(options, { dbClient }); +async function main() { + const app = await createApp(options, { dbClient }) + + app.listen({ port: PORT }, (error, address) => { + if (error) { + app.log.error(error); + process.exit(1); + } + app.log.info(`Server is started successfully.`) + }); +} -app.listen({ port: PORT }, (error, address) => { - if (error) { - app.log.error(error); - process.exit(1); - } - app.log.info(`Server is started successfully.`) -}); \ No newline at end of file +main(); From f8b38e68dad772452c166c2ec924e6682f993b5e Mon Sep 17 00:00:00 2001 From: Daniel Hatas <21317298+samson84@users.noreply.github.com> Date: Fri, 15 Nov 2024 05:50:21 +0100 Subject: [PATCH 5/7] Add a route and an e2e test --- jest.config.js | 1 - package-lock.json | 997 +++++++++++++++++- package.json | 1 + src/controller/app.ts | 8 +- .../routes/owner/owner.routes.test.ts | 49 + src/db.ts | 4 + src/service/owner.service.ts | 10 +- src/service/pet.service.ts | 11 +- test/app.test.ts | 22 - test/e2e.test.ts | 81 ++ 10 files changed, 1130 insertions(+), 54 deletions(-) create mode 100644 src/controller/routes/owner/owner.routes.test.ts delete mode 100644 test/app.test.ts create mode 100644 test/e2e.test.ts diff --git a/jest.config.js b/jest.config.js index cc83674..0ad179e 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,7 +1,6 @@ /** @type {import('ts-jest').JestConfigWithTsJest} **/ module.exports = { testEnvironment: "node", - rootDir: "./test", transform: { "^.+.tsx?$": ["ts-jest",{tsconfig: "tsconfig-test.json"}], }, diff --git a/package-lock.json b/package-lock.json index afce7e8..890a597 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,6 +25,7 @@ "nodemon": "^3.1.7", "pino-pretty": "^11.2.2", "rimraf": "^6.0.1", + "testcontainers": "^10.14.0", "ts-jest": "^29.2.5", "ts-node": "^10.9.2", "typescript": "^5.6.2" @@ -663,6 +664,13 @@ "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==", + "dev": true, + "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", @@ -705,6 +713,16 @@ "fast-uri": "^3.0.0" } }, + "node_modules/@fastify/busboy": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", + "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, "node_modules/@fastify/error": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@fastify/error/-/error-4.0.0.tgz", @@ -1213,6 +1231,17 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", @@ -1313,6 +1342,29 @@ "@babel/types": "^7.20.7" } }, + "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", + "dependencies": { + "@types/node": "*", + "@types/ssh2": "*" + } + }, + "node_modules/@types/dockerode": { + "version": "3.3.31", + "resolved": "https://registry.npmjs.org/@types/dockerode/-/dockerode-3.3.31.tgz", + "integrity": "sha512-42R9eoVqJDSvVspV89g7RwRqfNExgievLNWoHkg7NoWIqAmavIbgQBb4oc0qRtHkxE+I3Xxvqv7qVXFABKPBTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/docker-modem": "*", + "@types/node": "*", + "@types/ssh2": "*" + } + }, "node_modules/@types/graceful-fs": { "version": "4.1.9", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", @@ -1452,6 +1504,43 @@ "node": ">=12" } }, + "node_modules/@types/ssh2": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@types/ssh2/-/ssh2-1.15.1.tgz", + "integrity": "sha512-ZIbEqKAsi5gj35y4P4vkJYly642wIbY6PqoN0xiyQGshKUGXR9WQjF/iF9mXBQ8uBKy3ezfsCkcoHKhd0BzuDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "^18.11.18" + } + }, + "node_modules/@types/ssh2-streams": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/@types/ssh2-streams/-/ssh2-streams-0.1.12.tgz", + "integrity": "sha512-Sy8tpEmCce4Tq0oSOYdfqaBpA3hDM8SoxoFh5vzFsu2oL+znzGz8oVWW7xb4K920yYMUY+PIG31qZnFMfPWNCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/ssh2/node_modules/@types/node": { + "version": "18.19.64", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.64.tgz", + "integrity": "sha512-955mDqvO2vFf/oL7V3WiUtiz+BugyX8uVbaT2H8oj3+8dRyH2FLiNdowe7eNqRM7IOIZvzDH76EoAT+gwm6aIQ==", + "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", @@ -1609,6 +1698,131 @@ "node": ">= 8" } }, + "node_modules/archiver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", + "integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "archiver-utils": "^5.0.2", + "async": "^3.2.4", + "buffer-crc32": "^1.0.0", + "readable-stream": "^4.0.0", + "readdir-glob": "^1.1.2", + "tar-stream": "^3.0.0", + "zip-stream": "^6.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/archiver-utils": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz", + "integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob": "^10.0.0", + "graceful-fs": "^4.2.0", + "is-stream": "^2.0.1", + "lazystream": "^1.0.0", + "lodash": "^4.17.15", + "normalize-path": "^3.0.0", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/archiver-utils/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/archiver-utils/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/archiver-utils/node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/archiver-utils/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/archiver-utils/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/archiver-utils/node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", @@ -1626,6 +1840,16 @@ "sprintf-js": "~1.0.2" } }, + "node_modules/asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, "node_modules/async": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", @@ -1633,6 +1857,13 @@ "dev": true, "license": "MIT" }, + "node_modules/async-lock": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/async-lock/-/async-lock-1.4.1.tgz", + "integrity": "sha512-Az2ZTpuytrtqENulXwO3GGv1Bztugx6TT37NIo7imr/Qo0gsYiGtSdBa2B6fsXhTpVZDNfu1Qn3pk531e3q+nQ==", + "dev": true, + "license": "MIT" + }, "node_modules/atomic-sleep": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", @@ -1652,6 +1883,13 @@ "fastq": "^1.17.1" } }, + "node_modules/b4a": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.7.tgz", + "integrity": "sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/babel-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", @@ -1785,6 +2023,57 @@ "dev": true, "license": "MIT" }, + "node_modules/bare-events": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.5.0.tgz", + "integrity": "sha512-/E8dDe9dsbLyh2qrZ64PEPadOQ0F4gbl1sUJOrmph7xOiIxfY8vwab/4bFLh4Y88/Hk/ujKcrQKc+ps0mv873A==", + "dev": true, + "license": "Apache-2.0", + "optional": true + }, + "node_modules/bare-fs": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-2.3.5.tgz", + "integrity": "sha512-SlE9eTxifPDJrT6YgemQ1WGFleevzwY+XAP1Xqgl56HtcrisC2CHCZ2tq6dBpcH2TnNxwUEUGhweo+lrQtYuiw==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bare-events": "^2.0.0", + "bare-path": "^2.0.0", + "bare-stream": "^2.0.0" + } + }, + "node_modules/bare-os": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-2.4.4.tgz", + "integrity": "sha512-z3UiI2yi1mK0sXeRdc4O1Kk8aOa/e+FNWZcTiPB/dfTWyLypuE99LibgRaQki914Jq//yAWylcAt+mknKdixRQ==", + "dev": true, + "license": "Apache-2.0", + "optional": true + }, + "node_modules/bare-path": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-2.1.3.tgz", + "integrity": "sha512-lh/eITfU8hrj9Ru5quUp0Io1kJWIk1bTjzo7JH1P5dWmQ2EL4hFUlfI8FonAhSlgIfhn63p84CDY/x+PisgcXA==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bare-os": "^2.1.0" + } + }, + "node_modules/bare-stream": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.3.2.tgz", + "integrity": "sha512-EFZHSIBkDgSHIwj2l2QZfP4U5OcD4xFAOwhSb/vlr9PIqyGJGvB/nfClJbcnh3EY4jtPE4zsb5ztae96bVF79A==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "streamx": "^2.20.0" + } + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -1805,6 +2094,16 @@ ], "license": "MIT" }, + "node_modules/bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -1818,6 +2117,58 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bl/node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "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": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/bl/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -1922,6 +2273,16 @@ "ieee754": "^1.2.1" } }, + "node_modules/buffer-crc32": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", + "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -1929,6 +2290,26 @@ "dev": true, "license": "MIT" }, + "node_modules/buildcheck": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/buildcheck/-/buildcheck-0.0.6.tgz", + "integrity": "sha512-8f9ZJCUXyT1M35Jx7MkBgmBMo3oHTTBIPLiY9xyL0pl3T5RwcPEY8cUHr5LBNfu/fk6c2T4DJZuVM/8ZZT2D2A==", + "dev": true, + "optional": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/byline": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/byline/-/byline-5.0.0.tgz", + "integrity": "sha512-s6webAy+R4SR8XVuJWt2V2rGvhnrhxN+9S15GNuTK3wKPOXFF6RNc+8ug2XhH+2s4f+uudG4kUVYmYOQWL2g0Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -2022,6 +2403,13 @@ "fsevents": "~2.3.2" } }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true, + "license": "ISC" + }, "node_modules/ci-info": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", @@ -2105,6 +2493,23 @@ "dev": true, "license": "MIT" }, + "node_modules/compress-commons": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", + "integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "crc-32": "^1.2.0", + "crc32-stream": "^6.0.0", + "is-stream": "^2.0.1", + "normalize-path": "^3.0.0", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -2128,6 +2533,55 @@ "node": ">= 0.6" } }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cpu-features": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/cpu-features/-/cpu-features-0.0.10.tgz", + "integrity": "sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "dependencies": { + "buildcheck": "~0.0.6", + "nan": "^2.19.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/crc32-stream": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-6.0.0.tgz", + "integrity": "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==", + "dev": true, + "license": "MIT", + "dependencies": { + "crc-32": "^1.2.0", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/create-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", @@ -2215,44 +2669,148 @@ } } }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "license": "MIT", + "engines": { + "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": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/docker-compose": { + "version": "0.24.8", + "resolved": "https://registry.npmjs.org/docker-compose/-/docker-compose-0.24.8.tgz", + "integrity": "sha512-plizRs/Vf15H+GCVxq2EUvyPK7ei9b/cVesHvjnX4xaXjM9spHe2Ytq0BitndFgvTJ3E3NljPNUEl7BAN43iZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "yaml": "^2.2.2" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/docker-modem": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/docker-modem/-/docker-modem-3.0.8.tgz", + "integrity": "sha512-f0ReSURdM3pcKPNS30mxOHSbaFLcknGmQjwSfmbcdOw1XWKXVhukM3NJHhr7NpY9BIyyWQb0EBo3KQvvuU5egQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "debug": "^4.1.1", + "readable-stream": "^3.5.0", + "split-ca": "^1.0.1", + "ssh2": "^1.11.0" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/docker-modem/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/dockerode": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/dockerode/-/dockerode-3.3.5.tgz", + "integrity": "sha512-/0YNa3ZDNeLr/tSckmD69+Gq+qVNhvKfAHNeZJBnp7EOP6RGKV8ORrJHkUn20So5wU+xxT7+1n5u8PjHbfjbSA==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", + "dependencies": { + "@balena/dockerignore": "^1.0.2", + "docker-modem": "^3.0.0", + "tar-fs": "~2.0.1" + }, "engines": { - "node": ">=0.10.0" + "node": ">= 8.0" } }, - "node_modules/detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "node_modules/dockerode/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dev": true, "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, "engines": { - "node": ">=8" + "node": ">= 6" } }, - "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "node_modules/dockerode/node_modules/tar-fs": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.0.1.tgz", + "integrity": "sha512-6tzWDMeroL87uF/+lin46k+Q+46rAJ0SyPGz7OW7wTgblI273hsBqk2C1j0/xNadNLKDTUL9BukSjB7cwgmlPA==", "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" + "license": "MIT", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.0.0" } }, - "node_modules/diff-sequences": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", - "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "node_modules/dockerode/node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", "dev": true, "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=6" } }, "node_modules/dotenv": { @@ -2458,6 +3016,13 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "license": "MIT" }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "dev": true, + "license": "MIT" + }, "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", @@ -2688,6 +3253,13 @@ "node": ">= 0.6" } }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true, + "license": "MIT" + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -2750,6 +3322,19 @@ "node": ">=8.0.0" } }, + "node_modules/get-port": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-5.1.1.tgz", + "integrity": "sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-stream": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", @@ -3049,6 +3634,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -3847,6 +4439,52 @@ "node": ">=6" } }, + "node_modules/lazystream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", + "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 0.6.3" + } + }, + "node_modules/lazystream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/lazystream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/lazystream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -3888,6 +4526,13 @@ "node": ">=8" } }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true, + "license": "MIT" + }, "node_modules/lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", @@ -4002,6 +4647,26 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "dev": true, + "license": "MIT" + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -4009,6 +4674,14 @@ "dev": true, "license": "MIT" }, + "node_modules/nan": { + "version": "2.22.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.22.0.tgz", + "integrity": "sha512-nbajikzWTMwsW+eSsNm3QwlOs7het9gGJU5dDZzRTQGk03vyBOauxgI4VakDzE0PtsGTmXPsXTbbjVhRwR5mpw==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -4590,6 +5263,13 @@ "node": ">= 0.6.0" } }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true, + "license": "MIT" + }, "node_modules/process-warning": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-4.0.0.tgz", @@ -4610,6 +5290,35 @@ "node": ">= 6" } }, + "node_modules/proper-lockfile": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", + "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "retry": "^0.12.0", + "signal-exit": "^3.0.2" + } + }, + "node_modules/properties-reader": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/properties-reader/-/properties-reader-2.3.0.tgz", + "integrity": "sha512-z597WicA7nDZxK12kZqHr2TcvwNU1GCfA5UwfDY/HDp3hXPoPlb5rlEx9bwGTiJnc0OqbBTkU975jDToth8Gxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mkdirp": "^1.0.4" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/steveukx/properties?sponsor=1" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -4658,6 +5367,13 @@ ], "license": "MIT" }, + "node_modules/queue-tick": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", + "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==", + "dev": true, + "license": "MIT" + }, "node_modules/quick-format-unescaped": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", @@ -4687,6 +5403,39 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/readdir-glob": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", + "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.1.0" + } + }, + "node_modules/readdir-glob/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/readdir-glob/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -4794,6 +5543,16 @@ "node": ">=10" } }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -4918,6 +5677,13 @@ "node": ">=10" } }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, "node_modules/secure-json-parse": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz", @@ -5032,6 +5798,13 @@ "source-map": "^0.6.0" } }, + "node_modules/split-ca": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/split-ca/-/split-ca-1.0.1.tgz", + "integrity": "sha512-Q5thBSxp5t8WPTTJQS59LrGqOZqOsrhDGDVm8azCqIBjSBd7nd9o2PM+mDulQQkh8h//4U6hFZnc/mul8t5pWQ==", + "dev": true, + "license": "ISC" + }, "node_modules/split2": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", @@ -5048,6 +5821,46 @@ "dev": true, "license": "BSD-3-Clause" }, + "node_modules/ssh-remote-port-forward": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/ssh-remote-port-forward/-/ssh-remote-port-forward-1.0.4.tgz", + "integrity": "sha512-x0LV1eVDwjf1gmG7TTnfqIzf+3VPRz7vrNIjX6oYLbeCrf/PeVY6hkT68Mg+q02qXxQhrLjB0jfgvhevoCRmLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ssh2": "^0.5.48", + "ssh2": "^1.4.0" + } + }, + "node_modules/ssh-remote-port-forward/node_modules/@types/ssh2": { + "version": "0.5.52", + "resolved": "https://registry.npmjs.org/@types/ssh2/-/ssh2-0.5.52.tgz", + "integrity": "sha512-lbLLlXxdCZOSJMCInKH2+9V/77ET2J6NPQHpFI0kda61Dd1KglJs+fPQBchizmzYSOJBgdTajhPqBO1xxLywvg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/ssh2-streams": "*" + } + }, + "node_modules/ssh2": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/ssh2/-/ssh2-1.16.0.tgz", + "integrity": "sha512-r1X4KsBGedJqo7h8F5c4Ybpcr5RjyP+aWIG007uBPRjmdQWfEiVLzSK71Zji1B9sKxwaCvD8y8cwSkYrlLiRRg==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "asn1": "^0.2.6", + "bcrypt-pbkdf": "^1.0.2" + }, + "engines": { + "node": ">=10.16.0" + }, + "optionalDependencies": { + "cpu-features": "~0.0.10", + "nan": "^2.20.0" + } + }, "node_modules/stack-utils": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", @@ -5061,6 +5874,21 @@ "node": ">=10" } }, + "node_modules/streamx": { + "version": "2.20.2", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.20.2.tgz", + "integrity": "sha512-aDGDLU+j9tJcUdPGOaHmVF1u/hhI+CsGkT02V3OKlHDV7IukOI+nTWAGkiZEKCO35rWN1wIr4tS7YFr1f4qSvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-fifo": "^1.3.2", + "queue-tick": "^1.0.1", + "text-decoder": "^1.1.0" + }, + "optionalDependencies": { + "bare-events": "^2.2.0" + } + }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -5201,6 +6029,33 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/tar-fs": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.6.tgz", + "integrity": "sha512-iokBDQQkUyeXhgPYaZxmczGPhnhXZ0CmrqI+MOb/WFGS9DW5wnfrLgtjUJBvz50vQ3qfRwJ62QVoCFu8mPVu5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + }, + "optionalDependencies": { + "bare-fs": "^2.1.1", + "bare-path": "^2.1.0" + } + }, + "node_modules/tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -5216,6 +6071,37 @@ "node": ">=8" } }, + "node_modules/testcontainers": { + "version": "10.14.0", + "resolved": "https://registry.npmjs.org/testcontainers/-/testcontainers-10.14.0.tgz", + "integrity": "sha512-8fReFeQ4bk17T2vHHzcFavBG8UHuHwsdVj+48TchtsCSklwmSUTkg/b57hVjxZdxN1ed/GfF63WZ39I4syV5tQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@balena/dockerignore": "^1.0.2", + "@types/dockerode": "^3.3.29", + "archiver": "^7.0.1", + "async-lock": "^1.4.1", + "byline": "^5.0.0", + "debug": "^4.3.5", + "docker-compose": "^0.24.8", + "dockerode": "^3.3.5", + "get-port": "^5.1.1", + "proper-lockfile": "^4.1.2", + "properties-reader": "^2.3.0", + "ssh-remote-port-forward": "^1.0.4", + "tar-fs": "^3.0.6", + "tmp": "^0.2.3", + "undici": "^5.28.4" + } + }, + "node_modules/text-decoder": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.1.tgz", + "integrity": "sha512-x9v3H/lTKIJKQQe7RPQkLfKAnc9lUTkWDypIQgTzPJAq+5/GCDHonmshfvlsNSj58yyshbIJJDLmU15qNERrXQ==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/thread-stream": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz", @@ -5225,6 +6111,16 @@ "real-require": "^0.2.0" } }, + "node_modules/tmp": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", + "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", @@ -5373,6 +6269,13 @@ } } }, + "node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", + "dev": true, + "license": "Unlicense" + }, "node_modules/type-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", @@ -5417,6 +6320,19 @@ "dev": true, "license": "MIT" }, + "node_modules/undici": { + "version": "5.28.4", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.4.tgz", + "integrity": "sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@fastify/busboy": "^2.0.0" + }, + "engines": { + "node": ">=14.0" + } + }, "node_modules/undici-types": { "version": "6.19.8", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", @@ -5455,6 +6371,13 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, "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", @@ -5587,6 +6510,19 @@ "dev": true, "license": "ISC" }, + "node_modules/yaml": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.0.tgz", + "integrity": "sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", @@ -5638,6 +6574,21 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zip-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz", + "integrity": "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "archiver-utils": "^5.0.0", + "compress-commons": "^6.0.2", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } } } } diff --git a/package.json b/package.json index fd1de69..5f23b8f 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "nodemon": "^3.1.7", "pino-pretty": "^11.2.2", "rimraf": "^6.0.1", + "testcontainers": "^10.14.0", "ts-jest": "^29.2.5", "ts-node": "^10.9.2", "typescript": "^5.6.2" diff --git a/src/controller/app.ts b/src/controller/app.ts index b244062..c2a822c 100644 --- a/src/controller/app.ts +++ b/src/controller/app.ts @@ -1,9 +1,9 @@ import fastify from 'fastify'; -import { PetService } from '../service/pet.service'; +import { PetService, PetServiceInterface } from '../service/pet.service'; import { PetRepository } from '../repository/pet.repository'; import { DbClient } from '../db'; import { OwnerRepository } from '../repository/owner.repository'; -import { OwnerService } from '../service/owner.service'; +import { OwnerService, OwnerServiceInterface } from '../service/owner.service'; import { createPetRoutes } from './routes/pet/pet.routes'; import { createOwnerRoutes } from './routes/owner/owner.routes'; import createGreeterPlugin from './plugins/greeter'; @@ -14,8 +14,8 @@ type Dependencies = { declare module 'fastify' { interface FastifyInstance { - petService: PetService, - ownerService: OwnerService + petService: PetServiceInterface, + ownerService: OwnerServiceInterface } } diff --git a/src/controller/routes/owner/owner.routes.test.ts b/src/controller/routes/owner/owner.routes.test.ts new file mode 100644 index 0000000..5a1aa35 --- /dev/null +++ b/src/controller/routes/owner/owner.routes.test.ts @@ -0,0 +1,49 @@ +import fastify, { FastifyInstance } from "fastify"; +import { createOwnerRoutes } from "./owner.routes"; +import { OwnerServiceInterface } from "../../../service/owner.service"; +import { PetServiceInterface } from "../../../service/pet.service"; + +declare module 'fastify' { + interface FastifyInstance { + petService: PetServiceInterface, + ownerService: OwnerServiceInterface + } +} + + +describe('/owner', () => { + let app: FastifyInstance; + + beforeEach(async () => { + app = fastify(); + }) + + it('should getAll owners', async () => { + const owners = [{ id: 1, name: 'Joe', age: 4 }]; + const expected = [{ id: 1, name: 'Joe', age: 4 }]; + const ownerServiceMock: OwnerServiceInterface = { + getAll: jest.fn(async () => owners), + create: jest.fn(), + getById: jest.fn(), + } + const petServiceMock: PetServiceInterface = { + getAll: jest.fn(), + adopt: jest.fn(), + create: jest.fn(), + getById: jest.fn(), + } + app.decorate('petService', petServiceMock); + app.decorate('ownerService', ownerServiceMock); + await app.register(createOwnerRoutes, { prefix: '/api/owners' }) + + const response = await app.inject().get('/api/owners'); + const body = JSON.parse(response.body); + + expect(body).toStrictEqual(expected); + expect(ownerServiceMock.getAll).toHaveBeenCalledTimes(2) + }) + + afterEach(() => { + app?.close(); + }) +}) \ No newline at end of file diff --git a/src/db.ts b/src/db.ts index 604dc03..e11801d 100644 --- a/src/db.ts +++ b/src/db.ts @@ -2,6 +2,7 @@ import {Pool} from 'pg'; export type DbClient = { query: (query: string, params?: any[]) => Promise; + close: () => Promise } export function createPgClient(connectionString: string): DbClient { @@ -12,6 +13,9 @@ export function createPgClient(connectionString: string): DbClient { async query(sql: string, params?: any[]) { const result = await pool.query(sql, params); return result.rows; + }, + async close() { + await pool.end(); } } diff --git a/src/service/owner.service.ts b/src/service/owner.service.ts index d3985b2..f99d7d4 100644 --- a/src/service/owner.service.ts +++ b/src/service/owner.service.ts @@ -1,7 +1,13 @@ -import { OwnerToCreate } from "../entity/owner.type"; +import { Owner, OwnerToCreate } from "../entity/owner.type"; import { OwnerRepository } from "../repository/owner.repository"; -export class OwnerService { +export interface OwnerServiceInterface { + getAll: () => Promise + getById: (id: number) => Promise + create: (ownerProps: OwnerToCreate) => Promise +} + +export class OwnerService implements OwnerServiceInterface { private readonly repository; constructor(repository: OwnerRepository) { diff --git a/src/service/pet.service.ts b/src/service/pet.service.ts index f9134d0..8ce816c 100644 --- a/src/service/pet.service.ts +++ b/src/service/pet.service.ts @@ -1,7 +1,14 @@ -import { PetToCreate } from "../entity/pet.type"; +import { Pet, PetToCreate } from "../entity/pet.type"; import { PetRepository } from "../repository/pet.repository" -export class PetService { +export interface PetServiceInterface { + getAll: () => Promise + getById: (id: number) => Promise + create: (petProps: PetToCreate) => Promise + adopt: (petId: number, ownerId: number) => Promise +} + +export class PetService implements PetServiceInterface { private readonly repository; constructor(repository: PetRepository) { diff --git a/test/app.test.ts b/test/app.test.ts deleted file mode 100644 index 1d69c77..0000000 --- a/test/app.test.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { FastifyInstance } from "fastify"; -import createApp from "../src/controller/app" - -let app: FastifyInstance | undefined; - -beforeEach(() => { - app = createApp({logger: false}); -}) - -it('respond to the GET /hello', async () => { - const response = await app! - .inject() - .get('/hello') - const body = JSON.parse(response.body) - - expect(response.statusCode).toStrictEqual(200); - expect(body).toStrictEqual({hello: 'World!'}) -}) - -afterEach(() => { - app?.close() -}) \ No newline at end of file diff --git a/test/e2e.test.ts b/test/e2e.test.ts new file mode 100644 index 0000000..165d72d --- /dev/null +++ b/test/e2e.test.ts @@ -0,0 +1,81 @@ +import { GenericContainer, StartedTestContainer } from 'testcontainers' +import { createPgClient, DbClient } from '../src/db'; +import { FastifyInstance } from 'fastify'; +import createApp from '../src/controller/app'; + +describe('/api/pets', () => { + let container: StartedTestContainer; + let app: FastifyInstance; + let adminClient: DbClient; + let appClient: DbClient; + + beforeAll(async () => { + container = await new GenericContainer('postgres:14') + .withExposedPorts(5432) + .withEnvironment({ POSTGRES_PASSWORD: 'postgres' }) + .start(); + const connectionString = `postgres://postgres:postgres@${container.getHost()}:${container.getMappedPort(5432)}/postgres` + adminClient = createPgClient(connectionString); + }) + + beforeEach(async () => { + const dbName = `pets_${Date.now()}` + await adminClient.query(`CREATE DATABASE ${dbName};`) + const connectionString = `postgres://postgres:postgres@${container.getHost()}:${container.getMappedPort(5432)}/${dbName}` + appClient = createPgClient(connectionString) + await appClient.query(` +CREATE TABLE pet_owner ( + id serial PRIMARY KEY, + name varchar(50), + age integer +); + +CREATE TYPE pet_kind as ENUM('dog', 'cat', 'reptile', 'insect'); + +CREATE TABLE pet ( + id serial PRIMARY KEY, + name varchar(50), + age integer, + weight_in_kg numeric, + owner_id integer, + kind pet_kind, + FOREIGN key (owner_id) REFERENCES pet_owner (id) +); `); + app = await createApp({}, { dbClient: appClient }) + }) + + describe('POST /api/pets', () => { + + + it('should add a new pet', async () => { + const response = await app.inject() + .post('/api/pets') + .body({ + name: 'Fluffy', + age: 6, + weightInKg: 4, + kind: 'cat' + }); + const body = JSON.parse(response.body); + + expect(body).toStrictEqual({ + id: 1, + name: 'Fluffy', + age: 6, + weightInKg: 4, + kind: 'cat' + }) + + + }) + }) + + afterEach(async () => { + await app?.close(); + await appClient?.close(); + }); + afterAll(async () => { + await adminClient?.close(); + await container?.stop(); + }) +}) \ No newline at end of file From fdd90c4da85c47f33ad56d7cdfe570a83e1e4358 Mon Sep 17 00:00:00 2001 From: Daniel Hatas <21317298+samson84@users.noreply.github.com> Date: Fri, 15 Nov 2024 18:35:10 +0100 Subject: [PATCH 6/7] Create a factory function for the pet service mock . --- .../routes/owner/owner.routes.test.ts | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/controller/routes/owner/owner.routes.test.ts b/src/controller/routes/owner/owner.routes.test.ts index 5a1aa35..7536e6a 100644 --- a/src/controller/routes/owner/owner.routes.test.ts +++ b/src/controller/routes/owner/owner.routes.test.ts @@ -10,6 +10,15 @@ declare module 'fastify' { } } +function createPetServiceMock(overrides: Partial = {}) { + return { + getAll: jest.fn(), + adopt: jest.fn(), + create: jest.fn(), + getById: jest.fn(), + ...overrides + } +} describe('/owner', () => { let app: FastifyInstance; @@ -26,12 +35,10 @@ describe('/owner', () => { create: jest.fn(), getById: jest.fn(), } - const petServiceMock: PetServiceInterface = { - getAll: jest.fn(), - adopt: jest.fn(), - create: jest.fn(), - getById: jest.fn(), - } + const petServiceMock: PetServiceInterface = createPetServiceMock({ + getAll: jest.fn() + }); + app.decorate('petService', petServiceMock); app.decorate('ownerService', ownerServiceMock); await app.register(createOwnerRoutes, { prefix: '/api/owners' }) @@ -40,7 +47,7 @@ describe('/owner', () => { const body = JSON.parse(response.body); expect(body).toStrictEqual(expected); - expect(ownerServiceMock.getAll).toHaveBeenCalledTimes(2) + expect(ownerServiceMock.getAll).toHaveBeenCalledTimes(1) }) afterEach(() => { From d108fc33a43f092ec7fa59a7b8324234295a0488 Mon Sep 17 00:00:00 2001 From: Daniel Hatas <21317298+samson84@users.noreply.github.com> Date: Fri, 15 Nov 2024 18:54:34 +0100 Subject: [PATCH 7/7] increase timeout for e2e tests --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5f23b8f..afb33fc 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "prebuild": "npm run clean", "build": "tsc --project tsconfig.json ", "start": "node build/src/server.js", - "test": "jest", + "test": "jest --testTimeout 15000", "dev": "nodemon --exec ts-node src/server.ts" }, "keywords": [],