feat: add version-service for IT0 App version management

New NestJS microservice (port 3009) providing complete version management
API for IT0 App, designed to integrate with the existing mobile-upgrade
frontend (update.szaiai.com).

Backend — packages/services/version-service/ (9 new files):
- AppVersion entity: platform (ANDROID/IOS), versionName, buildNumber,
  changelog, downloadUrl, fileSize, isForceUpdate, isEnabled, minOsVersion
- REST controller with 8 endpoints:
  GET/POST /api/v1/versions — list (with platform/disabled filters) & create
  GET/PUT/DELETE /api/v1/versions/:id — single CRUD
  PATCH /api/v1/versions/:id/toggle — enable/disable
  POST /api/v1/versions/upload — multipart APK/IPA upload (500MB limit)
  POST /api/v1/versions/parse — extract version info from APK/IPA
- File storage: /data/versions/{platform}/ via Docker volume
- APK/IPA parsing: app-info-parser package
- Database: public.app_versions table (non-tenant, platform-level)
- No JWT auth (internal version management, consistent with existing apps)

Infrastructure changes:
- Dockerfile.service: added version-service package.json COPY lines
- docker-compose.yml: version-service container (13009:3009), version_data
  volume, api-gateway depends_on
- kong.yml: version-service route (/api/v1/versions), CORS origin for
  update.szaiai.com (mobile-upgrade frontend domain)

Deployment note: nginx needs /downloads/versions/ location + client_max_body_size 500m

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-03-03 07:48:31 -08:00
parent 26369be760
commit f6dffe02c5
13 changed files with 774 additions and 0 deletions

View File

@ -24,6 +24,7 @@ COPY packages/services/inventory-service/package.json packages/services/inventor
COPY packages/services/monitor-service/package.json packages/services/monitor-service/
COPY packages/services/comm-service/package.json packages/services/comm-service/
COPY packages/services/audit-service/package.json packages/services/audit-service/
COPY packages/services/version-service/package.json packages/services/version-service/
# Install all dependencies (cached unless package.json changes)
RUN pnpm install --frozen-lockfile
@ -60,6 +61,7 @@ COPY --from=builder /app/packages/services/inventory-service/package.json packag
COPY --from=builder /app/packages/services/monitor-service/package.json packages/services/monitor-service/
COPY --from=builder /app/packages/services/comm-service/package.json packages/services/comm-service/
COPY --from=builder /app/packages/services/audit-service/package.json packages/services/audit-service/
COPY --from=builder /app/packages/services/version-service/package.json packages/services/version-service/
# Install production dependencies only
RUN pnpm install --frozen-lockfile --prod

View File

@ -63,6 +63,8 @@ services:
condition: service_healthy
audit-service:
condition: service_healthy
version-service:
condition: service_healthy
healthcheck:
test: ["CMD", "kong", "health"]
interval: 10s
@ -311,6 +313,39 @@ services:
networks:
- it0-network
version-service:
build:
context: ../..
dockerfile: Dockerfile.service
args:
SERVICE_NAME: version-service
SERVICE_PORT: 3009
container_name: it0-version-service
restart: unless-stopped
ports:
- "13009:3009"
volumes:
- version_data:/data/versions
environment:
- DB_HOST=postgres
- DB_PORT=5432
- DB_USERNAME=${POSTGRES_USER:-it0}
- DB_PASSWORD=${POSTGRES_PASSWORD:-it0_dev}
- DB_DATABASE=${POSTGRES_DB:-it0}
- VERSION_SERVICE_PORT=3009
- DOWNLOAD_BASE_URL=https://it0api.szaiai.com/downloads/versions
healthcheck:
test: ["CMD-SHELL", "node -e \"require('http').get('http://localhost:3009/',r=>{process.exit(r.statusCode<500?0:1)}).on('error',()=>process.exit(1))\""]
interval: 30s
timeout: 5s
retries: 3
start_period: 15s
depends_on:
postgres:
condition: service_healthy
networks:
- it0-network
# ===== LiveKit Infrastructure =====
# NOTE: livekit-server, voice-agent, voice-service use host networking
# to eliminate docker-proxy overhead for real-time audio (WebRTC UDP).
@ -429,6 +464,7 @@ services:
volumes:
postgres_data:
claude_tenants:
version_data:
networks:
it0-network:

View File

@ -113,6 +113,14 @@ services:
- /api/v1/audit
strip_path: false
- name: version-service
url: http://version-service:3009
routes:
- name: version-routes
paths:
- /api/v1/versions
strip_path: false
plugins:
# ===== Global plugins (apply to ALL routes) =====
- name: cors
@ -121,6 +129,7 @@ plugins:
- http://localhost:3000
- https://it0.szaiai.com
- http://it0.szaiai.com
- https://update.szaiai.com
methods:
- GET
- POST

