feat: migration commands (#828)

* feat: migration commands

* Update packages/server/src/modules/CLI/commands/TenantsMigrateRollback.command.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update packages/server/src/modules/CLI/commands/TenantsMigrateLatest.command.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update packages/server/src/modules/CLI/commands/TenantsList.command.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update packages/server/src/modules/CLI/commands/SystemMigrateRollback.command.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update packages/server/src/modules/CLI/commands/TenantsMigrateLatest.command.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Ahmed Bouhuolia
2025-10-22 21:58:02 +02:00
committed by GitHub
parent 9d714ac78e
commit 3bd0e89146
17 changed files with 608 additions and 21 deletions

View File

@@ -14,7 +14,15 @@
"test:watch": "lerna run test:watch",
"test:e2e": "lerna run test:e2e",
"start:debug": "lerna run start:debug",
"prepare": "husky install"
"prepare": "husky install",
"system:migrate:make": "lerna run cli:system:migrate:make --scope \"@bigcapital/server\"",
"tenants:migrate:make": "lerna run cli:tenants:migrate:make --scope \"@bigcapital/server\"",
"system:migrate:rollback": "lerna run cli:system:migrate:rollback --scope \"@bigcapital/server\"",
"tenants:migrate:rollback": "lerna run cli:tenants:migrate:rollback --scope \"@bigcapital/server\"",
"system:migrate:latest": "lerna run cli:system:migrate:latest --scope \"@bigcapital/server\"",
"tenants:migrate:latest": "lerna run cli:tenants:migrate:latest --scope \"@bigcapital/server\"",
"system:seed:latest": "lerna run cli:system:seed:latest --scope \"@bigcapital/server\"",
"tenants:seed:latest": "lerna run cli:tenants:seed:latest --scope \"@bigcapital/server\""
},
"devDependencies": {
"@commitlint/cli": "^17.4.2",

View File

@@ -18,7 +18,17 @@
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json --watchAll"
"test:e2e": "jest --config ./test/jest-e2e.json --watchAll",
"cli": "ts-node -r tsconfig-paths/register src/cli.ts",
"cli:system:migrate:latest": "ts-node -r tsconfig-paths/register src/cli.ts system:migrate:latest",
"cli:system:migrate:rollback": "ts-node -r tsconfig-paths/register src/cli.ts system:migrate:rollback",
"cli:system:migrate:make": "ts-node -r tsconfig-paths/register src/cli.ts system:migrate:make",
"cli:tenants:migrate:latest": "ts-node -r tsconfig-paths/register src/cli.ts tenants:migrate:latest",
"cli:tenants:migrate:rollback": "ts-node -r tsconfig-paths/register src/cli.ts tenants:migrate:rollback",
"cli:tenants:migrate:make": "ts-node -r tsconfig-paths/register src/cli.ts tenants:migrate:make",
"cli:tenants:list": "ts-node -r tsconfig-paths/register src/cli.ts tenants:list",
"cli:system:seed:latest": "ts-node -r tsconfig-paths/register src/cli.ts system:seed:latest",
"cli:tenants:seed:latest": "ts-node -r tsconfig-paths/register src/cli.ts tenants:seed:latest"
},
"dependencies": {
"@aws-sdk/client-s3": "^3.576.0",
@@ -84,6 +94,7 @@
"multer-s3": "^3.0.1",
"mysql": "^2.18.1",
"mysql2": "^3.11.3",
"nest-commander": "^3.20.1",
"nestjs-cls": "^5.2.0",
"nestjs-i18n": "^10.4.9",
"nestjs-redis": "^1.3.3",

View File

@@ -0,0 +1,8 @@
import { CommandFactory } from 'nest-commander';
import { CLIModule } from './modules/CLI/CLI.module';
async function bootstrap() {
await CommandFactory.run(CLIModule);
}
bootstrap();

View File

@@ -1,5 +1,5 @@
import { parseBoolean } from '@/utils/parse-boolean';
import { registerAs } from '@nestjs/config';
import { parseBoolean } from '@/utils/parse-boolean';
export default registerAs('signupConfirmation', () => ({
enabled: parseBoolean<boolean>(process.env.SIGNUP_EMAIL_CONFIRMATION, false),

View File

@@ -7,4 +7,6 @@ export default registerAs('systemDatabase', () => ({
user: process.env.SYSTEM_DB_USER || process.env.DB_USER,
password: process.env.SYSTEM_DB_PASSWORD || process.env.DB_PASSWORD,
databaseName: process.env.SYSTEM_DB_NAME || process.env.DB_NAME,
migrationDir: process.env.SYSTEM_DB_MIGRATION_DIR || './src/database/migrations',
seedsDir: process.env.SYSTEM_DB_SEEDS_DIR || './src/database/seeds',
}));

View File

@@ -0,0 +1,35 @@
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { config } from '../../common/config';
import { CommandRunnerModule } from 'nest-commander';
import { SystemMigrateLatestCommand } from './commands/SystemMigrateLatest.command';
import { SystemMigrateRollbackCommand } from './commands/SystemMigrateRollback.command';
import { SystemMigrateMakeCommand } from './commands/SystemMigrateMake.command';
import { TenantsMigrateLatestCommand } from './commands/TenantsMigrateLatest.command';
import { TenantsMigrateRollbackCommand } from './commands/TenantsMigrateRollback.command';
import { TenantsMigrateMakeCommand } from './commands/TenantsMigrateMake.command';
import { TenantsListCommand } from './commands/TenantsList.command';
import { SystemSeedLatestCommand } from './commands/SystemSeedLatest.command';
import { TenantsSeedLatestCommand } from './commands/TenantsSeedLatest.command';
@Module({
imports: [
ConfigModule.forRoot({
load: config,
isGlobal: true,
}),
CommandRunnerModule,
],
providers: [
SystemMigrateLatestCommand,
SystemMigrateRollbackCommand,
SystemMigrateMakeCommand,
TenantsMigrateLatestCommand,
TenantsMigrateRollbackCommand,
TenantsMigrateMakeCommand,
TenantsListCommand,
SystemSeedLatestCommand,
TenantsSeedLatestCommand,
],
})
export class CLIModule { }

View File

@@ -0,0 +1,83 @@
import { CommandRunner } from 'nest-commander';
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import Knex from 'knex';
import { knexSnakeCaseMappers } from 'objection';
@Injectable()
export abstract class BaseCommand extends CommandRunner {
constructor(protected readonly configService: ConfigService) {
super();
}
protected initSystemKnex(): any {
return Knex({
client: this.configService.get('systemDatabase.client'),
connection: {
host: this.configService.get('systemDatabase.host'),
user: this.configService.get('systemDatabase.user'),
password: this.configService.get('systemDatabase.password'),
database: this.configService.get('systemDatabase.databaseName'),
charset: 'utf8',
},
migrations: {
directory: this.configService.get('systemDatabase.migrationDir'),
},
seeds: {
directory: this.configService.get('systemDatabase.seedsDir'),
},
pool: { min: 0, max: 7 },
...knexSnakeCaseMappers({ upperCase: true }),
});
}
protected initTenantKnex(organizationId: string = ''): any {
return Knex({
client: this.configService.get('tenantDatabase.client'),
connection: {
host: this.configService.get('tenantDatabase.host'),
user: this.configService.get('tenantDatabase.user'),
password: this.configService.get('tenantDatabase.password'),
database: `${this.configService.get('tenantDatabase.dbNamePrefix')}${organizationId}`,
charset: 'utf8',
},
migrations: {
directory: this.configService.get('tenantDatabase.migrationsDir') || './src/database/migrations',
},
seeds: {
directory: this.configService.get('tenantDatabase.seedsDir') || './src/database/seeds/core',
},
pool: {
min: 0,
max: 5,
},
...knexSnakeCaseMappers({ upperCase: true }),
});
}
protected getAllSystemTenants(knex: any) {
return knex('tenants');
}
protected getAllInitializedTenants(knex: any) {
return knex('tenants').whereNotNull('initializedAt');
}
protected exit(text: any): never {
if (text instanceof Error) {
console.error(`Error: ${text.message}\n${text.stack}`);
} else {
console.error(`Error: ${text}`);
}
process.exit(1);
}
protected success(text: string): never {
console.log(text);
process.exit(0);
}
protected log(text: string): void {
console.log(text);
}
}

View File

@@ -0,0 +1,32 @@
import { Command } from 'nest-commander';
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { BaseCommand } from './BaseCommand';
@Injectable()
@Command({
name: 'system:migrate:latest',
description: 'Migrate latest migration of the system database.',
})
export class SystemMigrateLatestCommand extends BaseCommand {
constructor(configService: ConfigService) {
super(configService);
}
async run(): Promise<void> {
try {
const sysKnex = this.initSystemKnex();
const [batchNo, log] = await sysKnex.migrate.latest();
if (log.length === 0) {
this.success('Already up to date');
}
this.success(
`Batch ${batchNo} run: ${log.length} migrations`
);
} catch (error) {
this.exit(error);
}
}
}

View File

@@ -0,0 +1,33 @@
import { Command } from 'nest-commander';
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { BaseCommand } from './BaseCommand';
@Injectable()
@Command({
name: 'system:migrate:make',
description: 'Create a named migration file to the system database.',
arguments: '<name>',
})
export class SystemMigrateMakeCommand extends BaseCommand {
constructor(configService: ConfigService) {
super(configService);
}
async run(passedParams: string[]): Promise<void> {
const [name] = passedParams;
if (!name) {
this.exit('Migration name is required');
return;
}
try {
const sysKnex = this.initSystemKnex();
const migrationName = await sysKnex.migrate.make(name);
this.success(`Created Migration: ${migrationName}`);
} catch (error) {
this.exit(error);
}
}
}

View File

@@ -0,0 +1,32 @@
import { Command } from 'nest-commander';
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { BaseCommand } from './BaseCommand';
@Injectable()
@Command({
name: 'system:migrate:rollback',
description: 'Rollback the last batch of system migrations.',
})
export class SystemMigrateRollbackCommand extends BaseCommand {
constructor(configService: ConfigService) {
super(configService);
}
async run(): Promise<void> {
try {
const sysKnex = this.initSystemKnex();
const [batchNo, _log] = await sysKnex.migrate.rollback();
if (_log.length === 0) {
this.success('Already at the base migration');
}
this.success(
`Batch ${batchNo} rolled back: ${_log.length} migrations`
);
} catch (error) {
this.exit(error);
}
}
}

View File

@@ -0,0 +1,12 @@
import { Command, CommandRunner } from 'nest-commander';
@Command({
name: 'system:seed:latest',
description: 'Seed system database with the latest data',
})
export class SystemSeedLatestCommand extends CommandRunner {
async run(): Promise<void> {
console.log('System seeding with latest data - No operation performed');
// TODO: Implement system seeding logic
}
}

View File

@@ -0,0 +1,47 @@
import { Command, Option } from 'nest-commander';
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { BaseCommand } from './BaseCommand';
interface TenantsListOptions {
all?: boolean;
}
@Injectable()
@Command({
name: 'tenants:list',
description: 'Retrieve a list of all system tenants databases.',
})
export class TenantsListCommand extends BaseCommand {
constructor(configService: ConfigService) {
super(configService);
}
@Option({
flags: '-a, --all',
description: 'All tenants even if not initialized.',
})
parseAll(val: string): boolean {
return true;
}
async run(passedParams: string[], options: TenantsListOptions): Promise<void> {
try {
const sysKnex = this.initSystemKnex();
const tenants = options.all
? await this.getAllSystemTenants(sysKnex)
: await this.getAllInitializedTenants(sysKnex);
tenants.forEach((tenant: any) => {
const dbName = `${this.configService.get('tenantDatabase.dbNamePrefix')}${tenant.organizationId}`;
console.log(
`ID: ${tenant.id} | Organization ID: ${tenant.organizationId} | DB Name: ${dbName}`
);
});
this.success('---');
} catch (error) {
this.exit(error);
}
}
}

View File

@@ -0,0 +1,74 @@
import { Command, Option } from 'nest-commander';
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { PromisePool } from '@supercharge/promise-pool';
import { BaseCommand } from './BaseCommand';
interface TenantsMigrateLatestOptions {
tenant_id?: string;
}
@Injectable()
@Command({
name: 'tenants:migrate:latest',
description: 'Migrate all tenants or the given tenant id.',
})
export class TenantsMigrateLatestCommand extends BaseCommand {
private readonly MIGRATION_CONCURRENCY = 10;
constructor(configService: ConfigService) {
super(configService);
}
@Option({
flags: '-t, --tenant_id [tenant_id]',
description: 'Which organization id do you migrate.',
})
parseTenantId(val: string): string {
return val;
}
async run(passedParams: string[], options: TenantsMigrateLatestOptions): Promise<void> {
try {
const sysKnex = this.initSystemKnex();
const tenants = await this.getAllInitializedTenants(sysKnex);
const tenantsOrgsIds = tenants.map((tenant: any) => tenant.organizationId);
if (options.tenant_id && tenantsOrgsIds.indexOf(options.tenant_id) === -1) {
this.exit(`The given tenant id ${options.tenant_id} does not exist.`);
}
const migrateTenant = async (organizationId: string) => {
try {
const tenantKnex = this.initTenantKnex(organizationId);
const [batchNo, _log] = await tenantKnex.migrate.latest();
const tenantDb = `${this.configService.get('tenantDatabase.dbNamePrefix')}${organizationId}`;
if (_log.length === 0) {
this.log('Already up to date');
}
this.log(
`Tenant ${tenantDb} > Batch ${batchNo} run: ${_log.length} migrations`
);
this.log('-------------------');
} catch (error) {
this.exit(error);
}
};
if (!options.tenant_id) {
await PromisePool.withConcurrency(this.MIGRATION_CONCURRENCY)
.for(tenants)
.process((tenant: any) => {
return migrateTenant(tenant.organizationId);
});
this.success('All tenants are migrated.');
} else {
await migrateTenant(options.tenant_id);
}
} catch (error) {
this.exit(error);
}
}
}

View File

@@ -0,0 +1,33 @@
import { Command } from 'nest-commander';
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { BaseCommand } from './BaseCommand';
@Injectable()
@Command({
name: 'tenants:migrate:make',
description: 'Create a named migration file to the tenants database.',
arguments: '<name>',
})
export class TenantsMigrateMakeCommand extends BaseCommand {
constructor(configService: ConfigService) {
super(configService);
}
async run(passedParams: string[]): Promise<void> {
const [name] = passedParams;
if (!name) {
this.exit('Migration name is required');
return;
}
try {
const tenantKnex = this.initTenantKnex();
const migrationName = await tenantKnex.migrate.make(name);
this.success(`Created Migration: ${migrationName}`);
} catch (error) {
this.exit(error);
}
}
}

View File

@@ -0,0 +1,74 @@
import { Command, Option } from 'nest-commander';
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { PromisePool } from '@supercharge/promise-pool';
import { BaseCommand } from './BaseCommand';
interface TenantsMigrateRollbackOptions {
tenant_id?: string;
}
@Injectable()
@Command({
name: 'tenants:migrate:rollback',
description: 'Rollback the last batch of tenants migrations.',
})
export class TenantsMigrateRollbackCommand extends BaseCommand {
private readonly MIGRATION_CONCURRENCY = 10;
constructor(configService: ConfigService) {
super(configService);
}
@Option({
flags: '-t, --tenant_id [tenant_id]',
description: 'Which organization id do you migrate.',
})
parseTenantId(val: string): string {
return val;
}
async run(passedParams: string[], options: TenantsMigrateRollbackOptions): Promise<void> {
try {
const sysKnex = this.initSystemKnex();
const tenants = await this.getAllInitializedTenants(sysKnex);
const tenantsOrgsIds = tenants.map((tenant: any) => tenant.organizationId);
if (options.tenant_id && tenantsOrgsIds.indexOf(options.tenant_id) === -1) {
this.exit(`The given tenant id ${options.tenant_id} does not exist.`);
}
const migrateTenant = async (organizationId: string) => {
try {
const tenantKnex = this.initTenantKnex(organizationId);
const [batchNo, _log] = await tenantKnex.migrate.rollback();
const tenantDb = `${this.configService.get('tenantDatabase.dbNamePrefix')}${organizationId}`;
if (_log.length === 0) {
this.log('Already at the base migration');
}
this.log(
`Tenant: ${tenantDb} > Batch ${batchNo} rolled back: ${_log.length} migrations`
);
this.log('---------------');
} catch (error) {
this.exit(error);
}
};
if (!options.tenant_id) {
await PromisePool.withConcurrency(this.MIGRATION_CONCURRENCY)
.for(tenants)
.process((tenant: any) => {
return migrateTenant(tenant.organizationId);
});
this.success('All tenants are rollbacked.');
} else {
await migrateTenant(options.tenant_id);
}
} catch (error) {
this.exit(error);
}
}
}

View File

@@ -0,0 +1,12 @@
import { Command, CommandRunner } from 'nest-commander';
@Command({
name: 'tenants:seed:latest',
description: 'Seed all tenant databases with the latest data',
})
export class TenantsSeedLatestCommand extends CommandRunner {
async run(): Promise<void> {
console.log('Tenants seeding with latest data - No operation performed');
// TODO: Implement tenants seeding logic
}
}

127
pnpm-lock.yaml generated
View File

@@ -228,6 +228,9 @@ importers:
mysql2:
specifier: ^3.11.3
version: 3.11.4
nest-commander:
specifier: ^3.20.1
version: 3.20.1(@nestjs/common@10.4.7)(@nestjs/core@10.4.7)(@types/inquirer@8.2.12)(@types/node@20.5.1)(typescript@5.6.3)
nestjs-cls:
specifier: ^5.2.0
version: 5.2.0(@nestjs/common@10.4.7)(@nestjs/core@10.4.7)(reflect-metadata@0.2.2)(rxjs@7.8.1)
@@ -5867,6 +5870,15 @@ packages:
resolution: {integrity: sha512-cEee/Z+I12mZcFJshKcCqC8tuX5hG3s+d+9nZ3LabqKF1vKdF41B92pJVCBggjAGORAeOzyyDDKrZwIkLffeOQ==}
dev: true
/@fig/complete-commander@3.2.0(commander@11.1.0):
resolution: {integrity: sha512-1Holl3XtRiANVKURZwgpjCnPuV4RsHp+XC0MhgvyAX/avQwj7F2HUItYOvGi/bXjJCkEzgBZmVfCr0HBA+q+Bw==}
peerDependencies:
commander: ^11.1.0
dependencies:
commander: 11.1.0
prettier: 3.3.3
dev: false
/@floating-ui/core@1.6.8:
resolution: {integrity: sha512-7XJ9cPU+yI2QeLS+FCSlqNFZJq8arvswefkZrYI1yQBbftw6FyrZOxYSh+9S7z7TpeWlRt9zJ5IhM1WIL334jA==}
dependencies:
@@ -5907,6 +5919,17 @@ packages:
deprecated: the package is rather renamed to @formatjs/ecma-abstract with some changes in functionality (primarily selectUnit is removed and we don't plan to make any further changes to this package
dev: false
/@golevelup/nestjs-discovery@5.0.0(@nestjs/common@10.4.7)(@nestjs/core@10.4.7):
resolution: {integrity: sha512-NaIWLCLI+XvneUK05LH2idHLmLNITYT88YnpOuUQmllKtiJNIS3woSt7QXrMZ5k3qUWuZpehEVz1JtlX4I1KyA==}
peerDependencies:
'@nestjs/common': ^11.0.20
'@nestjs/core': ^11.0.20
dependencies:
'@nestjs/common': 10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1)
'@nestjs/core': 10.4.7(@nestjs/common@10.4.7)(@nestjs/platform-express@10.4.7)(@nestjs/websockets@10.4.20)(reflect-metadata@0.2.2)(rxjs@7.8.1)
lodash: 4.17.21
dev: false
/@humanfs/core@0.19.0:
resolution: {integrity: sha512-2cbWIHbZVEweE853g8jymffCA+NCMiuqeECeBBLm8dg2oFdjuGJhgN4UAbI+6v0CKbbhvtXA4qV8YR5Ji86nmw==}
engines: {node: '>=18.18.0'}
@@ -5961,6 +5984,20 @@ packages:
warning: 4.0.3
dev: false
/@inquirer/external-editor@1.0.2(@types/node@20.5.1):
resolution: {integrity: sha512-yy9cOoBnx58TlsPrIxauKIFQTiyH+0MK4e97y4sV9ERbI+zDxw7i2hxHLCIEGIE/8PPvDxGhgzIOTSOWcs6/MQ==}
engines: {node: '>=18'}
peerDependencies:
'@types/node': '>=18'
peerDependenciesMeta:
'@types/node':
optional: true
dependencies:
'@types/node': 20.5.1
chardet: 2.1.0
iconv-lite: 0.7.0
dev: false
/@ioredis/commands@1.2.0:
resolution: {integrity: sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==}
dev: false
@@ -11681,6 +11718,13 @@ packages:
'@types/node': 20.5.1
dev: false
/@types/inquirer@8.2.12:
resolution: {integrity: sha512-YxURZF2ZsSjU5TAe06tW0M3sL4UI9AMPA6dd8I72uOtppzNafcY38xkYgCZ/vsVOAyNdzHmvtTpLWilOrbP0dQ==}
dependencies:
'@types/through': 0.0.33
rxjs: 7.8.1
dev: false
/@types/ioredis@5.0.0:
resolution: {integrity: sha512-zJbJ3FVE17CNl5KXzdeSPtdltc4tMT3TzC6fxQS0sQngkbFZ6h+0uTafsRqu+eSLIugf6Yb0Ea0SUuRr42Nk9g==}
deprecated: This is a stub types definition. ioredis provides its own type definitions, so you do not need this installed.
@@ -12073,6 +12117,12 @@ packages:
pretty-format: 25.5.0
dev: false
/@types/through@0.0.33:
resolution: {integrity: sha512-HsJ+z3QuETzP3cswwtzt2vEIiHBk/dCcHGhbmG5X3ecnwFD/lPrMpliGXxSCg03L9AhrdwA4Oz/qfspkDW+xGQ==}
dependencies:
'@types/node': 20.5.1
dev: false
/@types/trusted-types@2.0.7:
resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==}
dev: false
@@ -14135,7 +14185,6 @@ packages:
buffer: 5.7.1
inherits: 2.0.4
readable-stream: 3.6.2
dev: true
/bluebird@3.7.2:
resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==}
@@ -14338,7 +14387,6 @@ packages:
dependencies:
base64-js: 1.5.1
ieee754: 1.2.1
dev: true
/buffer@6.0.3:
resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==}
@@ -14679,6 +14727,10 @@ packages:
resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==}
dev: true
/chardet@2.1.0:
resolution: {integrity: sha512-bNFETTG/pM5ryzQ9Ad0lJOTa6HWD/YsScAR3EnCPZRPlQh77JocYktSHOUHelyhm8IARL+o4c4F1bP5KVOjiRA==}
dev: false
/check-error@2.1.1:
resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==}
engines: {node: '>= 16'}
@@ -14768,7 +14820,6 @@ packages:
engines: {node: '>=8'}
dependencies:
restore-cursor: 3.1.0
dev: true
/cli-spinners@2.6.1:
resolution: {integrity: sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g==}
@@ -14778,7 +14829,6 @@ packages:
/cli-spinners@2.9.2:
resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==}
engines: {node: '>=6'}
dev: true
/cli-table3@0.6.5:
resolution: {integrity: sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==}
@@ -14792,7 +14842,6 @@ packages:
/cli-width@3.0.0:
resolution: {integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==}
engines: {node: '>= 10'}
dev: true
/cli-width@4.1.0:
resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==}
@@ -14826,7 +14875,6 @@ packages:
/clone@1.0.4:
resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==}
engines: {node: '>=0.8'}
dev: true
/clsx@1.2.1:
resolution: {integrity: sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==}
@@ -14928,6 +14976,11 @@ packages:
resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==}
engines: {node: '>=14'}
/commander@11.1.0:
resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==}
engines: {node: '>=16'}
dev: false
/commander@2.20.3:
resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
@@ -15909,7 +15962,6 @@ packages:
resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==}
dependencies:
clone: 1.0.4
dev: true
/define-data-property@1.1.4:
resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==}
@@ -17735,7 +17787,6 @@ packages:
engines: {node: '>=8'}
dependencies:
escape-string-regexp: 1.0.5
dev: true
/file-entry-cache@6.0.1:
resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==}
@@ -19136,6 +19187,13 @@ packages:
dependencies:
safer-buffer: 2.1.2
/iconv-lite@0.7.0:
resolution: {integrity: sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==}
engines: {node: '>=0.10.0'}
dependencies:
safer-buffer: 2.1.2
dev: false
/icss-utils@5.1.0(postcss@8.4.47):
resolution: {integrity: sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==}
engines: {node: ^10 || ^12 || >= 14}
@@ -19287,6 +19345,29 @@ packages:
wrap-ansi: 6.2.0
dev: true
/inquirer@8.2.7(@types/node@20.5.1):
resolution: {integrity: sha512-UjOaSel/iddGZJ5xP/Eixh6dY1XghiBw4XK13rCCIJcJfyhhoul/7KhLLUGtebEj6GDYM6Vnx/mVsjx2L/mFIA==}
engines: {node: '>=12.0.0'}
dependencies:
'@inquirer/external-editor': 1.0.2(@types/node@20.5.1)
ansi-escapes: 4.3.2
chalk: 4.1.2
cli-cursor: 3.1.0
cli-width: 3.0.0
figures: 3.2.0
lodash: 4.17.21
mute-stream: 0.0.8
ora: 5.4.1
run-async: 2.4.1
rxjs: 7.8.1
string-width: 4.2.3
strip-ansi: 6.0.1
through: 2.3.8
wrap-ansi: 6.2.0
transitivePeerDependencies:
- '@types/node'
dev: false
/inquirer@9.2.15:
resolution: {integrity: sha512-vI2w4zl/mDluHt9YEQ/543VTCwPKWiHzKtm9dM2V0NdFcqEexDAjUHzO1oA60HRNaVifGXXM1tRRNluLVHa0Kg==}
engines: {node: '>=18'}
@@ -19581,7 +19662,6 @@ packages:
/is-interactive@1.0.0:
resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==}
engines: {node: '>=8'}
dev: true
/is-lambda@1.0.1:
resolution: {integrity: sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==}
@@ -19766,7 +19846,6 @@ packages:
/is-unicode-supported@0.1.0:
resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==}
engines: {node: '>=10'}
dev: true
/is-weakmap@2.0.2:
resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==}
@@ -21695,7 +21774,6 @@ packages:
dependencies:
chalk: 4.1.2
is-unicode-supported: 0.1.0
dev: true
/long@5.2.3:
resolution: {integrity: sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==}
@@ -22408,7 +22486,6 @@ packages:
/mute-stream@0.0.8:
resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==}
dev: true
/mute-stream@1.0.0:
resolution: {integrity: sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==}
@@ -22527,6 +22604,26 @@ packages:
/neo-async@2.6.2:
resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==}
/nest-commander@3.20.1(@nestjs/common@10.4.7)(@nestjs/core@10.4.7)(@types/inquirer@8.2.12)(@types/node@20.5.1)(typescript@5.6.3):
resolution: {integrity: sha512-LRI7z6UlWy2vWyQR0PYnAXsaRyJvpfiuvOCmx2jk2kLXJH9+y/omPDl9NE3fq4WMaE0/AhviuUjA12eC/zDqXw==}
peerDependencies:
'@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0
'@nestjs/core': ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0
'@types/inquirer': ^8.1.3
dependencies:
'@fig/complete-commander': 3.2.0(commander@11.1.0)
'@golevelup/nestjs-discovery': 5.0.0(@nestjs/common@10.4.7)(@nestjs/core@10.4.7)
'@nestjs/common': 10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1)
'@nestjs/core': 10.4.7(@nestjs/common@10.4.7)(@nestjs/platform-express@10.4.7)(@nestjs/websockets@10.4.20)(reflect-metadata@0.2.2)(rxjs@7.8.1)
'@types/inquirer': 8.2.12
commander: 11.1.0
cosmiconfig: 8.3.6(typescript@5.6.3)
inquirer: 8.2.7(@types/node@20.5.1)
transitivePeerDependencies:
- '@types/node'
- typescript
dev: false
/nestjs-cls@5.2.0(@nestjs/common@10.4.7)(@nestjs/core@10.4.7)(reflect-metadata@0.2.2)(rxjs@7.8.1):
resolution: {integrity: sha512-xabZQ7aPHttZ5TwC4rEyYgsxm3/ArM+Dz4oJPWc5Q1p+jOp+UaDe37fKna6sIMeUmYpvZxMVtUKIhv7CfLxbOw==}
engines: {node: '>=18'}
@@ -23254,7 +23351,6 @@ packages:
log-symbols: 4.1.0
strip-ansi: 6.0.1
wcwidth: 1.0.1
dev: true
/orderedmap@2.1.1:
resolution: {integrity: sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==}
@@ -24697,7 +24793,6 @@ packages:
resolution: {integrity: sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==}
engines: {node: '>=14'}
hasBin: true
dev: true
/pretty-bytes@5.6.0:
resolution: {integrity: sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==}
@@ -26698,7 +26793,6 @@ packages:
dependencies:
onetime: 5.1.2
signal-exit: 3.0.7
dev: true
/ret@0.1.15:
resolution: {integrity: sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==}
@@ -26821,7 +26915,6 @@ packages:
/run-async@2.4.1:
resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==}
engines: {node: '>=0.12.0'}
dev: true
/run-async@3.0.0:
resolution: {integrity: sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==}
@@ -29754,7 +29847,6 @@ packages:
resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==}
dependencies:
defaults: 1.0.4
dev: true
/web-vitals@2.1.4:
resolution: {integrity: sha512-sVWcwhU5mX6crfI5Vd2dC4qchyTqxV8URinzt25XqVh+bHEPGH4C3NPrNionCP7Obx59wrYEbNlw4Z8sjALzZg==}
@@ -30425,7 +30517,6 @@ packages:
ansi-styles: 4.3.0
string-width: 4.2.3
strip-ansi: 6.0.1
dev: true
/wrap-ansi@7.0.0:
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}