mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-05-28 18:26:54 +00:00
feat(api): add update vaildate email endpoint (#50276)
Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com> Co-authored-by: mot01 <tmondloch01@gmail.com>
This commit is contained in:
@@ -17,6 +17,7 @@
|
||||
"ajv": "8.12.0",
|
||||
"ajv-formats": "2.1.1",
|
||||
"connect-mongo": "4.6.0",
|
||||
"date-fns": "2.30.0",
|
||||
"dotenv": "16.3.1",
|
||||
"fast-uri": "2.2.0",
|
||||
"fastify": "4.21.0",
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { devLogin, setupServer, superRequest } from '../../jest.utils';
|
||||
import { defaultUser } from '../utils/default-user';
|
||||
|
||||
import { isPictureWithProtocol } from './settings';
|
||||
|
||||
const baseProfileUI = {
|
||||
@@ -24,6 +26,11 @@ const profileUI = {
|
||||
showPortfolio: true
|
||||
};
|
||||
|
||||
const developerUserEmail = 'foo@bar.com';
|
||||
const otherDeveloperUserEmail = 'bar@bar.com';
|
||||
const unusedEmailOne = 'nobody@would.com';
|
||||
const unusedEmailTwo = 'would@they.com';
|
||||
|
||||
const updateErrorResponse = {
|
||||
type: 'danger',
|
||||
message: 'flash.wrong-updating'
|
||||
@@ -92,9 +99,22 @@ describe('settingRoutes', () => {
|
||||
// profileUI, but we're interested in how the profileUI is updated. As
|
||||
// such, setting this explicitly isolates these tests.
|
||||
await fastifyTestInstance.prisma.user.updateMany({
|
||||
where: { email: 'foo@bar.com' },
|
||||
where: { email: developerUserEmail },
|
||||
data: { profileUI: baseProfileUI }
|
||||
});
|
||||
|
||||
const otherUser = await fastifyTestInstance.prisma.user.findFirst({
|
||||
where: { email: otherDeveloperUserEmail }
|
||||
});
|
||||
|
||||
if (!otherUser) {
|
||||
await fastifyTestInstance.prisma.user.create({
|
||||
data: {
|
||||
...defaultUser,
|
||||
email: otherDeveloperUserEmail
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe('/update-my-profileui', () => {
|
||||
@@ -107,7 +127,7 @@ describe('settingRoutes', () => {
|
||||
});
|
||||
|
||||
const user = await fastifyTestInstance.prisma.user.findFirst({
|
||||
where: { email: 'foo@bar.com' }
|
||||
where: { email: developerUserEmail }
|
||||
});
|
||||
|
||||
expect(response.body).toEqual({
|
||||
@@ -130,7 +150,7 @@ describe('settingRoutes', () => {
|
||||
});
|
||||
|
||||
const user = await fastifyTestInstance.prisma.user.findFirst({
|
||||
where: { email: 'foo@bar.com' }
|
||||
where: { email: developerUserEmail }
|
||||
});
|
||||
|
||||
expect(user?.profileUI).toEqual(profileUI);
|
||||
@@ -156,6 +176,166 @@ describe('settingRoutes', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('/update-my-email', () => {
|
||||
beforeEach(async () => {
|
||||
await fastifyTestInstance.prisma.user.updateMany({
|
||||
where: { email: developerUserEmail },
|
||||
data: {
|
||||
newEmail: null,
|
||||
emailVerified: true,
|
||||
emailVerifyTTL: null,
|
||||
emailAuthLinkTTL: null
|
||||
}
|
||||
});
|
||||
});
|
||||
test('PUT returns 200 status code with "success" message', async () => {
|
||||
const response = await superRequest('/update-my-email', {
|
||||
method: 'PUT',
|
||||
setCookies
|
||||
}).send({ email: 'foo@foo.com' });
|
||||
|
||||
expect(response?.body).toEqual({
|
||||
message: 'flash.email-valid',
|
||||
type: 'success'
|
||||
});
|
||||
expect(response?.statusCode).toEqual(200);
|
||||
});
|
||||
|
||||
test("PUT updates the user's record in preparation for receiving auth email", async () => {
|
||||
const response = await superRequest('/update-my-email', {
|
||||
method: 'PUT',
|
||||
setCookies
|
||||
}).send({ email: unusedEmailOne });
|
||||
|
||||
const user = await fastifyTestInstance.prisma.user.findFirstOrThrow({
|
||||
where: { email: developerUserEmail },
|
||||
select: { emailVerifyTTL: true, emailVerified: true, newEmail: true }
|
||||
});
|
||||
const emailVerifyTTL = user?.emailVerifyTTL;
|
||||
expect(emailVerifyTTL).toBeTruthy();
|
||||
// This throw is to mollify TS (if this is necessary a lot, create a
|
||||
// helper)
|
||||
if (!emailVerifyTTL) {
|
||||
throw new Error('emailVerifyTTL is not defined');
|
||||
}
|
||||
|
||||
expect(response?.statusCode).toEqual(200);
|
||||
|
||||
// expect the emailVerifyTTL to be within 10 seconds of the current time
|
||||
const tenSeconds = 10 * 1000;
|
||||
expect(emailVerifyTTL.getTime()).toBeGreaterThan(
|
||||
Date.now() - tenSeconds
|
||||
);
|
||||
expect(emailVerifyTTL.getTime()).toBeLessThan(Date.now() + tenSeconds);
|
||||
|
||||
expect(user?.emailVerified).toEqual(false);
|
||||
expect(user?.newEmail).toEqual(unusedEmailOne);
|
||||
});
|
||||
|
||||
test('PUT rejects invalid email addresses', async () => {
|
||||
const response = await superRequest('/update-my-email', {
|
||||
method: 'PUT',
|
||||
setCookies
|
||||
}).send({ email: 'invalid' });
|
||||
|
||||
// We cannot use fastify's default validation failure response here
|
||||
// because the client consumes the response and displays it to the user.
|
||||
expect(response?.body).toEqual({
|
||||
type: 'danger',
|
||||
message: 'Email format is invalid'
|
||||
});
|
||||
expect(response?.statusCode).toEqual(400);
|
||||
});
|
||||
|
||||
test('PUT accepts requests to update to the current email address (ignoring case) if it is not verified', async () => {
|
||||
await fastifyTestInstance.prisma.user.updateMany({
|
||||
where: { email: developerUserEmail },
|
||||
data: { emailVerified: false }
|
||||
});
|
||||
const response = await superRequest('/update-my-email', {
|
||||
method: 'PUT',
|
||||
setCookies
|
||||
}).send({ email: developerUserEmail.toUpperCase() });
|
||||
|
||||
expect(response?.statusCode).toEqual(200);
|
||||
expect(response?.body).toEqual({
|
||||
message: 'flash.email-valid',
|
||||
type: 'success'
|
||||
});
|
||||
});
|
||||
|
||||
test('PUT rejects a request to update to the existing email (ignoring case) address', async () => {
|
||||
const response = await superRequest('/update-my-email', {
|
||||
method: 'PUT',
|
||||
setCookies
|
||||
}).send({ email: developerUserEmail.toUpperCase() });
|
||||
|
||||
expect(response?.body).toEqual({
|
||||
type: 'info',
|
||||
message: `${developerUserEmail} is already associated with this account.
|
||||
You can update a new email address instead.`
|
||||
});
|
||||
expect(response?.statusCode).toEqual(400);
|
||||
});
|
||||
|
||||
test('PUT rejects a request to update to the same email (ignoring case) twice', async () => {
|
||||
const successResponse = await superRequest('/update-my-email', {
|
||||
method: 'PUT',
|
||||
setCookies
|
||||
}).send({ email: unusedEmailOne });
|
||||
|
||||
expect(successResponse?.statusCode).toEqual(200);
|
||||
|
||||
const failResponse = await superRequest('/update-my-email', {
|
||||
method: 'PUT',
|
||||
setCookies
|
||||
}).send({ email: unusedEmailOne.toUpperCase() });
|
||||
|
||||
expect(failResponse?.body).toEqual({
|
||||
type: 'info',
|
||||
message: `We have already sent an email confirmation request to ${unusedEmailOne}.
|
||||
Please wait 5 minutes to resend an authentication link.`
|
||||
});
|
||||
expect(failResponse?.statusCode).toEqual(429);
|
||||
});
|
||||
|
||||
test('PUT rejects a request if the new email is already in use', async () => {
|
||||
const response = await superRequest('/update-my-email', {
|
||||
method: 'PUT',
|
||||
setCookies
|
||||
}).send({ email: otherDeveloperUserEmail });
|
||||
|
||||
expect(response?.body).toEqual({
|
||||
type: 'info',
|
||||
message: `${otherDeveloperUserEmail} is already associated with another account.`
|
||||
});
|
||||
expect(response?.statusCode).toEqual(400);
|
||||
});
|
||||
|
||||
test('PUT rejects the second request if is immediately after the first', async () => {
|
||||
const successResponse = await superRequest('/update-my-email', {
|
||||
method: 'PUT',
|
||||
setCookies
|
||||
}).send({ email: unusedEmailOne });
|
||||
|
||||
expect(successResponse?.statusCode).toEqual(200);
|
||||
|
||||
const failResponse = await superRequest('/update-my-email', {
|
||||
method: 'PUT',
|
||||
setCookies
|
||||
}).send({ email: unusedEmailTwo });
|
||||
|
||||
expect(failResponse?.statusCode).toEqual(429);
|
||||
|
||||
expect(failResponse?.body).toEqual({
|
||||
type: 'info',
|
||||
message: `Please wait 5 minutes to resend an authentication link.`
|
||||
});
|
||||
});
|
||||
|
||||
// TODO: test that the correct email gets sent
|
||||
});
|
||||
|
||||
describe('/update-my-theme', () => {
|
||||
test('PUT returns 200 status code with "success" message', async () => {
|
||||
const response = await superRequest('/update-my-theme', {
|
||||
|
||||
@@ -1,11 +1,38 @@
|
||||
import { type FastifyPluginCallbackTypebox } from '@fastify/type-provider-typebox';
|
||||
import type { FastifyError, FastifyReply, FastifyRequest } from 'fastify';
|
||||
import { getMinutes, isBefore, sub } from 'date-fns';
|
||||
import { isProfane } from 'no-profanity';
|
||||
|
||||
import { blocklistedUsernames } from '../../../shared/config/constants';
|
||||
import { isValidUsername } from '../../../shared/utils/validate';
|
||||
import { schemas } from '../schemas';
|
||||
|
||||
// TODO: move getWaitMessage and getWaitPeriod to own module and add tests
|
||||
function getWaitMessage(lastEmailSentAt: Date | null) {
|
||||
const minutesLeft = getWaitPeriod(lastEmailSentAt);
|
||||
if (minutesLeft <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const timeToWait = minutesLeft
|
||||
? `${minutesLeft} minute${minutesLeft > 1 ? 's' : ''}`
|
||||
: 'a few seconds';
|
||||
|
||||
return `Please wait ${timeToWait} to resend an authentication link.`;
|
||||
}
|
||||
|
||||
function getWaitPeriod(lastEmailSentAt: Date | null) {
|
||||
if (!lastEmailSentAt) return 0;
|
||||
|
||||
const now = new Date();
|
||||
const fiveMinutesAgo = sub(now, { minutes: 5 });
|
||||
const isWaitPeriodOver = isBefore(lastEmailSentAt, fiveMinutesAgo);
|
||||
|
||||
return isWaitPeriodOver
|
||||
? 0
|
||||
: 5 - (getMinutes(now) - getMinutes(lastEmailSentAt));
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate an image url.
|
||||
*
|
||||
@@ -92,6 +119,107 @@ export const settingRoutes: FastifyPluginCallbackTypebox = (
|
||||
}
|
||||
);
|
||||
|
||||
fastify.put(
|
||||
'/update-my-email',
|
||||
{
|
||||
schema: schemas.updateMyEmail,
|
||||
// We need to customize the responses to validation failures:
|
||||
attachValidation: true
|
||||
},
|
||||
async (req, reply) => {
|
||||
if (req.validationError) {
|
||||
void reply.code(400);
|
||||
return { message: 'Email format is invalid', type: 'danger' } as const;
|
||||
}
|
||||
|
||||
const user = await fastify.prisma.user.findUniqueOrThrow({
|
||||
where: { id: req.session.user.id },
|
||||
select: {
|
||||
email: true,
|
||||
emailVerifyTTL: true,
|
||||
newEmail: true,
|
||||
emailVerified: true,
|
||||
emailAuthLinkTTL: true
|
||||
}
|
||||
});
|
||||
const newEmail = req.body.email.toLowerCase();
|
||||
const currentEmailFormatted = user.email.toLowerCase();
|
||||
const isVerifiedEmail = user.emailVerified;
|
||||
const isOwnEmail = newEmail === currentEmailFormatted;
|
||||
if (isOwnEmail && isVerifiedEmail) {
|
||||
void reply.code(400);
|
||||
return {
|
||||
type: 'info',
|
||||
message: `${newEmail} is already associated with this account.
|
||||
You can update a new email address instead.`
|
||||
} as const;
|
||||
}
|
||||
|
||||
const isResendUpdateToSameEmail =
|
||||
newEmail === user.newEmail?.toLowerCase();
|
||||
const isLinkSentWithinLimitTTL = getWaitMessage(user.emailVerifyTTL);
|
||||
|
||||
if (isResendUpdateToSameEmail && isLinkSentWithinLimitTTL) {
|
||||
void reply.code(429);
|
||||
return {
|
||||
type: 'info',
|
||||
message: `We have already sent an email confirmation request to ${newEmail}.
|
||||
${isLinkSentWithinLimitTTL}`
|
||||
} as const;
|
||||
}
|
||||
|
||||
const isEmailAlreadyTaken =
|
||||
(await fastify.prisma.user.count({ where: { email: newEmail } })) > 0;
|
||||
|
||||
if (isEmailAlreadyTaken && !isOwnEmail) {
|
||||
void reply.code(400);
|
||||
return {
|
||||
type: 'info',
|
||||
message: `${newEmail} is already associated with another account.`
|
||||
} as const;
|
||||
}
|
||||
|
||||
// ToDo(MVP): email the new email and wait user to confirm it, before we update the user schema.
|
||||
try {
|
||||
await fastify.prisma.user.update({
|
||||
where: { id: req.session.user.id },
|
||||
data: {
|
||||
newEmail,
|
||||
emailVerified: false,
|
||||
emailVerifyTTL: new Date()
|
||||
}
|
||||
});
|
||||
|
||||
// TODO: combine emailVerifyTTL and emailAuthLinkTTL? I'm not sure why
|
||||
// we need emailVeriftyTTL given that the main thing we want is to
|
||||
// restrict the rate of attempts and the emailAuthLinkTTL already does
|
||||
// that.
|
||||
const tooManyRequestsMessage = getWaitMessage(user.emailAuthLinkTTL);
|
||||
|
||||
if (tooManyRequestsMessage) {
|
||||
void reply.code(429);
|
||||
return {
|
||||
type: 'info',
|
||||
message: tooManyRequestsMessage
|
||||
} as const;
|
||||
}
|
||||
|
||||
await fastify.prisma.user.update({
|
||||
where: { id: req.session.user.id },
|
||||
data: {
|
||||
emailAuthLinkTTL: new Date()
|
||||
}
|
||||
});
|
||||
|
||||
return { message: 'flash.email-valid', type: 'success' } as const;
|
||||
} catch (err) {
|
||||
fastify.log.error(err);
|
||||
void reply.code(500);
|
||||
return { message: 'flash.wrong-updating', type: 'danger' } as const;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
fastify.put(
|
||||
'/update-my-theme',
|
||||
{
|
||||
|
||||
@@ -216,6 +216,25 @@ export const schemas = {
|
||||
})
|
||||
}
|
||||
},
|
||||
updateMyEmail: {
|
||||
body: Type.Object({
|
||||
email: Type.String({ format: 'email', maxLength: 1024 })
|
||||
}),
|
||||
response: {
|
||||
200: Type.Object({
|
||||
message: Type.Literal('flash.email-valid'),
|
||||
type: Type.Literal('success')
|
||||
}),
|
||||
'4xx': Type.Object({
|
||||
message: Type.String(),
|
||||
type: Type.Union([Type.Literal('danger'), Type.Literal('info')])
|
||||
}),
|
||||
500: Type.Object({
|
||||
message: Type.Literal('flash.wrong-updating'),
|
||||
type: Type.Literal('danger')
|
||||
})
|
||||
}
|
||||
},
|
||||
// User:
|
||||
deleteMyAccount: {
|
||||
response: {
|
||||
|
||||
Generated
+23
-20
@@ -210,6 +210,9 @@ importers:
|
||||
connect-mongo:
|
||||
specifier: 4.6.0
|
||||
version: 4.6.0(express-session@1.17.3)(mongodb@4.17.1)
|
||||
date-fns:
|
||||
specifier: 2.30.0
|
||||
version: 2.30.0
|
||||
dotenv:
|
||||
specifier: 16.3.1
|
||||
version: 16.3.1
|
||||
@@ -6452,7 +6455,7 @@ packages:
|
||||
/@redux-saga/core@1.2.3:
|
||||
resolution: {integrity: sha512-U1JO6ncFBAklFTwoQ3mjAeQZ6QGutsJzwNBjgVLSWDpZTRhobUzuVDS1qH3SKGJD8fvqoaYOjp6XJ3gCmeZWgA==}
|
||||
dependencies:
|
||||
'@babel/runtime': 7.20.13
|
||||
'@babel/runtime': 7.23.1
|
||||
'@redux-saga/deferred': 1.2.1
|
||||
'@redux-saga/delay-p': 1.2.1
|
||||
'@redux-saga/is': 1.1.3
|
||||
@@ -11040,7 +11043,7 @@ packages:
|
||||
/babel-plugin-macros@2.8.0:
|
||||
resolution: {integrity: sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg==}
|
||||
dependencies:
|
||||
'@babel/runtime': 7.20.13
|
||||
'@babel/runtime': 7.23.1
|
||||
cosmiconfig: 6.0.0
|
||||
resolve: 1.22.6
|
||||
|
||||
@@ -11173,7 +11176,7 @@ packages:
|
||||
gatsby: ^3.0.0-next.0
|
||||
dependencies:
|
||||
'@babel/core': 7.23.0
|
||||
'@babel/runtime': 7.20.13
|
||||
'@babel/runtime': 7.23.1
|
||||
'@babel/types': 7.23.0
|
||||
gatsby: 3.15.0(@types/node@20.8.0)(babel-eslint@10.1.0)(eslint-import-resolver-typescript@3.5.5)(eslint-plugin-testing-library@3.9.0)(react-dom@16.14.0)(react@16.14.0)(typescript@4.9.5)(webpack-cli@4.10.0)
|
||||
gatsby-core-utils: 2.15.0
|
||||
@@ -11657,7 +11660,7 @@ packages:
|
||||
'@babel/plugin-transform-spread': 7.22.5(@babel/core@7.23.0)
|
||||
'@babel/preset-env': 7.22.20(@babel/core@7.23.0)
|
||||
'@babel/preset-react': 7.22.15(@babel/core@7.23.0)
|
||||
'@babel/runtime': 7.20.13
|
||||
'@babel/runtime': 7.23.1
|
||||
babel-plugin-dynamic-import-node: 2.3.3
|
||||
babel-plugin-macros: 2.8.0
|
||||
babel-plugin-transform-react-remove-prop-types: 0.4.24
|
||||
@@ -13379,7 +13382,7 @@ packages:
|
||||
resolution: {integrity: sha512-l6U4w4h8uGwKH92I8lopiY9hpa6BPN8IsTm06VwfG4U7xZH1HI5KuP7MVO126cCy+iG482cerhMFE/kTJ/J0Jw==}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
'@babel/runtime': 7.20.13
|
||||
'@babel/runtime': 7.23.1
|
||||
|
||||
/create-hash@1.2.0:
|
||||
resolution: {integrity: sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==}
|
||||
@@ -14449,13 +14452,13 @@ packages:
|
||||
/dom-helpers@3.4.0:
|
||||
resolution: {integrity: sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==}
|
||||
dependencies:
|
||||
'@babel/runtime': 7.20.13
|
||||
'@babel/runtime': 7.23.1
|
||||
dev: false
|
||||
|
||||
/dom-helpers@5.2.1:
|
||||
resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==}
|
||||
dependencies:
|
||||
'@babel/runtime': 7.20.13
|
||||
'@babel/runtime': 7.23.1
|
||||
csstype: 3.1.2
|
||||
dev: false
|
||||
|
||||
@@ -15248,7 +15251,7 @@ packages:
|
||||
peerDependencies:
|
||||
graphql: ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0
|
||||
dependencies:
|
||||
'@babel/runtime': 7.20.13
|
||||
'@babel/runtime': 7.23.1
|
||||
graphql: 15.8.0
|
||||
graphql-config: 3.4.1(@types/node@20.8.0)(graphql@15.8.0)(typescript@4.9.5)
|
||||
lodash.flatten: 4.4.0
|
||||
@@ -16943,7 +16946,7 @@ packages:
|
||||
resolution: {integrity: sha512-QspRxfSgD4Yb5syp/yNPN+ljXgatfgqq4/TIIJw5mVxVMhNenb8mQ8ihVL5vdhV7x3wUjKTwVIjZ+eU/sMLz7g==}
|
||||
engines: {node: '>=12.13.0'}
|
||||
dependencies:
|
||||
'@babel/runtime': 7.20.13
|
||||
'@babel/runtime': 7.23.1
|
||||
ci-info: 2.0.0
|
||||
configstore: 5.0.1
|
||||
file-type: 16.5.4
|
||||
@@ -16958,12 +16961,12 @@ packages:
|
||||
resolution: {integrity: sha512-w9QSjOBJKi8nADypZycRBNEzRHyBEERUbMygMHEa/Lr8JkWi5YV5TplneDnJv9/R4VXiKA3CcpbIrpGqzp7qmA==}
|
||||
engines: {node: '>=12.13.0'}
|
||||
dependencies:
|
||||
'@babel/runtime': 7.20.13
|
||||
'@babel/runtime': 7.23.1
|
||||
|
||||
/gatsby-legacy-polyfills@1.15.0:
|
||||
resolution: {integrity: sha512-CysOx6kjH7jomkhUdMDy5oFPC0vfdHfh96O+ZENW5tk9k4SQ+G64weywm9EfC6coIFPYytBhW/SrE7ZwizKnPA==}
|
||||
dependencies:
|
||||
'@babel/runtime': 7.20.13
|
||||
'@babel/runtime': 7.23.1
|
||||
core-js-compat: 3.9.0
|
||||
|
||||
/gatsby-link@3.15.0(@gatsbyjs/reach-router@1.3.9)(react-dom@16.14.0)(react@16.14.0):
|
||||
@@ -16985,7 +16988,7 @@ packages:
|
||||
resolution: {integrity: sha512-Nj5lcMJaACCaWVgRc6T45U7KxhqEPpWZo/IlOaCdX2pWjK/5oWI3hVtfukfFpDRunYtIdw2Vl8bicLgU+viAWA==}
|
||||
engines: {node: '>=12.13.0'}
|
||||
dependencies:
|
||||
'@babel/runtime': 7.20.13
|
||||
'@babel/runtime': 7.23.1
|
||||
bluebird: 3.7.2
|
||||
chokidar: 3.5.3
|
||||
fs-exists-cached: 1.0.0
|
||||
@@ -17041,7 +17044,7 @@ packages:
|
||||
peerDependencies:
|
||||
gatsby: ^3.0.0-next.0
|
||||
dependencies:
|
||||
'@babel/runtime': 7.20.13
|
||||
'@babel/runtime': 7.23.1
|
||||
'@babel/traverse': 7.23.0
|
||||
'@sindresorhus/slugify': 1.1.2
|
||||
chokidar: 3.5.3
|
||||
@@ -17111,7 +17114,7 @@ packages:
|
||||
'@babel/plugin-proposal-numeric-separator': 7.18.6(@babel/core@7.23.0)
|
||||
'@babel/plugin-proposal-optional-chaining': 7.17.12(@babel/core@7.23.0)
|
||||
'@babel/preset-typescript': 7.23.0(@babel/core@7.23.0)
|
||||
'@babel/runtime': 7.20.13
|
||||
'@babel/runtime': 7.23.1
|
||||
babel-plugin-remove-graphql-queries: 3.15.0(@babel/core@7.23.0)(gatsby@3.15.0)
|
||||
gatsby: 3.15.0(@types/node@20.8.0)(babel-eslint@10.1.0)(eslint-import-resolver-typescript@3.5.5)(eslint-plugin-testing-library@3.9.0)(react-dom@16.14.0)(react@16.14.0)(typescript@4.9.5)(webpack-cli@4.10.0)
|
||||
transitivePeerDependencies:
|
||||
@@ -17124,7 +17127,7 @@ packages:
|
||||
gatsby: ^3.0.0-next.0
|
||||
graphql: ^15.0.0
|
||||
dependencies:
|
||||
'@babel/runtime': 7.20.13
|
||||
'@babel/runtime': 7.23.1
|
||||
fastq: 1.15.0
|
||||
gatsby: 3.15.0(@types/node@20.8.0)(babel-eslint@10.1.0)(eslint-import-resolver-typescript@3.5.5)(eslint-plugin-testing-library@3.9.0)(react-dom@16.14.0)(react@16.14.0)(typescript@4.9.5)(webpack-cli@4.10.0)
|
||||
graphql: 15.8.0
|
||||
@@ -17151,7 +17154,7 @@ packages:
|
||||
react: ^16.9.0 || ^17.0.0
|
||||
react-dom: ^16.9.0 || ^17.0.0
|
||||
dependencies:
|
||||
'@babel/runtime': 7.20.13
|
||||
'@babel/runtime': 7.23.1
|
||||
'@gatsbyjs/reach-router': 1.3.9(react-dom@16.14.0)(react@16.14.0)
|
||||
prop-types: 15.8.1
|
||||
react: 16.14.0
|
||||
@@ -17165,7 +17168,7 @@ packages:
|
||||
'@babel/helper-plugin-utils': 7.22.5
|
||||
'@babel/plugin-proposal-optional-chaining': 7.17.12(@babel/core@7.23.0)
|
||||
'@babel/plugin-transform-react-jsx': 7.22.15(@babel/core@7.23.0)
|
||||
'@babel/runtime': 7.20.13
|
||||
'@babel/runtime': 7.23.1
|
||||
'@babel/standalone': 7.23.1
|
||||
'@babel/template': 7.22.15
|
||||
'@babel/types': 7.23.0
|
||||
@@ -17267,7 +17270,7 @@ packages:
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.22.13
|
||||
'@babel/runtime': 7.20.13
|
||||
'@babel/runtime': 7.23.1
|
||||
'@turist/fetch': 7.2.0(node-fetch@2.7.0)
|
||||
'@turist/time': 0.0.2
|
||||
async-retry-ng: 2.0.1
|
||||
@@ -17321,7 +17324,7 @@ packages:
|
||||
engines: {node: '>=12.13.0'}
|
||||
dependencies:
|
||||
'@babel/core': 7.23.0
|
||||
'@babel/runtime': 7.20.13
|
||||
'@babel/runtime': 7.23.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -25439,7 +25442,7 @@ packages:
|
||||
react: '>0.13.0'
|
||||
react-dom: '>0.13.0'
|
||||
dependencies:
|
||||
'@babel/runtime': 7.20.13
|
||||
'@babel/runtime': 7.23.1
|
||||
get-node-dimensions: 1.2.1
|
||||
prop-types: 15.8.1
|
||||
react: 16.14.0
|
||||
|
||||
Reference in New Issue
Block a user