View File

@ -0,0 +1,36 @@
{
"name": "@it0/version-service",
"version": "0.1.0",
"private": true,
"scripts": {
"build": "nest build",
"dev": "nest start --watch",
"start": "node dist/main",
"test": "jest"
},
"dependencies": {
"@nestjs/common": "^10.3.0",
"@nestjs/core": "^10.3.0",
"@nestjs/config": "^3.2.0",
"@nestjs/typeorm": "^10.0.0",
"@nestjs/platform-express": "^10.3.0",
"typeorm": "^0.3.20",
"pg": "^8.11.0",
"reflect-metadata": "^0.2.0",
"rxjs": "^7.8.0",
"multer": "^1.4.5-lts.1",
"app-info-parser": "^1.1.6",
"@it0/common": "workspace:*",
"@it0/database": "workspace:*"
},
"devDependencies": {
"@nestjs/cli": "^10.3.0",
"@types/express": "^4.17.21",
"@types/multer": "^1.4.11",
"@types/jest": "^29.5.0",
"@types/node": "^20.11.0",
"jest": "^29.7.0",
"ts-jest": "^29.1.0",
"typescript": "^5.4.0"
}
}

View File

@ -0,0 +1,14 @@
import { Platform } from '../../domain/entities/app-version.entity';
export class CreateVersionDto {
platform!: Platform;
versionName!: string;
buildNumber!: string;
changelog?: string;
downloadUrl?: string;
fileSize?: number;
isForceUpdate?: boolean;
isEnabled?: boolean;
minOsVersion?: string;
releaseDate?: string;
}

View File

@ -0,0 +1,10 @@
export class UpdateVersionDto {
versionName?: string;
buildNumber?: string;
changelog?: string;
downloadUrl?: string;
isForceUpdate?: boolean;
isEnabled?: boolean;
minOsVersion?: string;
releaseDate?: string;
}

View File

@ -0,0 +1,54 @@
import {
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
UpdateDateColumn,
} from 'typeorm';
export enum Platform {
ANDROID = 'ANDROID',
IOS = 'IOS',
}
@Entity('app_versions')
export class AppVersion {
@PrimaryGeneratedColumn('uuid')
id!: string;
@Column({ type: 'enum', enum: Platform })
platform!: Platform;
@Column({ type: 'varchar', length: 30 })
versionName!: string;
@Column({ type: 'varchar', length: 20 })
buildNumber!: string;
@Column({ type: 'text', nullable: true })
changelog?: string;
@Column({ type: 'varchar', length: 500, nullable: true })
downloadUrl?: string;
@Column({ type: 'bigint', nullable: true })
fileSize?: number;
@Column({ type: 'boolean', default: false })
isForceUpdate!: boolean;
@Column({ type: 'boolean', default: true })
isEnabled!: boolean;
@Column({ type: 'varchar', length: 20, nullable: true })
minOsVersion?: string;
@Column({ type: 'timestamptz', nullable: true })
releaseDate?: Date;
@CreateDateColumn({ type: 'timestamptz' })
createdAt!: Date;
@UpdateDateColumn({ type: 'timestamptz' })
updatedAt!: Date;
}

View File

@ -0,0 +1,50 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { AppVersion, Platform } from '../../domain/entities/app-version.entity';
export interface VersionListFilter {
platform?: Platform;
includeDisabled?: boolean;
}
@Injectable()
export class AppVersionRepository {
constructor(
@InjectRepository(AppVersion)
private readonly repo: Repository<AppVersion>,
) {}
async findAll(filter?: VersionListFilter): Promise<AppVersion[]> {
const qb = this.repo.createQueryBuilder('v');
if (filter?.platform) {
qb.andWhere('v.platform = :platform', { platform: filter.platform });
}
if (!filter?.includeDisabled) {
qb.andWhere('v.isEnabled = true');
}
qb.orderBy('v.createdAt', 'DESC');
return qb.getMany();
}
async findById(id: string): Promise<AppVersion | null> {
return this.repo.findOne({ where: { id } });
}
async create(data: Partial<AppVersion>): Promise<AppVersion> {
const entity = this.repo.create(data);
return this.repo.save(entity);
}
async update(id: string, data: Partial<AppVersion>): Promise<AppVersion | null> {
await this.repo.update(id, data);
return this.findById(id);
}
async delete(id: string): Promise<void> {
await this.repo.delete(id);
}
}

View File

