mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-04-30 16:01:14 -04:00
breaking(api): remove screenshot api (#61300)
This commit is contained in:
@@ -8,7 +8,6 @@
|
||||
"@fastify/accepts": "5.0.2",
|
||||
"@fastify/cookie": "11.0.1",
|
||||
"@fastify/csrf-protection": "7.1.0",
|
||||
"@fastify/multipart": "^9.0.3",
|
||||
"@fastify/oauth2": "8.1.2",
|
||||
"@fastify/swagger": "9.4.0",
|
||||
"@fastify/swagger-ui": "5.2.0",
|
||||
|
||||
@@ -48,7 +48,6 @@ import {
|
||||
import { isObjectID } from './utils/validation';
|
||||
import { getLogger } from './utils/logger';
|
||||
import {
|
||||
examEnvironmentMultipartRoutes,
|
||||
examEnvironmentOpenRoutes,
|
||||
examEnvironmentValidatedTokenRoutes
|
||||
} from './exam-environment/routes/exam-environment';
|
||||
@@ -214,7 +213,6 @@ export const build = async (
|
||||
fastify.addHook('onRequest', fastify.authorizeExamEnvironmentToken);
|
||||
|
||||
void fastify.register(examEnvironmentValidatedTokenRoutes);
|
||||
void fastify.register(examEnvironmentMultipartRoutes);
|
||||
done();
|
||||
});
|
||||
void fastify.register(examEnvironmentOpenRoutes);
|
||||
|
||||
@@ -3,7 +3,6 @@ import { Static } from '@fastify/type-provider-typebox';
|
||||
import jwt from 'jsonwebtoken';
|
||||
|
||||
import {
|
||||
createFetchMock,
|
||||
createSuperRequest,
|
||||
defaultUserId,
|
||||
devLogin,
|
||||
@@ -587,99 +586,6 @@ describe('/exam-environment/', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST /exam-environment/screenshot', () => {
|
||||
afterEach(async () => {
|
||||
await fastifyTestInstance.prisma.examEnvironmentExamAttempt.deleteMany();
|
||||
});
|
||||
|
||||
it('should return 400 if request is not multipart form data', async () => {
|
||||
const res = await superPost('/exam-environment/screenshot').set(
|
||||
'exam-environment-authorization-token',
|
||||
examEnvironmentAuthorizationToken
|
||||
);
|
||||
|
||||
expect(res.status).toBe(400);
|
||||
expect(res.body).toStrictEqual({
|
||||
code: 'FCC_EINVAL_EXAM_ENVIRONMENT_SCREENSHOT',
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
message: expect.any(String)
|
||||
});
|
||||
});
|
||||
|
||||
it('should return 400 if image is missing', async () => {
|
||||
const res = await superPost('/exam-environment/screenshot')
|
||||
.set(
|
||||
'exam-environment-authorization-token',
|
||||
examEnvironmentAuthorizationToken
|
||||
)
|
||||
.attach('file', '');
|
||||
|
||||
expect(res.status).toBe(400);
|
||||
expect(res.body).toStrictEqual({
|
||||
code: 'FCC_EINVAL_EXAM_ENVIRONMENT_SCREENSHOT',
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
message: expect.any(String)
|
||||
});
|
||||
});
|
||||
|
||||
it('should return 404 if there is no ongoing exam attempt', async () => {
|
||||
const res = await superPost('/exam-environment/screenshot')
|
||||
.set(
|
||||
'exam-environment-authorization-token',
|
||||
examEnvironmentAuthorizationToken
|
||||
)
|
||||
.attach('file', Buffer.from([]));
|
||||
|
||||
expect(res.status).toBe(404);
|
||||
expect(res.body).toStrictEqual({
|
||||
code: 'FCC_ERR_EXAM_ENVIRONMENT_EXAM_ATTEMPT',
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
message: expect.any(String)
|
||||
});
|
||||
});
|
||||
|
||||
it('should return 400 if image is of wrong format', async () => {
|
||||
await fastifyTestInstance.prisma.examEnvironmentExamAttempt.create({
|
||||
data: mock.examAttempt
|
||||
});
|
||||
|
||||
const res = await superPost('/exam-environment/screenshot')
|
||||
.set(
|
||||
'exam-environment-authorization-token',
|
||||
examEnvironmentAuthorizationToken
|
||||
)
|
||||
.attach('file', Buffer.from([]));
|
||||
|
||||
expect(res.status).toBe(400);
|
||||
expect(res.body).toStrictEqual({
|
||||
code: 'FCC_EINVAL_EXAM_ENVIRONMENT_SCREENSHOT',
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
message: expect.any(String)
|
||||
});
|
||||
});
|
||||
|
||||
it('should return 200 if request is valid and send image to screenshot upload service', async () => {
|
||||
// Mock image upload service response
|
||||
const imageUploadRes = createFetchMock({ ok: true });
|
||||
jest.spyOn(globalThis, 'fetch').mockImplementation(imageUploadRes);
|
||||
|
||||
await fastifyTestInstance.prisma.examEnvironmentExamAttempt.create({
|
||||
data: mock.examAttempt
|
||||
});
|
||||
|
||||
const res = await superPost('/exam-environment/screenshot')
|
||||
.set(
|
||||
'exam-environment-authorization-token',
|
||||
examEnvironmentAuthorizationToken
|
||||
)
|
||||
.attach('file', Buffer.from([0xff, 0xd8, 0xff, 0xff]));
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.body).toStrictEqual({});
|
||||
expect(globalThis.fetch).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /exam-environment/exams', () => {
|
||||
it('should return 200', async () => {
|
||||
const res = await superGet('/exam-environment/exams').set(
|
||||
@@ -1030,17 +936,6 @@ describe('/exam-environment/', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST /exam-environment/screenshot', () => {
|
||||
it('should return 403', async () => {
|
||||
const res = await superPost('/exam-environment/screenshot').set(
|
||||
'exam-environment-authorization-token',
|
||||
'invalid-token'
|
||||
);
|
||||
|
||||
expect(res.status).toBe(403);
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /exam-environment/token-meta', () => {
|
||||
it('should reject invalid tokens', async () => {
|
||||
const res = await superGet('/exam-environment/token-meta').set(
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
/* eslint-disable jsdoc/require-returns, jsdoc/require-param */
|
||||
import { type FastifyPluginCallbackTypebox } from '@fastify/type-provider-typebox';
|
||||
import fastifyMultipart from '@fastify/multipart';
|
||||
import { PrismaClientValidationError } from '@prisma/client/runtime/library';
|
||||
import { type FastifyInstance, type FastifyReply } from 'fastify';
|
||||
import { ExamEnvironmentExamModerationStatus } from '@prisma/client';
|
||||
@@ -8,7 +7,7 @@ import jwt from 'jsonwebtoken';
|
||||
|
||||
import * as schemas from '../schemas';
|
||||
import { mapErr, syncMapErr, UpdateReqType } from '../../utils';
|
||||
import { JWT_SECRET, SCREENSHOT_SERVICE_LOCATION } from '../../utils/env';
|
||||
import { JWT_SECRET } from '../../utils/env';
|
||||
import {
|
||||
checkPrerequisites,
|
||||
constructEnvExamAttempt,
|
||||
@@ -65,29 +64,6 @@ export const examEnvironmentValidatedTokenRoutes: FastifyPluginCallbackTypebox =
|
||||
done();
|
||||
};
|
||||
|
||||
/**
|
||||
* Wrapper for endpoints related to the exam environment desktop app.
|
||||
*
|
||||
* Requires multipart form data to be supported.
|
||||
*/
|
||||
export const examEnvironmentMultipartRoutes: FastifyPluginCallbackTypebox = (
|
||||
fastify,
|
||||
_options,
|
||||
done
|
||||
) => {
|
||||
void fastify.register(fastifyMultipart);
|
||||
|
||||
fastify.post(
|
||||
'/exam-environment/screenshot',
|
||||
{
|
||||
schema: schemas.examEnvironmentPostScreenshot
|
||||
// bodyLimit: 1024 * 1024 * 5 // 5MiB
|
||||
},
|
||||
postScreenshotHandler
|
||||
);
|
||||
done();
|
||||
};
|
||||
|
||||
/**
|
||||
* Wrapper for endpoints related to the exam environment desktop app.
|
||||
*
|
||||
@@ -684,155 +660,6 @@ async function postExamAttemptHandler(
|
||||
return reply.code(200).send();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles screenshots, sending them to the screenshot service for storage.
|
||||
*
|
||||
* Requires token to be validated.
|
||||
*/
|
||||
async function postScreenshotHandler(
|
||||
this: FastifyInstance,
|
||||
req: UpdateReqType<typeof schemas.examEnvironmentPostScreenshot>,
|
||||
reply: FastifyReply
|
||||
) {
|
||||
const logger = this.log.child({ req });
|
||||
logger.info({ userId: req.user?.id });
|
||||
const isMultipart = req.isMultipart();
|
||||
|
||||
if (!isMultipart) {
|
||||
logger.warn('Request is not multipart form data.');
|
||||
void reply.code(400);
|
||||
return reply.send(
|
||||
ERRORS.FCC_EINVAL_EXAM_ENVIRONMENT_SCREENSHOT(
|
||||
'Request is not multipart form data.'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const user = req.user!;
|
||||
const imgData = await req.file();
|
||||
|
||||
if (!imgData) {
|
||||
logger.warn('No image provided.');
|
||||
void reply.code(400);
|
||||
return reply.send(
|
||||
ERRORS.FCC_EINVAL_EXAM_ENVIRONMENT_SCREENSHOT('No image provided.')
|
||||
);
|
||||
}
|
||||
|
||||
const maybeAttempts = await mapErr(
|
||||
this.prisma.examEnvironmentExamAttempt.findMany({
|
||||
where: {
|
||||
userId: user.id
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
if (maybeAttempts.hasError) {
|
||||
logger.error(maybeAttempts.error);
|
||||
this.Sentry.captureException(maybeAttempts.error);
|
||||
void reply.code(500);
|
||||
return reply.send(
|
||||
ERRORS.FCC_ERR_EXAM_ENVIRONMENT(JSON.stringify(maybeAttempts.error))
|
||||
);
|
||||
}
|
||||
|
||||
const attempts = maybeAttempts.data;
|
||||
|
||||
if (attempts.length === 0) {
|
||||
logger.warn('No exam attempts found for user.');
|
||||
void reply.code(404);
|
||||
return reply.send(
|
||||
ERRORS.FCC_ERR_EXAM_ENVIRONMENT_EXAM_ATTEMPT(
|
||||
`No exam attempts found for user '${user.id}'.`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const latestAttempt = attempts.reduce((latest, current) =>
|
||||
latest.startTimeInMS > current.startTimeInMS ? latest : current
|
||||
);
|
||||
|
||||
const maybeExam = await mapErr(
|
||||
this.prisma.examEnvironmentExam.findUnique({
|
||||
where: {
|
||||
id: latestAttempt.examId
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
config: true
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
if (maybeExam.hasError) {
|
||||
logger.error(maybeExam.error);
|
||||
this.Sentry.captureException(maybeExam.error);
|
||||
void reply.code(500);
|
||||
return reply.send(
|
||||
ERRORS.FCC_ERR_EXAM_ENVIRONMENT(JSON.stringify(maybeExam.error))
|
||||
);
|
||||
}
|
||||
|
||||
const exam = maybeExam.data;
|
||||
|
||||
if (exam === null) {
|
||||
const error = {
|
||||
data: {
|
||||
examId: latestAttempt.examId,
|
||||
attemptId: latestAttempt.id
|
||||
},
|
||||
message: 'Unreachable. Attempt could not be related to an exam.'
|
||||
};
|
||||
logger.error(error.data, error.message);
|
||||
this.Sentry.captureException(error.data);
|
||||
void reply.code(500);
|
||||
return reply.send(
|
||||
ERRORS.FCC_ENOENT_EXAM_ENVIRONMENT_MISSING_EXAM(error.message)
|
||||
);
|
||||
}
|
||||
|
||||
const isAttemptExpired =
|
||||
latestAttempt.startTimeInMS + exam.config.totalTimeInMS < Date.now();
|
||||
if (isAttemptExpired) {
|
||||
logger.warn(
|
||||
{ examAttemptId: latestAttempt.id },
|
||||
'Attempt has exceeded submission time.'
|
||||
);
|
||||
void reply.code(403);
|
||||
return reply.send(
|
||||
ERRORS.FCC_EINVAL_EXAM_ENVIRONMENT_EXAM_ATTEMPT(
|
||||
'Attempt has exceeded submission time.'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const imgBinary = await imgData.toBuffer();
|
||||
|
||||
// Verify image is JPG using magic number
|
||||
if (imgBinary[0] !== 0xff || imgBinary[1] !== 0xd8 || imgBinary[2] !== 0xff) {
|
||||
logger.warn('Invalid image format');
|
||||
void reply.code(400);
|
||||
return reply.send(
|
||||
ERRORS.FCC_EINVAL_EXAM_ENVIRONMENT_SCREENSHOT('Invalid image format.')
|
||||
);
|
||||
}
|
||||
|
||||
void reply.code(200).send();
|
||||
|
||||
const uploadData = {
|
||||
image: imgBinary.toString('base64'),
|
||||
examAttemptId: latestAttempt.id
|
||||
};
|
||||
|
||||
await fetch(`${SCREENSHOT_SERVICE_LOCATION}/upload`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(uploadData)
|
||||
});
|
||||
}
|
||||
|
||||
async function getExams(
|
||||
this: FastifyInstance,
|
||||
req: UpdateReqType<typeof schemas.examEnvironmentExams>,
|
||||
|
||||
@@ -4,6 +4,5 @@ export {
|
||||
examEnvironmentGetExamAttempt
|
||||
} from './exam-environment-exam-attempt';
|
||||
export { examEnvironmentPostExamGeneratedExam } from './exam-environment-exam-generated-exam';
|
||||
export { examEnvironmentPostScreenshot } from './screenshot';
|
||||
export { examEnvironmentTokenMeta } from './token-meta';
|
||||
export { examEnvironmentExams } from './exam-environment-exams';
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
import { Type } from '@fastify/type-provider-typebox';
|
||||
import { STANDARD_ERROR } from '../utils/errors';
|
||||
|
||||
export const examEnvironmentPostScreenshot = {
|
||||
headers: Type.Object({
|
||||
'exam-environment-authorization-token': Type.String()
|
||||
}),
|
||||
response: {
|
||||
400: STANDARD_ERROR,
|
||||
500: STANDARD_ERROR
|
||||
}
|
||||
};
|
||||
@@ -39,11 +39,7 @@ export const ERRORS = {
|
||||
'FCC_ENOENT_EXAM_ENVIRONMENT_GENERATED_EXAM',
|
||||
'%s'
|
||||
),
|
||||
FCC_EINVAL_EXAM_ID: createError('FCC_EINVAL_EXAM_ID', '%s'),
|
||||
FCC_EINVAL_EXAM_ENVIRONMENT_SCREENSHOT: createError(
|
||||
'FCC_EINVAL_EXAM_ENVIRONMENT_SCREENSHOT',
|
||||
'%s'
|
||||
)
|
||||
FCC_EINVAL_EXAM_ID: createError('FCC_EINVAL_EXAM_ID', '%s')
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -153,10 +153,6 @@ if (process.env.FREECODECAMP_NODE_ENV !== 'development') {
|
||||
);
|
||||
}
|
||||
|
||||
if (process.env.FCC_ENABLE_EXAM_ENVIRONMENT === 'true') {
|
||||
assert.ok(process.env.SCREENSHOT_SERVICE_LOCATION);
|
||||
}
|
||||
|
||||
export const HOME_LOCATION = process.env.HOME_LOCATION;
|
||||
// Mailhog is used in development and test environments, hence the localhost
|
||||
// default.
|
||||
@@ -224,6 +220,4 @@ function undefinedOrBool(val: string | undefined): undefined | boolean {
|
||||
|
||||
return val === 'true';
|
||||
}
|
||||
export const SCREENSHOT_SERVICE_LOCATION =
|
||||
process.env.SCREENSHOT_SERVICE_LOCATION;
|
||||
export const DEPLOYMENT_VERSION = process.env.DEPLOYMENT_VERSION || 'unknown';
|
||||
|
||||
40
pnpm-lock.yaml
generated
40
pnpm-lock.yaml
generated
@@ -162,9 +162,6 @@ importers:
|
||||
'@fastify/csrf-protection':
|
||||
specifier: 7.1.0
|
||||
version: 7.1.0
|
||||
'@fastify/multipart':
|
||||
specifier: ^9.0.3
|
||||
version: 9.0.3
|
||||
'@fastify/oauth2':
|
||||
specifier: 8.1.2
|
||||
version: 8.1.2
|
||||
@@ -2658,9 +2655,6 @@ packages:
|
||||
'@fastify/ajv-compiler@4.0.2':
|
||||
resolution: {integrity: sha512-Rkiu/8wIjpsf46Rr+Fitd3HRP+VsxUFDDeag0hs9L0ksfnwx2g7SPQQTFL0E8Qv+rfXzQOxBJnjUB9ITUDjfWQ==}
|
||||
|
||||
'@fastify/busboy@3.1.1':
|
||||
resolution: {integrity: sha512-5DGmA8FTdB2XbDeEwc/5ZXBl6UbBAyBOOLlPuBnZ/N1SwdH9Ii+cOX3tBROlDgcTXxjOYnLMVoKk9+FXAw0CJw==}
|
||||
|
||||
'@fastify/cookie@11.0.1':
|
||||
resolution: {integrity: sha512-n1Ooz4bgQ5LcOlJQboWPfsMNxIrGV0SgU85UkctdpTlCQE0mtA3rlspOPUdqk9ubiiZn053ucnia4DjTquI4/g==}
|
||||
|
||||
@@ -2670,9 +2664,6 @@ packages:
|
||||
'@fastify/csrf@8.0.1':
|
||||
resolution: {integrity: sha512-dAmCrdfJ3CV/A/hHHK/rRBjjLRRSIltgJB0BxiVfbhr/31G6fgF8l2I8evtH8mjS5kTIvd0JOh7MOA3HA6eYDw==}
|
||||
|
||||
'@fastify/deepmerge@2.0.2':
|
||||
resolution: {integrity: sha512-3wuLdX5iiiYeZWP6bQrjqhrcvBIf0NHbQH1Ur1WbHvoiuTYUEItgygea3zs8aHpiitn0lOB8gX20u1qO+FDm7Q==}
|
||||
|
||||
'@fastify/error@4.0.0':
|
||||
resolution: {integrity: sha512-OO/SA8As24JtT1usTUTKgGH7uLvhfwZPwlptRi2Dp5P4KKmJI3gvsZ8MIHnNwDs4sLf/aai5LzTyl66xr7qMxA==}
|
||||
|
||||
@@ -2682,9 +2673,6 @@ packages:
|
||||
'@fastify/merge-json-schemas@0.2.1':
|
||||
resolution: {integrity: sha512-OA3KGBCy6KtIvLf8DINC5880o5iBlDX4SxzLQS8HorJAbqluzLRn80UXU0bxZn7UOFhFgpRJDasfwn9nG4FG4A==}
|
||||
|
||||
'@fastify/multipart@9.0.3':
|
||||
resolution: {integrity: sha512-pJogxQCrT12/6I5Fh6jr3narwcymA0pv4B0jbC7c6Bl9wnrxomEUnV0d26w6gUls7gSXmhG8JGRMmHFIPsxt1g==}
|
||||
|
||||
'@fastify/oauth2@8.1.2':
|
||||
resolution: {integrity: sha512-XZWFRWTZE2fkZ2pjuHNGtpFn1tOFgcJbU0205kHbfd16dn9xRc/6HmG0gHtN/g/BNkEL3EsQ54+pYEdh8dnBgA==}
|
||||
|
||||
@@ -14628,7 +14616,7 @@ snapshots:
|
||||
'@babel/traverse': 7.23.7
|
||||
'@babel/types': 7.23.9
|
||||
convert-source-map: 2.0.0
|
||||
debug: 4.3.4(supports-color@8.1.1)
|
||||
debug: 4.3.4
|
||||
gensync: 1.0.0-beta.2
|
||||
json5: 2.2.3
|
||||
semver: 6.3.1
|
||||
@@ -16373,7 +16361,7 @@ snapshots:
|
||||
'@babel/helper-split-export-declaration': 7.22.6
|
||||
'@babel/parser': 7.23.6
|
||||
'@babel/types': 7.23.9
|
||||
debug: 4.3.4(supports-color@8.1.1)
|
||||
debug: 4.3.4
|
||||
globals: 11.12.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
@@ -16397,7 +16385,7 @@ snapshots:
|
||||
'@babel/parser': 7.27.4
|
||||
'@babel/template': 7.27.2
|
||||
'@babel/types': 7.27.3
|
||||
debug: 4.3.4(supports-color@8.1.1)
|
||||
debug: 4.3.4
|
||||
globals: 11.12.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
@@ -16748,8 +16736,6 @@ snapshots:
|
||||
ajv-formats: 3.0.1(ajv@8.12.0)
|
||||
fast-uri: 3.0.6
|
||||
|
||||
'@fastify/busboy@3.1.1': {}
|
||||
|
||||
'@fastify/cookie@11.0.1':
|
||||
dependencies:
|
||||
cookie: 1.0.2
|
||||
@@ -16763,8 +16749,6 @@ snapshots:
|
||||
|
||||
'@fastify/csrf@8.0.1': {}
|
||||
|
||||
'@fastify/deepmerge@2.0.2': {}
|
||||
|
||||
'@fastify/error@4.0.0': {}
|
||||
|
||||
'@fastify/fast-json-stringify-compiler@5.0.3':
|
||||
@@ -16775,14 +16759,6 @@ snapshots:
|
||||
dependencies:
|
||||
dequal: 2.0.3
|
||||
|
||||
'@fastify/multipart@9.0.3':
|
||||
dependencies:
|
||||
'@fastify/busboy': 3.1.1
|
||||
'@fastify/deepmerge': 2.0.2
|
||||
'@fastify/error': 4.0.0
|
||||
fastify-plugin: 5.0.1
|
||||
secure-json-parse: 3.0.2
|
||||
|
||||
'@fastify/oauth2@8.1.2':
|
||||
dependencies:
|
||||
'@fastify/cookie': 11.0.1
|
||||
@@ -20335,9 +20311,9 @@ snapshots:
|
||||
|
||||
babel-plugin-macros@3.1.0:
|
||||
dependencies:
|
||||
'@babel/runtime': 7.23.9
|
||||
'@babel/runtime': 7.27.3
|
||||
cosmiconfig: 7.1.0
|
||||
resolve: 1.22.8
|
||||
resolve: 1.22.10
|
||||
|
||||
babel-plugin-polyfill-corejs2@0.4.7(@babel/core@7.23.0):
|
||||
dependencies:
|
||||
@@ -21712,6 +21688,10 @@ snapshots:
|
||||
dependencies:
|
||||
ms: 2.1.3
|
||||
|
||||
debug@4.3.4:
|
||||
dependencies:
|
||||
ms: 2.1.2
|
||||
|
||||
debug@4.3.4(supports-color@8.1.1):
|
||||
dependencies:
|
||||
ms: 2.1.2
|
||||
@@ -25285,7 +25265,7 @@ snapshots:
|
||||
|
||||
istanbul-lib-source-maps@4.0.1:
|
||||
dependencies:
|
||||
debug: 4.3.4(supports-color@8.1.1)
|
||||
debug: 4.3.4
|
||||
istanbul-lib-coverage: 3.2.0
|
||||
source-map: 0.6.1
|
||||
transitivePeerDependencies:
|
||||
|
||||
@@ -42,9 +42,6 @@ FORUM_LOCATION=https://forum.freecodecamp.org
|
||||
NEWS_LOCATION=https://www.freecodecamp.org/news
|
||||
RADIO_LOCATION=https://coderadio.freecodecamp.org
|
||||
|
||||
# Exam Env application paths
|
||||
SCREENSHOT_SERVICE_LOCATION=http://localhost:3003
|
||||
|
||||
# ---------------------
|
||||
# Build variants
|
||||
# ---------------------
|
||||
|
||||
Reference in New Issue
Block a user