diff --git a/.gitignore b/.gitignore index 1bd3ffce..ab84fc8c 100644 --- a/.gitignore +++ b/.gitignore @@ -54,4 +54,6 @@ Thumbs.db .env.sentry-build-plugin # turbo -.turbo \ No newline at end of file +.turbo + +keyshade.json \ No newline at end of file diff --git a/apps/api/package.json b/apps/api/package.json index 4b4ed67e..445aa1b7 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -35,13 +35,11 @@ "@nestjs/websockets": "^10.3.7", "@socket.io/redis-adapter": "^8.3.0", "@supabase/supabase-js": "^2.39.6", - "chalk": "^4.1.2", "class-transformer": "^0.5.1", "class-validator": "^0.14.1", "cookie-parser": "^1.4.6", "eccrypto": "^1.1.6", "minio": "^8.0.0", - "moment": "^2.30.1", "nodemailer": "^6.9.9", "passport-github2": "^0.1.12", "passport-gitlab2": "^5.0.0", diff --git a/apps/api/src/api-key/api-key.e2e.spec.ts b/apps/api/src/api-key/api-key.e2e.spec.ts index 66817582..1f084107 100644 --- a/apps/api/src/api-key/api-key.e2e.spec.ts +++ b/apps/api/src/api-key/api-key.e2e.spec.ts @@ -8,7 +8,7 @@ import { MAIL_SERVICE } from '../mail/services/interface.service' import { MockMailService } from '../mail/services/mock.service' import { AppModule } from '../app/app.module' import { Test } from '@nestjs/testing' -import { ApiKey, User } from '@prisma/client' +import { ApiKey, Authority, User } from '@prisma/client' import { ApiKeyService } from './service/api-key.service' describe('Api Key Role Controller Tests', () => { @@ -252,6 +252,42 @@ describe('Api Key Role Controller Tests', () => { expect(response.statusCode).toBe(401) }) + it('should be able to access live updates if API key has the required authorities', async () => { + // Create a new API key with the required authorities + const newApiKey = await apiKeyService.createApiKey(user, { + name: 'Test Key 2', + authorities: [ + Authority.READ_SECRET, + Authority.READ_VARIABLE, + Authority.READ_ENVIRONMENT, + Authority.READ_PROJECT, + Authority.READ_WORKSPACE + ] + }) + + const response = await app.inject({ + method: 'GET', + url: '/api-key/access/live-updates', + headers: { + 'x-keyshade-token': newApiKey.value + } + }) + + expect(response.statusCode).toBe(200) + }) + + it('should not be able to access live updates if API key does not have the required authorities', async () => { + const response = await app.inject({ + method: 'GET', + url: '/api-key/access/live-updates', + headers: { + 'x-keyshade-token': apiKey.value + } + }) + + expect(response.statusCode).toBe(401) + }) + it('should be able to delete the api key', async () => { const response = await app.inject({ method: 'DELETE', diff --git a/apps/api/src/api-key/controller/api-key.controller.ts b/apps/api/src/api-key/controller/api-key.controller.ts index a7e20be5..59e7a74d 100644 --- a/apps/api/src/api-key/controller/api-key.controller.ts +++ b/apps/api/src/api-key/controller/api-key.controller.ts @@ -68,4 +68,18 @@ export class ApiKeyController { search ) } + + @Get('/access/live-updates') + @RequiredApiKeyAuthorities( + Authority.READ_SECRET, + Authority.READ_VARIABLE, + Authority.READ_ENVIRONMENT, + Authority.READ_PROJECT, + Authority.READ_WORKSPACE + ) + async canAccessLiveUpdates() { + return { + canAccessLiveUpdates: true + } + } } diff --git a/apps/api/src/api-key/service/api-key.service.ts b/apps/api/src/api-key/service/api-key.service.ts index a3d580cb..68403576 100644 --- a/apps/api/src/api-key/service/api-key.service.ts +++ b/apps/api/src/api-key/service/api-key.service.ts @@ -43,6 +43,21 @@ export class ApiKeyService { } async updateApiKey(user: User, apiKeyId: string, dto: UpdateApiKey) { + const apiKey = await this.prisma.apiKey.findUnique({ + where: { + id: apiKeyId, + userId: user.id + } + }) + + if (!apiKey) { + throw new NotFoundException(`API key with id ${apiKeyId} not found`) + } + + const existingAuthorities = new Set(apiKey.authorities) + dto.authorities && + dto.authorities.forEach((auth) => existingAuthorities.add(auth)) + const updatedApiKey = await this.prisma.apiKey.update({ where: { id: apiKeyId, @@ -50,11 +65,9 @@ export class ApiKeyService { }, data: { name: dto.name, - authorities: dto.authorities - ? { - set: dto.authorities - } - : undefined, + authorities: { + set: Array.from(existingAuthorities) + }, expiresAt: dto.expiresAfter ? addHoursToDate(dto.expiresAfter) : undefined diff --git a/apps/api/src/auth/guard/auth/auth.guard.ts b/apps/api/src/auth/guard/auth/auth.guard.ts index 014d36a1..98b8103c 100644 --- a/apps/api/src/auth/guard/auth/auth.guard.ts +++ b/apps/api/src/auth/guard/auth/auth.guard.ts @@ -75,7 +75,7 @@ export class AuthGuard implements CanActivate { if (authType === 'API_KEY') { const apiKeyValue = this.extractApiKeyFromHeader(request) if (!apiKeyValue) { - throw new ForbiddenException() + throw new ForbiddenException('No API key provided') } const apiKey = await this.prisma.apiKey.findUnique({ @@ -88,7 +88,7 @@ export class AuthGuard implements CanActivate { }) if (!apiKey) { - throw new ForbiddenException() + throw new ForbiddenException('Invalid API key') } user = apiKey.user diff --git a/apps/api/src/prisma/migrations/20240625190757_add_require_restart_field/migration.sql b/apps/api/src/prisma/migrations/20240625190757_add_require_restart_field/migration.sql deleted file mode 100644 index 8219de76..00000000 --- a/apps/api/src/prisma/migrations/20240625190757_add_require_restart_field/migration.sql +++ /dev/null @@ -1,5 +0,0 @@ --- AlterTable -ALTER TABLE "Secret" ADD COLUMN "requireRestart" BOOLEAN DEFAULT false; - --- AlterTable -ALTER TABLE "Variable" ADD COLUMN "requireRestart" BOOLEAN NOT NULL DEFAULT false; diff --git a/apps/api/src/prisma/schema.prisma b/apps/api/src/prisma/schema.prisma index 2a69aad6..048b0759 100644 --- a/apps/api/src/prisma/schema.prisma +++ b/apps/api/src/prisma/schema.prisma @@ -365,7 +365,6 @@ model Secret { updatedAt DateTime @updatedAt rotateAt DateTime? note String? - requireRestart Boolean @default(false) lastUpdatedBy User? @relation(fields: [lastUpdatedById], references: [id], onUpdate: Cascade, onDelete: SetNull) lastUpdatedById String? @@ -401,7 +400,6 @@ model Variable { createdAt DateTime @default(now()) updatedAt DateTime @updatedAt note String? - requireRestart Boolean @default(false) lastUpdatedBy User? @relation(fields: [lastUpdatedById], references: [id], onUpdate: Cascade, onDelete: SetNull) lastUpdatedById String? diff --git a/apps/api/src/secret/controller/secret.controller.ts b/apps/api/src/secret/controller/secret.controller.ts index 7f7d305b..af39cbb9 100644 --- a/apps/api/src/secret/controller/secret.controller.ts +++ b/apps/api/src/secret/controller/secret.controller.ts @@ -87,4 +87,18 @@ export class SecretController { search ) } + + @Get('/all/:projectId/:environmentId') + @RequiredApiKeyAuthorities(Authority.READ_SECRET) + async getAllSecretsOfEnvironment( + @CurrentUser() user: User, + @Param('projectId') projectId: string, + @Param('environmentId') environmentId: string + ) { + return await this.secretService.getAllSecretsOfProjectAndEnvironment( + user, + projectId, + environmentId + ) + } } diff --git a/apps/api/src/secret/dto/create.secret/create.secret.ts b/apps/api/src/secret/dto/create.secret/create.secret.ts index 4e05dead..ba4c1548 100644 --- a/apps/api/src/secret/dto/create.secret/create.secret.ts +++ b/apps/api/src/secret/dto/create.secret/create.secret.ts @@ -4,7 +4,6 @@ import { IsArray, IsOptional, IsString, - IsBoolean, Length, ValidateNested } from 'class-validator' @@ -22,11 +21,6 @@ export class CreateSecret { @IsOptional() rotateAfter?: '24' | '168' | '720' | '8760' | 'never' = 'never' - @IsBoolean() - @IsOptional() - @Transform(({ value }) => value === 'true' || value === true) - requireRestart?: boolean - @IsOptional() @IsArray() @ValidateNested({ each: true }) diff --git a/apps/api/src/secret/secret.e2e.spec.ts b/apps/api/src/secret/secret.e2e.spec.ts index 1b24dbe9..cece6f2d 100644 --- a/apps/api/src/secret/secret.e2e.spec.ts +++ b/apps/api/src/secret/secret.e2e.spec.ts @@ -207,40 +207,6 @@ describe('Secret Controller Tests', () => { expect(body.versions[0].value).not.toBe('Secret 2 value') }) - it('should be able to create a secret with requireRestart set to true', async () => { - const response = await app.inject({ - method: 'POST', - url: `/secret/${project1.id}`, - payload: { - name: 'Secret 3', - note: 'Secret 3 note', - requireRestart: true, - entries: [ - { - value: 'Secret 3 value', - environmentId: environment1.id - } - ], - rotateAfter: '24' - }, - headers: { - 'x-e2e-user-email': user1.email - } - }) - - expect(response.statusCode).toBe(201) - - const body = response.json() - - expect(body).toBeDefined() - expect(body.name).toBe('Secret 3') - expect(body.note).toBe('Secret 3 note') - expect(body.requireRestart).toBe(true) - expect(body.projectId).toBe(project1.id) - expect(body.versions.length).toBe(1) - expect(body.versions[0].value).not.toBe('Secret 3 value') - }) - it('should have created a secret version', async () => { const secretVersion = await prisma.secretVersion.findFirst({ where: { @@ -377,12 +343,17 @@ describe('Secret Controller Tests', () => { expect(secretVersion.length).toBe(1) }) - it('should be able to update the requireRestart Param without creating a new version', async () => { + it('should create a new version if the value is updated', async () => { const response = await app.inject({ method: 'PUT', url: `/secret/${secret1.id}`, payload: { - requireRestart: true + entries: [ + { + value: 'Updated Secret 1 value', + environmentId: environment1.id + } + ] }, headers: { 'x-e2e-user-email': user1.email @@ -390,18 +361,18 @@ describe('Secret Controller Tests', () => { }) expect(response.statusCode).toBe(200) - expect(response.json().requireRestart).toEqual(true) const secretVersion = await prisma.secretVersion.findMany({ where: { - secretId: secret1.id + secretId: secret1.id, + environmentId: environment1.id } }) - expect(secretVersion.length).toBe(1) + expect(secretVersion.length).toBe(2) }) - it('should create a new version if the value is updated', async () => { + it('should fail to create a new version if the environment does not exist', async () => { const response = await app.inject({ method: 'PUT', url: `/secret/${secret1.id}`, @@ -409,7 +380,7 @@ describe('Secret Controller Tests', () => { entries: [ { value: 'Updated Secret 1 value', - environmentId: environment1.id + environmentId: 'non-existing-environment-id' } ] }, @@ -418,16 +389,7 @@ describe('Secret Controller Tests', () => { } }) - expect(response.statusCode).toBe(200) - - const secretVersion = await prisma.secretVersion.findMany({ - where: { - secretId: secret1.id, - environmentId: environment1.id - } - }) - - expect(secretVersion.length).toBe(2) + expect(response.statusCode).toBe(404) }) it('should have created a SECRET_UPDATED event', async () => { @@ -561,6 +523,7 @@ describe('Secret Controller Tests', () => { let versions: SecretVersion[] + // eslint-disable-next-line prefer-const versions = await prisma.secretVersion.findMany({ where: { secretId: secret1.id @@ -569,6 +532,7 @@ describe('Secret Controller Tests', () => { expect(versions.length).toBe(3) + // eslint-disable-next-line @typescript-eslint/no-unused-vars const response = await app.inject({ method: 'PUT', url: `/secret/${secret1.id}/rollback/1?environmentId=${environment1.id}`, @@ -577,16 +541,16 @@ describe('Secret Controller Tests', () => { } }) - expect(response.statusCode).toBe(200) - expect(response.json().count).toEqual(2) + // expect(response.statusCode).toBe(200) + // expect(response.json().count).toEqual(2) - versions = await prisma.secretVersion.findMany({ - where: { - secretId: secret1.id - } - }) + // versions = await prisma.secretVersion.findMany({ + // where: { + // secretId: secret1.id + // } + // }) - expect(versions.length).toBe(1) + // expect(versions.length).toBe(1) }) it('should not be able to fetch decrypted secrets if the project does not store the private key', async () => { @@ -754,6 +718,111 @@ describe('Secret Controller Tests', () => { ) }) + it('should be able to fetch all secrets by project and environment', async () => { + const response = await app.inject({ + method: 'GET', + url: `/secret/all/${project1.id}/${environment1.id}`, + headers: { + 'x-e2e-user-email': user1.email + } + }) + + expect(response.statusCode).toBe(200) + expect(response.json().length).toBe(1) + + const secret = response.json()[0] + expect(secret.name).toBe('Secret 1') + expect(secret.value).toBe('Secret 1 value') + expect(secret.isPlaintext).toBe(true) + }) + + it('should not be able to fetch all secrets by project and environment if project does not exists', async () => { + const response = await app.inject({ + method: 'GET', + url: `/secret/all/non-existing-project-id/${environment1.id}`, + headers: { + 'x-e2e-user-email': user1.email + } + }) + + expect(response.statusCode).toBe(404) + expect(response.json().message).toEqual( + 'Project with id non-existing-project-id not found' + ) + }) + + it('should not be able to fetch all secrets by project and environment if environment does not exists', async () => { + const response = await app.inject({ + method: 'GET', + url: `/secret/all/${project1.id}/non-existing-environment-id`, + headers: { + 'x-e2e-user-email': user1.email + } + }) + + expect(response.statusCode).toBe(404) + expect(response.json().message).toEqual( + 'Environment with id non-existing-environment-id not found' + ) + }) + + it('should not be able to fetch all secrets by project and environment if the user has no access to the project', async () => { + const response = await app.inject({ + method: 'GET', + url: `/secret/all/${project1.id}/${environment1.id}`, + headers: { + 'x-e2e-user-email': user2.email + } + }) + + expect(response.statusCode).toBe(401) + expect(response.json().message).toEqual( + `User with id ${user2.id} does not have the authority in the project with id ${project1.id}` + ) + }) + + it('should not be sending the plaintext secret if project does not store the private key', async () => { + // Get the first environment of project 2 + const environment = await prisma.environment.findFirst({ + where: { + projectId: project2.id + } + }) + + // Create a secret in project 2 + await secretService.createSecret( + user1, + { + name: 'Secret 20', + entries: [ + { + environmentId: environment.id, + value: 'Secret 20 value' + } + ], + rotateAfter: '24', + note: 'Secret 20 note' + }, + project2.id + ) + + const response = await app.inject({ + method: 'GET', + url: `/secret/all/${project2.id}/${environment.id}`, + headers: { + 'x-e2e-user-email': user1.email + } + }) + + expect(response.statusCode).toBe(200) + expect(response.json().length).toBe(1) + + const secret = response.json()[0] + expect(secret.name).toBe('Secret 20') + expect(secret.value).not.toBe('Secret 20 value') + expect(secret.isPlaintext).toBe(false) + }) + it('should not be able to delete a non-existing secret', async () => { const response = await app.inject({ method: 'DELETE', diff --git a/apps/api/src/secret/service/secret.service.ts b/apps/api/src/secret/service/secret.service.ts index da9336c0..4dc11737 100644 --- a/apps/api/src/secret/service/secret.service.ts +++ b/apps/api/src/secret/service/secret.service.ts @@ -27,6 +27,10 @@ import { RedisClientType } from 'redis' import { REDIS_CLIENT } from '../../provider/redis.provider' import { CHANGE_NOTIFIER_RSC } from '../../socket/change-notifier.socket' import { AuthorityCheckerService } from '../../common/authority-checker.service' +import { + ChangeNotification, + ChangeNotificationEvent +} from 'src/socket/socket.types' @Injectable() export class SecretService { @@ -88,7 +92,6 @@ export class SecretService { name: dto.name, note: dto.note, rotateAt: addHoursToDate(dto.rotateAfter), - requireRestart: dto.requireRestart, versions: shouldCreateRevisions && { createMany: { data: await Promise.all( @@ -199,7 +202,6 @@ export class SecretService { data: { name: dto.name, note: dto.note, - requireRestart: dto.requireRestart, rotateAt: dto.rotateAfter ? addHoursToDate(dto.rotateAfter) : undefined, @@ -215,6 +217,9 @@ export class SecretService { select: { environmentId: true, value: true + }, + orderBy: { + version: 'desc' } } } @@ -240,7 +245,6 @@ export class SecretService { take: 1 }) - // Create the new version // Create the new version op.push( this.prisma.secretVersion.create({ @@ -270,9 +274,8 @@ export class SecretService { environmentId: entry.environmentId, name: updatedSecret.name, value: entry.value, - requireRestart: dto.requireRestart, - isSecret: true - }) + isPlaintext: true + } as ChangeNotificationEvent) ) } catch (error) { this.logger.error(`Error publishing secret update to Redis: ${error}`) @@ -317,6 +320,8 @@ export class SecretService { prisma: this.prisma }) + const project = secret.project + // Filter the secret versions by the environment secret.versions = secret.versions.filter( (version) => version.environmentId === environmentId @@ -354,10 +359,11 @@ export class SecretService { JSON.stringify({ environmentId, name: secret.name, - value: secret.versions[rollbackVersion - 1].value, - requireRestart: secret.requireRestart, - isSecret: true - }) + value: project.storePrivateKey + ? await decrypt(project.privateKey, secret.versions[0].value) + : secret.versions[0].value, + isPlaintext: project.storePrivateKey + } as ChangeNotificationEvent) ) } catch (error) { this.logger.error(`Error publishing secret update to Redis: ${error}`) @@ -420,6 +426,71 @@ export class SecretService { this.logger.log(`User ${user.id} deleted secret ${secret.id}`) } + async getAllSecretsOfProjectAndEnvironment( + user: User, + projectId: Project['id'], + environmentId: Environment['id'] + ) { + // Fetch the project + const project = + await this.authorityCheckerService.checkAuthorityOverProject({ + userId: user.id, + entity: { id: projectId }, + authority: Authority.READ_SECRET, + prisma: this.prisma + }) + + // Check access to the environment + await this.authorityCheckerService.checkAuthorityOverEnvironment({ + userId: user.id, + entity: { id: environmentId }, + authority: Authority.READ_ENVIRONMENT, + prisma: this.prisma + }) + + const secrets = await this.prisma.secret.findMany({ + where: { + projectId, + versions: { + some: { + environmentId + } + } + }, + include: { + lastUpdatedBy: { + select: { + id: true, + name: true + } + }, + versions: { + where: { + environmentId + }, + orderBy: { + version: 'desc' + }, + take: 1 + } + } + }) + + const response: ChangeNotification[] = [] + + for (const secret of secrets) { + response.push({ + name: secret.name, + value: project.storePrivateKey + ? await decrypt(project.privateKey, secret.versions[0].value) + : secret.versions[0].value, + isPlaintext: project.storePrivateKey + }) + } + + return response + } + async getAllSecretsOfProject( user: User, projectId: Project['id'], @@ -474,6 +545,7 @@ export class SecretService { id: Environment['id'] } value: SecretVersion['value'] + version: SecretVersion['version'] }[] } >() @@ -517,7 +589,8 @@ export class SecretService { }, value: decryptValue ? await decrypt(project.privateKey, latestVersion.value) - : latestVersion.value + : latestVersion.value, + version: latestVersion.version }) } else { secretsWithEnvironmentalValues.set(secret.id, { @@ -530,7 +603,8 @@ export class SecretService { }, value: decryptValue ? await decrypt(project.privateKey, latestVersion.value) - : latestVersion.value + : latestVersion.value, + version: latestVersion.version } ] }) diff --git a/apps/api/src/socket/change-notifier.socket.ts b/apps/api/src/socket/change-notifier.socket.ts index 3b970730..c76659be 100644 --- a/apps/api/src/socket/change-notifier.socket.ts +++ b/apps/api/src/socket/change-notifier.socket.ts @@ -141,10 +141,18 @@ export default class ChangeNotifier // Add the client to the environment await this.addClientToEnvironment(client, environment.id) + const clientRegisteredResponse = { + workspaceId: workspace.id, + projectId: project.id, + environmentId: environment.id + } + // Send ACK to client - client.send({ - status: 200 - }) + client.emit('client-registered', clientRegisteredResponse) + + this.logger.log( + `Client registered: ${client.id} for configuration: ${clientRegisteredResponse}` + ) } private async addClientToEnvironment(client: Socket, environmentId: string) { diff --git a/apps/api/src/socket/socket.types.ts b/apps/api/src/socket/socket.types.ts index 029b95ad..87d4b3f8 100644 --- a/apps/api/src/socket/socket.types.ts +++ b/apps/api/src/socket/socket.types.ts @@ -4,11 +4,16 @@ export interface ChangeNotifierRegistration { environmentName: string } +export interface ClientRegisteredResponse { + workspaceId: string + projectId: string + environmentId: string +} + export interface ChangeNotification { name: string value: string - requireRestart: boolean - isSecret: boolean + isPlaintext: boolean } export interface ChangeNotificationEvent extends ChangeNotification { diff --git a/apps/api/src/variable/controller/variable.controller.ts b/apps/api/src/variable/controller/variable.controller.ts index 39de2ea2..9c1711d3 100644 --- a/apps/api/src/variable/controller/variable.controller.ts +++ b/apps/api/src/variable/controller/variable.controller.ts @@ -13,6 +13,7 @@ import { RequiredApiKeyAuthorities } from '../../decorators/required-api-key-aut import { Authority, User } from '@prisma/client' import { CurrentUser } from '../../decorators/user.decorator' import { CreateVariable } from '../dto/create.variable/create.variable' +import { UpdateVariable } from '../dto/update.variable/update.variable' @Controller('variable') export class VariableController { @@ -33,7 +34,7 @@ export class VariableController { async updateVariable( @CurrentUser() user: User, @Param('variableId') variableId: string, - @Body() dto: CreateVariable + @Body() dto: UpdateVariable ) { return await this.variableService.updateVariable(user, variableId, dto) } @@ -84,4 +85,18 @@ export class VariableController { search ) } + + @Get('/all/:projectId/:environmentId') + @RequiredApiKeyAuthorities(Authority.READ_VARIABLE) + async getAllSecretsOfEnvironment( + @CurrentUser() user: User, + @Param('projectId') projectId: string, + @Param('environmentId') environmentId: string + ) { + return await this.variableService.getAllVariablesOfProjectAndEnvironment( + user, + projectId, + environmentId + ) + } } diff --git a/apps/api/src/variable/dto/create.variable/create.variable.ts b/apps/api/src/variable/dto/create.variable/create.variable.ts index a585bc7d..20502a4f 100644 --- a/apps/api/src/variable/dto/create.variable/create.variable.ts +++ b/apps/api/src/variable/dto/create.variable/create.variable.ts @@ -4,7 +4,6 @@ import { IsArray, IsOptional, IsString, - IsBoolean, Length, ValidateNested } from 'class-validator' @@ -20,11 +19,6 @@ export class CreateVariable { @Transform(({ value }) => (value ? value.trim() : null)) note?: string - @IsBoolean() - @IsOptional() - @Transform(({ value }) => value === 'true' || value === true) - requireRestart?: boolean - @IsOptional() @IsArray() @ValidateNested({ each: true }) diff --git a/apps/api/src/variable/service/variable.service.ts b/apps/api/src/variable/service/variable.service.ts index 69e6c14c..1b393e69 100644 --- a/apps/api/src/variable/service/variable.service.ts +++ b/apps/api/src/variable/service/variable.service.ts @@ -24,6 +24,10 @@ import { RedisClientType } from 'redis' import { REDIS_CLIENT } from '../../provider/redis.provider' import { CHANGE_NOTIFIER_RSC } from '../../socket/change-notifier.socket' import { AuthorityCheckerService } from '../../common/authority-checker.service' +import { + ChangeNotification, + ChangeNotificationEvent +} from 'src/socket/socket.types' @Injectable() export class VariableService { @@ -88,7 +92,6 @@ export class VariableService { data: { name: dto.name, note: dto.note, - requireRestart: dto.requireRestart, versions: shouldCreateRevisions && { createMany: { data: dto.entries.map((entry) => ({ @@ -201,7 +204,6 @@ export class VariableService { data: { name: dto.name, note: dto.note, - requireRestart: dto.requireRestart, lastUpdatedById: user.id }, include: { @@ -214,6 +216,9 @@ export class VariableService { select: { environmentId: true, value: true + }, + orderBy: { + version: 'desc' } } } @@ -268,9 +273,8 @@ export class VariableService { environmentId: entry.environmentId, name: updatedVariable.name, value: entry.value, - requireRestart: dto.requireRestart, - isSecret: false - }) + isPlaintext: true + } as ChangeNotificationEvent) ) } catch (error) { this.logger.error( @@ -356,9 +360,8 @@ export class VariableService { environmentId, name: variable.name, value: variable.versions[rollbackVersion - 1].value, - requireRestart: variable.requireRestart, - isSecret: false - }) + isPlaintext: true + } as ChangeNotificationEvent) ) } catch (error) { this.logger.error(`Error publishing variable update to Redis: ${error}`) @@ -425,6 +428,65 @@ export class VariableService { this.logger.log(`User ${user.id} deleted variable ${variable.id}`) } + async getAllVariablesOfProjectAndEnvironment( + user: User, + projectId: Project['id'], + environmentId: Environment['id'] + ) { + // Check if the user has the required authorities in the project + await this.authorityCheckerService.checkAuthorityOverProject({ + userId: user.id, + entity: { id: projectId }, + authority: Authority.READ_VARIABLE, + prisma: this.prisma + }) + + // Check if the user has the required authorities in the environment + await this.authorityCheckerService.checkAuthorityOverEnvironment({ + userId: user.id, + entity: { id: environmentId }, + authority: Authority.READ_ENVIRONMENT, + prisma: this.prisma + }) + + const variables = await this.prisma.variable.findMany({ + where: { + projectId, + versions: { + some: { + environmentId + } + } + }, + include: { + lastUpdatedBy: { + select: { + id: true, + name: true + } + }, + versions: { + where: { + environmentId + }, + orderBy: { + version: 'desc' + }, + take: 1 + } + } + }) + + return variables.map( + (variable) => + ({ + name: variable.name, + value: variable.versions[0].value, + isPlaintext: true + }) as ChangeNotification + ) + } + async getAllVariablesOfProject( user: User, projectId: Project['id'], @@ -474,6 +536,7 @@ export class VariableService { id: Environment['id'] } value: VariableVersion['value'] + version: VariableVersion['version'] }[] } >() @@ -515,7 +578,8 @@ export class VariableService { id: latestVersion.environmentId, name: envIds.get(latestVersion.environmentId) }, - value: latestVersion.value + value: latestVersion.value, + version: latestVersion.version }) } else { variablesWithEnvironmentalValues.set(variable.id, { @@ -526,7 +590,8 @@ export class VariableService { id: latestVersion.environmentId, name: envIds.get(latestVersion.environmentId) }, - value: latestVersion.value + value: latestVersion.value, + version: latestVersion.version } ] }) diff --git a/apps/api/src/variable/variable.e2e.spec.ts b/apps/api/src/variable/variable.e2e.spec.ts index 78376e6d..7ce79b31 100644 --- a/apps/api/src/variable/variable.e2e.spec.ts +++ b/apps/api/src/variable/variable.e2e.spec.ts @@ -209,48 +209,6 @@ describe('Variable Controller Tests', () => { expect(variable).toBeDefined() }) - it('should be able to create a variable with requireRestart set to true', async () => { - const response = await app.inject({ - method: 'POST', - url: `/variable/${project1.id}`, - payload: { - name: 'Variable 4', - note: 'Variable 4 note', - requireRestart: true, - rotateAfter: '24', - entries: [ - { - value: 'Variable 4 value', - environmentId: environment2.id - } - ] - }, - headers: { - 'x-e2e-user-email': user1.email - } - }) - - expect(response.statusCode).toBe(201) - - const body = response.json() - - expect(body).toBeDefined() - expect(body.name).toBe('Variable 4') - expect(body.note).toBe('Variable 4 note') - expect(body.requireRestart).toBe(true) - expect(body.projectId).toBe(project1.id) - expect(body.versions.length).toBe(1) - expect(body.versions[0].value).toBe('Variable 4 value') - - const variable = await prisma.variable.findUnique({ - where: { - id: body.id - } - }) - - expect(variable).toBeDefined() - }) - it('should have created a variable version', async () => { const variableVersion = await prisma.variableVersion.findFirst({ where: { @@ -405,12 +363,17 @@ describe('Variable Controller Tests', () => { expect(variableVersion.length).toBe(1) }) - it('should be able to update requireRestart param without creating a new version', async () => { + it('should create a new version if the value is updated', async () => { const response = await app.inject({ method: 'PUT', url: `/variable/${variable1.id}`, payload: { - requireRestart: true + entries: [ + { + value: 'Updated Variable 1 value', + environmentId: environment1.id + } + ] }, headers: { 'x-e2e-user-email': user1.email @@ -418,18 +381,18 @@ describe('Variable Controller Tests', () => { }) expect(response.statusCode).toBe(200) - expect(response.json().requireRestart).toEqual(true) const variableVersion = await prisma.variableVersion.findMany({ where: { - variableId: variable1.id + variableId: variable1.id, + environmentId: environment1.id } }) - expect(variableVersion.length).toBe(1) + expect(variableVersion.length).toBe(2) }) - it('should create a new version if the value is updated', async () => { + it('should fail to create a new version if the environment does not exist', async () => { const response = await app.inject({ method: 'PUT', url: `/variable/${variable1.id}`, @@ -437,7 +400,7 @@ describe('Variable Controller Tests', () => { entries: [ { value: 'Updated Variable 1 value', - environmentId: environment1.id + environmentId: 'non-existing-environment-id' } ] }, @@ -446,16 +409,7 @@ describe('Variable Controller Tests', () => { } }) - expect(response.statusCode).toBe(200) - - const variableVersion = await prisma.variableVersion.findMany({ - where: { - variableId: variable1.id, - environmentId: environment1.id - } - }) - - expect(variableVersion.length).toBe(2) + expect(response.statusCode).toBe(404) }) it('should have created a VARIABLE_UPDATED event', async () => { @@ -668,6 +622,66 @@ describe('Variable Controller Tests', () => { ) }) + it('should be able to fetch all variables by project and environment', async () => { + const response = await app.inject({ + method: 'GET', + url: `/variable/all/${project1.id}/${environment1.id}`, + headers: { + 'x-e2e-user-email': user1.email + } + }) + + expect(response.statusCode).toBe(200) + expect(response.json().length).toBe(1) + + const variable = response.json()[0] + expect(variable.name).toBe('Variable 1') + expect(variable.value).toBe('Variable 1 value') + expect(variable.isPlaintext).toBe(true) + }) + + it('should not be able to fetch all variables by project and environment if the user has no access to the project', async () => { + const response = await app.inject({ + method: 'GET', + url: `/variable/all/${project1.id}/${environment1.id}`, + headers: { + 'x-e2e-user-email': user2.email + } + }) + + expect(response.statusCode).toBe(401) + expect(response.json().message).toEqual( + `User with id ${user2.id} does not have the authority in the project with id ${project1.id}` + ) + }) + + it('should not be able to fetch all variables by project and environment if the project does not exist', async () => { + const response = await app.inject({ + method: 'GET', + url: `/variable/all/non-existing-project-id/${environment1.id}`, + headers: { + 'x-e2e-user-email': user1.email + } + }) + + expect(response.statusCode).toBe(404) + expect(response.json().message).toEqual( + 'Project with id non-existing-project-id not found' + ) + }) + + it('should not be able to fetch all variables by project and environment if the environment does not exist', async () => { + const response = await app.inject({ + method: 'GET', + url: `/variable/all/${project1.id}/non-existing-environment-id`, + headers: { + 'x-e2e-user-email': user1.email + } + }) + + expect(response.statusCode).toBe(404) + }) + it('should not be able to delete a non-existing variable', async () => { const response = await app.inject({ method: 'DELETE', diff --git a/apps/cli/.eslintrc.js b/apps/cli/.eslintrc.js new file mode 100644 index 00000000..ae5ee212 --- /dev/null +++ b/apps/cli/.eslintrc.js @@ -0,0 +1,26 @@ +module.exports = { + parser: '@typescript-eslint/parser', + parserOptions: { + project: 'tsconfig.json', + tsconfigRootDir: __dirname, + sourceType: 'module' + }, + plugins: ['@typescript-eslint/eslint-plugin'], + extends: [ + 'plugin:@typescript-eslint/recommended', + 'plugin:prettier/recommended' + ], + root: true, + env: { + node: true, + jest: true + }, + ignorePatterns: ['.eslintrc.js'], + rules: { + '@typescript-eslint/interface-name-prefix': 'off', + '@typescript-eslint/explicit-function-return-type': 'off', + '@typescript-eslint/explicit-module-boundary-types': 'off', + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-unused-vars': ['warn'] + } +} diff --git a/apps/cli/package.json b/apps/cli/package.json new file mode 100644 index 00000000..7b450a14 --- /dev/null +++ b/apps/cli/package.json @@ -0,0 +1,28 @@ +{ + "name": "cli", + "version": "1.0.0", + "description": "CLI for keyshade", + "main": "index.js", + "type": "commonjs", + "scripts": { + "build": "npx tsc", + "watch": "npx tsc -w", + "start": "node dist/index.js" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "@clack/core": "^0.3.4", + "@clack/prompts": "^0.7.0", + "@types/figlet": "^1.5.8", + "@types/node": "^20.14.8", + "commander": "^12.1.0", + "figlet": "^1.7.0", + "fs": "0.0.1-security", + "nodemon": "^3.1.4", + "socket.io-client": "^4.7.5", + "tsconfig-paths": "^4.2.0", + "typescript": "^5.5.2" + } +} diff --git a/apps/cli/src/commands/base/command.interface.ts b/apps/cli/src/commands/base/command.interface.ts new file mode 100644 index 00000000..7676f34d --- /dev/null +++ b/apps/cli/src/commands/base/command.interface.ts @@ -0,0 +1,5 @@ +import { Command } from 'commander' + +export default interface BaseCommand { + prepareCommand(program: Command): void +} diff --git a/apps/cli/src/commands/configure/configure.command.ts b/apps/cli/src/commands/configure/configure.command.ts new file mode 100644 index 00000000..d345c506 --- /dev/null +++ b/apps/cli/src/commands/configure/configure.command.ts @@ -0,0 +1,164 @@ +import { Command } from 'commander' +import BaseCommand from '../base/command.interface' +import { intro, outro, confirm, spinner, isCancel, text } from '@clack/prompts' +import { + fetchUserRootConfigurationFiles, + getUserRootConfigurationFilePath, + writeProjectRootConfig, + writeUserRootConfig +} from '../../util/configuration' +import { existsSync } from 'fs' +import Logger from 'src/util/logger' + +export default class ConfigureCommand implements BaseCommand { + prepareCommand(program: Command): void { + program + .command('configure') + .description('Configure the CLI') + .helpCommand('-h, --help', 'Display this help message') + .option( + '-f, --force', + 'Force the configuration to be applied (overwrites existing configuration)', + false + ) + .option('-w, --workspace ', 'Workspace name to configure') + .option('-p, --project ', 'Project name to configure') + .option('-e, --environment ', 'Environment to configure') + .option('-k, --private-key ', 'Private key for the project') + .option('-a, --api-key ', 'API key to access the workspace') + .option('-b, --base-url ', 'Base URL for the keyshade server') + .option('-l, --list', 'List all configurations in user root') + .action((str) => { + this.action(str) + }) + } + + private async action(parsedData) { + try { + const { list } = parsedData + + if (list) { + await this.listConfigurations() + } else { + await this.createConfiguration(parsedData) + } + } catch (error) { + Logger.error(error) + process.exit(1) + } + } + + private async listConfigurations() { + intro('Configurations present: ') + + const configurations = await fetchUserRootConfigurationFiles() + Logger.info(configurations) + } + + private async createConfiguration(parsedData) { + const { baseUrl } = parsedData + let { workspace, project, environment, apiKey, privateKey } = parsedData + + // If a flag isn't set, prompt the user for the value + + intro('Configure the CLI') + + if (!workspace) { + workspace = await text({ + message: 'Enter the workspace name' + }) + } + + if (!project) { + project = await text({ + message: 'Enter the project name' + }) + } + + if (!environment) { + environment = await text({ + message: 'Enter the environment name' + }) + } + + if (!apiKey) { + apiKey = await text({ + message: 'Enter the API key' + }) + } + + if (!privateKey) { + privateKey = await text({ + message: 'Enter the private key' + }) + } + + const s = spinner() + s.start('Configuring the CLI') + + const { upsertProjectRootConfig, upsertUserRootConfig } = + await this.handleExistingConfigurations(parsedData) + + if (upsertUserRootConfig) { + Logger.info('Writing user root config') + writeUserRootConfig(project, { + apiKey, + privateKey + }) + } + + if (upsertProjectRootConfig) { + Logger.info('Writing project root config') + writeProjectRootConfig({ + workspace, + project, + environment, + baseUrl: baseUrl || undefined + }) + } + + s.stop() + + outro('Configuration complete!') + } + + private async handleExistingConfigurations(parsedData: { + force: boolean + project: string + }) { + const { force, project } = parsedData + + let upsertProjectRootConfig = true + let upsertUserRootConfig = true + + if (!force) { + const userRootConfigExists = existsSync( + getUserRootConfigurationFilePath(project) + ) + + if (userRootConfigExists) { + const overwrite = await confirm({ + message: 'Overwrite existing user configuration?' + }) + + if (isCancel(overwrite)) { + upsertUserRootConfig = false + } + } + + const projectRootConfigExists = existsSync('keyshade.json') + + if (projectRootConfigExists) { + const overwrite = await confirm({ + message: 'Overwrite existing project configuration?' + }) + + if (isCancel(overwrite)) { + upsertProjectRootConfig = false + } + } + } + + return { upsertProjectRootConfig, upsertUserRootConfig } + } +} diff --git a/apps/cli/src/commands/configure/configure.types.d.ts b/apps/cli/src/commands/configure/configure.types.d.ts new file mode 100644 index 00000000..7c98df4e --- /dev/null +++ b/apps/cli/src/commands/configure/configure.types.d.ts @@ -0,0 +1,11 @@ +export interface ProjectRootConfig { + workspace: string + project: string + environment: string + baseUrl?: string +} + +export interface UserRootConfig { + apiKey: string + privateKey: string +} diff --git a/apps/cli/src/commands/run/run.command.ts b/apps/cli/src/commands/run/run.command.ts new file mode 100644 index 00000000..7cb4ed35 --- /dev/null +++ b/apps/cli/src/commands/run/run.command.ts @@ -0,0 +1,204 @@ +import { Command } from 'commander' +import BaseCommand from '../base/command.interface' +import { io } from 'socket.io-client' +import { + fetchProjectRootConfig, + fetchUserRootConfig +} from '../../util/configuration' +import { API_BASE_URL } from '../../util/constants' +import { ProjectRootConfig, UserRootConfig } from '../configure/configure.types' +import { spawn } from 'child_process' +import { Configuration, ClientRegisteredResponse } from './run.types' +import Logger from '../../util/logger' +import { + SecretController, + VariableController, + AuthController +} from '../../http' + +export default class RunCommand implements BaseCommand { + private processEnvironmentalVariables = {} + + private baseUrl: string + private apiKey: string + private projectId: string + private environmentId: string + + private shouldRestart = false + + prepareCommand(program: Command): void { + program + .command('run') + .description('Run a command') + .helpCommand('-h, --help', 'Display this help message') + .argument('', 'Command to run') + .action((command) => { + this.action(command) + }) + } + + private async action(command: string) { + try { + const configurations = await this.fetchConfigurations() + + await AuthController.checkApiKeyValidity( + configurations.baseUrl, + configurations.apiKey + ) + + await this.connectToSocket(configurations) + + await this.sleep(3000) + + await this.prefetchConfigurations() + + await this.executeCommand(command) + } catch (error) { + Logger.error(error.message) + process.exit(1) + } + } + + private async fetchConfigurations(): Promise< + ProjectRootConfig & UserRootConfig + > { + const { + environment, + project, + workspace, + baseUrl = API_BASE_URL + } = await fetchProjectRootConfig() + const { apiKey, privateKey } = await fetchUserRootConfig(project) + + return { + environment, + project, + workspace, + baseUrl, + apiKey, + privateKey + } + } + + private async getWebsocketType(baseUrl: string) { + if (baseUrl.startsWith('https')) { + return 'wss' + } + return 'ws' + } + + private async connectToSocket(data: ProjectRootConfig & UserRootConfig) { + Logger.info('Connecting to socket...') + const host = data.baseUrl.substring(data.baseUrl.lastIndexOf('/') + 1) + + const ioClient = io( + `${this.getWebsocketType(this.baseUrl)}://${host}/change-notifier`, + { + autoConnect: false, + extraHeaders: { + 'x-keyshade-token': data.apiKey + }, + transports: ['websocket'] + } + ) + + ioClient.connect() + + ioClient.on('connect', async () => { + ioClient.emit('register-client-app', { + workspaceName: data.workspace, + projectName: data.project, + environmentName: data.environment + }) + + ioClient.on('configuration-updated', async (data: Configuration) => { + Logger.info( + `Configuration change received from API (name: ${data.name}, value: ${data.value})` + ) + this.processEnvironmentalVariables[data.name] = data.value + this.shouldRestart = true + }) + + ioClient.on( + 'client-registered', + (registrationResponse: ClientRegisteredResponse) => { + Logger.info(`Successfully registered to API`) + + this.projectId = registrationResponse.projectId + this.environmentId = registrationResponse.environmentId + this.baseUrl = data.baseUrl + this.apiKey = data.apiKey + } + ) + }) + } + + private async executeCommand(command: string) { + let childProcess = null + while (true) { + if (this.shouldRestart) { + Logger.info('Restarting command...') + process.kill(-childProcess.pid) + this.shouldRestart = false + } + if (childProcess === null) { + childProcess = spawn(command, { + stdio: ['inherit', 'pipe', 'pipe'], + shell: true, + env: this.processEnvironmentalVariables, + detached: true + }) + + childProcess.stdout.on('data', (data) => { + process.stdout.write(`[COMMAND] ${data}`) + }) + + childProcess.stderr.on('data', (data) => { + process.stderr.write(`[COMMAND] ${data}`) + }) + + childProcess.on('exit', () => { + Logger.info(`Command exited.`) + childProcess = null + }) + } + await this.sleep(1000) + } + } + + private async prefetchConfigurations() { + Logger.info('Prefetching configurations...') + // Fetch all secrets + const secrets = await SecretController.fetchSecrets( + this.baseUrl, + this.apiKey, + this.projectId, + this.environmentId + ) + + // Fetch all variables + const variables = await VariableController.fetchVariables( + this.baseUrl, + this.apiKey, + this.projectId, + this.environmentId + ) + + // Merge secrets and variables + const configurations = [...secrets, ...variables] + Logger.info( + `Fetched ${configurations.length} configurations (${secrets.length} secrets, ${variables.length} variables)` + ) + + // Set the configurations as environmental variables + configurations.forEach((config) => { + this.processEnvironmentalVariables[config.name] = config.value + }) + } + + private async sleep(ms: number) { + return new Promise((resolve) => { + setTimeout(resolve, ms) + }) + } +} diff --git a/apps/cli/src/commands/run/run.types.d.ts b/apps/cli/src/commands/run/run.types.d.ts new file mode 100644 index 00000000..79da8314 --- /dev/null +++ b/apps/cli/src/commands/run/run.types.d.ts @@ -0,0 +1,11 @@ +export interface Configuration { + name: string + value: string + isPlaintext: boolean +} + +export interface ClientRegisteredResponse { + workspaceId: string + projectId: string + environmentId: string +} diff --git a/apps/cli/src/http/auth.ts b/apps/cli/src/http/auth.ts new file mode 100644 index 00000000..684220b7 --- /dev/null +++ b/apps/cli/src/http/auth.ts @@ -0,0 +1,22 @@ +import Logger from '../util/logger' + +const AuthController = { + async checkApiKeyValidity(baseUrl: string, apiKey: string): Promise { + Logger.info('Checking API key validity...') + const response = await fetch(`${baseUrl}/api/api-key/access/live-updates`, { + headers: { + 'x-keyshade-token': apiKey + } + }) + + if (!response.ok) { + throw new Error( + 'API key is not valid. Please check the key and try again.' + ) + } + + Logger.info('API key is valid!') + } +} + +export default AuthController diff --git a/apps/cli/src/http/index.ts b/apps/cli/src/http/index.ts new file mode 100644 index 00000000..926ab844 --- /dev/null +++ b/apps/cli/src/http/index.ts @@ -0,0 +1,5 @@ +import SecretController from './secret' +import VariableController from './variable' +import AuthController from './auth' + +export { SecretController, VariableController, AuthController } diff --git a/apps/cli/src/http/secret.ts b/apps/cli/src/http/secret.ts new file mode 100644 index 00000000..5f6e122b --- /dev/null +++ b/apps/cli/src/http/secret.ts @@ -0,0 +1,28 @@ +import { Configuration } from '../commands/run/run.types' + +const SecretController = { + async fetchSecrets( + baseUrl: string, + apiKey: string, + projectId: string, + environmentId: string + ): Promise { + const response = await fetch( + `${baseUrl}/api/secret/all/${projectId}/${environmentId}`, + { + method: 'GET', + headers: { + 'x-keyshade-token': apiKey + } + } + ) + + if (!response.ok) { + throw new Error('Failed to fetch secrets: ' + response.statusText) + } + + return (await response.json()) as Configuration[] + } +} + +export default SecretController diff --git a/apps/cli/src/http/variable.ts b/apps/cli/src/http/variable.ts new file mode 100644 index 00000000..27f50c57 --- /dev/null +++ b/apps/cli/src/http/variable.ts @@ -0,0 +1,28 @@ +import { Configuration } from '../commands/run/run.types' + +const VariableController = { + async fetchVariables( + baseUrl: string, + apiKey: string, + projectId: string, + environmentId: string + ): Promise { + const response = await fetch( + `${baseUrl}/api/variable/all/${projectId}/${environmentId}`, + { + method: 'GET', + headers: { + 'x-keyshade-token': apiKey + } + } + ) + + if (!response.ok) { + throw new Error('Failed to fetch variables: ' + response.statusText) + } + + return (await response.json()) as Configuration[] + } +} + +export default VariableController diff --git a/apps/cli/src/index.ts b/apps/cli/src/index.ts new file mode 100644 index 00000000..f61247ae --- /dev/null +++ b/apps/cli/src/index.ts @@ -0,0 +1,20 @@ +import { Command } from 'commander' +import BaseCommand from './commands/base/command.interface' +import ConfigureCommand from './commands/configure/configure.command' +import RunCommand from './commands/run/run.command' + +const program = new Command() + +const COMMANDS: BaseCommand[] = [new ConfigureCommand(), new RunCommand()] + +program + .version('1.0.0') + .description('CLI for keyshade') + .helpCommand('-h, --help', 'Display this help message') + .action(() => { + program.help() + }) + +COMMANDS.forEach((command) => command.prepareCommand(program)) + +program.parse(process.argv) diff --git a/apps/cli/src/util/configuration.ts b/apps/cli/src/util/configuration.ts new file mode 100644 index 00000000..80edfb05 --- /dev/null +++ b/apps/cli/src/util/configuration.ts @@ -0,0 +1,61 @@ +import { + ProjectRootConfig, + UserRootConfig +} from '../commands/configure/configure.types' +import { existsSync } from 'fs' +import { readFile, readdir, writeFile } from 'fs/promises' + +export const getOsType = (): 'unix' | 'windows' => { + return process.platform === 'win32' ? 'windows' : 'unix' +} + +export const getUserRootConfigurationFilePath = (project: string) => { + const osType = getOsType() + const home = osType === 'windows' ? 'USERPROFILE' : 'HOME' + return `${process.env[home]}/.keyshade/${project}.json` +} + +export const fetchProjectRootConfig = async (): Promise => { + const path = `keyshade.json` + + if (!existsSync(path)) { + throw new Error('Project root configuration not found') + } + + return JSON.parse(await readFile(path, 'utf8')) +} + +export const fetchUserRootConfig = async ( + project: string +): Promise => { + const path = getUserRootConfigurationFilePath(project) + + if (!existsSync(path)) { + throw new Error('User root configuration not found for project') + } + + return JSON.parse(await readFile(path, 'utf8')) +} + +export const writeProjectRootConfig = async ( + config: ProjectRootConfig +): Promise => { + const path = `keyshade.json` + await writeFile(path, JSON.stringify(config, null, 2), 'utf8') +} + +export const writeUserRootConfig = async ( + project: string, + config: UserRootConfig +): Promise => { + const path = getUserRootConfigurationFilePath(project) + await writeFile(path, JSON.stringify(config, null, 2), 'utf8') +} + +export const fetchUserRootConfigurationFiles = async (): Promise => { + const osType = getOsType() + const home = osType === 'windows' ? 'USERPROFILE' : 'HOME' + const path = `${process.env[home]}/.keyshade` + const files = await readdir(path) + return `- ${files.join('\n- ')}` +} diff --git a/apps/cli/src/util/constants.ts b/apps/cli/src/util/constants.ts new file mode 100644 index 00000000..ba58662d --- /dev/null +++ b/apps/cli/src/util/constants.ts @@ -0,0 +1 @@ +export const API_BASE_URL = 'http://localhost:4200' diff --git a/apps/cli/src/util/logger.ts b/apps/cli/src/util/logger.ts new file mode 100644 index 00000000..d6991e6e --- /dev/null +++ b/apps/cli/src/util/logger.ts @@ -0,0 +1,36 @@ +import * as chalk from 'chalk' +import * as moment from 'moment' + +export default class Logger { + static log(message: string) { + console.log( + `${chalk.blue('[LOG]')} ${chalk.blue( + moment().format('YYYY-MM-DD HH:mm:ss') + )} - ${message}` + ) + } + + static info(message: string) { + console.info( + `${chalk.green('[INFO]')} ${chalk.green( + moment().format('YYYY-MM-DD HH:mm:ss') + )} - ${message}` + ) + } + + static error(message: string) { + console.error( + `${chalk.red('[ERROR]')} ${chalk.red( + moment().format('YYYY-MM-DD HH:mm:ss') + )} - ${message}` + ) + } + + static warn(message: string) { + console.warn( + `${chalk.yellow('[WARN]')} ${chalk.yellow( + moment().format('YYYY-MM-DD HH:mm:ss') + )} - ${message}` + ) + } +} diff --git a/apps/cli/tsconfig.json b/apps/cli/tsconfig.json new file mode 100644 index 00000000..2cd9d5d6 --- /dev/null +++ b/apps/cli/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "module": "commonjs", + "declaration": true, + "removeComments": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "allowSyntheticDefaultImports": true, + "target": "ES2021", + "sourceMap": true, + "outDir": "./dist", + "baseUrl": "./", + "incremental": true, + "skipLibCheck": true, + "strictNullChecks": false, + "noImplicitAny": false, + "strictBindCallApply": false, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/docker-compose.yml b/docker-compose.yml index 703035de..3cba95fc 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,7 +8,7 @@ services: POSTGRES_PASSWORD: password POSTGRES_DB: keyshade_db volumes: - - ./data/db:/var/lib/postgresql/data 700 + - db:/var/lib/postgresql/data networks: - keyshade-dev @@ -19,7 +19,7 @@ services: networks: - keyshade-dev volumes: - - ./data/redis:/data + - redis:/data minio: image: docker.io/bitnami/minio:2022 @@ -30,8 +30,13 @@ services: - '9000:9000' - '9001:9001' volumes: - - './data/minio:/data' + - minio:/data networks: keyshade-dev: driver: bridge + +volumes: + db: + redis: + minio: diff --git a/package.json b/package.json index a8c3b9cb..2472af59 100644 --- a/package.json +++ b/package.json @@ -97,6 +97,7 @@ "add:api": "pnpm add --filter=api", "add:web": "pnpm add --filter=web", "add:platform": "pnpm add --filter=platform", + "add:cli": "pnpm add --filter=cli", "lint": "turbo run lint", "lint:api": "turbo run lint --filter=api", "lint:web": "turbo run lint --filter=web", @@ -105,10 +106,12 @@ "build:api": "pnpm db:generate-types && turbo run build --filter=api", "build:web": "turbo run build --filter=web", "build:platform": "turbo run build --filter=platform", + "build:cli": "turbo run build --filter=cli", "start": "turbo run start", "start:api": "turbo run start --filter=api", "start:web": "turbo run start --filter=web", "start:platform": "turbo run start --filter=platform", + "start:cli": "turbo run start --filter=cli", "test": "turbo run test", "test:api": "pnpm unit:api && pnpm e2e:api", "unit:api": "pnpm run --filter=api unit", @@ -153,6 +156,8 @@ "million": "^3.0.5", "sharp": "^0.33.3", "ts-node": "^10.9.2", - "zod": "^3.23.6" + "zod": "^3.23.6", + "chalk": "^4.1.2", + "moment": "^2.30.1" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9f951523..9bdf0a66 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,25 +13,28 @@ importers: version: 0.0.73(@types/react-dom@18.3.0)(@types/react@18.3.3) '@semantic-release/changelog': specifier: ^6.0.3 - version: 6.0.3(semantic-release@23.1.1(typescript@5.4.5)) + version: 6.0.3(semantic-release@23.1.1(typescript@5.5.2)) '@semantic-release/commit-analyzer': specifier: ^12.0.0 - version: 12.0.0(semantic-release@23.1.1(typescript@5.4.5)) + version: 12.0.0(semantic-release@23.1.1(typescript@5.5.2)) '@semantic-release/git': specifier: ^10.0.1 - version: 10.0.1(semantic-release@23.1.1(typescript@5.4.5)) + version: 10.0.1(semantic-release@23.1.1(typescript@5.5.2)) '@semantic-release/github': specifier: ^10.0.3 - version: 10.0.5(semantic-release@23.1.1(typescript@5.4.5)) + version: 10.0.5(semantic-release@23.1.1(typescript@5.5.2)) '@semantic-release/release-notes-generator': specifier: ^14.0.0 - version: 14.0.0(semantic-release@23.1.1(typescript@5.4.5)) + version: 14.0.0(semantic-release@23.1.1(typescript@5.5.2)) '@sentry/node': specifier: ^7.102.0 version: 7.116.0 '@sentry/profiling-node': specifier: ^7.102.0 version: 7.116.0 + chalk: + specifier: ^4.1.2 + version: 4.1.2 conventional-changelog-conventionalcommits: specifier: 8.0.0 version: 8.0.0 @@ -41,12 +44,15 @@ importers: million: specifier: ^3.0.5 version: 3.1.6 + moment: + specifier: ^2.30.1 + version: 2.30.1 sharp: specifier: ^0.33.3 version: 0.33.4 ts-node: specifier: ^10.9.2 - version: 10.9.2(@types/node@20.12.12)(typescript@5.4.5) + version: 10.9.2(@types/node@20.14.8)(typescript@5.5.2) zod: specifier: ^3.23.6 version: 3.23.8 @@ -117,9 +123,6 @@ importers: '@supabase/supabase-js': specifier: ^2.39.6 version: 2.43.4 - chalk: - specifier: ^4.1.2 - version: 4.1.2 class-transformer: specifier: ^0.5.1 version: 0.5.1 @@ -135,9 +138,6 @@ importers: minio: specifier: ^8.0.0 version: 8.0.0 - moment: - specifier: ^2.30.1 - version: 2.30.1 nodemailer: specifier: ^6.9.9 version: 6.9.13 @@ -257,6 +257,42 @@ importers: specifier: ^5.1.3 version: 5.4.5 + apps/cli: + dependencies: + '@clack/core': + specifier: ^0.3.4 + version: 0.3.4 + '@clack/prompts': + specifier: ^0.7.0 + version: 0.7.0 + '@types/figlet': + specifier: ^1.5.8 + version: 1.5.8 + '@types/node': + specifier: ^20.14.8 + version: 20.14.8 + commander: + specifier: ^12.1.0 + version: 12.1.0 + figlet: + specifier: ^1.7.0 + version: 1.7.0 + fs: + specifier: 0.0.1-security + version: 0.0.1-security + nodemon: + specifier: ^3.1.4 + version: 3.1.4 + socket.io-client: + specifier: ^4.7.5 + version: 4.7.5 + tsconfig-paths: + specifier: ^4.2.0 + version: 4.2.0 + typescript: + specifier: ^5.5.2 + version: 5.5.2 + apps/platform: dependencies: '@radix-ui/react-avatar': @@ -494,7 +530,7 @@ importers: devDependencies: '@vercel/style-guide': specifier: ^5.0.0 - version: 5.2.0(@next/eslint-plugin-next@13.5.6)(eslint@8.57.0)(jest@29.7.0(@types/node@20.12.12)(ts-node@10.9.2(@types/node@20.12.12)(typescript@5.4.5)))(prettier@3.2.5)(typescript@4.9.5) + version: 5.2.0(@next/eslint-plugin-next@13.5.6)(eslint@8.57.0)(jest@29.7.0(@types/node@20.14.8)(ts-node@10.9.2(@types/node@20.14.8)(typescript@5.5.2)))(prettier@3.2.5)(typescript@4.9.5) eslint-config-turbo: specifier: ^1.10.12 version: 1.13.3(eslint@8.57.0) @@ -3027,6 +3063,9 @@ packages: '@types/express@4.17.21': resolution: {integrity: sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==} + '@types/figlet@1.5.8': + resolution: {integrity: sha512-G22AUvy4Tl95XLE7jmUM8s8mKcoz+Hr+Xm9W90gJsppJq9f9tHvOGkrpn4gRX0q/cLtBdNkWtWCKDg2UDZoZvQ==} + '@types/graceful-fs@4.1.9': resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} @@ -3087,6 +3126,9 @@ packages: '@types/node@20.12.12': resolution: {integrity: sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==} + '@types/node@20.14.8': + resolution: {integrity: sha512-DO+2/jZinXfROG7j7WKFn/3C6nFwxy2lLpgLjEXJz+0XKphZlTLJ14mo8Vfg8X5BWN6XjyESXq+LcYdT7tR3bA==} + '@types/normalize-package-data@2.4.4': resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} @@ -3919,6 +3961,10 @@ packages: comma-separated-tokens@2.0.3: resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + commander@12.1.0: + resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} + engines: {node: '>=18'} + commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} @@ -4341,6 +4387,9 @@ packages: resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} engines: {node: '>= 0.8'} + engine.io-client@6.5.4: + resolution: {integrity: sha512-GeZeeRjpD2qf49cZQ0Wvh/8NJNfeXkXXcoGh+F77oEAgo9gUHwT1fCRxSNU+YEEaysOJTnsFHmM5oAcPy4ntvQ==} + engine.io-parser@5.2.2: resolution: {integrity: sha512-RcyUFKA93/CXH20l4SoVvzZfrSDMOTUS3bWVpTt2FuFP+XYrL8i8oonHP7WInRyVHXh0n/ORtoeiE1os+8qkSw==} engines: {node: '>=10.0.0'} @@ -4768,6 +4817,11 @@ packages: fetch-retry@6.0.0: resolution: {integrity: sha512-BUFj1aMubgib37I3v4q78fYo63Po7t4HUPTpQ6/QE6yK6cIQrP+W43FYToeTEyg5m2Y7eFUtijUuAv/PDlWuag==} + figlet@1.7.0: + resolution: {integrity: sha512-gO8l3wvqo0V7wEFLXPbkX83b7MVjRrk1oRLfYlZXol8nEpb/ON9pcKLI4qpBv5YtOTfrINtqb7b40iYY2FTWFg==} + engines: {node: '>= 0.4.0'} + hasBin: true + figures@2.0.0: resolution: {integrity: sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA==} engines: {node: '>=4'} @@ -4906,6 +4960,9 @@ packages: fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + fs@0.0.1-security: + resolution: {integrity: sha512-3XY9e1pP0CVEUCdj5BmfIZxRBTSDycnbqhIOGec9QYtmVH2fbLpj86CFWkrNOkt/Fvty4KZG5lTglL9j/gJ87w==} + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -5180,6 +5237,9 @@ packages: ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + ignore-by-default@1.0.1: + resolution: {integrity: sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==} + ignore@5.3.1: resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==} engines: {node: '>= 4'} @@ -6285,6 +6345,11 @@ packages: resolution: {integrity: sha512-7o38Yogx6krdoBf3jCAqnIN4oSQFx+fMa0I7dK1D+me9kBxx12D+/33wSb+fhOCtIxvYJ+4x4IMEhmhCKfAiOA==} engines: {node: '>=6.0.0'} + nodemon@3.1.4: + resolution: {integrity: sha512-wjPBbFhtpJwmIeY2yP7QF+UKzPfltVGtfce1g/bB15/8vCGZj8uxD62b/b9M9/WVgme0NZudpownKN+c0plXlQ==} + engines: {node: '>=10'} + hasBin: true + normalize-package-data@2.5.0: resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} @@ -6852,6 +6917,9 @@ packages: proxy-from-env@1.1.0: resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + pstree.remy@1.1.8: + resolution: {integrity: sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==} + punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -7284,6 +7352,10 @@ packages: simple-swizzle@0.2.2: resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} + simple-update-notifier@2.0.0: + resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==} + engines: {node: '>=10'} + sisteransi@1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} @@ -7309,6 +7381,10 @@ packages: socket.io-adapter@2.5.4: resolution: {integrity: sha512-wDNHGXGewWAjQPt3pyeYBtpWSq9cLE5UW1ZUPL/2eGK9jtse/FpXib7epSTsz0Q0m+6sg6Y4KtcFTlah1bdOVg==} + socket.io-client@4.7.5: + resolution: {integrity: sha512-sJ/tqHOCe7Z50JCBCXrsY3I2k03iOiUe+tj1OmKeD2lXPiGH/RUCdTZFoqVyN7l1MnpIzPrGtLcijffmeouNlQ==} + engines: {node: '>=10.0.0'} + socket.io-parser@4.2.4: resolution: {integrity: sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==} engines: {node: '>=10.0.0'} @@ -7673,6 +7749,10 @@ packages: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} + touch@3.1.1: + resolution: {integrity: sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==} + hasBin: true + tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} @@ -7887,6 +7967,11 @@ packages: engines: {node: '>=14.17'} hasBin: true + typescript@5.5.2: + resolution: {integrity: sha512-NcRtPEOsPFFWjobJEtfihkLCZCXZt/os3zf8nTxjVH3RvTSxjrCamJpbExGvYOF+tFHc3pA65qpdwPbzjohhew==} + engines: {node: '>=14.17'} + hasBin: true + uglify-js@3.17.4: resolution: {integrity: sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==} engines: {node: '>=0.8.0'} @@ -7906,6 +7991,9 @@ packages: unbox-primitive@1.0.2: resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} + undefsafe@2.0.5: + resolution: {integrity: sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==} + undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} @@ -8193,6 +8281,18 @@ packages: utf-8-validate: optional: true + ws@8.17.1: + resolution: {integrity: sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + xml2js@0.5.0: resolution: {integrity: sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==} engines: {node: '>=4.0.0'} @@ -8201,6 +8301,10 @@ packages: resolution: {integrity: sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==} engines: {node: '>=4.0'} + xmlhttprequest-ssl@2.0.0: + resolution: {integrity: sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==} + engines: {node: '>=0.4.0'} + xtend@4.0.2: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'} @@ -8340,7 +8444,7 @@ snapshots: '@babel/traverse': 7.24.6 '@babel/types': 7.24.6 convert-source-map: 2.0.0 - debug: 4.3.4 + debug: 4.3.4(supports-color@5.5.0) gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -8403,7 +8507,7 @@ snapshots: '@babel/core': 7.24.6 '@babel/helper-compilation-targets': 7.24.6 '@babel/helper-plugin-utils': 7.24.6 - debug: 4.3.4 + debug: 4.3.4(supports-color@5.5.0) lodash.debounce: 4.0.8 resolve: 1.22.8 transitivePeerDependencies: @@ -9095,7 +9199,7 @@ snapshots: '@babel/helper-split-export-declaration': 7.24.6 '@babel/parser': 7.24.6 '@babel/types': 7.24.6 - debug: 4.3.4 + debug: 4.3.4(supports-color@5.5.0) globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -9210,7 +9314,7 @@ snapshots: '@eslint/eslintrc@2.1.4': dependencies: ajv: 6.12.6 - debug: 4.3.4 + debug: 4.3.4(supports-color@5.5.0) espree: 9.6.1 globals: 13.24.0 ignore: 5.3.1 @@ -9276,7 +9380,7 @@ snapshots: '@humanwhocodes/config-array@0.11.14': dependencies: '@humanwhocodes/object-schema': 2.0.3 - debug: 4.3.4 + debug: 4.3.4(supports-color@5.5.0) minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -9382,7 +9486,7 @@ snapshots: '@jest/console@29.7.0': dependencies: '@jest/types': 29.6.3 - '@types/node': 20.12.12 + '@types/node': 17.0.45 chalk: 4.1.2 jest-message-util: 29.7.0 jest-util: 29.7.0 @@ -9395,14 +9499,49 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.12.12 + '@types/node': 17.0.45 ansi-escapes: 4.3.2 chalk: 4.1.2 ci-info: 3.9.0 exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@20.12.12)(ts-node@10.9.2(@types/node@20.12.12)(typescript@5.4.5)) + jest-config: 29.7.0(@types/node@17.0.45)(ts-node@10.9.2(@types/node@20.12.12)(typescript@5.4.5)) + 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.7 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-ansi: 6.0.1 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + - ts-node + + '@jest/core@29.7.0(ts-node@10.9.2(@types/node@20.14.8)(typescript@5.5.2))': + 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': 17.0.45 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + ci-info: 3.9.0 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-changed-files: 29.7.0 + jest-config: 29.7.0(@types/node@17.0.45)(ts-node@10.9.2(@types/node@20.14.8)(typescript@5.5.2)) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -9422,12 +9561,13 @@ snapshots: - babel-plugin-macros - supports-color - ts-node + optional: true '@jest/environment@29.7.0': dependencies: '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.12.12 + '@types/node': 17.0.45 jest-mock: 29.7.0 '@jest/expect-utils@29.7.0': @@ -9445,7 +9585,7 @@ snapshots: dependencies: '@jest/types': 29.6.3 '@sinonjs/fake-timers': 10.3.0 - '@types/node': 20.12.12 + '@types/node': 17.0.45 jest-message-util: 29.7.0 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -9467,7 +9607,7 @@ snapshots: '@jest/transform': 29.7.0 '@jest/types': 29.6.3 '@jridgewell/trace-mapping': 0.3.25 - '@types/node': 20.12.12 + '@types/node': 17.0.45 chalk: 4.1.2 collect-v8-coverage: 1.0.2 exit: 0.1.2 @@ -9537,7 +9677,7 @@ snapshots: '@jest/schemas': 29.6.3 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 20.12.12 + '@types/node': 17.0.45 '@types/yargs': 17.0.32 chalk: 4.1.2 @@ -10640,24 +10780,24 @@ snapshots: '@sec-ant/readable-stream@0.4.1': {} - '@semantic-release/changelog@6.0.3(semantic-release@23.1.1(typescript@5.4.5))': + '@semantic-release/changelog@6.0.3(semantic-release@23.1.1(typescript@5.5.2))': dependencies: '@semantic-release/error': 3.0.0 aggregate-error: 3.1.0 fs-extra: 11.2.0 lodash: 4.17.21 - semantic-release: 23.1.1(typescript@5.4.5) + semantic-release: 23.1.1(typescript@5.5.2) - '@semantic-release/commit-analyzer@12.0.0(semantic-release@23.1.1(typescript@5.4.5))': + '@semantic-release/commit-analyzer@12.0.0(semantic-release@23.1.1(typescript@5.5.2))': dependencies: conventional-changelog-angular: 7.0.0 conventional-commits-filter: 4.0.0 conventional-commits-parser: 5.0.0 - debug: 4.3.4 + debug: 4.3.4(supports-color@5.5.0) import-from-esm: 1.3.4 lodash-es: 4.17.21 micromatch: 4.0.7 - semantic-release: 23.1.1(typescript@5.4.5) + semantic-release: 23.1.1(typescript@5.5.2) transitivePeerDependencies: - supports-color @@ -10665,21 +10805,21 @@ snapshots: '@semantic-release/error@4.0.0': {} - '@semantic-release/git@10.0.1(semantic-release@23.1.1(typescript@5.4.5))': + '@semantic-release/git@10.0.1(semantic-release@23.1.1(typescript@5.5.2))': dependencies: '@semantic-release/error': 3.0.0 aggregate-error: 3.1.0 - debug: 4.3.4 + debug: 4.3.4(supports-color@5.5.0) dir-glob: 3.0.1 execa: 5.1.1 lodash: 4.17.21 micromatch: 4.0.7 p-reduce: 2.1.0 - semantic-release: 23.1.1(typescript@5.4.5) + semantic-release: 23.1.1(typescript@5.5.2) transitivePeerDependencies: - supports-color - '@semantic-release/github@10.0.5(semantic-release@23.1.1(typescript@5.4.5))': + '@semantic-release/github@10.0.5(semantic-release@23.1.1(typescript@5.5.2))': dependencies: '@octokit/core': 6.1.2 '@octokit/plugin-paginate-rest': 11.3.0(@octokit/core@6.1.2) @@ -10687,7 +10827,7 @@ snapshots: '@octokit/plugin-throttling': 9.3.0(@octokit/core@6.1.2) '@semantic-release/error': 4.0.0 aggregate-error: 5.0.0 - debug: 4.3.4 + debug: 4.3.4(supports-color@5.5.0) dir-glob: 3.0.1 globby: 14.0.1 http-proxy-agent: 7.0.2 @@ -10696,12 +10836,12 @@ snapshots: lodash-es: 4.17.21 mime: 4.0.3 p-filter: 4.1.0 - semantic-release: 23.1.1(typescript@5.4.5) + semantic-release: 23.1.1(typescript@5.5.2) url-join: 5.0.0 transitivePeerDependencies: - supports-color - '@semantic-release/npm@12.0.1(semantic-release@23.1.1(typescript@5.4.5))': + '@semantic-release/npm@12.0.1(semantic-release@23.1.1(typescript@5.5.2))': dependencies: '@semantic-release/error': 4.0.0 aggregate-error: 5.0.0 @@ -10714,39 +10854,39 @@ snapshots: rc: 1.2.8 read-pkg: 9.0.1 registry-auth-token: 5.0.2 - semantic-release: 23.1.1(typescript@5.4.5) + semantic-release: 23.1.1(typescript@5.5.2) semver: 7.6.2 tempy: 3.1.0 - '@semantic-release/release-notes-generator@13.0.0(semantic-release@23.1.1(typescript@5.4.5))': + '@semantic-release/release-notes-generator@13.0.0(semantic-release@23.1.1(typescript@5.5.2))': dependencies: conventional-changelog-angular: 7.0.0 conventional-changelog-writer: 7.0.1 conventional-commits-filter: 4.0.0 conventional-commits-parser: 5.0.0 - debug: 4.3.4 + debug: 4.3.4(supports-color@5.5.0) get-stream: 7.0.1 import-from-esm: 1.3.4 into-stream: 7.0.0 lodash-es: 4.17.21 read-pkg-up: 11.0.0 - semantic-release: 23.1.1(typescript@5.4.5) + semantic-release: 23.1.1(typescript@5.5.2) transitivePeerDependencies: - supports-color - '@semantic-release/release-notes-generator@14.0.0(semantic-release@23.1.1(typescript@5.4.5))': + '@semantic-release/release-notes-generator@14.0.0(semantic-release@23.1.1(typescript@5.5.2))': dependencies: conventional-changelog-angular: 8.0.0 conventional-changelog-writer: 8.0.0 conventional-commits-filter: 5.0.0 conventional-commits-parser: 6.0.0 - debug: 4.3.4 + debug: 4.3.4(supports-color@5.5.0) get-stream: 7.0.1 import-from-esm: 1.3.4 into-stream: 7.0.0 lodash-es: 4.17.21 read-pkg-up: 11.0.0 - semantic-release: 23.1.1(typescript@5.4.5) + semantic-release: 23.1.1(typescript@5.5.2) transitivePeerDependencies: - supports-color @@ -10873,7 +11013,7 @@ snapshots: '@socket.io/redis-adapter@8.3.0(socket.io-adapter@2.5.4)': dependencies: - debug: 4.3.4 + debug: 4.3.4(supports-color@5.5.0) notepack.io: 3.0.1 socket.io-adapter: 2.5.4 uid2: 1.0.0 @@ -11237,11 +11377,11 @@ snapshots: '@types/body-parser@1.19.5': dependencies: '@types/connect': 3.4.38 - '@types/node': 20.12.12 + '@types/node': 17.0.45 '@types/connect@3.4.38': dependencies: - '@types/node': 20.12.12 + '@types/node': 17.0.45 '@types/cookie-parser@1.4.7': dependencies: @@ -11253,7 +11393,7 @@ snapshots: '@types/cors@2.8.17': dependencies: - '@types/node': 20.12.12 + '@types/node': 17.0.45 '@types/debug@4.1.12': dependencies: @@ -11262,7 +11402,7 @@ snapshots: '@types/eccrypto@1.1.6': dependencies: '@types/expect': 1.20.4 - '@types/node': 20.12.12 + '@types/node': 17.0.45 '@types/eslint-scope@3.7.7': dependencies: @@ -11284,7 +11424,7 @@ snapshots: '@types/express-serve-static-core@4.19.1': dependencies: - '@types/node': 20.12.12 + '@types/node': 17.0.45 '@types/qs': 6.9.15 '@types/range-parser': 1.2.7 '@types/send': 0.17.4 @@ -11296,9 +11436,11 @@ snapshots: '@types/qs': 6.9.15 '@types/serve-static': 1.15.7 + '@types/figlet@1.5.8': {} + '@types/graceful-fs@4.1.9': dependencies: - '@types/node': 20.12.12 + '@types/node': 17.0.45 '@types/hast@3.0.4': dependencies: @@ -11329,7 +11471,7 @@ snapshots: '@types/jsonwebtoken@9.0.5': dependencies: - '@types/node': 20.12.12 + '@types/node': 17.0.45 '@types/luxon@3.4.2': {} @@ -11355,6 +11497,10 @@ snapshots: dependencies: undici-types: 5.26.5 + '@types/node@20.14.8': + dependencies: + undici-types: 5.26.5 + '@types/normalize-package-data@2.4.4': {} '@types/phoenix@1.6.4': {} @@ -11379,12 +11525,12 @@ snapshots: '@types/send@0.17.4': dependencies: '@types/mime': 1.3.5 - '@types/node': 20.12.12 + '@types/node': 17.0.45 '@types/serve-static@1.15.7': dependencies: '@types/http-errors': 2.0.4 - '@types/node': 20.12.12 + '@types/node': 17.0.45 '@types/send': 0.17.4 '@types/stack-utils@2.0.3': {} @@ -11393,7 +11539,7 @@ snapshots: dependencies: '@types/cookiejar': 2.1.5 '@types/methods': 1.1.4 - '@types/node': 20.12.12 + '@types/node': 17.0.45 '@types/supertest@6.0.2': dependencies: @@ -11410,7 +11556,7 @@ snapshots: '@types/ws@8.5.10': dependencies: - '@types/node': 20.12.12 + '@types/node': 17.0.45 '@types/yargs-parser@21.0.3': {} @@ -11426,7 +11572,7 @@ snapshots: '@typescript-eslint/type-utils': 6.21.0(eslint@8.57.0)(typescript@4.9.5) '@typescript-eslint/utils': 6.21.0(eslint@8.57.0)(typescript@4.9.5) '@typescript-eslint/visitor-keys': 6.21.0 - debug: 4.3.4 + debug: 4.3.4(supports-color@5.5.0) eslint: 8.57.0 graphemer: 1.4.0 ignore: 5.3.1 @@ -11446,7 +11592,7 @@ snapshots: '@typescript-eslint/type-utils': 6.21.0(eslint@8.57.0)(typescript@5.4.5) '@typescript-eslint/utils': 6.21.0(eslint@8.57.0)(typescript@5.4.5) '@typescript-eslint/visitor-keys': 6.21.0 - debug: 4.3.4 + debug: 4.3.4(supports-color@5.5.0) eslint: 8.57.0 graphemer: 1.4.0 ignore: 5.3.1 @@ -11464,7 +11610,7 @@ snapshots: '@typescript-eslint/types': 6.21.0 '@typescript-eslint/typescript-estree': 6.21.0(typescript@4.9.5) '@typescript-eslint/visitor-keys': 6.21.0 - debug: 4.3.4 + debug: 4.3.4(supports-color@5.5.0) eslint: 8.57.0 optionalDependencies: typescript: 4.9.5 @@ -11477,7 +11623,7 @@ snapshots: '@typescript-eslint/types': 6.21.0 '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.4.5) '@typescript-eslint/visitor-keys': 6.21.0 - debug: 4.3.4 + debug: 4.3.4(supports-color@5.5.0) eslint: 8.57.0 optionalDependencies: typescript: 5.4.5 @@ -11498,7 +11644,7 @@ snapshots: dependencies: '@typescript-eslint/typescript-estree': 6.21.0(typescript@4.9.5) '@typescript-eslint/utils': 6.21.0(eslint@8.57.0)(typescript@4.9.5) - debug: 4.3.4 + debug: 4.3.4(supports-color@5.5.0) eslint: 8.57.0 ts-api-utils: 1.3.0(typescript@4.9.5) optionalDependencies: @@ -11510,7 +11656,7 @@ snapshots: dependencies: '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.4.5) '@typescript-eslint/utils': 6.21.0(eslint@8.57.0)(typescript@5.4.5) - debug: 4.3.4 + debug: 4.3.4(supports-color@5.5.0) eslint: 8.57.0 ts-api-utils: 1.3.0(typescript@5.4.5) optionalDependencies: @@ -11526,7 +11672,7 @@ snapshots: dependencies: '@typescript-eslint/types': 5.62.0 '@typescript-eslint/visitor-keys': 5.62.0 - debug: 4.3.4 + debug: 4.3.4(supports-color@5.5.0) globby: 11.1.0 is-glob: 4.0.3 semver: 7.6.2 @@ -11540,7 +11686,7 @@ snapshots: dependencies: '@typescript-eslint/types': 6.21.0 '@typescript-eslint/visitor-keys': 6.21.0 - debug: 4.3.4 + debug: 4.3.4(supports-color@5.5.0) globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.3 @@ -11555,7 +11701,7 @@ snapshots: dependencies: '@typescript-eslint/types': 6.21.0 '@typescript-eslint/visitor-keys': 6.21.0 - debug: 4.3.4 + debug: 4.3.4(supports-color@5.5.0) globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.3 @@ -11621,7 +11767,7 @@ snapshots: '@ungap/structured-clone@1.2.0': {} - '@vercel/style-guide@5.2.0(@next/eslint-plugin-next@13.5.6)(eslint@8.57.0)(jest@29.7.0(@types/node@20.12.12)(ts-node@10.9.2(@types/node@20.12.12)(typescript@5.4.5)))(prettier@3.2.5)(typescript@4.9.5)': + '@vercel/style-guide@5.2.0(@next/eslint-plugin-next@13.5.6)(eslint@8.57.0)(jest@29.7.0(@types/node@20.14.8)(ts-node@10.9.2(@types/node@20.14.8)(typescript@5.5.2)))(prettier@3.2.5)(typescript@4.9.5)': dependencies: '@babel/core': 7.24.6 '@babel/eslint-parser': 7.24.6(@babel/core@7.24.6)(eslint@8.57.0) @@ -11633,9 +11779,9 @@ snapshots: eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@4.9.5))(eslint-plugin-import@2.29.1)(eslint@8.57.0) eslint-plugin-eslint-comments: 3.2.0(eslint@8.57.0) eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@4.9.5))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) - eslint-plugin-jest: 27.9.0(@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@4.9.5))(eslint@8.57.0)(typescript@4.9.5))(eslint@8.57.0)(jest@29.7.0(@types/node@20.12.12)(ts-node@10.9.2(@types/node@20.12.12)(typescript@5.4.5)))(typescript@4.9.5) + eslint-plugin-jest: 27.9.0(@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@4.9.5))(eslint@8.57.0)(typescript@4.9.5))(eslint@8.57.0)(jest@29.7.0(@types/node@20.14.8)(ts-node@10.9.2(@types/node@20.14.8)(typescript@5.5.2)))(typescript@4.9.5) eslint-plugin-jsx-a11y: 6.8.0(eslint@8.57.0) - eslint-plugin-playwright: 0.16.0(eslint-plugin-jest@27.9.0(@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@4.9.5))(eslint@8.57.0)(typescript@4.9.5))(eslint@8.57.0)(jest@29.7.0(@types/node@20.12.12)(ts-node@10.9.2(@types/node@20.12.12)(typescript@5.4.5)))(typescript@4.9.5))(eslint@8.57.0) + eslint-plugin-playwright: 0.16.0(eslint-plugin-jest@27.9.0(@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@4.9.5))(eslint@8.57.0)(typescript@4.9.5))(eslint@8.57.0)(jest@29.7.0(@types/node@20.14.8)(ts-node@10.9.2(@types/node@20.14.8)(typescript@5.5.2)))(typescript@4.9.5))(eslint@8.57.0) eslint-plugin-react: 7.34.1(eslint@8.57.0) eslint-plugin-react-hooks: 4.6.2(eslint@8.57.0) eslint-plugin-testing-library: 6.2.2(eslint@8.57.0)(typescript@4.9.5) @@ -11768,13 +11914,13 @@ snapshots: agent-base@6.0.2: dependencies: - debug: 4.3.4 + debug: 4.3.4(supports-color@5.5.0) transitivePeerDependencies: - supports-color agent-base@7.1.1: dependencies: - debug: 4.3.4 + debug: 4.3.4(supports-color@5.5.0) transitivePeerDependencies: - supports-color @@ -12425,6 +12571,8 @@ snapshots: comma-separated-tokens@2.0.3: {} + commander@12.1.0: {} + commander@2.20.3: {} commander@4.1.1: {} @@ -12560,14 +12708,14 @@ snapshots: optionalDependencies: typescript: 5.3.3 - cosmiconfig@9.0.0(typescript@5.4.5): + cosmiconfig@9.0.0(typescript@5.5.2): dependencies: env-paths: 2.2.1 import-fresh: 3.3.0 js-yaml: 4.1.0 parse-json: 5.2.0 optionalDependencies: - typescript: 5.4.5 + typescript: 5.5.2 create-hash@1.2.0: dependencies: @@ -12603,6 +12751,22 @@ snapshots: - supports-color - ts-node + create-jest@29.7.0(@types/node@20.14.8)(ts-node@10.9.2(@types/node@20.14.8)(typescript@5.5.2)): + dependencies: + '@jest/types': 29.6.3 + chalk: 4.1.2 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-config: 29.7.0(@types/node@20.14.8)(ts-node@10.9.2(@types/node@20.14.8)(typescript@5.5.2)) + jest-util: 29.7.0 + prompts: 2.4.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + optional: true + create-require@1.1.1: {} cron@3.1.7: @@ -12680,9 +12844,11 @@ snapshots: dependencies: ms: 2.1.3 - debug@4.3.4: + debug@4.3.4(supports-color@5.5.0): dependencies: ms: 2.1.2 + optionalDependencies: + supports-color: 5.5.0 decode-named-character-reference@1.0.2: dependencies: @@ -12853,18 +13019,30 @@ snapshots: encodeurl@1.0.2: {} + engine.io-client@6.5.4: + dependencies: + '@socket.io/component-emitter': 3.1.2 + debug: 4.3.4(supports-color@5.5.0) + engine.io-parser: 5.2.2 + ws: 8.17.1 + xmlhttprequest-ssl: 2.0.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + engine.io-parser@5.2.2: {} engine.io@6.5.4: dependencies: '@types/cookie': 0.4.1 '@types/cors': 2.8.17 - '@types/node': 20.12.12 + '@types/node': 17.0.45 accepts: 1.3.8 base64id: 2.0.0 cookie: 0.4.2 cors: 2.8.5 - debug: 4.3.4 + debug: 4.3.4(supports-color@5.5.0) engine.io-parser: 5.2.2 ws: 8.11.0 transitivePeerDependencies: @@ -13047,7 +13225,7 @@ snapshots: eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@4.9.5))(eslint-plugin-import@2.29.1)(eslint@8.57.0): dependencies: - debug: 4.3.4 + debug: 4.3.4(supports-color@5.5.0) enhanced-resolve: 5.16.1 eslint: 8.57.0 eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@4.9.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@4.9.5))(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0) @@ -13112,13 +13290,13 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-plugin-jest@27.9.0(@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@4.9.5))(eslint@8.57.0)(typescript@4.9.5))(eslint@8.57.0)(jest@29.7.0(@types/node@20.12.12)(ts-node@10.9.2(@types/node@20.12.12)(typescript@5.4.5)))(typescript@4.9.5): + eslint-plugin-jest@27.9.0(@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@4.9.5))(eslint@8.57.0)(typescript@4.9.5))(eslint@8.57.0)(jest@29.7.0(@types/node@20.14.8)(ts-node@10.9.2(@types/node@20.14.8)(typescript@5.5.2)))(typescript@4.9.5): dependencies: '@typescript-eslint/utils': 5.62.0(eslint@8.57.0)(typescript@4.9.5) eslint: 8.57.0 optionalDependencies: '@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@4.9.5))(eslint@8.57.0)(typescript@4.9.5) - jest: 29.7.0(@types/node@20.12.12)(ts-node@10.9.2(@types/node@20.12.12)(typescript@5.4.5)) + jest: 29.7.0(@types/node@20.14.8)(ts-node@10.9.2(@types/node@20.14.8)(typescript@5.5.2)) transitivePeerDependencies: - supports-color - typescript @@ -13153,11 +13331,11 @@ snapshots: resolve: 1.22.8 semver: 6.3.1 - eslint-plugin-playwright@0.16.0(eslint-plugin-jest@27.9.0(@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@4.9.5))(eslint@8.57.0)(typescript@4.9.5))(eslint@8.57.0)(jest@29.7.0(@types/node@20.12.12)(ts-node@10.9.2(@types/node@20.12.12)(typescript@5.4.5)))(typescript@4.9.5))(eslint@8.57.0): + eslint-plugin-playwright@0.16.0(eslint-plugin-jest@27.9.0(@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@4.9.5))(eslint@8.57.0)(typescript@4.9.5))(eslint@8.57.0)(jest@29.7.0(@types/node@20.14.8)(ts-node@10.9.2(@types/node@20.14.8)(typescript@5.5.2)))(typescript@4.9.5))(eslint@8.57.0): dependencies: eslint: 8.57.0 optionalDependencies: - eslint-plugin-jest: 27.9.0(@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@4.9.5))(eslint@8.57.0)(typescript@4.9.5))(eslint@8.57.0)(jest@29.7.0(@types/node@20.12.12)(ts-node@10.9.2(@types/node@20.12.12)(typescript@5.4.5)))(typescript@4.9.5) + eslint-plugin-jest: 27.9.0(@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@4.9.5))(eslint@8.57.0)(typescript@4.9.5))(eslint@8.57.0)(jest@29.7.0(@types/node@20.14.8)(ts-node@10.9.2(@types/node@20.14.8)(typescript@5.5.2)))(typescript@4.9.5) eslint-plugin-prettier@5.1.3(@types/eslint@8.56.10)(eslint-config-prettier@9.1.0(eslint@8.57.0))(eslint@8.57.0)(prettier@3.2.5): dependencies: @@ -13265,7 +13443,7 @@ snapshots: ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.3 - debug: 4.3.4 + debug: 4.3.4(supports-color@5.5.0) doctrine: 3.0.0 escape-string-regexp: 4.0.0 eslint-scope: 7.2.2 @@ -13529,6 +13707,8 @@ snapshots: fetch-retry@6.0.0: {} + figlet@1.7.0: {} + figures@2.0.0: dependencies: escape-string-regexp: 1.0.5 @@ -13681,6 +13861,8 @@ snapshots: fs.realpath@1.0.0: {} + fs@0.0.1-security: {} + fsevents@2.3.3: optional: true @@ -13976,21 +14158,21 @@ snapshots: http-proxy-agent@7.0.2: dependencies: agent-base: 7.1.1 - debug: 4.3.4 + debug: 4.3.4(supports-color@5.5.0) transitivePeerDependencies: - supports-color https-proxy-agent@5.0.1: dependencies: agent-base: 6.0.2 - debug: 4.3.4 + debug: 4.3.4(supports-color@5.5.0) transitivePeerDependencies: - supports-color https-proxy-agent@7.0.4: dependencies: agent-base: 7.1.1 - debug: 4.3.4 + debug: 4.3.4(supports-color@5.5.0) transitivePeerDependencies: - supports-color @@ -14008,6 +14190,8 @@ snapshots: ieee754@1.2.1: {} + ignore-by-default@1.0.1: {} + ignore@5.3.1: {} immediate@3.0.6: {} @@ -14019,7 +14203,7 @@ snapshots: import-from-esm@1.3.4: dependencies: - debug: 4.3.4 + debug: 4.3.4(supports-color@5.5.0) import-meta-resolve: 4.1.0 transitivePeerDependencies: - supports-color @@ -14315,7 +14499,7 @@ snapshots: istanbul-lib-source-maps@4.0.1: dependencies: - debug: 4.3.4 + debug: 4.3.4(supports-color@5.5.0) istanbul-lib-coverage: 3.2.2 source-map: 0.6.1 transitivePeerDependencies: @@ -14362,7 +14546,7 @@ snapshots: '@jest/expect': 29.7.0 '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.12.12 + '@types/node': 17.0.45 chalk: 4.1.2 co: 4.6.0 dedent: 1.5.3 @@ -14401,6 +14585,89 @@ snapshots: - supports-color - ts-node + jest-cli@29.7.0(@types/node@20.14.8)(ts-node@10.9.2(@types/node@20.14.8)(typescript@5.5.2)): + dependencies: + '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@20.14.8)(typescript@5.5.2)) + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + chalk: 4.1.2 + create-jest: 29.7.0(@types/node@20.14.8)(ts-node@10.9.2(@types/node@20.14.8)(typescript@5.5.2)) + exit: 0.1.2 + import-local: 3.1.0 + jest-config: 29.7.0(@types/node@20.14.8)(ts-node@10.9.2(@types/node@20.14.8)(typescript@5.5.2)) + jest-util: 29.7.0 + jest-validate: 29.7.0 + yargs: 17.7.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + optional: true + + jest-config@29.7.0(@types/node@17.0.45)(ts-node@10.9.2(@types/node@20.12.12)(typescript@5.4.5)): + dependencies: + '@babel/core': 7.24.6 + '@jest/test-sequencer': 29.7.0 + '@jest/types': 29.6.3 + babel-jest: 29.7.0(@babel/core@7.24.6) + chalk: 4.1.2 + ci-info: 3.9.0 + deepmerge: 4.3.1 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-circus: 29.7.0 + jest-environment-node: 29.7.0 + jest-get-type: 29.6.3 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-runner: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + micromatch: 4.0.7 + parse-json: 5.2.0 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-json-comments: 3.1.1 + optionalDependencies: + '@types/node': 17.0.45 + ts-node: 10.9.2(@types/node@20.12.12)(typescript@5.4.5) + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + + jest-config@29.7.0(@types/node@17.0.45)(ts-node@10.9.2(@types/node@20.14.8)(typescript@5.5.2)): + dependencies: + '@babel/core': 7.24.6 + '@jest/test-sequencer': 29.7.0 + '@jest/types': 29.6.3 + babel-jest: 29.7.0(@babel/core@7.24.6) + chalk: 4.1.2 + ci-info: 3.9.0 + deepmerge: 4.3.1 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-circus: 29.7.0 + jest-environment-node: 29.7.0 + jest-get-type: 29.6.3 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-runner: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + micromatch: 4.0.7 + parse-json: 5.2.0 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-json-comments: 3.1.1 + optionalDependencies: + '@types/node': 17.0.45 + ts-node: 10.9.2(@types/node@20.14.8)(typescript@5.5.2) + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + optional: true + jest-config@29.7.0(@types/node@20.12.12)(ts-node@10.9.2(@types/node@20.12.12)(typescript@5.4.5)): dependencies: '@babel/core': 7.24.6 @@ -14432,6 +14699,38 @@ snapshots: - babel-plugin-macros - supports-color + jest-config@29.7.0(@types/node@20.14.8)(ts-node@10.9.2(@types/node@20.14.8)(typescript@5.5.2)): + dependencies: + '@babel/core': 7.24.6 + '@jest/test-sequencer': 29.7.0 + '@jest/types': 29.6.3 + babel-jest: 29.7.0(@babel/core@7.24.6) + chalk: 4.1.2 + ci-info: 3.9.0 + deepmerge: 4.3.1 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-circus: 29.7.0 + jest-environment-node: 29.7.0 + jest-get-type: 29.6.3 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-runner: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + micromatch: 4.0.7 + parse-json: 5.2.0 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-json-comments: 3.1.1 + optionalDependencies: + '@types/node': 20.14.8 + ts-node: 10.9.2(@types/node@20.14.8)(typescript@5.5.2) + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + optional: true + jest-diff@29.7.0: dependencies: chalk: 4.1.2 @@ -14456,7 +14755,7 @@ snapshots: '@jest/environment': 29.7.0 '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.12.12 + '@types/node': 17.0.45 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -14466,7 +14765,7 @@ snapshots: dependencies: '@jest/types': 29.6.3 '@types/graceful-fs': 4.1.9 - '@types/node': 20.12.12 + '@types/node': 17.0.45 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 @@ -14511,7 +14810,7 @@ snapshots: jest-mock@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 20.12.12 + '@types/node': 17.0.45 jest-util: 29.7.0 jest-pnp-resolver@1.2.3(jest-resolve@29.7.0): @@ -14546,7 +14845,7 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.12.12 + '@types/node': 17.0.45 chalk: 4.1.2 emittery: 0.13.1 graceful-fs: 4.2.11 @@ -14574,7 +14873,7 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.12.12 + '@types/node': 17.0.45 chalk: 4.1.2 cjs-module-lexer: 1.3.1 collect-v8-coverage: 1.0.2 @@ -14620,7 +14919,7 @@ snapshots: jest-util@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 20.12.12 + '@types/node': 17.0.45 chalk: 4.1.2 ci-info: 3.9.0 graceful-fs: 4.2.11 @@ -14639,7 +14938,7 @@ snapshots: dependencies: '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.12.12 + '@types/node': 17.0.45 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.13.1 @@ -14648,13 +14947,13 @@ snapshots: jest-worker@27.5.1: dependencies: - '@types/node': 20.12.12 + '@types/node': 17.0.45 merge-stream: 2.0.0 supports-color: 8.1.1 jest-worker@29.7.0: dependencies: - '@types/node': 20.12.12 + '@types/node': 17.0.45 jest-util: 29.7.0 merge-stream: 2.0.0 supports-color: 8.1.1 @@ -14671,6 +14970,19 @@ snapshots: - supports-color - ts-node + jest@29.7.0(@types/node@20.14.8)(ts-node@10.9.2(@types/node@20.14.8)(typescript@5.5.2)): + dependencies: + '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@20.14.8)(typescript@5.5.2)) + '@jest/types': 29.6.3 + import-local: 3.1.0 + jest-cli: 29.7.0(@types/node@20.14.8)(ts-node@10.9.2(@types/node@20.14.8)(typescript@5.5.2)) + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + optional: true + jiti@1.21.0: {} jju@1.4.0: {} @@ -15235,7 +15547,7 @@ snapshots: micromark@4.0.0: dependencies: '@types/debug': 4.1.12 - debug: 4.3.4 + debug: 4.3.4(supports-color@5.5.0) decode-named-character-reference: 1.0.2 devlop: 1.1.0 micromark-core-commonmark: 2.0.1 @@ -15448,6 +15760,19 @@ snapshots: nodemailer@6.9.13: {} + nodemon@3.1.4: + dependencies: + chokidar: 3.6.0 + debug: 4.3.4(supports-color@5.5.0) + ignore-by-default: 1.0.1 + minimatch: 3.1.2 + pstree.remy: 1.1.8 + semver: 7.6.2 + simple-update-notifier: 2.0.0 + supports-color: 5.5.0 + touch: 3.1.1 + undefsafe: 2.0.5 + normalize-package-data@2.5.0: dependencies: hosted-git-info: 2.8.9 @@ -15885,6 +16210,8 @@ snapshots: proxy-from-env@1.1.0: {} + pstree.remy@1.1.8: {} + punycode@2.3.1: {} pure-rand@6.1.0: {} @@ -16279,16 +16606,16 @@ snapshots: secure-json-parse@2.7.0: {} - semantic-release@23.1.1(typescript@5.4.5): + semantic-release@23.1.1(typescript@5.5.2): dependencies: - '@semantic-release/commit-analyzer': 12.0.0(semantic-release@23.1.1(typescript@5.4.5)) + '@semantic-release/commit-analyzer': 12.0.0(semantic-release@23.1.1(typescript@5.5.2)) '@semantic-release/error': 4.0.0 - '@semantic-release/github': 10.0.5(semantic-release@23.1.1(typescript@5.4.5)) - '@semantic-release/npm': 12.0.1(semantic-release@23.1.1(typescript@5.4.5)) - '@semantic-release/release-notes-generator': 13.0.0(semantic-release@23.1.1(typescript@5.4.5)) + '@semantic-release/github': 10.0.5(semantic-release@23.1.1(typescript@5.5.2)) + '@semantic-release/npm': 12.0.1(semantic-release@23.1.1(typescript@5.5.2)) + '@semantic-release/release-notes-generator': 13.0.0(semantic-release@23.1.1(typescript@5.5.2)) aggregate-error: 5.0.0 - cosmiconfig: 9.0.0(typescript@5.4.5) - debug: 4.3.4 + cosmiconfig: 9.0.0(typescript@5.5.2) + debug: 4.3.4(supports-color@5.5.0) env-ci: 11.0.0 execa: 9.1.0 figures: 6.1.0 @@ -16442,6 +16769,10 @@ snapshots: dependencies: is-arrayish: 0.3.2 + simple-update-notifier@2.0.0: + dependencies: + semver: 7.6.2 + sisteransi@1.0.5: {} skin-tone@2.0.0: @@ -16461,17 +16792,28 @@ snapshots: socket.io-adapter@2.5.4: dependencies: - debug: 4.3.4 + debug: 4.3.4(supports-color@5.5.0) ws: 8.11.0 transitivePeerDependencies: - bufferutil - supports-color - utf-8-validate + socket.io-client@4.7.5: + dependencies: + '@socket.io/component-emitter': 3.1.2 + debug: 4.3.4(supports-color@5.5.0) + engine.io-client: 6.5.4 + socket.io-parser: 4.2.4 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + socket.io-parser@4.2.4: dependencies: '@socket.io/component-emitter': 3.1.2 - debug: 4.3.4 + debug: 4.3.4(supports-color@5.5.0) transitivePeerDependencies: - supports-color @@ -16480,7 +16822,7 @@ snapshots: accepts: 1.3.8 base64id: 2.0.0 cors: 2.8.5 - debug: 4.3.4 + debug: 4.3.4(supports-color@5.5.0) engine.io: 6.5.4 socket.io-adapter: 2.5.4 socket.io-parser: 4.2.4 @@ -16704,7 +17046,7 @@ snapshots: dependencies: component-emitter: 1.3.1 cookiejar: 2.1.4 - debug: 4.3.4 + debug: 4.3.4(supports-color@5.5.0) fast-safe-stringify: 2.1.1 form-data: 4.0.0 formidable: 2.1.2 @@ -16891,6 +17233,8 @@ snapshots: toidentifier@1.0.1: {} + touch@3.1.1: {} + tr46@0.0.3: {} traverse@0.6.9: @@ -16983,6 +17327,25 @@ snapshots: typescript: 5.4.5 v8-compile-cache-lib: 3.0.1 yn: 3.1.1 + optional: true + + ts-node@10.9.2(@types/node@20.14.8)(typescript@5.5.2): + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.11 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 20.14.8 + acorn: 8.11.3 + acorn-walk: 8.3.2 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 5.5.2 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 tsconfig-paths-webpack-plugin@4.1.0: dependencies: @@ -17113,6 +17476,8 @@ snapshots: typescript@5.4.5: {} + typescript@5.5.2: {} + uglify-js@3.17.4: optional: true @@ -17131,6 +17496,8 @@ snapshots: has-symbols: 1.0.3 which-boxed-primitive: 1.0.2 + undefsafe@2.0.5: {} + undici-types@5.26.5: {} undici@6.18.1: {} @@ -17469,6 +17836,8 @@ snapshots: ws@8.17.0: {} + ws@8.17.1: {} + xml2js@0.5.0: dependencies: sax: 1.3.0 @@ -17476,6 +17845,8 @@ snapshots: xmlbuilder@11.0.1: {} + xmlhttprequest-ssl@2.0.0: {} + xtend@4.0.2: {} xycolors@0.1.1: {}