@ -0,0 +1,214 @@
import {
Controller,
Get,
Post,
Put,
Delete,
Patch,
Param,
Query,
Body,
UploadedFile,
UseInterceptors,
NotFoundException,
Logger,
} from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import { ConfigService } from '@nestjs/config';
import { diskStorage } from 'multer';
import * as fs from 'fs';
import * as path from 'path';
import { AppVersionRepository } from '../../../infrastructure/repositories/app-version.repository';
import { Platform } from '../../../domain/entities/app-version.entity';
import { CreateVersionDto } from '../../../application/dtos/create-version.dto';
import { UpdateVersionDto } from '../../../application/dtos/update-version.dto';
const UPLOAD_DIR = '/data/versions';
@Controller('api/v1/versions')
export class VersionController {
private readonly logger = new Logger(VersionController.name);
private readonly downloadBaseUrl: string;
constructor(
private readonly versionRepo: AppVersionRepository,
private readonly config: ConfigService,
) {
this.downloadBaseUrl = this.config.get<string>(
'DOWNLOAD_BASE_URL',
'https://it0api.szaiai.com/downloads/versions',
);
// Ensure upload directories exist
for (const p of [Platform.ANDROID, Platform.IOS]) {
const dir = path.join(UPLOAD_DIR, p.toLowerCase());
fs.mkdirSync(dir, { recursive: true });
}
}
@Get()
async list(
@Query('platform') platform?: string,
@Query('includeDisabled') includeDisabled?: string,
) {
const filter: { platform?: Platform; includeDisabled?: boolean } = {};
if (platform) {
filter.platform = platform.toUpperCase() as Platform;
}
if (includeDisabled === 'true') {
filter.includeDisabled = true;
}
return this.versionRepo.findAll(filter);
}
@Get(':id')
async getById(@Param('id') id: string) {
const version = await this.versionRepo.findById(id);
if (!version) throw new NotFoundException('Version not found');
return version;
}
@Post()
async create(@Body() dto: CreateVersionDto) {
return this.versionRepo.create({
...dto,
releaseDate: dto.releaseDate ? new Date(dto.releaseDate) : undefined,
});
}
@Put(':id')
async update(@Param('id') id: string, @Body() dto: UpdateVersionDto) {
const existing = await this.versionRepo.findById(id);
if (!existing) throw new NotFoundException('Version not found');
return this.versionRepo.update(id, {
...dto,
releaseDate: dto.releaseDate ? new Date(dto.releaseDate) : undefined,
});
}
@Delete(':id')
async delete(@Param('id') id: string) {
const version = await this.versionRepo.findById(id);
if (!version) throw new NotFoundException('Version not found');
// Delete the associated file if it exists
if (version.downloadUrl) {
const platformDir = version.platform.toLowerCase();
const filename = path.basename(new URL(version.downloadUrl).pathname);
const filePath = path.join(UPLOAD_DIR, platformDir, filename);
if (fs.existsSync(filePath)) {
fs.unlinkSync(filePath);
this.logger.log(`Deleted file: ${filePath}`);
}
}
await this.versionRepo.delete(id);
return { deleted: true };
}
@Patch(':id/toggle')
async toggle(@Param('id') id: string, @Body('isEnabled') isEnabled: boolean) {
const existing = await this.versionRepo.findById(id);
if (!existing) throw new NotFoundException('Version not found');
return this.versionRepo.update(id, { isEnabled });
}
@Post('upload')
@UseInterceptors(
FileInterceptor('file', {
storage: diskStorage({
destination: (req, _file, cb) => {
const platform = (req.body.platform || 'ANDROID').toLowerCase();
const dir = path.join(UPLOAD_DIR, platform);
fs.mkdirSync(dir, { recursive: true });
cb(null, dir);
},
filename: (_req, file, cb) => {
// Preserve original filename with timestamp prefix to avoid collisions
const timestamp = Date.now();
const safeName = file.originalname.replace(/[^a-zA-Z0-9._-]/g, '_');
cb(null, `${timestamp}_${safeName}`);
},
}),
limits: { fileSize: 500 * 1024 * 1024 }, // 500 MB
}),
)
async upload(
@UploadedFile() file: Express.Multer.File,
@Body('platform') platform: string,
@Body('versionName') versionName: string,
@Body('buildNumber') buildNumber: string,
@Body('changelog') changelog?: string,
@Body('isForceUpdate') isForceUpdate?: string,
@Body('minOsVersion') minOsVersion?: string,
@Body('releaseDate') releaseDate?: string,
) {
const platformEnum = (platform || 'ANDROID').toUpperCase() as Platform;
const platformDir = platformEnum.toLowerCase();
const downloadUrl = `${this.downloadBaseUrl}/${platformDir}/${file.filename}`;
return this.versionRepo.create({
platform: platformEnum,
versionName,
buildNumber,
changelog: changelog || undefined,
downloadUrl,
fileSize: file.size,
isForceUpdate: isForceUpdate === 'true',
isEnabled: true,
minOsVersion: minOsVersion || undefined,
releaseDate: releaseDate ? new Date(releaseDate) : undefined,
});
}
@Post('parse')
@UseInterceptors(
FileInterceptor('file', {
storage: diskStorage({
destination: '/tmp',
filename: (_req, file, cb) => {
cb(null, `parse_${Date.now()}_${file.originalname}`);
},
}),
limits: { fileSize: 500 * 1024 * 1024 },
}),
)
async parsePackage(
@UploadedFile() file: Express.Multer.File,
@Body('platform') platform: string,
) {
try {
// Dynamic import for app-info-parser (CommonJS module)
const AppInfoParser = (await import('app-info-parser')).default;
const parser = new AppInfoParser(file.path);
const info = await parser.parse();
let result: { versionName?: string; versionCode?: string; minSdkVersion?: string } = {};
if (platform?.toUpperCase() === 'IOS') {
result = {
versionName: info.CFBundleShortVersionString,
versionCode: info.CFBundleVersion,
minSdkVersion: info.MinimumOSVersion,
};
} else {
result = {
versionName: info.versionName,
versionCode: String(info.versionCode),
minSdkVersion: info.usesSdk?.minSdkVersion
? String(info.usesSdk.minSdkVersion)
: undefined,
};
}
return result;
} catch (err) {
this.logger.warn(`Failed to parse package: ${(err as Error).message}`);
return { versionName: null, versionCode: null, minSdkVersion: null };
} finally {
// Clean up temp file
if (fs.existsSync(file.path)) {
fs.unlinkSync(file.path);
}
}
}
}

