Updates to Express v5 (#7)

Updates the Express dependency to version 5.2.1 and its corresponding types.

This change also adapts the request validator middleware to extract validated query parameters and store them in `res.locals` instead of modifying `req.query` to align with Express v5's intended usage and prevent potential conflicts.

Includes a fix for a server startup error, logging the error and exiting the process, and adds a type definition file for Express locals.
This commit is contained in:
Carlos Santos 2026-02-24 11:54:43 +01:00 committed by GitHub
parent 78060c0cdf
commit 177134d2a6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 93 additions and 133 deletions

View File

@ -68,7 +68,7 @@
"cors": "2.8.6",
"cron": "4.4.0",
"dotenv": "16.6.1",
"express": "4.22.1",
"express": "5.2.1",
"express-rate-limit": "7.5.1",
"inversify": "6.2.2",
"ioredis": "5.6.1",
@ -87,7 +87,7 @@
"@types/bcrypt": "5.0.2",
"@types/cookie-parser": "1.4.9",
"@types/cors": "2.8.19",
"@types/express": "4.17.25",
"@types/express": "5.0.6",
"@types/jest": "29.5.14",
"@types/lodash.merge": "4.6.9",
"@types/ms": "2.1.0",

View File

@ -19,7 +19,7 @@ export const startRecording = async (req: Request, res: Response) => {
const logger = container.get(LoggerService);
const recordingService = container.get(RecordingService);
const { roomId, config } = req.body;
const { fields } = req.query as { fields?: MeetRecordingField[] };
const { fields } = res.locals.validatedQuery as { fields?: MeetRecordingField[] };
logger.info(`Starting recording in room '${roomId}'`);
try {
@ -39,7 +39,7 @@ export const startRecording = async (req: Request, res: Response) => {
export const stopRecording = async (req: Request, res: Response) => {
const logger = container.get(LoggerService);
const recordingId = req.params.recordingId;
const { fields } = req.query as { fields?: MeetRecordingField[] };
const { fields } = res.locals.validatedQuery as { fields?: MeetRecordingField[] };
try {
logger.info(`Stopping recording '${recordingId}'`);
@ -58,7 +58,7 @@ export const stopRecording = async (req: Request, res: Response) => {
export const getRecordings = async (req: Request, res: Response) => {
const logger = container.get(LoggerService);
const recordingService = container.get(RecordingService);
const queryParams = req.query;
const queryParams = res.locals.validatedQuery ?? {};
logger.info('Getting all recordings');
@ -82,7 +82,7 @@ export const getRecordings = async (req: Request, res: Response) => {
export const bulkDeleteRecordings = async (req: Request, res: Response) => {
const logger = container.get(LoggerService);
const recordingService = container.get(RecordingService);
const { recordingIds } = req.query as { recordingIds: string[] };
const { recordingIds } = res.locals.validatedQuery as { recordingIds: string[] };
logger.info(`Deleting recordings: ${recordingIds}`);
@ -105,7 +105,7 @@ export const getRecording = async (req: Request, res: Response) => {
const logger = container.get(LoggerService);
const recordingService = container.get(RecordingService);
const recordingId = req.params.recordingId;
const { fields } = req.query as { fields?: MeetRecordingField[] };
const { fields } = res.locals.validatedQuery as { fields?: MeetRecordingField[] };
logger.info(`Getting recording '${recordingId}'`);
@ -220,13 +220,14 @@ export const getRecordingUrl = async (req: Request, res: Response) => {
const logger = container.get(LoggerService);
const recordingService = container.get(RecordingService);
const recordingId = req.params.recordingId;
const privateAccess = req.query.privateAccess === 'true';
const { privateAccess } = res.locals.validatedQuery as { privateAccess: string };
const isPrivateAccess = privateAccess === 'true';
logger.info(`Getting URL for recording '${recordingId}'`);
try {
const recordingSecrets = await recordingService.getRecordingAccessSecrets(recordingId);
const secret = privateAccess ? recordingSecrets.privateAccessSecret : recordingSecrets.publicAccessSecret;
const secret = isPrivateAccess ? recordingSecrets.privateAccessSecret : recordingSecrets.publicAccessSecret;
const recordingUrl = `${getBaseUrl()}/recording/${recordingId}?secret=${secret}`;
return res.status(200).json({ url: recordingUrl });
@ -239,7 +240,7 @@ export const downloadRecordingsZip = async (req: Request, res: Response) => {
const logger = container.get(LoggerService);
const recordingService = container.get(RecordingService);
const { recordingIds } = req.query as { recordingIds: string[] };
const { recordingIds } = res.locals.validatedQuery as { recordingIds: string[] };
const validRecordings: MeetRecordingInfo[] = [];
logger.info(`Preparing ZIP download for recordings: ${recordingIds}`);

View File

@ -32,7 +32,7 @@ export const getRoomMembers = async (req: Request, res: Response) => {
const roomMemberService = container.get(RoomMemberService);
const { roomId } = req.params;
const filters = req.query as MeetRoomMemberFilters;
const filters = res.locals.validatedQuery as MeetRoomMemberFilters;
try {
logger.verbose(`Getting members for room '${roomId}'`);
@ -100,7 +100,7 @@ export const bulkDeleteRoomMembers = async (req: Request, res: Response) => {
const roomMemberService = container.get(RoomMemberService);
const { roomId } = req.params;
const { memberIds } = req.query as { memberIds: string[] };
const { memberIds } = res.locals.validatedQuery as { memberIds: string[] };
try {
logger.verbose(`Deleting members from room '${roomId}' with IDs: ${memberIds}`);

View File

@ -22,7 +22,7 @@ export const createRoom = async (req: Request, res: Response) => {
const roomService = container.get(RoomService);
const options: MeetRoomOptions = req.body;
// Fields are merged from headers into req.query by the middleware
const { fields, extraFields } = req.query as {
const { fields, extraFields } = res.locals.validatedQuery as {
fields?: MeetRoomField[];
extraFields?: MeetRoomExtraField[];
};
@ -46,7 +46,7 @@ export const createRoom = async (req: Request, res: Response) => {
export const getRooms = async (req: Request, res: Response) => {
const logger = container.get(LoggerService);
const roomService = container.get(RoomService);
const queryParams = req.query as MeetRoomFilters;
const queryParams = res.locals.validatedQuery as MeetRoomFilters;
logger.verbose(`Getting all rooms with filters: ${JSON.stringify(queryParams)}`);
@ -71,7 +71,7 @@ export const getRoom = async (req: Request, res: Response) => {
const { roomId } = req.params;
// Zod already validated and transformed to typed arrays
const { fields, extraFields } = req.query as {
const { fields, extraFields } = res.locals.validatedQuery as {
fields?: MeetRoomField[];
extraFields?: MeetRoomExtraField[];
};
@ -101,7 +101,7 @@ export const deleteRoom = async (req: Request, res: Response) => {
const roomService = container.get(RoomService);
const { roomId } = req.params;
const { fields, extraFields, withMeeting, withRecordings } = req.query as {
const { fields, extraFields, withMeeting, withRecordings } = res.locals.validatedQuery as {
fields?: MeetRoomField[];
extraFields?: MeetRoomExtraField[];
withMeeting: MeetRoomDeletionPolicyWithMeeting;
@ -141,7 +141,7 @@ export const bulkDeleteRooms = async (req: Request, res: Response) => {
const logger = container.get(LoggerService);
const roomService = container.get(RoomService);
const { roomIds, fields, extraFields, withMeeting, withRecordings } = req.query as {
const { roomIds, fields, extraFields, withMeeting, withRecordings } = res.locals.validatedQuery as {
roomIds: string[];
fields?: MeetRoomField[];
extraFields?: MeetRoomExtraField[];
@ -150,7 +150,7 @@ export const bulkDeleteRooms = async (req: Request, res: Response) => {
};
try {
logger.verbose(`Deleting rooms: ${roomIds} with options: ${JSON.stringify(req.query)}`);
logger.verbose(`Deleting rooms: ${roomIds} with options: ${JSON.stringify(res.locals.validatedQuery)}`);
const deleteOpts: MeetRoomDeletionOptions = {
withMeeting,

View File

@ -10,9 +10,9 @@ import {
} from '../models/error.model.js';
import { LoggerService } from '../services/logger.service.js';
import { RequestSessionService } from '../services/request-session.service.js';
import { TokenService } from '../services/token.service.js';
import { UserService } from '../services/user.service.js';
import { getBaseUrl } from '../utils/url.utils.js';
import { TokenService } from '../services/token.service.js';
export const createUser = async (req: Request, res: Response) => {
const userOptions = req.body as MeetUserOptions;
@ -31,7 +31,7 @@ export const createUser = async (req: Request, res: Response) => {
};
export const getUsers = async (req: Request, res: Response) => {
const queryParams = req.query as MeetUserFilters;
const queryParams = res.locals.validatedQuery as MeetUserFilters;
const logger = container.get(LoggerService);
logger.verbose(`Getting all users`);
@ -186,7 +186,7 @@ export const deleteUser = async (req: Request, res: Response) => {
};
export const bulkDeleteUsers = async (req: Request, res: Response) => {
const { userIds } = req.query as { userIds: string[] };
const { userIds } = res.locals.validatedQuery as { userIds: string[] };
const logger = container.get(LoggerService);
logger.verbose(`Deleting users: ${userIds}`);

View File

@ -15,7 +15,8 @@ import {
export const validateStartRecordingReq = (req: Request, res: Response, next: NextFunction) => {
// Merge X-Fields header into query params before validation
mergeRecordingHeaderFieldsIntoQuery(req.headers, req.query);
const query = req.query;
mergeRecordingHeaderFieldsIntoQuery(req.headers, query);
const bodyResult = StartRecordingReqSchema.safeParse(req.body);
@ -25,27 +26,28 @@ export const validateStartRecordingReq = (req: Request, res: Response, next: Nex
req.body = bodyResult.data;
const queryResult = RecordingQueryFieldsSchema.safeParse(req.query);
const queryResult = RecordingQueryFieldsSchema.safeParse(query);
if (!queryResult.success) {
return rejectUnprocessableRequest(res, queryResult.error);
}
req.query = queryResult.data;
res.locals.validatedQuery = queryResult.data;
next();
};
export const validateGetRecordingsReq = (req: Request, res: Response, next: NextFunction) => {
// Merge X-Fields header into query params before validation
mergeRecordingHeaderFieldsIntoQuery(req.headers, req.query);
const query = req.query;
mergeRecordingHeaderFieldsIntoQuery(req.headers, query);
const { success, error, data } = RecordingFiltersSchema.safeParse(req.query);
const { success, error, data } = RecordingFiltersSchema.safeParse(query);
if (!success) {
return rejectUnprocessableRequest(res, error);
}
req.query = {
res.locals.validatedQuery = {
...data,
maxItems: data.maxItems?.toString()
};
@ -59,7 +61,7 @@ export const validateBulkDeleteRecordingsReq = (req: Request, res: Response, nex
return rejectUnprocessableRequest(res, error);
}
req.query = data;
res.locals.validatedQuery = data;
next();
};
@ -77,11 +79,12 @@ export const withValidRecordingId = (req: Request, res: Response, next: NextFunc
export const validateGetRecordingReq = (req: Request, res: Response, next: NextFunction) => {
// Merge X-Fields header into query params before validation
mergeRecordingHeaderFieldsIntoQuery(req.headers, req.query);
const query = req.query;
mergeRecordingHeaderFieldsIntoQuery(req.headers, query);
const { success, error, data } = GetRecordingReqSchema.safeParse({
params: req.params,
query: req.query
query
});
if (!success) {
@ -89,17 +92,18 @@ export const validateGetRecordingReq = (req: Request, res: Response, next: NextF
}
req.params.recordingId = data.params.recordingId;
req.query = data.query;
res.locals.validatedQuery = data.query;
next();
};
export const validateStopRecordingReq = (req: Request, res: Response, next: NextFunction) => {
// Merge X-Fields header into query params before validation
mergeRecordingHeaderFieldsIntoQuery(req.headers, req.query);
const query = req.query;
mergeRecordingHeaderFieldsIntoQuery(req.headers, query);
const { success, error, data } = StopRecordingReqSchema.safeParse({
params: req.params,
query: req.query
query
});
if (!success) {
@ -107,7 +111,7 @@ export const validateStopRecordingReq = (req: Request, res: Response, next: Next
}
req.params.recordingId = data.params.recordingId;
req.query = data.query;
res.locals.validatedQuery = data.query;
next();
};
@ -139,6 +143,6 @@ export const validateGetRecordingUrlReq = (req: Request, res: Response, next: Ne
}
req.params.recordingId = data.params.recordingId;
req.query.privateAccess = data.query.privateAccess ? 'true' : 'false';
res.locals.validatedQuery = { privateAccess: data.query.privateAccess ? 'true' : 'false' };
next();
};

View File

@ -28,7 +28,7 @@ export const validateGetRoomMembersReq = (req: Request, res: Response, next: Nex
return rejectUnprocessableRequest(res, error);
}
req.query = {
res.locals.validatedQuery = {
...data,
maxItems: data.maxItems?.toString()
};
@ -42,7 +42,7 @@ export const validateBulkDeleteRoomMembersReq = (req: Request, res: Response, ne
return rejectUnprocessableRequest(res, error);
}
req.query = data;
res.locals.validatedQuery = data;
next();
};

View File

@ -15,7 +15,8 @@ import {
} from '../../models/zod-schemas/room.schema.js';
export const validateCreateRoomReq = (req: Request, res: Response, next: NextFunction) => {
mergeHeaderFieldsIntoQuery(req.headers, req.query);
const query = req.query;
mergeHeaderFieldsIntoQuery(req.headers, query);
const bodyResult = RoomOptionsSchema.safeParse(req.body);
@ -25,27 +26,28 @@ export const validateCreateRoomReq = (req: Request, res: Response, next: NextFun
req.body = bodyResult.data;
const { success, error, data } = RoomQueryFieldsSchema.safeParse(req.query);
const { success, error, data } = RoomQueryFieldsSchema.safeParse(query);
if (!success) {
return rejectUnprocessableRequest(res, error);
}
req.query = data;
res.locals.validatedQuery = data;
next();
};
export const validateGetRoomsReq = (req: Request, res: Response, next: NextFunction) => {
// Merge X-Fields and X-ExtraFields headers into query params before validation
mergeHeaderFieldsIntoQuery(req.headers, req.query);
const query = req.query;
mergeHeaderFieldsIntoQuery(req.headers, query);
const { success, error, data } = RoomFiltersSchema.safeParse(req.query);
const { success, error, data } = RoomFiltersSchema.safeParse(query);
if (!success) {
return rejectUnprocessableRequest(res, error);
}
req.query = {
res.locals.validatedQuery = {
...data,
maxItems: data.maxItems?.toString()
};
@ -54,15 +56,16 @@ export const validateGetRoomsReq = (req: Request, res: Response, next: NextFunct
export const validateBulkDeleteRoomsReq = (req: Request, res: Response, next: NextFunction) => {
// Merge X-Fields and X-ExtraFields headers into query params before validation
mergeHeaderFieldsIntoQuery(req.headers, req.query);
const query = req.query;
mergeHeaderFieldsIntoQuery(req.headers, query);
const { success, error, data } = BulkDeleteRoomsReqSchema.safeParse(req.query);
const { success, error, data } = BulkDeleteRoomsReqSchema.safeParse(query);
if (!success) {
return rejectUnprocessableRequest(res, error);
}
req.query = data;
res.locals.validatedQuery = data;
next();
};
@ -80,15 +83,16 @@ export const withValidRoomId = (req: Request, res: Response, next: NextFunction)
export const validateGetRoomReq = (req: Request, res: Response, next: NextFunction) => {
// Merge X-Fields and X-ExtraFields headers into query params before validation
mergeHeaderFieldsIntoQuery(req.headers, req.query);
const query = req.query;
mergeHeaderFieldsIntoQuery(req.headers, query);
const { success, error, data } = RoomQueryFieldsSchema.safeParse(req.query);
const { success, error, data } = RoomQueryFieldsSchema.safeParse(query);
if (!success) {
return rejectUnprocessableRequest(res, error);
}
req.query = data;
res.locals.validatedQuery = data;
next();
};
@ -102,15 +106,16 @@ export const validateDeleteRoomReq = (req: Request, res: Response, next: NextFun
req.params.roomId = roomIdResult.data;
// Merge X-Fields and X-ExtraFields headers into query params before validation
mergeHeaderFieldsIntoQuery(req.headers, req.query);
const query = req.query;
mergeHeaderFieldsIntoQuery(req.headers, query);
const queryParamsResult = DeleteRoomReqSchema.safeParse(req.query);
const queryParamsResult = DeleteRoomReqSchema.safeParse(query);
if (!queryParamsResult.success) {
return rejectUnprocessableRequest(res, queryParamsResult.error);
}
req.query = queryParamsResult.data;
res.locals.validatedQuery = queryParamsResult.data;
next();
};

View File

@ -27,7 +27,7 @@ export const validateGetUsersReq = (req: Request, res: Response, next: NextFunct
return rejectUnprocessableRequest(res, error);
}
req.query = {
res.locals.validatedQuery = {
...data,
maxItems: data.maxItems?.toString()
};
@ -41,7 +41,7 @@ export const validateBulkDeleteUsersReq = (req: Request, res: Response, next: Ne
return rejectUnprocessableRequest(res, error);
}
req.query = data;
res.locals.validatedQuery = data;
next();
};

View File

@ -127,7 +127,7 @@ const startServer = (app: express.Application) => {
const basePath = getBasePath();
const basePathDisplay = basePath === '/' ? '' : basePath.slice(0, -1);
app.listen(MEET_ENV.SERVER_PORT, async () => {
const server = app.listen(MEET_ENV.SERVER_PORT, () => {
console.log(' ');
console.log('---------------------------------------------------------');
console.log(' ');
@ -145,6 +145,11 @@ const startServer = (app: express.Application) => {
);
logEnvVars();
});
server.on('error', (error: Error) => {
console.error('Server failed to start:', error.message);
process.exit(1);
});
};
/**

View File

@ -0,0 +1,9 @@
declare global {
namespace Express {
interface Locals {
validatedQuery?: Record<string, unknown>;
}
}
}
export {};

View File

@ -380,11 +380,6 @@ describe('Recordings API Tests', () => {
expectValidationError(response, 'maxItems', 'must be a positive number');
});
it('should fail when fields is not a string', async () => {
const response = await getAllRecordings({ fields: { invalid: 'object' } });
expectValidationError(response, 'fields', 'Expected string');
});
it('should fail when sortField is invalid', async () => {
const response = await getAllRecordings({ sortField: 'invalidField' });
expectValidationError(response, 'sortField', 'Invalid enum value');

View File

@ -229,11 +229,6 @@ describe('Room API Tests', () => {
expectValidationError(response, 'maxItems', 'must be a positive number');
});
it('should fail when fields is not a string', async () => {
const response = await getRooms({ fields: { invalid: 'data' } });
expectValidationError(response, 'fields', 'Expected string');
});
it('should fail when sortField is invalid', async () => {
const response = await getRooms({ sortField: 'invalidField' });
expectValidationError(response, 'sortField', 'Invalid enum value');

82
pnpm-lock.yaml generated
View File

@ -105,11 +105,11 @@ importers:
specifier: 16.6.1
version: 16.6.1
express:
specifier: 4.22.1
version: 4.22.1
specifier: 5.2.1
version: 5.2.1
express-rate-limit:
specifier: 7.5.1
version: 7.5.1(express@4.22.1)
version: 7.5.1(express@5.2.1)
inversify:
specifier: 6.2.2
version: 6.2.2(reflect-metadata@0.2.2)
@ -152,13 +152,13 @@ importers:
version: 5.0.2
'@types/cookie-parser':
specifier: 1.4.9
version: 1.4.9(@types/express@4.17.25)
version: 1.4.9(@types/express@5.0.6)
'@types/cors':
specifier: 2.8.19
version: 2.8.19
'@types/express':
specifier: 4.17.25
version: 4.17.25
specifier: 5.0.6
version: 5.0.6
'@types/jest':
specifier: 29.5.14
version: 29.5.14
@ -4133,9 +4133,6 @@ packages:
'@types/express@4.17.23':
resolution: {integrity: sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==}
'@types/express@4.17.25':
resolution: {integrity: sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==}
'@types/express@5.0.6':
resolution: {integrity: sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==}
@ -6080,10 +6077,6 @@ packages:
resolution: {integrity: sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==}
engines: {node: '>= 0.10.0'}
express@4.22.1:
resolution: {integrity: sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==}
engines: {node: '>= 0.10.0'}
express@5.2.1:
resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==}
engines: {node: '>= 18'}
@ -14834,9 +14827,9 @@ snapshots:
dependencies:
'@types/express': 4.17.23
'@types/cookie-parser@1.4.9(@types/express@4.17.25)':
'@types/cookie-parser@1.4.9(@types/express@5.0.6)':
dependencies:
'@types/express': 4.17.25
'@types/express': 5.0.6
'@types/cookiejar@2.1.5': {}
@ -14887,13 +14880,6 @@ snapshots:
'@types/qs': 6.14.0
'@types/serve-static': 2.2.0
'@types/express@4.17.25':
dependencies:
'@types/body-parser': 1.19.6
'@types/express-serve-static-core': 4.19.7
'@types/qs': 6.14.0
'@types/serve-static': 1.15.10
'@types/express@5.0.6':
dependencies:
'@types/body-parser': 1.19.6
@ -17375,10 +17361,6 @@ snapshots:
dependencies:
express: 4.21.2
express-rate-limit@7.5.1(express@4.22.1):
dependencies:
express: 4.22.1
express-rate-limit@7.5.1(express@5.2.1):
dependencies:
express: 5.2.1
@ -17419,42 +17401,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
express@4.22.1:
dependencies:
accepts: 1.3.8
array-flatten: 1.1.1
body-parser: 1.20.4
content-disposition: 0.5.4
content-type: 1.0.5
cookie: 0.7.2
cookie-signature: 1.0.6
debug: 2.6.9
depd: 2.0.0
encodeurl: 2.0.0
escape-html: 1.0.3
etag: 1.8.1
finalhandler: 1.3.1
fresh: 0.5.2
http-errors: 2.0.1
merge-descriptors: 1.0.3
methods: 1.1.2
on-finished: 2.4.1
parseurl: 1.3.3
path-to-regexp: 0.1.12
proxy-addr: 2.0.7
qs: 6.14.0
range-parser: 1.2.1
safe-buffer: 5.2.1
send: 0.19.2
serve-static: 1.16.3
setprototypeof: 1.2.0
statuses: 2.0.2
type-is: 1.6.18
utils-merge: 1.0.1
vary: 1.1.2
transitivePeerDependencies:
- supports-color
express@5.2.1:
dependencies:
accepts: 2.0.0
@ -17477,7 +17423,7 @@ snapshots:
once: 1.4.0
parseurl: 1.3.3
proxy-addr: 2.0.7
qs: 6.14.0
qs: 6.15.0
range-parser: 1.2.1
router: 2.2.0
send: 1.2.1
@ -18028,7 +17974,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
http-proxy-middleware@2.0.9(@types/express@4.17.25):
http-proxy-middleware@2.0.9(@types/express@4.17.23):
dependencies:
'@types/http-proxy': 1.17.17
http-proxy: 1.18.1(debug@4.4.3)
@ -18036,7 +17982,7 @@ snapshots:
is-plain-obj: 3.0.0
micromatch: 4.0.8
optionalDependencies:
'@types/express': 4.17.25
'@types/express': 4.17.23
transitivePeerDependencies:
- debug
@ -22109,7 +22055,7 @@ snapshots:
dependencies:
'@types/bonjour': 3.5.13
'@types/connect-history-api-fallback': 1.5.4
'@types/express': 4.17.25
'@types/express': 4.17.23
'@types/express-serve-static-core': 4.19.7
'@types/serve-index': 1.9.4
'@types/serve-static': 1.15.10
@ -22121,9 +22067,9 @@ snapshots:
colorette: 2.0.20
compression: 1.8.1
connect-history-api-fallback: 2.0.0
express: 4.22.1
express: 4.21.2
graceful-fs: 4.2.11
http-proxy-middleware: 2.0.9(@types/express@4.17.25)
http-proxy-middleware: 2.0.9(@types/express@4.17.23)
ipaddr.js: 2.3.0
launch-editor: 2.12.0
open: 10.2.0