chore(repo) init AppSchedule module
This commit is contained in:
parent
ef50d7075f
commit
1b7c586b69
@ -8,6 +8,7 @@ import { EnumifyModule } from './modules/enumify/enumify.module';
|
|||||||
import { AppFileTransactionsModule } from './modules/file-transactions/file-transactions.module';
|
import { AppFileTransactionsModule } from './modules/file-transactions/file-transactions.module';
|
||||||
import { AppHealthModule } from './modules/health/health.module';
|
import { AppHealthModule } from './modules/health/health.module';
|
||||||
import { AppLoggerModule } from './modules/logger/logger.module';
|
import { AppLoggerModule } from './modules/logger/logger.module';
|
||||||
|
import { AppScheduleModule } from './modules/schedule/schedule.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
@ -16,6 +17,7 @@ import { AppLoggerModule } from './modules/logger/logger.module';
|
|||||||
AppDatabaseModule,
|
AppDatabaseModule,
|
||||||
AppHealthModule,
|
AppHealthModule,
|
||||||
AppFileTransactionsModule,
|
AppFileTransactionsModule,
|
||||||
|
AppScheduleModule,
|
||||||
EnumifyModule,
|
EnumifyModule,
|
||||||
],
|
],
|
||||||
controllers: [AppController],
|
controllers: [AppController],
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
import { Global, Module } from '@nestjs/common';
|
import { Global, Module } from '@nestjs/common';
|
||||||
|
import { ConfigModule } from '@nestjs/config';
|
||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
import { DataSource } from 'typeorm';
|
import { DataSource } from 'typeorm';
|
||||||
import { DatabaseConfig, databaseConfig } from './database.config';
|
import { DatabaseConfig, databaseConfig } from './database.config';
|
||||||
import { APP_DATASOURCES } from './database.constants';
|
import { APP_DATASOURCES } from './database.constants';
|
||||||
import { typeormTransactionalDataSourceFactory } from './utils/typeorm-data-source-factory';
|
import { typeormTransactionalDataSourceFactory } from './utils/typeorm-data-source-factory';
|
||||||
import { typeormEntitiesFromImport } from './utils/typeorm-import-entities';
|
|
||||||
import { typeormModuleOptionsFactory } from './utils/typeorm-module-options-factory';
|
import { typeormModuleOptionsFactory } from './utils/typeorm-module-options-factory';
|
||||||
import { ConfigModule } from '@nestjs/config';
|
|
||||||
|
|
||||||
const dataSources: DataSource[] = [];
|
const dataSources: DataSource[] = [];
|
||||||
|
|
||||||
@ -15,10 +14,7 @@ const typeormModules = [
|
|||||||
imports: databaseConfig.asProvider().imports,
|
imports: databaseConfig.asProvider().imports,
|
||||||
dataSourceFactory: typeormTransactionalDataSourceFactory(),
|
dataSourceFactory: typeormTransactionalDataSourceFactory(),
|
||||||
useFactory: async (dbConfig: DatabaseConfig) => {
|
useFactory: async (dbConfig: DatabaseConfig) => {
|
||||||
// const entities = await import('./path/to/entities');
|
const config = await typeormModuleOptionsFactory(dbConfig);
|
||||||
const entities = {};
|
|
||||||
|
|
||||||
const config = await typeormModuleOptionsFactory(dbConfig, [typeormEntitiesFromImport(entities)]);
|
|
||||||
return config;
|
return config;
|
||||||
},
|
},
|
||||||
inject: [databaseConfig.KEY],
|
inject: [databaseConfig.KEY],
|
||||||
|
@ -0,0 +1 @@
|
|||||||
|
export { TipiJobsEntity } from './tipi_jobs.entity';
|
@ -0,0 +1,50 @@
|
|||||||
|
import { Column, Entity, Index } from 'typeorm';
|
||||||
|
import { Enumify } from '../../enumify/enumify';
|
||||||
|
|
||||||
|
export class InviaMailSeErrori {
|
||||||
|
@Column('bit', { name: 'flag', default: () => '(0)' })
|
||||||
|
flagAttivo: boolean;
|
||||||
|
|
||||||
|
@Column('varchar', { name: 'oggetto', nullable: true, length: 255 })
|
||||||
|
oggetto: string | null;
|
||||||
|
|
||||||
|
@Column('varchar', { name: 'destinatari', nullable: true, length: 255 })
|
||||||
|
destinatari: string | null;
|
||||||
|
|
||||||
|
@Column('varchar', { name: 'cc', nullable: true, length: 255 })
|
||||||
|
cc: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity('tipi_jobs')
|
||||||
|
export class TipiJobsEntity extends Enumify {
|
||||||
|
static _ = TipiJobsEntity.closeEnum();
|
||||||
|
|
||||||
|
// Columns
|
||||||
|
|
||||||
|
@Column('int', { primary: true, name: 'id' })
|
||||||
|
id: number;
|
||||||
|
|
||||||
|
@Column('varchar', { name: 'nome', length: 50 })
|
||||||
|
nome: string;
|
||||||
|
|
||||||
|
@Column('varchar', { name: 'descrizione', nullable: true, length: 255 })
|
||||||
|
descrizione: string | null;
|
||||||
|
|
||||||
|
@Column(() => InviaMailSeErrori, { prefix: 'invia_mail_se_errori'})
|
||||||
|
inviaMailSeErrori: InviaMailSeErrori;
|
||||||
|
|
||||||
|
@Column('bit', { name: 'flag_attivo', default: () => '(0)' })
|
||||||
|
flagAttivo: boolean;
|
||||||
|
|
||||||
|
@Column('varchar', { name: 'pattern_cron', length: 20 })
|
||||||
|
patternCron: string;
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
id: number,
|
||||||
|
nome: string,
|
||||||
|
patternCron: string,
|
||||||
|
) {
|
||||||
|
super(id, nome);
|
||||||
|
this.patternCron = patternCron;
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,9 @@
|
|||||||
import { TypeOrmModuleOptions } from '@nestjs/typeorm';
|
import { TypeOrmModuleOptions } from '@nestjs/typeorm';
|
||||||
import { DatabaseConfig } from '../database.config';
|
import { DatabaseConfig } from '../database.config';
|
||||||
|
import { SnakeNamingStrategy } from 'typeorm-naming-strategies';
|
||||||
|
|
||||||
export const typeormModuleOptionsFactory = async (
|
export const typeormModuleOptionsFactory = async (
|
||||||
databaseConfig: DatabaseConfig,
|
databaseConfig: DatabaseConfig,
|
||||||
entities: any[],
|
|
||||||
name?: string
|
name?: string
|
||||||
): Promise<TypeOrmModuleOptions> => {
|
): Promise<TypeOrmModuleOptions> => {
|
||||||
return {
|
return {
|
||||||
@ -14,9 +14,10 @@ export const typeormModuleOptionsFactory = async (
|
|||||||
username: databaseConfig.username,
|
username: databaseConfig.username,
|
||||||
password: databaseConfig.password,
|
password: databaseConfig.password,
|
||||||
database: databaseConfig.database,
|
database: databaseConfig.database,
|
||||||
entities: [...entities],
|
autoLoadEntities: true,
|
||||||
synchronize: false,
|
synchronize: false,
|
||||||
logging: process.env['NODE_ENV'] !== 'production',
|
logging: process.env['NODE_ENV'] !== 'production',
|
||||||
|
namingStrategy: new SnakeNamingStrategy(),
|
||||||
options: {
|
options: {
|
||||||
...(!databaseConfig.secure ? { trustServerCertificate: true } : {}),
|
...(!databaseConfig.secure ? { trustServerCertificate: true } : {}),
|
||||||
},
|
},
|
||||||
|
12
apps/ebitemp-api/src/app/modules/schedule/schedule.module.ts
Normal file
12
apps/ebitemp-api/src/app/modules/schedule/schedule.module.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { ScheduleModule } from '@nestjs/schedule';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
import { TipiJobsEntity } from '../database/entities';
|
||||||
|
import { SchedulerService } from './schedule.service';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [TypeOrmModule.forFeature([TipiJobsEntity]), ScheduleModule.forRoot()],
|
||||||
|
providers: [SchedulerService],
|
||||||
|
exports: [],
|
||||||
|
})
|
||||||
|
export class AppScheduleModule {}
|
@ -0,0 +1,52 @@
|
|||||||
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
|
import { SchedulerRegistry } from '@nestjs/schedule';
|
||||||
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
import { CronJob, CronTime } from 'cron';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
import { TipiJobsEntity } from '../database/entities/tipi_jobs.entity';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class SchedulerService {
|
||||||
|
private readonly logger = new Logger(SchedulerService.name);
|
||||||
|
|
||||||
|
private static readonly TIME_ZONE = 'Europe/Rome';
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly schedulerRegistry: SchedulerRegistry,
|
||||||
|
@InjectRepository(TipiJobsEntity) private readonly tipiJobsRepository: Repository<TipiJobsEntity>
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async onApplicationBootstrap() {
|
||||||
|
try {
|
||||||
|
const jobs = this.schedulerRegistry.getCronJobs();
|
||||||
|
const jobTypes = await this.tipiJobsRepository.find();
|
||||||
|
|
||||||
|
for (const [jobName, job] of jobs) {
|
||||||
|
this.rescheduleJobAccordingToJobType(jobTypes, jobName, job);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
this.logger.error(err.message);
|
||||||
|
this.logger.warn(`Error while retrieving scheduled jobs on database. Skipping jobs.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private rescheduleJobAccordingToJobType(jobTypes: TipiJobsEntity[], jobName: string, job: CronJob) {
|
||||||
|
const jobType = jobTypes.find((jobType) => jobType.nome == jobName);
|
||||||
|
|
||||||
|
if (!jobType) {
|
||||||
|
this.logger.warn(`Job type for job '${jobName}' not found on database. Skipping job.`);
|
||||||
|
this.schedulerRegistry.deleteCronJob(jobName);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const jobTime = new CronTime(jobType.patternCron, SchedulerService.TIME_ZONE);
|
||||||
|
job.setTime(jobTime);
|
||||||
|
job.start();
|
||||||
|
|
||||||
|
this.logger.log(
|
||||||
|
`Job type id '${jobType.id}' found for job '${jobName}' [isActive: ${jobType.flagAttivo}, cronPattern: ${
|
||||||
|
jobType.patternCron
|
||||||
|
}]. Upcoming date => ${job.nextDate()}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -18,12 +18,14 @@
|
|||||||
"@nestjs/core": "^11.0.8",
|
"@nestjs/core": "^11.0.8",
|
||||||
"@nestjs/platform-express": "^11.0.8",
|
"@nestjs/platform-express": "^11.0.8",
|
||||||
"@nestjs/platform-fastify": "^11.0.8",
|
"@nestjs/platform-fastify": "^11.0.8",
|
||||||
|
"@nestjs/schedule": "^5.0.1",
|
||||||
"@nestjs/terminus": "^11.0.0",
|
"@nestjs/terminus": "^11.0.0",
|
||||||
"@nestjs/typeorm": "^11.0.0",
|
"@nestjs/typeorm": "^11.0.0",
|
||||||
"@nx/devkit": "20.4.2",
|
"@nx/devkit": "20.4.2",
|
||||||
"axios": "^1.7.9",
|
"axios": "^1.7.9",
|
||||||
"cacheable": "^1.8.8",
|
"cacheable": "^1.8.8",
|
||||||
"connection-string": "^4.4.0",
|
"connection-string": "^4.4.0",
|
||||||
|
"cron": "^3.5.0",
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.13",
|
||||||
"flatted": "^3.3.2",
|
"flatted": "^3.3.2",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
@ -34,6 +36,7 @@
|
|||||||
"reflect-metadata": "^0.2.2",
|
"reflect-metadata": "^0.2.2",
|
||||||
"rxjs": "^7.8.1",
|
"rxjs": "^7.8.1",
|
||||||
"typeorm": "^0.3.20",
|
"typeorm": "^0.3.20",
|
||||||
|
"typeorm-naming-strategies": "^4.1.0",
|
||||||
"typeorm-scoped": "^1.2.0",
|
"typeorm-scoped": "^1.2.0",
|
||||||
"typeorm-transactional": "^0.5.0",
|
"typeorm-transactional": "^0.5.0",
|
||||||
"zod": "^3.24.1"
|
"zod": "^3.24.1"
|
||||||
|
44
pnpm-lock.yaml
generated
44
pnpm-lock.yaml
generated
@ -29,6 +29,9 @@ dependencies:
|
|||||||
'@nestjs/platform-fastify':
|
'@nestjs/platform-fastify':
|
||||||
specifier: ^11.0.8
|
specifier: ^11.0.8
|
||||||
version: 11.0.8(@nestjs/common@11.0.8)(@nestjs/core@11.0.8)
|
version: 11.0.8(@nestjs/common@11.0.8)(@nestjs/core@11.0.8)
|
||||||
|
'@nestjs/schedule':
|
||||||
|
specifier: ^5.0.1
|
||||||
|
version: 5.0.1(@nestjs/common@11.0.8)(@nestjs/core@11.0.8)
|
||||||
'@nestjs/terminus':
|
'@nestjs/terminus':
|
||||||
specifier: ^11.0.0
|
specifier: ^11.0.0
|
||||||
version: 11.0.0(@nestjs/common@11.0.8)(@nestjs/core@11.0.8)(@nestjs/typeorm@11.0.0)(reflect-metadata@0.2.2)(rxjs@7.8.1)(typeorm@0.3.20)
|
version: 11.0.0(@nestjs/common@11.0.8)(@nestjs/core@11.0.8)(@nestjs/typeorm@11.0.0)(reflect-metadata@0.2.2)(rxjs@7.8.1)(typeorm@0.3.20)
|
||||||
@ -47,6 +50,9 @@ dependencies:
|
|||||||
connection-string:
|
connection-string:
|
||||||
specifier: ^4.4.0
|
specifier: ^4.4.0
|
||||||
version: 4.4.0
|
version: 4.4.0
|
||||||
|
cron:
|
||||||
|
specifier: ^3.5.0
|
||||||
|
version: 3.5.0
|
||||||
dayjs:
|
dayjs:
|
||||||
specifier: ^1.11.13
|
specifier: ^1.11.13
|
||||||
version: 1.11.13
|
version: 1.11.13
|
||||||
@ -77,6 +83,9 @@ dependencies:
|
|||||||
typeorm:
|
typeorm:
|
||||||
specifier: ^0.3.20
|
specifier: ^0.3.20
|
||||||
version: 0.3.20(mssql@11.0.1)(ts-node@10.9.2)
|
version: 0.3.20(mssql@11.0.1)(ts-node@10.9.2)
|
||||||
|
typeorm-naming-strategies:
|
||||||
|
specifier: ^4.1.0
|
||||||
|
version: 4.1.0(typeorm@0.3.20)
|
||||||
typeorm-scoped:
|
typeorm-scoped:
|
||||||
specifier: ^1.2.0
|
specifier: ^1.2.0
|
||||||
version: 1.2.0(typeorm@0.3.20)
|
version: 1.2.0(typeorm@0.3.20)
|
||||||
@ -2524,6 +2533,17 @@ packages:
|
|||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@nestjs/schedule@5.0.1(@nestjs/common@11.0.8)(@nestjs/core@11.0.8):
|
||||||
|
resolution: {integrity: sha512-kFoel84I4RyS2LNPH6yHYTKxB16tb3auAEciFuc788C3ph6nABkUfzX5IE+unjVaRX+3GuruJwurNepMlHXpQg==}
|
||||||
|
peerDependencies:
|
||||||
|
'@nestjs/common': ^10.0.0 || ^11.0.0
|
||||||
|
'@nestjs/core': ^10.0.0 || ^11.0.0
|
||||||
|
dependencies:
|
||||||
|
'@nestjs/common': 11.0.8(reflect-metadata@0.2.2)(rxjs@7.8.1)
|
||||||
|
'@nestjs/core': 11.0.8(@nestjs/common@11.0.8)(@nestjs/platform-express@11.0.8)(reflect-metadata@0.2.2)(rxjs@7.8.1)
|
||||||
|
cron: 3.5.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@nestjs/schematics@11.0.0(typescript@5.7.3):
|
/@nestjs/schematics@11.0.0(typescript@5.7.3):
|
||||||
resolution: {integrity: sha512-wts8lG0GfNWw3Wk9aaG5I/wcMIAdm7HjjeThQfUZhJxeIFT82Z3F5+0cYdHH4ii2pYQGiCSrR1VcuMwPiHoecg==}
|
resolution: {integrity: sha512-wts8lG0GfNWw3Wk9aaG5I/wcMIAdm7HjjeThQfUZhJxeIFT82Z3F5+0cYdHH4ii2pYQGiCSrR1VcuMwPiHoecg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -3794,6 +3814,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-w/P33JFeySuhN6JLkysYUK2gEmy9kHHFN7E8ro0tkfmlDOgxBDzWEZ/J8cWA+fHqFevpswDTFZnDx+R9lbL6xw==}
|
resolution: {integrity: sha512-w/P33JFeySuhN6JLkysYUK2gEmy9kHHFN7E8ro0tkfmlDOgxBDzWEZ/J8cWA+fHqFevpswDTFZnDx+R9lbL6xw==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@types/luxon@3.4.2:
|
||||||
|
resolution: {integrity: sha512-TifLZlFudklWlMBfhubvgqTXRzLDI5pCbGa4P8a3wPyUQSW+1xQ5eDsreP9DWHX3tjq1ke96uYG/nwundroWcA==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@types/mime@1.3.5:
|
/@types/mime@1.3.5:
|
||||||
resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==}
|
resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==}
|
||||||
dev: true
|
dev: true
|
||||||
@ -5308,6 +5332,13 @@ packages:
|
|||||||
/create-require@1.1.1:
|
/create-require@1.1.1:
|
||||||
resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
|
resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
|
||||||
|
|
||||||
|
/cron@3.5.0:
|
||||||
|
resolution: {integrity: sha512-0eYZqCnapmxYcV06uktql93wNWdlTmmBFP2iYz+JPVcQqlyFYcn1lFuIk4R54pkOmE7mcldTAPZv6X5XA4Q46A==}
|
||||||
|
dependencies:
|
||||||
|
'@types/luxon': 3.4.2
|
||||||
|
luxon: 3.5.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/cross-spawn@5.1.0:
|
/cross-spawn@5.1.0:
|
||||||
resolution: {integrity: sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==}
|
resolution: {integrity: sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -8082,6 +8113,11 @@ packages:
|
|||||||
yallist: 3.1.1
|
yallist: 3.1.1
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/luxon@3.5.0:
|
||||||
|
resolution: {integrity: sha512-rh+Zjr6DNfUYR3bPwJEnuwDdqMbxZW7LOQfUN4B54+Cl+0o5zaU9RJ6bcidfDtC1cWCZXQ+nvX8bf6bAji37QQ==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/magic-string@0.30.0:
|
/magic-string@0.30.0:
|
||||||
resolution: {integrity: sha512-LA+31JYDJLs82r2ScLrlz1GjSgu66ZV518eyWT+S8VhyQn/JL0u9MeBOvQMGYiPk1DBiSN9DDMOcXvigJZaViQ==}
|
resolution: {integrity: sha512-LA+31JYDJLs82r2ScLrlz1GjSgu66ZV518eyWT+S8VhyQn/JL0u9MeBOvQMGYiPk1DBiSN9DDMOcXvigJZaViQ==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
@ -10875,6 +10911,14 @@ packages:
|
|||||||
- typeorm-aurora-data-api-driver
|
- typeorm-aurora-data-api-driver
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/typeorm-naming-strategies@4.1.0(typeorm@0.3.20):
|
||||||
|
resolution: {integrity: sha512-vPekJXzZOTZrdDvTl1YoM+w+sUIfQHG4kZTpbFYoTsufyv9NIBRe4Q+PdzhEAFA2std3D9LZHEb1EjE9zhRpiQ==}
|
||||||
|
peerDependencies:
|
||||||
|
typeorm: ^0.2.0 || ^0.3.0
|
||||||
|
dependencies:
|
||||||
|
typeorm: 0.3.20(mssql@11.0.1)(ts-node@10.9.2)
|
||||||
|
dev: false
|
||||||
|
|
||||||
/typeorm-scoped@1.2.0(typeorm@0.3.20):
|
/typeorm-scoped@1.2.0(typeorm@0.3.20):
|
||||||
resolution: {integrity: sha512-fVZUFIAHCib6Sq/k1wGzwtnFb9uP+g/hLDaoosczJpSNAG2YA2CyPR5J3+UuTz11kvdAfQO0Udk+6rL8xX2/Wg==}
|
resolution: {integrity: sha512-fVZUFIAHCib6Sq/k1wGzwtnFb9uP+g/hLDaoosczJpSNAG2YA2CyPR5J3+UuTz11kvdAfQO0Udk+6rL8xX2/Wg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
Reference in New Issue
Block a user