View File

@ -0,0 +1,25 @@
import { NestFactory } from '@nestjs/core';
import { Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { VersionModule } from './version.module';
const logger = new Logger('VersionService');
process.on('unhandledRejection', (reason) => {
logger.error(`Unhandled Rejection: ${reason}`);
});
process.on('uncaughtException', (error) => {
logger.error(`Uncaught Exception: ${error.message}`, error.stack);
});
async function bootstrap() {
const app = await NestFactory.create(VersionModule);
const config = app.get(ConfigService);
const port = config.get<number>('VERSION_SERVICE_PORT', 3009);
await app.listen(port);
logger.log(`version-service running on port ${port}`);
}
bootstrap().catch((err) => {
logger.error(`Failed to start version-service: ${err.message}`, err.stack);
process.exit(1);
});

View File

@ -0,0 +1,30 @@
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AppVersion } from './domain/entities/app-version.entity';
import { AppVersionRepository } from './infrastructure/repositories/app-version.repository';
import { VersionController } from './interfaces/rest/controllers/version.controller';
@Module({
imports: [
ConfigModule.forRoot({ isGlobal: true }),
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (config: ConfigService) => ({
type: 'postgres' as const,
host: config.get<string>('DB_HOST', 'localhost'),
port: config.get<number>('DB_PORT', 5432),
username: config.get<string>('DB_USERNAME', 'it0'),
password: config.get<string>('DB_PASSWORD', 'it0_dev'),
database: config.get<string>('DB_DATABASE', 'it0'),
entities: [AppVersion],
synchronize: true,
}),
}),
TypeOrmModule.forFeature([AppVersion]),
],
controllers: [VersionController],
providers: [AppVersionRepository],
})
export class VersionModule {}

View File

@ -0,0 +1,15 @@
{
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"outDir": "./dist",
"baseUrl": ".",
"paths": {
"@it0/common": ["../../shared/common/src"],
"@it0/common/*": ["../../shared/common/src/*"],
"@it0/database": ["../../shared/database/src"],
"@it0/database/*": ["../../shared/database/src/*"]
}
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "test"]
}

View File

