diff --git a/apps/server/package.json b/apps/server/package.json index 3ba970e..9bd59e9 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -37,7 +37,7 @@ "@nestjs/jwt": "^11.0.2", "@nestjs/mapped-types": "^2.1.1", "@nestjs/passport": "^11.0.5", - "@nestjs/platform-express": "^11.1.19", + "@nestjs/platform-express": "^11.1.28", "@nestjs/schedule": "^6.1.3", "@nestjs/throttler": "^6.5.0", "@nestjs/typeorm": "^11.0.1", @@ -47,7 +47,7 @@ "class-validator": "^0.15.1", "echarts": "^6.1.0", "exceljs": "^4.4.0", - "multer": "^2.1.1", + "multer": "^2.2.0", "mysql2": "^3.22.2", "passport": "^0.7.0", "passport-jwt": "^4.0.1", @@ -55,7 +55,7 @@ "pdfkit": "^0.18.0", "reflect-metadata": "^0.2.2", "rxjs": "^7.8.1", - "typeorm": "^0.3.28" + "typeorm": "^0.3.31" }, "optionalDependencies": { "better-sqlite3": "^12.9.0" diff --git a/apps/server/src/attendance/attendance-workflow.integration.spec.ts b/apps/server/src/attendance/attendance-workflow.integration.spec.ts new file mode 100644 index 0000000..6c301f2 --- /dev/null +++ b/apps/server/src/attendance/attendance-workflow.integration.spec.ts @@ -0,0 +1,331 @@ +import type { INestApplication } from '@nestjs/common'; +import { Test } from '@nestjs/testing'; +import { getRepositoryToken } from '@nestjs/typeorm'; +import request from 'supertest'; +import type { Repository } from 'typeorm'; +import { AppModule } from '../app.module'; +import type { DingTalkAttendanceResult } from '../integration/dingtalk.service'; +import { DingTalkService } from '../integration/dingtalk.service'; +import { + AttendanceRecord, + Organization, + Role, + Student, + StudentDingMapping, + User, +} from '../entities'; +import { createStudentImportTemplateWorkbook } from '../students/student-import'; + +const LESSON_DATE = new Intl.DateTimeFormat('en-CA', { + timeZone: 'Asia/Shanghai', + year: 'numeric', + month: '2-digit', + day: '2-digit', +}).format(new Date()); +const STUDENT_A_DING_ID = 'integration-student-a'; +const STUDENT_B_DING_ID = 'integration-student-b'; + +const auth = (token: string) => ({ Authorization: `Bearer ${token}` }); + +function chinaWeekDay(date: string): number { + const day = new Date(`${date}T00:00:00+08:00`).getDay(); + return day === 0 ? 7 : day; +} + +function attendanceResult( + userId: string, + checkId: string, + actualCheckTime: string, +): DingTalkAttendanceResult { + return { + userId, + userName: '', + workDate: LESSON_DATE, + timeResult: 'Normal', + locationResult: 'Normal', + planCheckTime: `${LESSON_DATE}T00:00:00+08:00`, + actualCheckTime, + checkId, + checkType: 'OnDuty', + sourceType: 'ATM', + deviceName: '集成测试考勤机', + deviceId: 'integration-device', + }; +} + +describe('attendance workflow integration', () => { + let app: INestApplication; + let adminToken: string; + let teacherToken: string; + let mockedPunches: DingTalkAttendanceResult[]; + const originalEnv = { + DB_TYPE: process.env.DB_TYPE, + DB_DATABASE: process.env.DB_DATABASE, + DB_SYNCHRONIZE: process.env.DB_SYNCHRONIZE, + SEED_DEV: process.env.SEED_DEV, + ADMIN_PASSWORD: process.env.ADMIN_PASSWORD, + }; + + beforeAll(async () => { + process.env.DB_TYPE = 'sqlite'; + process.env.DB_DATABASE = ':memory:'; + process.env.DB_SYNCHRONIZE = 'true'; + process.env.SEED_DEV = 'true'; + process.env.ADMIN_PASSWORD = 'admin123'; + + mockedPunches = []; + const dingTalk = { + fetchAttendanceResults: jest.fn(async () => mockedPunches), + }; + + const moduleRef = await Test.createTestingModule({ imports: [AppModule] }) + .overrideProvider(DingTalkService) + .useValue(dingTalk) + .compile(); + + app = moduleRef.createNestApplication(); + app.setGlobalPrefix('api'); + await app.init(); + + const login = await request(app.getHttpServer()) + .post('/api/auth/login') + .send({ username: 'admin', password: 'admin123' }) + .expect(201); + adminToken = login.body.access_token; + }); + + afterAll(async () => { + await app?.close(); + for (const [key, value] of Object.entries(originalEnv)) { + if (value === undefined) delete process.env[key]; + else process.env[key] = value; + } + }); + + it('imports students, builds a teacher class schedule, refreshes punches, and scopes reads', async () => { + const roleRepo = app.get>(getRepositoryToken(Role)); + const userRepo = app.get>(getRepositoryToken(User)); + const studentRepo = app.get>(getRepositoryToken(Student)); + const mappingRepo = app.get>( + getRepositoryToken(StudentDingMapping), + ); + const organizationRepo = app.get>(getRepositoryToken(Organization)); + const attendanceRepo = app.get>( + getRepositoryToken(AttendanceRecord), + ); + + const teacherRole = await roleRepo.findOneByOrFail({ code: 'teacher' }); + const teacherCreate = await request(app.getHttpServer()) + .post('/api/rbac/users') + .set(auth(adminToken)) + .send({ + username: 'integration-teacher', + password: 'teacher123', + name: '集成测试任课教师', + roleIds: [teacherRole.id], + }) + .expect(201); + expect(teacherCreate.body.message).toBe('用户创建成功'); + + const teacher = await userRepo.findOneByOrFail({ username: 'integration-teacher' }); + const teacherId = teacher.id; + const teacherLogin = await request(app.getHttpServer()) + .post('/api/auth/login') + .send({ username: 'integration-teacher', password: 'teacher123' }) + .expect(201); + teacherToken = teacherLogin.body.access_token; + + const host = await organizationRepo.findOneByOrFail({ isHost: true, status: 'active' }); + const workbook = createStudentImportTemplateWorkbook(); + const sheet = workbook.getWorksheet('学生基础+档案+录取')!; + sheet.spliceRows(2, 1); + sheet.addRow({ + phone: '13800000001', + name: '集成学生甲', + studentNo: 'IT001', + organization: host.name, + }); + sheet.addRow({ + phone: '13800000002', + name: '集成学生乙', + studentNo: 'IT002', + organization: host.name, + }); + const workbookBuffer = Buffer.from(await workbook.xlsx.writeBuffer()); + + const importResult = await request(app.getHttpServer()) + .post('/api/students/import') + .set(auth(adminToken)) + .attach('file', workbookBuffer, { + filename: 'attendance-workflow-students.xlsx', + contentType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + }) + .expect(201); + expect(importResult.body).toMatchObject({ imported: 2, skipped: 0 }); + + const [studentA, studentB] = await Promise.all([ + studentRepo.findOneByOrFail({ phone: '13800000001' }), + studentRepo.findOneByOrFail({ phone: '13800000002' }), + ]); + await mappingRepo.save([ + mappingRepo.create({ dingUserId: STUDENT_A_DING_ID, studentId: studentA.id }), + mappingRepo.create({ dingUserId: STUDENT_B_DING_ID, studentId: studentB.id }), + ]); + + const classResult = await request(app.getHttpServer()) + .post('/api/classes') + .set(auth(adminToken)) + .send({ + name: '集成考勤班', + code: 'ATTENDANCE-INTEGRATION', + classType: 'culture', + status: 'active', + startDate: LESSON_DATE, + endDate: LESSON_DATE, + }) + .expect(201); + const classId = classResult.body.id; + + await request(app.getHttpServer()) + .post(`/api/classes/${classId}/students`) + .set(auth(adminToken)) + .send({ studentIds: [studentA.id, studentB.id] }) + .expect(201) + .expect(({ body }) => expect(body).toMatchObject({ added: 2, skipped: 0 })); + + await request(app.getHttpServer()) + .post(`/api/classes/${classId}/teachers`) + .set(auth(adminToken)) + .send({ userId: teacherId, roleType: 'subject_teacher', subject: '语文' }) + .expect(201); + + const classroomResult = await request(app.getHttpServer()) + .post('/api/classrooms') + .set(auth(adminToken)) + .send({ name: '集成测试教室', building: '测试楼', floor: 1, capacity: 30, roomType: '小' }) + .expect(201); + + const scheduleResult = await request(app.getHttpServer()) + .post('/api/class-schedules') + .set(auth(adminToken)) + .send({ + classId, + classroomId: classroomResult.body.id, + weekDay: chinaWeekDay(LESSON_DATE), + startTime: '00:00', + endTime: '23:59', + attendanceAdvanceMinutes: 0, + startDate: LESSON_DATE, + endDate: LESSON_DATE, + subject: '语文', + teacherId, + scheduleType: 'INTERNAL', + }) + .expect(201); + const scheduleId = scheduleResult.body.id; + + const initialPull = await request(app.getHttpServer()) + .post(`/api/attendance-lessons/schedules/${scheduleId}/pull`) + .set(auth(teacherToken)) + .send({ date: LESSON_DATE }) + .expect(201); + expect(initialPull.body.records).toHaveLength(2); + expect(initialPull.body.records.map((record: AttendanceRecord) => record.status)).toEqual([ + 'pending', + 'pending', + ]); + + const studentARecord = initialPull.body.records.find( + (record: AttendanceRecord) => record.studentId === studentA.id, + ); + await request(app.getHttpServer()) + .put(`/api/attendance-records/${studentARecord.id}`) + .set(auth(teacherToken)) + .send({ status: 'absent', remark: '教师本地覆盖' }) + .expect(200) + .expect(({ body }) => expect(body).toMatchObject({ status: 'absent', source: 'manual' })); + + mockedPunches = [ + attendanceResult(STUDENT_A_DING_ID, 'integration-check-a', `${LESSON_DATE}T01:00:00.000Z`), + attendanceResult(STUDENT_B_DING_ID, 'integration-check-b', `${LESSON_DATE}T01:05:00.000Z`), + ]; + + const refreshed = await request(app.getHttpServer()) + .post(`/api/attendance-lessons/schedules/${scheduleId}/pull`) + .set(auth(teacherToken)) + .send({ date: LESSON_DATE }) + .expect(201); + expect(refreshed.body.records).toHaveLength(2); + expect( + refreshed.body.records.find((record: AttendanceRecord) => record.studentId === studentA.id), + ).toMatchObject({ status: 'absent', source: 'manual', remark: '教师本地覆盖' }); + expect( + refreshed.body.records.find((record: AttendanceRecord) => record.studentId === studentB.id), + ).toMatchObject({ status: 'present', source: 'dingtalk', punchSource: 'ATM' }); + + const teacherRecords = await request(app.getHttpServer()) + .get( + `/api/attendance-records?classId=${classId}&dateFrom=${LESSON_DATE}&dateTo=${LESSON_DATE}`, + ) + .set(auth(teacherToken)) + .expect(200); + expect(teacherRecords.body.list).toHaveLength(2); + const teacherView: Array> = + teacherRecords.body.list + .map((record: AttendanceRecord) => ({ + studentId: record.studentId, + status: record.status, + source: record.source, + })) + .sort((left, right) => left.studentId - right.studentId); + expect(teacherView).toEqual([ + { studentId: studentA.id, status: 'absent', source: 'manual' }, + { studentId: studentB.id, status: 'present', source: 'dingtalk' }, + ]); + + const adminRecords = await request(app.getHttpServer()) + .get( + `/api/attendance-records?classId=${classId}&dateFrom=${LESSON_DATE}&dateTo=${LESSON_DATE}`, + ) + .set(auth(adminToken)) + .expect(200); + expect(adminRecords.body.list).toHaveLength(2); + expect( + adminRecords.body.list + .map((record: AttendanceRecord) => ({ + studentId: record.studentId, + status: record.status, + source: record.source, + })) + .sort( + (left: Pick, right: Pick) => + left.studentId - right.studentId, + ), + ).toEqual(teacherView); + + const unassignedClass = await request(app.getHttpServer()) + .post('/api/classes') + .set(auth(adminToken)) + .send({ + name: '未分配教师班级', + code: 'UNASSIGNED-INTEGRATION', + classType: 'culture', + status: 'active', + }) + .expect(201); + await request(app.getHttpServer()) + .get(`/api/attendance-records?classId=${unassignedClass.body.id}`) + .set(auth(teacherToken)) + .expect(400); + + const persisted = await attendanceRepo.find({ + where: { classId }, + order: { studentId: 'ASC' }, + }); + expect(persisted).toHaveLength(2); + expect(persisted).toEqual([ + expect.objectContaining({ studentId: studentA.id, status: 'absent', source: 'manual' }), + expect.objectContaining({ studentId: studentB.id, status: 'present', source: 'dingtalk' }), + ]); + }); +}); diff --git a/package-lock.json b/package-lock.json index eb35ac2..85bce0e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -69,7 +69,7 @@ "@nestjs/jwt": "^11.0.2", "@nestjs/mapped-types": "^2.1.1", "@nestjs/passport": "^11.0.5", - "@nestjs/platform-express": "^11.1.19", + "@nestjs/platform-express": "^11.1.28", "@nestjs/schedule": "^6.1.3", "@nestjs/throttler": "^6.5.0", "@nestjs/typeorm": "^11.0.1", @@ -79,7 +79,7 @@ "class-validator": "^0.15.1", "echarts": "^6.1.0", "exceljs": "^4.4.0", - "multer": "^2.1.1", + "multer": "^2.2.0", "mysql2": "^3.22.2", "passport": "^0.7.0", "passport-jwt": "^4.0.1", @@ -87,7 +87,7 @@ "pdfkit": "^0.18.0", "reflect-metadata": "^0.2.2", "rxjs": "^7.8.1", - "typeorm": "^0.3.28" + "typeorm": "^0.3.31" }, "devDependencies": { "@eslint/eslintrc": "^3.2.0", @@ -2378,9 +2378,9 @@ } }, "node_modules/@jest/reporters/node_modules/brace-expansion": { - "version": "2.1.1", - "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.1.1.tgz", - "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.1.2.tgz", + "integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==", "dev": true, "license": "MIT", "dependencies": { @@ -3248,14 +3248,14 @@ } }, "node_modules/@nestjs/platform-express": { - "version": "11.1.27", - "resolved": "https://registry.npmmirror.com/@nestjs/platform-express/-/platform-express-11.1.27.tgz", - "integrity": "sha512-0ZFhz6H6EdGh4xQVbUNwjoAwBuz73P7FvUAl67h9CTdMqQlJDaQYJApBv8pKfVZ1fGjMCbl0m9DcC6pXaZPWSQ==", + "version": "11.1.28", + "resolved": "https://registry.npmmirror.com/@nestjs/platform-express/-/platform-express-11.1.28.tgz", + "integrity": "sha512-hU+9Sz4m+onHrR5AmelI59QKmY/Re546bPnygnpqqeQdHDiJpBgjWbL4t6Jr73CBpS60cpyng7WzjgphNB9iwA==", "license": "MIT", "dependencies": { "cors": "2.8.6", "express": "5.2.1", - "multer": "2.1.1", + "multer": "2.2.0", "path-to-regexp": "8.4.2", "tslib": "2.8.1" }, @@ -3268,68 +3268,6 @@ "@nestjs/core": "^11.0.0" } }, - "node_modules/@nestjs/platform-express/node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmmirror.com/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/@nestjs/platform-express/node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/@nestjs/platform-express/node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/@nestjs/platform-express/node_modules/multer": { - "version": "2.1.1", - "resolved": "https://registry.npmmirror.com/multer/-/multer-2.1.1.tgz", - "integrity": "sha512-mo+QTzKlx8R7E5ylSXxWzGoXoZbOsRMpyitcht8By2KHvMbf3tjwosZ/Mu/XYU6UuJ3VZnODIrak5ZrPiPyB6A==", - "license": "MIT", - "dependencies": { - "append-field": "^1.0.0", - "busboy": "^1.6.0", - "concat-stream": "^2.0.0", - "type-is": "^1.6.18" - }, - "engines": { - "node": ">= 10.16.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/@nestjs/platform-express/node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmmirror.com/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "license": "MIT", - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/@nestjs/schedule": { "version": "6.1.3", "resolved": "https://registry.npmmirror.com/@nestjs/schedule/-/schedule-6.1.3.tgz", @@ -7610,6 +7548,7 @@ "version": "4.2.0", "resolved": "https://registry.npmmirror.com/ansis/-/ansis-4.2.0.tgz", "integrity": "sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==", + "dev": true, "license": "ISC", "engines": { "node": ">=14" @@ -8174,9 +8113,9 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.15", - "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.15.tgz", - "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "version": "1.1.16", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.16.tgz", + "integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -12289,9 +12228,9 @@ } }, "node_modules/jest-config/node_modules/brace-expansion": { - "version": "2.1.1", - "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.1.1.tgz", - "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.1.2.tgz", + "integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==", "dev": true, "license": "MIT", "dependencies": { @@ -12814,9 +12753,9 @@ } }, "node_modules/jest-runtime/node_modules/brace-expansion": { - "version": "2.1.1", - "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.1.1.tgz", - "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.1.2.tgz", + "integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==", "dev": true, "license": "MIT", "dependencies": { @@ -15966,9 +15905,9 @@ } }, "node_modules/readdir-glob/node_modules/brace-expansion": { - "version": "2.1.1", - "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.1.1.tgz", - "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.1.2.tgz", + "integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -17898,16 +17837,16 @@ "license": "MIT" }, "node_modules/typeorm": { - "version": "0.3.30", - "resolved": "https://registry.npmmirror.com/typeorm/-/typeorm-0.3.30.tgz", - "integrity": "sha512-8T35PzjefOdqc2ZR9mwLQj0pUGp6lQhMbK2EvVMwJVJWlaoHm0v/Q6dThNOZkFchD+0yMg8gwjKM28ePiLSXSQ==", + "version": "0.3.31", + "resolved": "https://registry.npmmirror.com/typeorm/-/typeorm-0.3.31.tgz", + "integrity": "sha512-6u9EFtdLBgHjnPm78NStVeM+I/1MolTzKykDDcydzKUkh6E++YS6XViU/fePJbvDvEGU4Xq34KOM/CLeer9I2A==", "license": "MIT", "dependencies": { "@sqltools/formatter": "^1.2.5", - "ansis": "^4.2.0", + "ansis": "^4.3.1", "app-root-path": "^3.1.0", "buffer": "^6.0.3", - "dayjs": "^1.11.20", + "dayjs": "^1.11.21", "debug": "^4.4.3", "dedent": "^1.7.2", "dotenv": "^16.6.1", @@ -17917,7 +17856,7 @@ "sql-highlight": "^6.1.0", "tslib": "^2.8.1", "uuid": "^11.1.1", - "yargs": "^17.7.2" + "yargs": "^17.7.3" }, "bin": { "typeorm": "cli.js", @@ -17938,13 +17877,13 @@ "mongodb": "^5.8.0 || ^6.0.0", "mssql": "^9.1.1 || ^10.0.0 || ^11.0.0 || ^12.0.0", "mysql2": "^2.2.5 || ^3.0.1", - "oracledb": "^6.3.0", + "oracledb": "^6.3.0 || ^7.0.0", "pg": "^8.5.1", "pg-native": "^3.0.0", "pg-query-stream": "^4.0.0", "redis": "^3.1.1 || ^4.0.0 || ^5.0.14", "sql.js": "^1.4.0", - "sqlite3": "^5.0.3", + "sqlite3": "^5.0.3 || ^6.0.0", "ts-node": "^10.7.0", "typeorm-aurora-data-api-driver": "^2.0.0 || ^3.0.0" }, @@ -17999,10 +17938,19 @@ } } }, + "node_modules/typeorm/node_modules/ansis": { + "version": "4.3.1", + "resolved": "https://registry.npmmirror.com/ansis/-/ansis-4.3.1.tgz", + "integrity": "sha512-BJ8/l4R5LRE7hW9WdSuGYrLSHi2ynxeFpDFbH0K/CgNeY/tyhk+vO6TYxXC5r5CpUhNVX310xzPsN/H9lCdfOA==", + "license": "ISC", + "engines": { + "node": ">=14" + } + }, "node_modules/typeorm/node_modules/brace-expansion": { - "version": "2.1.1", - "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.1.1.tgz", - "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.1.2.tgz", + "integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0"