@ -589,6 +589,73 @@ importers:
specifier: ^5.4.0
version: 5.9.3
packages/services/version-service:
dependencies:
'@it0/common':
specifier: workspace:*
version: link:../../shared/common
'@it0/database':
specifier: workspace:*
version: link:../../shared/database
'@nestjs/common':
specifier: ^10.3.0
version: 10.4.22(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2)
'@nestjs/config':
specifier: ^3.2.0
version: 3.3.0(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(rxjs@7.8.2)
'@nestjs/core':
specifier: ^10.3.0
version: 10.4.22(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/microservices@10.4.22)(@nestjs/platform-express@10.4.22)(@nestjs/websockets@10.4.22)(reflect-metadata@0.2.2)(rxjs@7.8.2)
'@nestjs/platform-express':
specifier: ^10.3.0
version: 10.4.22(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.22)
'@nestjs/typeorm':
specifier: ^10.0.0
version: 10.0.2(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.22)(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.28(ioredis@5.9.2)(pg@8.18.0))
app-info-parser:
specifier: ^1.1.6
version: 1.1.6
multer:
specifier: ^1.4.5-lts.1
version: 1.4.5-lts.2
pg:
specifier: ^8.11.0
version: 8.18.0
reflect-metadata:
specifier: ^0.2.0
version: 0.2.2
rxjs:
specifier: ^7.8.0
version: 7.8.2
typeorm:
specifier: ^0.3.20
version: 0.3.28(ioredis@5.9.2)(pg@8.18.0)
devDependencies:
'@nestjs/cli':
specifier: ^10.3.0
version: 10.4.9
'@types/express':
specifier: ^4.17.21
version: 4.17.25
'@types/jest':
specifier: ^29.5.0
version: 29.5.14
'@types/multer':
specifier: ^1.4.11
version: 1.4.13
'@types/node':
specifier: ^20.11.0
version: 20.19.33
jest:
specifier: ^29.7.0
version: 29.7.0(@types/node@20.19.33)
ts-jest:
specifier: ^29.1.0
version: 29.4.6(@babel/core@7.29.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.29.0))(jest-util@29.7.0)(jest@29.7.0(@types/node@20.19.33))(typescript@5.9.3)
typescript:
specifier: ^5.4.0
version: 5.9.3
packages/shared/common:
dependencies:
'@nestjs/common':
@ -1407,6 +1474,9 @@ packages:
'@types/minimatch@5.1.2':
resolution: {integrity: sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==}
'@types/multer@1.4.13':
resolution: {integrity: sha512-bhhdtPw7JqCiEfC9Jimx5LqX9BDIPJEh2q/fQ4bqbBPtyEZYr3cvF22NwG0DmPZNYA0CAf2CnqDB4KIGGpJcaw==}
'@types/node-fetch@2.6.13':
resolution: {integrity: sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==}
@ -1510,6 +1580,10 @@ packages:
'@webassemblyjs/wast-printer@1.14.1':
resolution: {integrity: sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==}
'@xmldom/xmldom@0.8.11':
resolution: {integrity: sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw==}
engines: {node: '>=10.0.0'}
'@xtuc/ieee754@1.2.0':
resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==}
@ -1600,6 +1674,10 @@ packages:
resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
engines: {node: '>= 8'}
app-info-parser@1.1.6:
resolution: {integrity: sha512-ZAFCM0bN88cbpsMoRhL/JfdX3b+nb5iBEXcu30xABvbaqtw6tXfHujDnuKSpNmA3P0uwpkIxTV/Wun5HfEch8A==}
hasBin: true
app-root-path@3.1.0:
resolution: {integrity: sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA==}
engines: {node: '>= 6.0.0'}
@ -1671,6 +1749,10 @@ packages:
bcryptjs@2.4.3:
resolution: {integrity: sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==}
big-integer@1.6.52:
resolution: {integrity: sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==}
engines: {node: '>=0.6'}
binary-extensions@2.3.0:
resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
engines: {node: '>=8'}
@ -1682,6 +1764,10 @@ packages:
resolution: {integrity: sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==}
engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
bplist-parser@0.2.0:
resolution: {integrity: sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw==}
engines: {node: '>= 5.10.0'}
brace-expansion@1.1.12:
resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==}
@ -1704,6 +1790,9 @@ packages:
bser@2.1.1:
resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==}
buffer-crc32@0.2.13:
resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==}
buffer-equal-constant-time@1.0.1:
resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==}
@ -1716,6 +1805,9 @@ packages:
buffer@6.0.3:
resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==}
bufferpack@0.0.6:
resolution: {integrity: sha512-MTWvLHElqczrIVhge9qHtqgNigJFyh0+tCDId5yCbFAfuekHWIG+uAgvoHVflwrDPuY/e47JE1ki5qcM7w4uLg==}
bull@4.16.5:
resolution: {integrity: sha512-lDsx2BzkKe7gkCYiT5Acj02DpTwDznl/VNN7Psn7M3USPG7Vs/BaClZJJTAG+ufAR9++N1/NiUTdaFBWDIl5TQ==}
engines: {node: '>=12'}
@ -1724,6 +1816,10 @@ packages:
resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==}
engines: {node: '>=10.16.0'}
bytebuffer@5.0.1:
resolution: {integrity: sha512-IuzSdmADppkZ6DlpycMkm8l9zeEq16fWtLvunEwFiYciR/BHo4E8/xs5piFquG+Za8OWmMqHF8zuRviz2LHvRQ==}
engines: {node: '>=0.8'}
bytes@3.1.2:
resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
engines: {node: '>= 0.8'}
@ -1755,6 +1851,9 @@ packages:
caniuse-lite@1.0.30001769:
resolution: {integrity: sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==}
cgbi-to-png@1.0.7:
resolution: {integrity: sha512-YR80kxTPuq9oRpZUdQmNEQWrmTKLINk1cfLVfyrV7Rfr9KLtLJdcockPKbreIr4JYAq+DhHBR7w+WA/tF5VDaQ==}
chalk@4.1.2:
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
engines: {node: '>=10'}
@ -1848,6 +1947,10 @@ packages:
resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==}
engines: {node: '>= 6'}
commander@7.2.0:
resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==}
engines: {node: '>= 10'}
comment-json@4.2.5:
resolution: {integrity: sha512-bKw/r35jR3HGt5PEPm1ljsQQGyCrR8sFGNiN5L+ykDHdpO8Smxkrkla9Yi6NkQyUrb8V54PGhfMs6NrIwtxtdw==}
engines: {node: '>= 6'}
@ -1855,6 +1958,10 @@ packages:
concat-map@0.0.1:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
concat-stream@1.6.2:
resolution: {integrity: sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==}
engines: {'0': node >= 0.8}
concat-stream@2.0.0:
resolution: {integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==}
engines: {'0': node >= 6.0}
@ -1900,6 +2007,9 @@ packages:
typescript:
optional: true
crc@3.8.0:
resolution: {integrity: sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==}
create-jest@29.7.0:
resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@ -2151,6 +2261,9 @@ packages:
fb-watchman@2.0.2:
resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==}
fd-slicer@1.1.0:
resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==}
fflate@0.8.2:
resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==}
@ -2431,12 +2544,18 @@ packages:
resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==}
engines: {node: '>=10'}
isarray@1.0.0:
resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==}
isarray@2.0.5:
resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==}
isexe@2.0.0:
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
isomorphic-unzip@1.1.5:
resolution: {integrity: sha512-2McA51lWhmO3Kk438jxVcYeh6L+AOqVnl9XdX1yI7GlLA9RwEyTBgGem1rNuRIU2abAmOiv+IagThdUxASY4IA==}
istanbul-lib-coverage@3.2.2:
resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==}
engines: {node: '>=8'}
@ -2722,6 +2841,10 @@ packages:
resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==}
engines: {node: '>=10'}
long@3.2.0:
resolution: {integrity: sha512-ZYvPPOMqUwPoDsbJaR10iQJYnMuZhRTvHYl62ErLIEX7RgFlziSBUUvrt3OVfc47QlHHpzPZYP17g3Fv7oeJkg==}
engines: {node: '>=0.6'}
lru-cache@10.4.3:
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
@ -2824,6 +2947,11 @@ packages:
msgpackr@1.11.8:
resolution: {integrity: sha512-bC4UGzHhVvgDNS7kn9tV8fAucIYUBuGojcaLiz7v+P63Lmtm0Xeji8B/8tYKddALXxJLpwIeBmUN3u64C4YkRA==}
multer@1.4.5-lts.2:
resolution: {integrity: sha512-VzGiVigcG9zUAoCNU+xShztrlr1auZOlurXynNvO9GiWD1/mTBbUljOKY+qMeazBqXgRnjzeEgJI/wyjJUHg9A==}
engines: {node: '>= 6.0.0'}
deprecated: Multer 1.x is impacted by a number of vulnerabilities, which have been patched in 2.x. You should upgrade to the latest 2.x version.
multer@2.0.2:
resolution: {integrity: sha512-u7f2xaZ/UG8oLXHvtF/oWTRvT44p9ecwBBqTwgJVq0+4BW1g8OW01TyMEGWBHbyMOYVHXslaut7qEQ1meATXgw==}
engines: {node: '>= 10.16.0'}
@ -2996,6 +3124,9 @@ packages:
pause@0.0.1:
resolution: {integrity: sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==}
pend@1.2.0:
resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==}
pg-cloudflare@1.3.0:
resolution: {integrity: sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ==}
@ -3049,6 +3180,10 @@ packages:
resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==}
engines: {node: '>=8'}
plist@3.1.0:
resolution: {integrity: sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==}
engines: {node: '>=10.4.0'}
pluralize@8.0.0:
resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==}
engines: {node: '>=4'}
@ -3077,6 +3212,9 @@ packages:
resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
process-nextick-args@2.0.1:
resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==}
prompts@2.4.2:
resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==}
engines: {node: '>= 6'}
@ -3116,6 +3254,9 @@ packages:
react-is@18.3.1:
resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==}
readable-stream@2.3.8:
resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==}
readable-stream@3.6.2:
resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==}
engines: {node: '>= 6'}
@ -3189,6 +3330,9 @@ packages:
rxjs@7.8.2:
resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==}
safe-buffer@5.1.2:
resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==}
safe-buffer@5.2.1:
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
@ -3324,6 +3468,18 @@ packages:
resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==}
engines: {node: '>= 0.8'}
stream-to-buffer@0.1.0:
resolution: {integrity: sha512-Da4WoKaZyu3nf+bIdIifh7IPkFjARBnBK+pYqn0EUJqksjV9afojjaCCHUemH30Jmu7T2qcKvlZm2ykN38uzaw==}
engines: {node: '>= 0.8'}
stream-to@0.2.2:
resolution: {integrity: sha512-Kg1BSDTwgGiVMtTCJNlo7kk/xzL33ZuZveEBRt6rXw+f1WLK/8kmz2NVCT/Qnv0JkV85JOHcLhD82mnXsR3kPw==}
engines: {node: '>= 0.10.0'}
streamifier@0.1.1:
resolution: {integrity: sha512-zDgl+muIlWzXNsXeyUfOk9dChMjlpkq0DRsxujtYPgyJ676yQ8jEm6zzaaWHFDg5BNcLuif0eD2MTyJdZqXpdg==}
engines: {node: '>=0.10'}
streamsearch@1.1.0:
resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==}
engines: {node: '>=10.0.0'}
@ -3340,6 +3496,9 @@ packages:
resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==}
engines: {node: '>=12'}
string_decoder@1.1.1:
resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==}
string_decoder@1.3.0:
resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
@ -3784,6 +3943,10 @@ packages:
resolution: {integrity: sha512-Eux0i2QdDYKbdbA6AM6xE4m6ZTZr4G4xF9kahI2ukSEMCzwce2eX9WlTI5J3s+NU7hpasFsr8hWIONae7LluAQ==}
engines: {node: '>=6.0'}
xmlbuilder@15.1.1:
resolution: {integrity: sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==}
engines: {node: '>=8.0'}
xtend@4.0.2:
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
engines: {node: '>=0.4'}
@ -3803,6 +3966,9 @@ packages:
resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
engines: {node: '>=12'}
yauzl@2.10.0:
resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==}
yocto-queue@0.1.0:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'}
@ -4682,6 +4848,10 @@ snapshots:
'@types/minimatch@5.1.2': {}
'@types/multer@1.4.13':
dependencies:
'@types/express': 4.17.25
'@types/node-fetch@2.6.13':
dependencies:
'@types/node': 20.19.33
@ -4836,6 +5006,8 @@ snapshots:
'@webassemblyjs/ast': 1.14.1
'@xtuc/long': 4.2.2
'@xmldom/xmldom@0.8.11': {}
'@xtuc/ieee754@1.2.0': {}
'@xtuc/long@4.2.2': {}
@ -4924,6 +5096,15 @@ snapshots:
normalize-path: 3.0.0
picomatch: 2.3.1
app-info-parser@1.1.6:
dependencies:
bplist-parser: 0.2.0
bytebuffer: 5.0.1
cgbi-to-png: 1.0.7
commander: 7.2.0
isomorphic-unzip: 1.1.5
plist: 3.1.0
app-root-path@3.1.0: {}
append-field@1.0.0: {}
@ -5017,6 +5198,8 @@ snapshots:
bcryptjs@2.4.3: {}
big-integer@1.6.52: {}
binary-extensions@2.3.0: {}
bl@4.1.0:
@ -5042,6 +5225,10 @@ snapshots:
transitivePeerDependencies:
- supports-color
bplist-parser@0.2.0:
dependencies:
big-integer: 1.6.52
brace-expansion@1.1.12:
dependencies:
balanced-match: 1.0.2
@ -5071,6 +5258,8 @@ snapshots:
dependencies:
node-int64: 0.4.0
buffer-crc32@0.2.13: {}
buffer-equal-constant-time@1.0.1: {}
buffer-from@1.1.2: {}
@ -5085,6 +5274,8 @@ snapshots:
base64-js: 1.5.1
ieee754: 1.2.1
bufferpack@0.0.6: {}
bull@4.16.5:
dependencies:
cron-parser: 4.9.0
@ -5101,6 +5292,10 @@ snapshots:
dependencies:
streamsearch: 1.1.0
bytebuffer@5.0.1:
dependencies:
long: 3.2.0
bytes@3.1.2: {}
call-bind-apply-helpers@1.0.2:
@ -5128,6 +5323,13 @@ snapshots:
caniuse-lite@1.0.30001769: {}
cgbi-to-png@1.0.7:
dependencies:
bufferpack: 0.0.6
crc: 3.8.0
stream-to-buffer: 0.1.0
streamifier: 0.1.1
chalk@4.1.2:
dependencies:
ansi-styles: 4.3.0
@ -5209,6 +5411,8 @@ snapshots:
commander@4.1.1: {}
commander@7.2.0: {}
comment-json@4.2.5:
dependencies:
array-timsort: 1.0.3
@ -5219,6 +5423,13 @@ snapshots:
concat-map@0.0.1: {}
concat-stream@1.6.2:
dependencies:
buffer-from: 1.1.2
inherits: 2.0.4
readable-stream: 2.3.8
typedarray: 0.0.6
concat-stream@2.0.0:
dependencies:
buffer-from: 1.1.2
@ -5261,6 +5472,10 @@ snapshots:
optionalDependencies:
typescript: 5.7.2
crc@3.8.0:
dependencies:
buffer: 5.7.1
create-jest@29.7.0(@types/node@20.19.33):
dependencies:
'@jest/types': 29.6.3
@ -5513,6 +5728,10 @@ snapshots:
dependencies:
bser: 2.1.1
fd-slicer@1.1.0:
dependencies:
pend: 1.2.0
fflate@0.8.2: {}
figures@3.2.0:
@ -5837,10 +6056,17 @@ snapshots:
is-unicode-supported@0.1.0: {}
isarray@1.0.0: {}
isarray@2.0.5: {}
isexe@2.0.0: {}
isomorphic-unzip@1.1.5:
dependencies:
buffer: 5.7.1
yauzl: 2.10.0
istanbul-lib-coverage@3.2.2: {}
istanbul-lib-instrument@5.2.1:
@ -6326,6 +6552,8 @@ snapshots:
chalk: 4.1.2
is-unicode-supported: 0.1.0
long@3.2.0: {}
lru-cache@10.4.3: {}
lru-cache@5.1.1:
@ -6415,6 +6643,16 @@ snapshots:
optionalDependencies:
msgpackr-extract: 3.0.3
multer@1.4.5-lts.2:
dependencies:
append-field: 1.0.0
busboy: 1.6.0
concat-stream: 1.6.2
mkdirp: 0.5.6
object-assign: 4.1.1
type-is: 1.6.18
xtend: 4.0.2
multer@2.0.2:
dependencies:
append-field: 1.0.0
@ -6563,6 +6801,8 @@ snapshots:
pause@0.0.1: {}
pend@1.2.0: {}
pg-cloudflare@1.3.0:
optional: true
@ -6610,6 +6850,12 @@ snapshots:
dependencies:
find-up: 4.1.0
plist@3.1.0:
dependencies:
'@xmldom/xmldom': 0.8.11
base64-js: 1.5.1
xmlbuilder: 15.1.1
pluralize@8.0.0: {}
possible-typed-array-names@1.1.0: {}
@ -6630,6 +6876,8 @@ snapshots:
ansi-styles: 5.2.0
react-is: 18.3.1
process-nextick-args@2.0.1: {}
prompts@2.4.2:
dependencies:
kleur: 3.0.3
@ -6667,6 +6915,16 @@ snapshots:
react-is@18.3.1: {}
readable-stream@2.3.8:
dependencies:
core-util-is: 1.0.3
inherits: 2.0.4
isarray: 1.0.0
process-nextick-args: 2.0.1
safe-buffer: 5.1.2
string_decoder: 1.1.1
util-deprecate: 1.0.2
readable-stream@3.6.2:
dependencies:
inherits: 2.0.4
@ -6726,6 +6984,8 @@ snapshots:
dependencies:
tslib: 2.8.1
safe-buffer@5.1.2: {}
safe-buffer@5.2.1: {}
safer-buffer@2.1.2: {}
@ -6897,6 +7157,14 @@ snapshots:
statuses@2.0.2: {}
stream-to-buffer@0.1.0:
dependencies:
stream-to: 0.2.2
stream-to@0.2.2: {}
streamifier@0.1.1: {}
streamsearch@1.1.0: {}
string-length@4.0.2:
@ -6916,6 +7184,10 @@ snapshots:
emoji-regex: 9.2.2
strip-ansi: 7.1.2
string_decoder@1.1.1:
dependencies:
safe-buffer: 5.1.2
string_decoder@1.3.0:
dependencies:
safe-buffer: 5.2.1
@ -7286,6 +7558,8 @@ snapshots:
xmlbuilder@13.0.2: {}
xmlbuilder@15.1.1: {}
xtend@4.0.2: {}
y18n@5.0.8: {}
@ -7304,6 +7578,11 @@ snapshots:
y18n: 5.0.8
yargs-parser: 21.1.1
yauzl@2.10.0:
dependencies:
buffer-crc32: 0.2.13
fd-slicer: 1.1.0
yocto-queue@0.1.0: {}
zod@4.3.6: {}