From 49fbe88369a7aca1aaa63e5f5e64e29183bf7ee1 Mon Sep 17 00:00:00 2001 From: Oliver Eyton-Williams Date: Thu, 12 Jun 2025 09:25:37 +0200 Subject: [PATCH] feat(client): integrate new test runner (#60318) --- client/serve/serve.json | 9 + .../Challenges/rechallenge/builders.ts | 12 +- .../redux/execute-challenge-saga.js | 55 +- .../src/templates/Challenges/utils/build.ts | 183 +-- .../src/templates/Challenges/utils/frame.ts | 213 +-- client/tsconfig.json | 2 +- .../5dc174fcf86c76b9248c6eb2.md | 10 - .../62a2509ba163e020bb9d84ea.md | 2 +- .../62b46e3a8d4be31be5af793d.md | 8 +- .../66f55eac933ff64ce654ca74.md | 2 +- .../669e81368e52b3a5c35a2dc5.md | 2 +- .../5dc174fcf86c76b9248c6eb2.md | 9 - .../671fa47e415d88263d349a10.md | 2 +- curriculum/test/stubs/index.html | 3 + curriculum/test/test-challenges.js | 113 +- e2e/output.spec.ts | 30 +- pnpm-lock.yaml | 1170 +++++++++-------- .../browser-scripts/frame-runner.ts | 110 -- .../browser-scripts/package.json | 10 +- .../browser-scripts/python-test-evaluator.ts | 192 --- .../browser-scripts/test-evaluator.ts | 163 --- .../browser-scripts/test-runner.ts | 3 + .../browser-scripts/webpack.config.cjs | 12 +- 23 files changed, 922 insertions(+), 1393 deletions(-) delete mode 100644 tools/client-plugins/browser-scripts/frame-runner.ts delete mode 100644 tools/client-plugins/browser-scripts/python-test-evaluator.ts delete mode 100644 tools/client-plugins/browser-scripts/test-evaluator.ts create mode 100644 tools/client-plugins/browser-scripts/test-runner.ts diff --git a/client/serve/serve.json b/client/serve/serve.json index 2dffd3ddd72..28afb35fc83 100644 --- a/client/serve/serve.json +++ b/client/serve/serve.json @@ -19,6 +19,15 @@ } ] }, + { + "source": "js/test-runner/*/*.js", + "headers": [ + { + "key": "Cache-Control", + "value": "public, max-age=172800, immutable" + } + ] + }, { "source": "{misc/*.js,sw.js,python-input-sw.js}", "headers": [ diff --git a/client/src/templates/Challenges/rechallenge/builders.ts b/client/src/templates/Challenges/rechallenge/builders.ts index 56496838601..7ba2525ea7c 100644 --- a/client/src/templates/Challenges/rechallenge/builders.ts +++ b/client/src/templates/Challenges/rechallenge/builders.ts @@ -10,8 +10,7 @@ interface ConcatHTMLOptions { export function concatHtml({ required = [], template, - contents, - testRunner + contents }: ConcatHTMLOptions): string { const embedSource = template ? _template(template) @@ -33,14 +32,7 @@ A required file can not have both a src and a link: src = ${src}, link = ${link} }) .join('\n'); - // The script has an id so that tests can look for it, if needed. - const testRunnerScript = testRunner - ? `` - : ''; - - return `${head}${ - embedSource({ source: contents }) || '' - }${testRunnerScript}`; + return `${head}${embedSource({ source: contents }) || ''}`; } export function createPythonTerminal(pythonRunnerSrc: string): string { diff --git a/client/src/templates/Challenges/redux/execute-challenge-saga.js b/client/src/templates/Challenges/redux/execute-challenge-saga.js index a7079a83b14..3aeee3ba3b7 100644 --- a/client/src/templates/Challenges/redux/execute-challenge-saga.js +++ b/client/src/templates/Challenges/redux/execute-challenge-saga.js @@ -114,9 +114,6 @@ export function* executeChallengeSaga({ payload }) { const hooks = yield select(challengeHooksSelector); yield put(updateTests(tests)); - yield fork(takeEveryLog, consoleProxy); - const proxyLogger = args => consoleProxy.put(args); - const challengeData = yield select(challengeDataSelector); const challengeMeta = yield select(challengeMetaSelector); // The buildData is used even if there are build errors, so that lessons @@ -127,13 +124,7 @@ export function* executeChallengeSaga({ payload }) { disableLoopProtectPreview: challengeMeta.disableLoopProtectPreview, usesTestRunner: true }); - const document = yield getContext('document'); - const testRunner = yield call( - getTestRunner, - { ...buildData, hooks }, - { proxyLogger }, - document - ); + const testRunner = yield call(getTestRunner, { ...buildData, hooks }); const testResults = yield executeTests(testRunner, tests); yield put(updateTests(testResults)); @@ -169,14 +160,6 @@ export function* executeChallengeSaga({ payload }) { } } -function* takeEveryLog(channel) { - // TODO: move all stringifying and escaping into the reducer so there is a - // single place responsible for formatting the logs. - yield takeEvery(channel, function* (args) { - yield put(updateLogs(escape(args))); - }); -} - function* takeEveryConsole(channel) { // TODO: move all stringifying and escaping into the reducer so there is a // single place responsible for formatting the console output. @@ -199,22 +182,27 @@ function* executeTests(testRunner, tests, testTimeout = 5000) { for (let i = 0; i < tests.length; i++) { const { text, testString } = tests[i]; const newTest = { text, testString, running: false }; - // only the last test outputs console.logs to avoid log duplication. - const firstTest = i === 1; + // only the first test outputs console.logs to avoid log duplication. + const firstTest = i === 0; try { - const { pass, err } = yield call( - testRunner, - testString, - testTimeout, - firstTest - ); + const { + pass, + err, + logs = [] + } = yield call(testRunner, testString, testTimeout); + + const logString = logs.map(log => log.msg).join('\n'); + if (firstTest && logString) { + yield put(updateLogs(logString)); + } + if (pass) { newTest.pass = true; } else { throw err; } } catch (err) { - const { actual, expected, errorType } = err; + const { actual, expected, type } = err; newTest.message = text .replace('--fcc-expected--', expected) @@ -222,9 +210,9 @@ function* executeTests(testRunner, tests, testTimeout = 5000) { if (err === 'timeout') { newTest.err = 'Test timed out'; newTest.message = `${newTest.message} (${newTest.err})`; - } else if (errorType) { + } else if (type) { const msgKey = - errorType === 'indentation' + type === 'IndentationError' ? 'learn.indentation-error' : 'learn.syntax-error'; newTest.message = `

${i18next.t(msgKey)}

`; @@ -300,11 +288,12 @@ export function* previewChallengeSaga(action) { yield call(updatePreview, buildData, finalDocument, proxyLogger); } } else if (isJavaScriptChallenge(challengeData)) { - const runUserCode = getTestRunner(buildData, { - proxyLogger - }); + const runUserCode = yield call(getTestRunner, buildData); // without a testString the testRunner just evaluates the user's code - yield call(runUserCode, null, previewTimeout); + const out = yield call(runUserCode, null, previewTimeout); + + if (out) + yield put(updateConsole(out.logs?.map(log => log.msg).join('\n'))); } } } catch (err) { diff --git a/client/src/templates/Challenges/utils/build.ts b/client/src/templates/Challenges/utils/build.ts index 2dcab5945c3..2df6485fb8d 100644 --- a/client/src/templates/Challenges/utils/build.ts +++ b/client/src/templates/Challenges/utils/build.ts @@ -1,7 +1,4 @@ import { challengeTypes } from '../../../../../shared/config/challenge-types'; -import frameRunnerData from '../../../../../client/config/browser-scripts/frame-runner.json'; -import jsTestEvaluatorData from '../../../../../client/config/browser-scripts/test-evaluator.json'; -import pyTestEvaluatorData from '../../../../../client/config/browser-scripts/python-test-evaluator.json'; import type { ChallengeFile } from '../../../redux/prop-types'; import { concatHtml } from '../rechallenge/builders'; @@ -12,16 +9,14 @@ import { getMultifileJSXTransformers } from '../rechallenge/transformers'; import { - createTestFramer, runTestInTestFrame, createMainPreviewFramer, createProjectPreviewFramer, ProxyLogger, - TestRunnerConfig, Context, - Source + Source, + prepTestRunner } from './frame'; -import { WorkerExecutor } from './worker-executor'; interface BuildChallengeData extends Context { challengeType: number; @@ -38,19 +33,6 @@ interface BuildOptions { usesTestRunner?: boolean; } -const { filename: jsTestEvaluator } = jsTestEvaluatorData; -const { filename: pyTestEvaluator } = pyTestEvaluatorData; - -const frameRunnerSrc = `/js/${frameRunnerData.filename}.js`; - -const pythonWorkerExecutor = new WorkerExecutor(pyTestEvaluator, { - terminateWorker: false, - maxWorkers: 1 -}); -const jsWorkerExecutor = new WorkerExecutor(jsTestEvaluator, { - terminateWorker: true -}); - type ApplyFunctionProps = ( file: ChallengeFile ) => Promise | ChallengeFile; @@ -127,91 +109,35 @@ export async function buildChallenge( throw new Error(`Cannot build challenge of type ${challengeType}`); } -const testRunners = { - [challengeTypes.js]: getJSTestRunner, - [challengeTypes.html]: getDOMTestRunner, - [challengeTypes.backend]: getDOMTestRunner, - [challengeTypes.pythonProject]: getDOMTestRunner, - [challengeTypes.python]: getPyTestRunner, - [challengeTypes.multifileCertProject]: getDOMTestRunner, - [challengeTypes.multifilePythonCertProject]: getPyTestRunner, - [challengeTypes.lab]: getDOMTestRunner, - [challengeTypes.pyLab]: getPyTestRunner, - [challengeTypes.dailyChallengeJs]: getJSTestRunner, - [challengeTypes.dailyChallengePy]: getPyTestRunner +export const runnerTypes: Record = { + [challengeTypes.js]: 'javascript', + [challengeTypes.html]: 'dom', + [challengeTypes.backend]: 'dom', + [challengeTypes.jsProject]: 'javascript', + [challengeTypes.pythonProject]: 'python', + [challengeTypes.python]: 'python', + [challengeTypes.modern]: 'dom', + [challengeTypes.multifileCertProject]: 'dom', + [challengeTypes.multifilePythonCertProject]: 'python', + [challengeTypes.lab]: 'dom', + [challengeTypes.jsLab]: 'javascript', + [challengeTypes.pyLab]: 'python', + [challengeTypes.dailyChallengeJs]: 'javascript', + [challengeTypes.dailyChallengePy]: 'python' }; -export function getTestRunner( - buildData: BuildChallengeData, - runnerConfig: TestRunnerConfig, - document: Document -) { +export async function getTestRunner(buildData: BuildChallengeData) { const { challengeType } = buildData; - const testRunner = testRunners[challengeType]; - if (testRunner) { - return testRunner(buildData, runnerConfig, document); + const type = runnerTypes[challengeType]; + if (!type) { + throw new Error( + `Cannot get test runner for challenge type ${challengeType}` + ); } - throw new Error(`Cannot get test runner for challenge type ${challengeType}`); -} + await prepTestRunner({ ...buildData, type }); -function getJSTestRunner( - { build, sources }: BuildChallengeData, - { proxyLogger }: TestRunnerConfig -) { - return getWorkerTestRunner( - { build, sources }, - { proxyLogger }, - jsWorkerExecutor - ); -} - -function getPyTestRunner( - { build, sources }: BuildChallengeData, - { proxyLogger }: TestRunnerConfig -) { - return getWorkerTestRunner( - { build, sources }, - { proxyLogger }, - pythonWorkerExecutor - ); -} - -function getWorkerTestRunner( - { build, sources }: Pick, - { proxyLogger }: TestRunnerConfig, - workerExecutor: WorkerExecutor -) { - const code = { - contents: sources.index, - editableContents: sources.editableContents - }; - - interface TestWorkerExecutor extends WorkerExecutor { - on: (event: string, listener: (...args: string[]) => void) => void; - done: () => void; - } - - return (testString: string, testTimeout: number, firstTest = true) => { - const result = workerExecutor.execute( - { build, testString, code, sources, firstTest }, - testTimeout - ) as TestWorkerExecutor; - - result.on('LOG', proxyLogger); - return result.done; - }; -} - -async function getDOMTestRunner( - buildData: BuildChallengeData, - { proxyLogger }: TestRunnerConfig, - document: Document -) { - await new Promise(resolve => - createTestFramer(document, proxyLogger, resolve)(buildData) - ); return (testString: string, testTimeout: number) => - runTestInTestFrame(document, testString, testTimeout); + runTestInTestFrame(testString, testTimeout, type); } type BuildResult = { @@ -226,7 +152,12 @@ type BuildResult = { // abstraction (function, class, whatever) and then create the various functions // out of it. export async function buildDOMChallenge( - { challengeFiles, required = [], template = '' }: BuildChallengeData, + { + challengeFiles, + required = [], + template = '', + challengeType + }: BuildChallengeData, options?: BuildOptions ): Promise { // TODO: make this required in the schema. @@ -247,7 +178,6 @@ export async function buildDOMChallenge( : getTransformers(options)) as unknown as ApplyFunctionProps[]; const pipeLine = composeFunctions(...transformers); - const usesTestRunner = options?.usesTestRunner ?? false; const finalFiles = await Promise.all(challengeFiles.map(pipeLine)); const error = finalFiles.find(({ error }) => error)?.error; const contents = (await embedFilesInHtml(finalFiles)) as string; @@ -255,18 +185,15 @@ export async function buildDOMChallenge( // if there is an error, we just build the test runner so that it can be // used to run tests against the code without actually running the code. const toBuild = error - ? { ...(usesTestRunner && { testRunner: frameRunnerSrc }) } + ? {} : { required, template, - contents, - ...(usesTestRunner && { testRunner: frameRunnerSrc }) + contents }; return { - // TODO: Stop overwriting challengeType with 'html'. Figure out why it's - // necessary at the moment. - challengeType: challengeTypes.html, + challengeType, build: concatHtml(toBuild), sources: buildSourceMap(finalFiles), loadEnzyme: requiresReact16, @@ -275,7 +202,10 @@ export async function buildDOMChallenge( } export async function buildJSChallenge( - { challengeFiles }: { challengeFiles?: ChallengeFile[] }, + { + challengeFiles, + challengeType + }: { challengeFiles?: ChallengeFile[]; challengeType: number }, options: BuildOptions ): Promise { if (!challengeFiles) throw Error('No challenge files provided'); @@ -289,7 +219,7 @@ export async function buildJSChallenge( const toBuild = error ? [] : finalFiles; return { - challengeType: challengeTypes.js, + challengeType, build: toBuild .reduce( (body, challengeFile) => [ @@ -306,16 +236,17 @@ export async function buildJSChallenge( }; } -function buildBackendChallenge({ url }: BuildChallengeData) { +function buildBackendChallenge({ url, challengeType }: BuildChallengeData) { return { - challengeType: challengeTypes.backend, - build: concatHtml({ testRunner: frameRunnerSrc }), + challengeType, + build: '', sources: { contents: url } }; } export async function buildPythonChallenge({ - challengeFiles + challengeFiles, + challengeType }: BuildChallengeData): Promise { if (!challengeFiles) throw new Error('No challenge files provided'); const pipeLine = composeFunctions( @@ -323,13 +254,12 @@ export async function buildPythonChallenge({ ); const finalFiles = await Promise.all(challengeFiles.map(pipeLine)); const error = finalFiles.find(({ error }) => error)?.error; + const sources = buildSourceMap(finalFiles); return { - challengeType: - challengeFiles[0].editableRegionBoundaries?.length === 0 - ? challengeTypes.multifilePythonCertProject - : challengeTypes.python, - sources: buildSourceMap(finalFiles), + challengeType, + sources, + build: sources?.contents, error }; } @@ -339,16 +269,7 @@ export function updatePreview( document: Document, proxyLogger: ProxyLogger ): Promise { - // TODO: either create a 'buildType' or use the real challengeType here - // (buildData.challengeType is set to 'html' for challenges that can be - // previewed, hence this being true for python challenges, multifile steps and - // so on). - - if ( - buildData.challengeType === challengeTypes.html || - buildData.challengeType === challengeTypes.multifileCertProject || - buildData.challengeType === challengeTypes.lab - ) { + if (challengeHasPreview(buildData)) { return new Promise(resolve => createMainPreviewFramer( document, @@ -379,11 +300,7 @@ export function updateProjectPreview( buildData: BuildChallengeData, document: Document ): void { - if ( - buildData.challengeType === challengeTypes.html || - buildData.challengeType === challengeTypes.multifileCertProject || - buildData.challengeType === challengeTypes.lab - ) { + if (challengeHasPreview(buildData)) { createProjectPreviewFramer( document, getDocumentTitle(buildData) diff --git a/client/src/templates/Challenges/utils/frame.ts b/client/src/templates/Challenges/utils/frame.ts index 1c0078b86cc..e267041f388 100644 --- a/client/src/templates/Challenges/utils/frame.ts +++ b/client/src/templates/Challenges/utils/frame.ts @@ -1,12 +1,25 @@ import { flow } from 'lodash-es'; import i18next, { type i18n } from 'i18next'; +import { + version as _helperVersion, + type FCCTestRunner +} from '../../../../../tools/client-plugins/browser-scripts/test-runner'; + import { format } from '../../../utils/format'; import type { FrameDocument, PythonDocument } from '../../../../../tools/client-plugins/browser-scripts'; +export const helperVersion = _helperVersion; + +declare global { + interface Window { + FCCTestRunner: FCCTestRunner; + } +} + const utilsFormat: (x: T) => string = format; export interface Source { @@ -30,11 +43,8 @@ export interface Context { build: string; sources: Source; hooks?: Hooks; - loadEnzyme?: () => void; -} - -export interface TestRunnerConfig { - proxyLogger: ProxyLogger; + type: 'dom' | 'javascript' | 'python'; + loadEnzyme?: boolean; } export type ProxyLogger = (msg: string) => void; @@ -76,10 +86,9 @@ export const scrollManager = new ScrollManager(); // we use two different frames to make them all essentially pure functions // main iframe is responsible rendering the preview and is where we proxy the export const mainPreviewId = 'fcc-main-frame'; -// the test frame is responsible for running the assert tests -export const testId = 'fcc-test-frame'; // the project preview frame demos the finished project export const projectPreviewId = 'fcc-project-preview-frame'; +const ASSET_PATH = `/js/test-runner/${helperVersion}/`; const DOCUMENT_NOT_FOUND_ERROR = 'misc.document-notfound'; @@ -149,14 +158,6 @@ const createHeader = (id = mainPreviewId) => `; -const createBeforeAllScript = (beforeAll?: string) => { - if (!beforeAll) return ''; - - return ``; -}; - type TestResult = | { pass: boolean } | { err: { message: string; stack?: string } }; @@ -172,20 +173,43 @@ function getContentDocument( } export const runTestInTestFrame = async function ( - document: Document, test: string, - timeout: number + timeout: number, + type: 'dom' | 'javascript' | 'python' ): Promise { - const contentDocument = getContentDocument(document, testId); - if (contentDocument) { - return await Promise.race([ - new Promise< - { pass: boolean } | { err: { message: string; stack?: string } } - // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors - >((_, reject) => setTimeout(() => reject('timeout'), timeout)), - contentDocument.__runTest(test) - ]); - } + const runner = window?.FCCTestRunner.getRunner(type); + + return await Promise.race([ + new Promise< + { pass: boolean } | { err: { message: string; stack?: string } } + >((_, reject) => setTimeout(() => reject(Error('timeout')), timeout)), + runner?.runTest(test) + ]); +}; + +export const prepTestRunner = async ({ + sources, + loadEnzyme, + build, + hooks, + type +}: { + sources: Source; + loadEnzyme?: boolean; + build: string; + hooks?: Hooks; + type: 'dom' | 'javascript' | 'python'; +}) => { + const source = type === 'dom' ? prefixDoctype({ build, sources }) : build; + await loadTestRunner(document); + await window?.FCCTestRunner.createTestRunner({ + type, + code: sources, + source, + assetPath: ASSET_PATH, + hooks, + loadEnzyme + }); }; export const runPythonInFrame = function ( @@ -200,10 +224,49 @@ export const runPythonInFrame = function ( void contentDocument?.__runPython(code); }; +const TEST_RUNNER_ID = 'fcc-test-runner'; +const createRunnerScript = (document: Document) => { + const script = document.createElement('script'); + script.src = ASSET_PATH + 'index.js'; + script.id = TEST_RUNNER_ID; + return script; +}; + +const loadTestRunner = async (document: Document) => { + const done = new Promise((resolve, reject) => { + const alreadyLoaded = !!window?.FCCTestRunner; + + if (alreadyLoaded) return resolve(); + + const script = + document.getElementById(TEST_RUNNER_ID) ?? createRunnerScript(document); + + const errorListener = (err: ErrorEvent) => { + console.error(err); + reject(new Error('Test runner failed to load')); + }; + + script.addEventListener( + 'load', + () => { + // Since it's loaded, we no longer need to listen for errors + script.removeEventListener('error', errorListener); + resolve(); + }, + { once: true } + ); + script.addEventListener('error', errorListener, { once: true }); + + document.head.appendChild(script); + }); + return done; +}; + const createFrame = (document: Document, id: string, title?: string) => (frameContext: Context) => { const frame = document.createElement('iframe'); + frame.srcdoc = createContent(id, frameContext); frame.id = id; if (typeof title === 'string') { @@ -216,37 +279,20 @@ const createFrame = }; }; -const hiddenFrameClassName = 'hide-test-frame'; -const mountFrame = - (document: Document, id: string) => (frameContext: Context) => { - const { element }: { element: HTMLIFrameElement } = frameContext; - const oldFrame = document.getElementById(element.id) as HTMLIFrameElement; - if (oldFrame) { - element.className = oldFrame.className || hiddenFrameClassName; - oldFrame.parentNode!.replaceChild(element, oldFrame); - // only test frames can be added (and hidden) here, other frames must be - // added by react - } else if (id === testId) { - element.className = hiddenFrameClassName; - document.body.appendChild(element); - } - return { - ...frameContext, - element, - window: element.contentWindow - }; - }; - -// Tests should not use functions that directly interact with the user, so -// they're overridden. If tests need to spy on these functions, they can supply -// the spy themselves. -const overrideUserInteractions = (frameContext: Context) => { - if (frameContext.window) { - frameContext.window.prompt = () => null; - frameContext.window.alert = () => {}; - frameContext.window.confirm = () => false; +const mountFrame = (document: Document) => (frameContext: Context) => { + const { element }: { element: HTMLIFrameElement } = frameContext; + const oldFrame = document.getElementById(element.id) as HTMLIFrameElement; + if (oldFrame) { + element.className = oldFrame.className; + oldFrame.parentNode!.replaceChild(element, oldFrame); + // only test frames can be added (and hidden) here, other frames must be + // added by react } - return frameContext; + return { + ...frameContext, + element, + window: element.contentWindow + }; }; const noop = (x: T) => x; @@ -310,21 +356,6 @@ const updateWindowI18next = (frameContext: Context) => { return frameContext; }; -const initTestFrame = (frameReady?: () => void) => (frameContext: Context) => { - waitForFrame(frameContext) - .then(async () => { - const { sources, loadEnzyme } = frameContext; - await frameContext.window?.document?.__initTestFrame({ - code: sources, - loadEnzyme - }); - - if (frameReady) frameReady(); - }) - .catch(handleDocumentNotFound); - return frameContext; -}; - const initMainFrame = (frameReady?: () => void, proxyLogger?: ProxyLogger) => (frameContext: Context) => { @@ -386,16 +417,24 @@ const waitForFrame = (frameContext: Context) => { }); }; -export const createContent = ( - id: string, - { build, sources, hooks }: { build: string; sources: Source; hooks?: Hooks } -) => { +export const prefixDoctype = ({ + build, + sources +}: { + build: string; + sources: Source; +}) => { // DOCTYPE should be the first thing written to the frame, so if the user code // includes a DOCTYPE declaration, we need to find it and write it first. const doctype = sources.contents?.match(/^/i)?.[0] || ''; - return ( - doctype + createBeforeAllScript(hooks?.beforeAll) + createHeader(id) + build - ); + return doctype + build; +}; + +const createContent = ( + id: string, + { build, sources }: { build: string; sources: Source; hooks?: Hooks } +) => { + return prefixDoctype({ build: createHeader(id) + build, sources }); }; const restoreScrollPosition = (frameContext: Context) => { @@ -433,20 +472,6 @@ export const createProjectPreviewFramer = ( frameTitle }); -export const createTestFramer = ( - document: Document, - proxyLogger: ProxyLogger, - frameReady: () => void -): ((args: Context) => void) => - createFramer({ - document, - id: testId, - init: initTestFrame, - proxyLogger, - frameReady, - updateWindowFunctions: overrideUserInteractions - }); - const createFramer = ({ document, id, @@ -466,7 +491,7 @@ const createFramer = ({ }) => flow( createFrame(document, id, frameTitle), - mountFrame(document, id), + mountFrame(document), updateWindowFunctions ?? noop, updateProxyConsole(proxyLogger), updateWindowI18next, diff --git a/client/tsconfig.json b/client/tsconfig.json index c399cd236b3..f4f14157901 100644 --- a/client/tsconfig.json +++ b/client/tsconfig.json @@ -3,7 +3,7 @@ "lib": ["WebWorker", "DOM", "DOM.Iterable", "es2023"], "target": "ES2023", "module": "es2020", - "moduleResolution": "node", + "moduleResolution": "bundler", "allowJs": true, "jsx": "react", "strict": true, diff --git a/curriculum/challenges/english/14-responsive-web-design-22/learn-html-by-building-a-cat-photo-app/5dc174fcf86c76b9248c6eb2.md b/curriculum/challenges/english/14-responsive-web-design-22/learn-html-by-building-a-cat-photo-app/5dc174fcf86c76b9248c6eb2.md index 83abb16cfdd..2af6314da3a 100644 --- a/curriculum/challenges/english/14-responsive-web-design-22/learn-html-by-building-a-cat-photo-app/5dc174fcf86c76b9248c6eb2.md +++ b/curriculum/challenges/english/14-responsive-web-design-22/learn-html-by-building-a-cat-photo-app/5dc174fcf86c76b9248c6eb2.md @@ -46,16 +46,6 @@ Your `h1` element's text should be `CatPhotoApp`. You have either omitted the te assert(document.querySelector('h1').innerText.toLowerCase() === 'catphotoapp'); ``` -You appear to be using a browser extension that is modifying the page. Be sure to turn off all browser extensions. - -```js -if(__checkForBrowserExtensions){ - assert.isAtMost(document.querySelectorAll('script').length, 2); - assert.equal(document.querySelectorAll('style').length, 1); - assert.equal(document.querySelectorAll('link').length, 0); -} -``` - # --seed-- ## --seed-contents-- diff --git a/curriculum/challenges/english/15-javascript-algorithms-and-data-structures-22/learn-basic-javascript-by-building-a-role-playing-game/62a2509ba163e020bb9d84ea.md b/curriculum/challenges/english/15-javascript-algorithms-and-data-structures-22/learn-basic-javascript-by-building-a-role-playing-game/62a2509ba163e020bb9d84ea.md index bf66f389157..534110c7b28 100644 --- a/curriculum/challenges/english/15-javascript-algorithms-and-data-structures-22/learn-basic-javascript-by-building-a-role-playing-game/62a2509ba163e020bb9d84ea.md +++ b/curriculum/challenges/english/15-javascript-algorithms-and-data-structures-22/learn-basic-javascript-by-building-a-role-playing-game/62a2509ba163e020bb9d84ea.md @@ -20,7 +20,7 @@ Now you can start writing your JavaScript. Begin by creating a `script` element. You should have a `script` element. ```js -assert.isAtLeast(document.querySelectorAll('script').length, 2); +assert.isAtLeast(document.querySelectorAll('script').length, 1); ``` Your `script` element should have an opening tag. diff --git a/curriculum/challenges/english/15-javascript-algorithms-and-data-structures-22/learn-basic-javascript-by-building-a-role-playing-game/62b46e3a8d4be31be5af793d.md b/curriculum/challenges/english/15-javascript-algorithms-and-data-structures-22/learn-basic-javascript-by-building-a-role-playing-game/62b46e3a8d4be31be5af793d.md index 5b9f514bdba..a3e2158bd08 100644 --- a/curriculum/challenges/english/15-javascript-algorithms-and-data-structures-22/learn-basic-javascript-by-building-a-role-playing-game/62b46e3a8d4be31be5af793d.md +++ b/curriculum/challenges/english/15-javascript-algorithms-and-data-structures-22/learn-basic-javascript-by-building-a-role-playing-game/62b46e3a8d4be31be5af793d.md @@ -24,12 +24,8 @@ Your `script` element should come at the end of your `body` element. ```js const script = document.querySelector('script[data-src$="script.js"]'); -assert.equal(script.previousElementSibling.tagName, "DIV"); -// When building the test frame, the runner script is always inserted after user -// code. This means the learner's script should be the penultimate element in -// the body. -assert.equal(script.nextElementSibling.id, "fcc-test-runner"); -assert.equal(script.parentElement.tagName, "BODY"); +const lastChild = document.querySelector('body').lastElementChild; +assert.equal(script, lastChild); ``` # --seed-- diff --git a/curriculum/challenges/english/25-front-end-development/lab-markdown-to-html-converter/66f55eac933ff64ce654ca74.md b/curriculum/challenges/english/25-front-end-development/lab-markdown-to-html-converter/66f55eac933ff64ce654ca74.md index 9a5b5466c2b..f60a0f4d9a6 100644 --- a/curriculum/challenges/english/25-front-end-development/lab-markdown-to-html-converter/66f55eac933ff64ce654ca74.md +++ b/curriculum/challenges/english/25-front-end-development/lab-markdown-to-html-converter/66f55eac933ff64ce654ca74.md @@ -848,7 +848,7 @@ assert.equal(italics[0].innerText, "quote"); You should have only one `script` element in your HTML. ```js -assert.lengthOf(document.querySelectorAll("script"), 3); +assert.lengthOf(document.querySelectorAll("script"), 1); ``` # --seed-- diff --git a/curriculum/challenges/english/25-front-end-development/lab-video-compilation-page/669e81368e52b3a5c35a2dc5.md b/curriculum/challenges/english/25-front-end-development/lab-video-compilation-page/669e81368e52b3a5c35a2dc5.md index 66e399765dd..7194e0a97b4 100644 --- a/curriculum/challenges/english/25-front-end-development/lab-video-compilation-page/669e81368e52b3a5c35a2dc5.md +++ b/curriculum/challenges/english/25-front-end-development/lab-video-compilation-page/669e81368e52b3a5c35a2dc5.md @@ -32,7 +32,7 @@ assert.equal(document.querySelector('body :first-child').tagName, 'MAIN'); Your `main` element should be the only child of the `body` element. ```js -assert.equal(document.querySelector('body').children.length, 2); +assert.equal(document.querySelector('body').children.length, 1); ``` You should have an `h1` element with the topic of your page inside the `main` element. diff --git a/curriculum/challenges/english/25-front-end-development/workshop-cat-photo-app/5dc174fcf86c76b9248c6eb2.md b/curriculum/challenges/english/25-front-end-development/workshop-cat-photo-app/5dc174fcf86c76b9248c6eb2.md index dc2c16253d4..6986a0647f0 100644 --- a/curriculum/challenges/english/25-front-end-development/workshop-cat-photo-app/5dc174fcf86c76b9248c6eb2.md +++ b/curriculum/challenges/english/25-front-end-development/workshop-cat-photo-app/5dc174fcf86c76b9248c6eb2.md @@ -38,15 +38,6 @@ Your `h1` element's text should be `CatPhotoApp`. You have either omitted the te assert.equal(document.querySelector('h1')?.innerText.toLowerCase(), 'catphotoapp'); ``` -You appear to be using a browser extension that is modifying the page. Be sure to turn off all browser extensions. - -```js -if(__checkForBrowserExtensions){ - assert.isAtMost(document.querySelectorAll('script').length, 2); - assert.equal(document.querySelectorAll('style').length, 1); - assert.equal(document.querySelectorAll('link').length, 0); -} -``` # --seed-- diff --git a/curriculum/challenges/english/25-front-end-development/workshop-storytelling-app/671fa47e415d88263d349a10.md b/curriculum/challenges/english/25-front-end-development/workshop-storytelling-app/671fa47e415d88263d349a10.md index b305b452d7d..bec2ba3b341 100644 --- a/curriculum/challenges/english/25-front-end-development/workshop-storytelling-app/671fa47e415d88263d349a10.md +++ b/curriculum/challenges/english/25-front-end-development/workshop-storytelling-app/671fa47e415d88263d349a10.md @@ -14,7 +14,7 @@ Next, you will start working on the `JavaScript`. For that, begin by linking the You should create a `script` element. ```js -assert.lengthOf(document.querySelectorAll('script'), 3); +assert.lengthOf(document.querySelectorAll('script'), 1); ``` Your `script` element should have a `src` attribute set to `script.js`. diff --git a/curriculum/test/stubs/index.html b/curriculum/test/stubs/index.html index e69de29bb2d..6ecf47f74a2 100644 --- a/curriculum/test/stubs/index.html +++ b/curriculum/test/stubs/index.html @@ -0,0 +1,3 @@ + + + diff --git a/curriculum/test/test-challenges.js b/curriculum/test/test-challenges.js index a8edf0f86f4..55d88a2d1d9 100644 --- a/curriculum/test/test-challenges.js +++ b/curriculum/test/test-challenges.js @@ -23,32 +23,20 @@ require('@babel/register')({ only: [clientPath] }); const { - buildDOMChallenge, - buildPythonChallenge, buildChallenge, - buildFunctions + runnerTypes } = require('../../client/src/templates/Challenges/utils/build'); -const { - WorkerExecutor -} = require('../../client/src/templates/Challenges/utils/worker-executor'); const { challengeTypes, hasNoSolution } = require('../../shared/config/challenge-types'); -// the config files are created during the build, but not before linting -const javaScriptTestEvaluator = - require('../../client/config/browser-scripts/test-evaluator.json').filename; -const pythonTestEvaluator = - require('../../client/config/browser-scripts/python-test-evaluator.json').filename; - const { getLines } = require('../../shared/utils/get-lines'); - const { getChallengesForLang, getMetaForBlock } = require('../get-challenges'); const { challengeSchemaValidator } = require('../schema/challenge-schema'); const { testedLang, getSuperOrder } = require('../utils'); const { - createContent, - testId + prefixDoctype, + helperVersion } = require('../../client/src/templates/Challenges/utils/frame'); const { chapterBasedSuperBlocks } = require('../../shared/config/curriculum'); const ChallengeTitles = require('./utils/challenge-titles'); @@ -135,8 +123,6 @@ spinner.text = 'Populate tests.'; let browser; let page; -// This worker can be reused since it clears its environment between tests. -let pythonWorker; setup() .then(runTests) @@ -148,7 +134,13 @@ async function setup() { host: '127.0.0.1', port: '8080', root: path.resolve(__dirname, 'stubs'), - mount: [['/js', path.join(clientPath, 'static/js')]], + mount: [ + [ + '/dist', + path.join(clientPath, `static/js/test-runner/${helperVersion}`) + ], + ['/js', path.join(clientPath, 'static/js')] + ], open: false, logLevel: 0 }); @@ -166,9 +158,6 @@ async function setup() { }); global.Worker = createPseudoWorker(await newPageContext(browser)); - pythonWorker = new WorkerExecutor(pythonTestEvaluator, { - terminateWorker: false - }); page = await newPageContext(browser); await page.setViewport({ width: 300, height: 150 }); @@ -401,7 +390,11 @@ function populateTestsForLang({ lang, challenges, meta, superBlocks }) { challenge.challengeFiles, buildChallenge ); - } catch { + } catch (e) { + console.error( + `Error creating test runner for initial contents` + ); + console.error(e); fails = true; } if (!fails) { @@ -540,27 +533,15 @@ async function createTestRunner( { usesTestRunner: true } ); - const code = { - contents: sources.index, - editableContents: sources.editableContents - }; - - const buildFunction = buildFunctions[challenge.challengeType]; - - const runsInBrowser = buildFunction === buildDOMChallenge; - const runsInPythonWorker = buildFunction === buildPythonChallenge; - - const evaluator = await (runsInBrowser - ? getContextEvaluator({ - // passing in challengeId so it's easier to debug timeouts - challengeId: challenge.id, - build, - sources, - code, - loadEnzyme, - hooks: challenge.hooks - }) - : getWorkerEvaluator({ build, sources, code, runsInPythonWorker })); + const evaluator = await getContextEvaluator({ + // passing in challengeId so it's easier to debug timeouts + challengeId: challenge.id, + build, + sources, + type: runnerTypes[challenge.challengeType], + loadEnzyme, + hooks: challenge.hooks + }); return async ({ text, testString }) => { try { @@ -569,7 +550,6 @@ async function createTestRunner( throw err; } } catch (err) { - // add more info to the error so the failing test can be identified. text = 'Test text: ' + text; const newMessage = solutionFromNext ? 'Check next step for solution!\n' + text @@ -625,43 +605,42 @@ ${testString} timeout ) ), - await page.evaluate(async testString => { - return await document.__runTest(testString); - }, testString) + await page.evaluate( + async (testString, type) => { + return await window.FCCTestRunner.getRunner(type).runTest( + testString + ); + }, + testString, + config.type + ) ]) }; } -async function getWorkerEvaluator({ +async function initializeTestRunner({ build, sources, - code, - runsInPythonWorker + type, + hooks, + loadEnzyme }) { - // The python worker clears the globals between tests, so it should be fine - // to use the same evaluator for all tests. TODO: check if this is true for - // sys, since sys.modules is not being reset. - const testWorker = runsInPythonWorker - ? pythonWorker - : new WorkerExecutor(javaScriptTestEvaluator, { terminateWorker: true }); - return { - evaluate: async (testString, timeout) => - await testWorker.execute({ testString, build, code, sources }, timeout) - .done - }; -} + const source = type === 'dom' ? prefixDoctype({ build, sources }) : build; -async function initializeTestRunner({ build, sources, loadEnzyme, hooks }) { - await page.reload(); - await page.setContent(createContent(testId, { build, sources, hooks })); await page.evaluate( - async (sources, loadEnzyme) => { - await document.__initTestFrame({ + async (sources, source, type, hooks, loadEnzyme) => { + await window.FCCTestRunner.createTestRunner({ + source, + type, code: sources, + hooks, loadEnzyme }); }, sources, + source, + type, + hooks, loadEnzyme ); } diff --git a/e2e/output.spec.ts b/e2e/output.spec.ts index 461f2706be9..1404fac3854 100644 --- a/e2e/output.spec.ts +++ b/e2e/output.spec.ts @@ -25,6 +25,7 @@ interface InsertTextParameters { containerId?: string; isMobile: boolean; text: string; + updatesConsole?: boolean; } const replaceTextInCodeEditor = async ({ @@ -32,12 +33,20 @@ const replaceTextInCodeEditor = async ({ browserName, isMobile, text, - containerId = 'editor-container-indexhtml' + containerId = 'editor-container-indexhtml', + updatesConsole = false }: InsertTextParameters) => { await expect(async () => { await clearEditor({ page, browserName, isMobile }); await getEditors(page).fill(text); await expect(page.getByTestId(containerId)).toContainText(text); + if (updatesConsole) { + await expect( + page.getByRole('region', { + name: translations.learn['editor-tabs'].console + }) + ).not.toContainText('Your test output will go here'); + } }).toPass(); }; @@ -151,7 +160,8 @@ test.describe('Challenge Output Component Tests', () => { page, isMobile, text: 'var', - containerId: 'editor-container-scriptjs' + containerId: 'editor-container-scriptjs', + updatesConsole: true }); if (isMobile) { @@ -165,20 +175,19 @@ test.describe('Challenge Output Component Tests', () => { ).toHaveText(outputTexts.syntaxError); }); - test('should contain reference error output when var is entered in editor', async ({ + test('should contain a reference error when an undefined var is entered in editor', async ({ browserName, page, isMobile }) => { - const referenceErrorRegex = - /ReferenceError: (myName is not defined|Can't find variable: myName)/; await focusEditor({ page, isMobile }); await replaceTextInCodeEditor({ browserName, page, isMobile, text: 'myName', - containerId: 'editor-container-scriptjs' + containerId: 'editor-container-scriptjs', + updatesConsole: true }); if (isMobile) { @@ -189,7 +198,7 @@ test.describe('Challenge Output Component Tests', () => { page.getByRole('region', { name: translations.learn['editor-tabs'].console }) - ).toHaveText(referenceErrorRegex); + ).toContainText('ReferenceError: myName is not defined'); }); test('should contain final output after test fail', async ({ @@ -216,7 +225,8 @@ test.describe('Challenge Output Component Tests', () => { page, isMobile, text: 'var myName;', - containerId: 'editor-container-scriptjs' + containerId: 'editor-container-scriptjs', + updatesConsole: true }); await runChallengeTest(page, isMobile); await closeButton.click(); @@ -284,7 +294,7 @@ test.describe('Custom output for Set and Map', () => { page.getByRole('region', { name: translations.learn['editor-tabs'].console }) - ).toContainText('Set(3) {1, set, 10}'); + ).toContainText(`Set(3) {1, 'set', 10}`); await focusEditor({ page, isMobile }); await replaceTextInCodeEditor({ @@ -303,6 +313,6 @@ test.describe('Custom output for Set and Map', () => { page.getByRole('region', { name: translations.learn['editor-tabs'].console }) - ).toContainText('Map(2) {1 => one, two => 2}'); + ).toContainText(`Map(2) {1 => 'one', 'two' => 2}`); }); }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c3d1334dd95..3c294bbf44e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -227,7 +227,7 @@ importers: version: 6.10.0(@aws-sdk/credential-providers@3.521.0)(socks@2.8.3) nanoid: specifier: '3' - version: 3.3.11 + version: 3.3.7 no-profanity: specifier: 1.5.1 version: 1.5.1 @@ -297,7 +297,7 @@ importers: version: 7.23.3(@babel/core@7.23.7) '@babel/plugin-transform-runtime': specifier: ^7.19.6 - version: 7.27.4(@babel/core@7.23.7) + version: 7.23.7(@babel/core@7.23.7) '@babel/preset-env': specifier: 7.23.7 version: 7.23.7(@babel/core@7.23.7) @@ -637,7 +637,7 @@ importers: version: 2.0.5 '@types/validator': specifier: ^13.7.12 - version: 13.15.1 + version: 13.11.2 autoprefixer: specifier: 10.4.17 version: 10.4.17(postcss@8.4.35) @@ -941,6 +941,9 @@ importers: tools/client-plugins/browser-scripts: dependencies: + '@freecodecamp/curriculum-helpers': + specifier: ^4.4.0 + version: 4.4.0(typescript@5.8.2) react: specifier: '16' version: 16.14.0 @@ -963,24 +966,9 @@ importers: '@babel/preset-typescript': specifier: 7.23.3 version: 7.23.3(@babel/core@7.23.7) - '@freecodecamp/curriculum-helpers': - specifier: 4.1.0 - version: 4.1.0 - '@types/chai': - specifier: 4.3.12 - version: 4.3.12 '@types/copy-webpack-plugin': specifier: ^8.0.1 version: 8.0.1(webpack-cli@4.10.0) - '@types/enzyme': - specifier: 3.10.16 - version: 3.10.16 - '@types/enzyme-adapter-react-16': - specifier: 1.0.9 - version: 1.0.9 - '@types/jquery': - specifier: 3.5.29 - version: 3.5.29 '@types/lodash-es': specifier: 4.17.12 version: 4.17.12 @@ -990,21 +978,9 @@ importers: babel-loader: specifier: 8.3.0 version: 8.3.0(@babel/core@7.23.7)(webpack@5.90.3) - chai: - specifier: 4.4.1 - version: 4.4.1 copy-webpack-plugin: specifier: 9.1.0 version: 9.1.0(webpack@5.90.3) - enzyme: - specifier: 3.11.0 - version: 3.11.0 - enzyme-adapter-react-16: - specifier: 1.15.8 - version: 1.15.8(enzyme@3.11.0)(react-dom@16.14.0(react@16.14.0))(react@16.14.0) - jquery: - specifier: 3.7.1 - version: 3.7.1 lodash-es: specifier: 4.17.21 version: 4.17.21 @@ -1483,10 +1459,6 @@ packages: resolution: {integrity: sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==} engines: {node: '>=6.9.0'} - '@babel/compat-data@7.27.3': - resolution: {integrity: sha512-V42wFfx1ymFte+ecf6iXghnnP8kWTO+ZLXIyZq+1LAXHHvTZdVxicn4yiVYdYMGaCO3tmqub11AorKkv+iodqw==} - engines: {node: '>=6.9.0'} - '@babel/core@7.10.5': resolution: {integrity: sha512-O34LQooYVDXPl7QWCdW9p4NR+QlzOr7xShPPJz8GsuCU3/8ua/wqTr7gmnxXv+WBESiGU/G5s16i6tUvHkNb+w==} engines: {node: '>=6.9.0'} @@ -1545,10 +1517,6 @@ packages: resolution: {integrity: sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==} engines: {node: '>=6.9.0'} - '@babel/helper-compilation-targets@7.27.2': - resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==} - engines: {node: '>=6.9.0'} - '@babel/helper-create-class-features-plugin@7.22.15': resolution: {integrity: sha512-jKkwA59IXcvSaiK2UN45kKwSC9o+KuoXsBDvHvU/7BecYIp8GQ2UwrVvFgJASUT+hBnwJx6MhvMCuMzwZZ7jlg==} engines: {node: '>=6.9.0'} @@ -1571,11 +1539,6 @@ packages: peerDependencies: '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 - '@babel/helper-define-polyfill-provider@0.6.4': - resolution: {integrity: sha512-jljfR1rGnXXNWnmQg2K3+bvhkxB51Rl32QRaOTuwwjviGrHzIbSc8+x9CpraDtbT7mfyjXObULP4w/adunNwAw==} - peerDependencies: - '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 - '@babel/helper-environment-visitor@7.22.20': resolution: {integrity: sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==} engines: {node: '>=6.9.0'} @@ -1697,10 +1660,6 @@ packages: resolution: {integrity: sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-option@7.27.1': - resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} - engines: {node: '>=6.9.0'} - '@babel/helper-wrap-function@7.22.20': resolution: {integrity: sha512-pms/UwkOpnQe/PDAEdV/d7dVCoBbB+R4FvYoHGZz+4VPcg7RtYy2KP7S2lbuWM6FCSgob5wshfGESbC/hzNXZw==} engines: {node: '>=6.9.0'} @@ -2270,12 +2229,6 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-runtime@7.27.4': - resolution: {integrity: sha512-D68nR5zxU64EUzV8i7T3R5XP0Xhrou/amNnddsRQssx6GrTLdZl1rLxyjtVZBd+v/NVX4AbTPOB5aU8thAZV1A==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-shorthand-properties@7.23.3': resolution: {integrity: sha512-ED2fgqZLmexWiN+YNFX26fx4gh5qHDhn1O2gvEhreLW2iI63Sqm4llRLCXALKrCnbN4Jy0VcMQZl/SAzqug/jg==} engines: {node: '>=6.9.0'} @@ -2943,8 +2896,8 @@ packages: '@fortawesome/fontawesome-svg-core': ~1 || ~6 react: '>=16.3' - '@freecodecamp/curriculum-helpers@4.1.0': - resolution: {integrity: sha512-RdYiOJriKy/Bk+s9b79lq+K7z+prFRDDETpgHnb1peqdpiuMxdGTOw63poXj7c4SyXv/wteENP673midxAVdzA==} + '@freecodecamp/curriculum-helpers@4.4.0': + resolution: {integrity: sha512-TPjPj/Etm/YfFFDdRJUjYketDsbQ4fizrzUYuPpbL4CtAGIrz9gFEdNMpQmgiY4MG18aUAj7eCG05zUmFDdxIQ==} engines: {pnpm: '>= 10'} '@freecodecamp/loop-protect@3.0.0': @@ -3673,6 +3626,11 @@ packages: peerDependencies: '@opentelemetry/api': ^1.8 + '@puppeteer/browsers@2.10.5': + resolution: {integrity: sha512-eifa0o+i8dERnngJwKrfp3dEq7ia5XFyoqB17S4gK8GhsQE4/P8nxOfQSE0zQHxzzLo/cmF+7+ywEQ7wK7Fb+w==} + engines: {node: '>=18'} + hasBin: true + '@puppeteer/browsers@2.2.3': resolution: {integrity: sha512-bJ0UBsk0ESOs6RFcLXOt99a3yTDcOKlzfjad+rhFwdaG1Lu/Wzq58GHYCDTlZ9z6mldf4g+NTb+TXEfe0PpnsQ==} engines: {node: '>=18'} @@ -3901,9 +3859,15 @@ packages: '@sinonjs/commons@3.0.0': resolution: {integrity: sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==} + '@sinonjs/commons@3.0.1': + resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==} + '@sinonjs/fake-timers@10.3.0': resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==} + '@sinonjs/fake-timers@14.0.0': + resolution: {integrity: sha512-QfoXRaUTjMVVn/ZbnD4LS3TPtqOkOdKIYCKldIVPnuClcwRKat6LI2mRZ2s5qiBfO6Fy03An35dSls/2/FEc0Q==} + '@smithy/abort-controller@2.1.2': resolution: {integrity: sha512-iwUxrFm/ZFCXhzhtZ6JnoJzAsqUrVfBAZUTQj8ypXGtIjwXZpKqmgYiuqrDERiydDI5gesqvsC4Rqe57GGhbVg==} engines: {node: '>=14.0.0'} @@ -4534,9 +4498,6 @@ packages: '@types/canvas-confetti@1.9.0': resolution: {integrity: sha512-aBGj/dULrimR1XDZLtG9JwxX1b4HPRF6CX9Yfwh3NvstZEm1ZL7RBnel4keCPSqs1ANRu1u2Aoz9R+VmtjYuTg==} - '@types/chai@4.3.12': - resolution: {integrity: sha512-zNKDHG/1yxm8Il6uCCVsm+dRdEsJlFoDu73X17y09bId6UwoYww+vFBsAcRzl8knM1sab3Dp1VRikFQwDOtDDw==} - '@types/chai@4.3.20': resolution: {integrity: sha512-/pC9HAB5I/xMlc5FP77qjCnI16ChlJfW0tGa0IUcFn38VJrTV6DeZ60NU5KZBtaOZqjdpwTWohz5HU1RrhiYxQ==} @@ -4585,11 +4546,8 @@ packages: '@types/enzyme-adapter-react-16@1.0.9': resolution: {integrity: sha512-z24MMxGtUL8HhXdye3tWzjp+19QTsABqLaX2oOZpxMPHRJgLfahQmOeTTrEBQd9ogW20+UmPBXD9j+XOasFHvw==} - '@types/enzyme@3.10.16': - resolution: {integrity: sha512-17uMdJjSKjvdn/MhO/G2lRNPZGvJxFpvgONrsRoS1+khtJ6UcnCwC9v3gk2UqPyAkMZb6a1VYxScc/vOgkDl9w==} - - '@types/enzyme@3.10.18': - resolution: {integrity: sha512-RaO/TyyHZvXkpzinbMTZmd/S5biU4zxkvDsn22ujC29t9FMSzq8tnn8f2MxQ2P8GVhFRG5jTAL05DXKyTtpEQQ==} + '@types/enzyme@3.10.19': + resolution: {integrity: sha512-kIfCo6/DdpgCHgmrLgPTugjzbZ46BUK8S2IP0kYo8+62LD2l1k8mSVsc+zQYNTdjDRoh2E9Spxu6F1NnEiW38Q==} '@types/eslint-scope@3.7.5': resolution: {integrity: sha512-JNvhIEyxVW6EoMIFIvj93ZOywYFatlpu9deeH6eSx6PE3WHYvHaQtmHmQeNw7aA81bYGBPPQqdtBm6b1SsQMmA==} @@ -4684,8 +4642,8 @@ packages: '@types/jest@29.5.12': resolution: {integrity: sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw==} - '@types/jquery@3.5.29': - resolution: {integrity: sha512-oXQQC9X9MOPRrMhPHHOsXqeQDnWeCDT3PelUIg/Oy8FAbzSZtFHRjc7IpbfFVmpLtJ+UOoywpRsuO5Jxjybyeg==} + '@types/jquery@3.5.32': + resolution: {integrity: sha512-b9Xbf4CkMqS02YH8zACqN1xzdxc3cO735Qe5AbSUFmyOiaWAbcpqh9Wna+Uk0vgACvoQHpWDg2rGdHkYPLmCiQ==} '@types/js-yaml@4.0.5': resolution: {integrity: sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA==} @@ -4856,6 +4814,9 @@ packages: '@types/shimmer@1.2.0': resolution: {integrity: sha512-UE7oxhQLLd9gub6JKIAhDq06T0F6FnztwMNRvYgjeQSBeMc1ZG/tA47EwfduvkuQS8apbkM/lpLpWsaCeYsXVg==} + '@types/sinonjs__fake-timers@8.1.5': + resolution: {integrity: sha512-mQkU2jY8jJEF7YHjHvsQO8+3ughTL1mcnn96igfhONmR+fUPSKIkefQYpSe8bsly2Ep7oQbn/6VG5/9/0qcArQ==} + '@types/sizzle@2.3.4': resolution: {integrity: sha512-jA2llq2zNkg8HrALI7DtWzhALcVH0l7i89yhY3iBdOz6cBPeACoFq+fkQrjHA39t1hnSFOboZ7A/AY5MMZSlag==} @@ -4901,9 +4862,6 @@ packages: '@types/validator@13.11.2': resolution: {integrity: sha512-nIKVVQKT6kGKysnNt+xLobr+pFJNssJRi2s034wgWeFBUx01fI8BeHTW2TcRp7VcFu9QCYG8IlChTuovcm0oKQ==} - '@types/validator@13.15.1': - resolution: {integrity: sha512-9gG6ogYcoI2mCMLdcO0NYI0AYrbxIjv0MDmy/5Ywo6CpWWrqYayc+mmgxRsCgtcGJm9BSbXkMsmxGah1iGHAAQ==} - '@types/vfile-message@2.0.0': resolution: {integrity: sha512-GpTIuDpb9u4zIO165fUy9+fXcULdD8HFRNli04GehoMVbeNq7D6OBnqSmg3lxZnC+UvgUhEWKxdKiwYUkGltIw==} deprecated: This is a stub types definition. vfile-message provides its own type definitions, so you do not need this installed. @@ -5429,16 +5387,14 @@ packages: resolution: {integrity: sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==} engines: {node: '>= 14'} + agent-base@7.1.3: + resolution: {integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==} + engines: {node: '>= 14'} + aggregate-error@3.1.0: resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} engines: {node: '>=8'} - airbnb-prop-types@2.16.0: - resolution: {integrity: sha512-7WHOFolP/6cS96PhKNrslCLMYAI8yB1Pp6u6XmxozQOiZbsI5ycglZr5cHhBFfuRcQQjzCMith5ZPZdYiJCxUg==} - deprecated: This package has been renamed to 'prop-types-tools' - peerDependencies: - react: ^0.14 || ^15.0.0 || ^16.0.0-alpha - ajv-formats@2.1.1: resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} peerDependencies: @@ -5626,13 +5582,6 @@ packages: resolution: {integrity: sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==} engines: {node: '>=0.10.0'} - array.prototype.filter@1.0.3: - resolution: {integrity: sha512-VizNcj/RGJiUyQBgzwxzE5oHdeuXY5hSbbmKMlphj1cy1Vl7Pn2asCGbSrru6hSQjmCzqTBPVWAF/whmEOVHbw==} - engines: {node: '>= 0.4'} - - array.prototype.find@2.2.2: - resolution: {integrity: sha512-DRumkfW97iZGOfn+lIXbkVrXL04sfYKX+EfOodo8XboR5sxPDVvOjZTF/rysusa9lmhmSOeD6Vp6RKQP+eP4Tg==} - array.prototype.findlast@1.2.5: resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==} engines: {node: '>= 0.4'} @@ -5719,6 +5668,9 @@ packages: async@1.5.2: resolution: {integrity: sha512-nSVgobk4rv61R9PUSDtYt7mPVB2olxNR5RWJcAsH676/ef11bUZwvu7+RGYrYauVdDPcO519v68wRhXQtxsV9w==} + async@3.2.6: + resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} + asynciterator.prototype@1.0.0: resolution: {integrity: sha512-wwHYEIS0Q80f5mosx3L/dfG5t5rjEa9Ft51GTaNt862EnpyGHpgz2RkZvLPp1oF5TnAiTohkEKVEu8pQPJI7Vg==} @@ -5767,6 +5719,9 @@ packages: axios@0.21.4: resolution: {integrity: sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==} + axios@1.9.0: + resolution: {integrity: sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==} + axobject-query@3.2.1: resolution: {integrity: sha512-jsyHu61e6N4Vbz/v18DHwWYKK0bSWLqn47eeDSKPB7m8tqMHF9YJ+mhIk2lVteyZrY8tnSj/jHOv4YiTCuCJgg==} @@ -5821,11 +5776,6 @@ packages: resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==} engines: {node: '>=10', npm: '>=6'} - babel-plugin-polyfill-corejs2@0.4.13: - resolution: {integrity: sha512-3sX/eOms8kd3q2KZ6DAhKPc0dgm525Gqq5NtWKZ7QYYZEv57OQ54KtblzJzH1lQF/eQxO8KjWGIK9IPUJNus5g==} - peerDependencies: - '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 - babel-plugin-polyfill-corejs2@0.4.7: resolution: {integrity: sha512-LidDk/tEGDfuHW2DWh/Hgo4rmnw3cduK6ZkOI1NPFceSK3n/yAGeOsNT7FLnSGHkXj3RHGSEVkN3FsCTY6w2CQ==} peerDependencies: @@ -5836,11 +5786,6 @@ packages: peerDependencies: '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 - babel-plugin-polyfill-corejs3@0.11.1: - resolution: {integrity: sha512-yGCqvBT4rwMczo28xkH/noxJ6MZ4nJfkVYdoDaC/utLtWrXxv27HVrzAeSbqR8SxDsp46n0YF47EbHoixy6rXQ==} - peerDependencies: - '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 - babel-plugin-polyfill-corejs3@0.8.7: resolution: {integrity: sha512-KyDvZYxAzkC0Aj2dAPyDzi2Ym15e5JKZSK+maI7NAwSqofvuFglbSsxE7wUOvTg9oFVnHMzVzBKcqEb4PJgtOA==} peerDependencies: @@ -5851,11 +5796,6 @@ packages: peerDependencies: '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 - babel-plugin-polyfill-regenerator@0.6.4: - resolution: {integrity: sha512-7gD3pRadPrbjhjLyxebmx/WrFYcuSjZ0XbdUujQMZ/fcE9oeewk2U/7PCvez84UeuK3oSjmPZ0Ch0dlupQvGzw==} - peerDependencies: - '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 - babel-plugin-preval@5.1.0: resolution: {integrity: sha512-G5R+xmo5LS41A4UyZjOjV0mp9AvkuCyUOAJ6TOv/jTZS+VKh7L7HUDRcCSOb0YCM/u0fFarh7Diz0wjY8rFNFg==} engines: {node: '>=10', npm: '>=6'} @@ -5908,18 +5848,48 @@ packages: bare-events@2.4.2: resolution: {integrity: sha512-qMKFd2qG/36aA4GwvKq8MxnPgCQAmBWmSyLWsJcbn8v03wvIPQ/hG1Ms8bPzndZxMDoHpxez5VOS+gC9Yi24/Q==} + bare-events@2.5.4: + resolution: {integrity: sha512-+gFfDkR8pj4/TrWCGUGWmJIkBwuxPS5F+a5yWjOHQt2hHvNZd5YLzadjmDUtFmMM4y429bnKLa8bYBMHcYdnQA==} + bare-fs@2.3.1: resolution: {integrity: sha512-W/Hfxc/6VehXlsgFtbB5B4xFcsCl+pAh30cYhoFyXErf6oGrwjh8SwiPAdHgpmWonKuYpZgGywN0SXt7dgsADA==} + bare-fs@4.1.5: + resolution: {integrity: sha512-1zccWBMypln0jEE05LzZt+V/8y8AQsQQqxtklqaIyg5nu6OAYFhZxPXinJTSG+kU5qyNmeLgcn9AW7eHiCHVLA==} + engines: {bare: '>=1.16.0'} + peerDependencies: + bare-buffer: '*' + peerDependenciesMeta: + bare-buffer: + optional: true + bare-os@2.4.0: resolution: {integrity: sha512-v8DTT08AS/G0F9xrhyLtepoo9EJBJ85FRSMbu1pQUlAf6A8T0tEEQGMVObWeqpjhSPXsE0VGlluFBJu2fdoTNg==} + bare-os@3.6.1: + resolution: {integrity: sha512-uaIjxokhFidJP+bmmvKSgiMzj2sV5GPHaZVAIktcxcpCyBFFWO+YlikVAdhmUo2vYFvFhOXIAlldqV29L8126g==} + engines: {bare: '>=1.14.0'} + bare-path@2.1.3: resolution: {integrity: sha512-lh/eITfU8hrj9Ru5quUp0Io1kJWIk1bTjzo7JH1P5dWmQ2EL4hFUlfI8FonAhSlgIfhn63p84CDY/x+PisgcXA==} + bare-path@3.0.0: + resolution: {integrity: sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==} + bare-stream@2.1.3: resolution: {integrity: sha512-tiDAH9H/kP+tvNO5sczyn9ZAA7utrSMobyDchsnyyXBuUe2FSQWbxhtuHB8jwpHYYevVo2UJpcmvvjrbHboUUQ==} + bare-stream@2.6.5: + resolution: {integrity: sha512-jSmxKJNJmHySi6hC42zlZnq00rga4jjxcgNZjY9N5WlOe/iOoGRtdwGsHzQv2RlH2KOYMwGUXhf2zXd32BA9RA==} + peerDependencies: + bare-buffer: '*' + bare-events: '*' + peerDependenciesMeta: + bare-buffer: + optional: true + bare-events: + optional: true + base-64@1.0.0: resolution: {integrity: sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==} @@ -6090,11 +6060,6 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true - browserslist@4.25.0: - resolution: {integrity: sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA==} - engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} - hasBin: true - bs-logger@0.2.6: resolution: {integrity: sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==} engines: {node: '>= 6'} @@ -6285,13 +6250,6 @@ packages: check-error@1.0.3: resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==} - cheerio-select@2.1.0: - resolution: {integrity: sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==} - - cheerio@1.0.0-rc.12: - resolution: {integrity: sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==} - engines: {node: '>= 6'} - chokidar@3.5.3: resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} engines: {node: '>= 8.10.0'} @@ -6312,6 +6270,11 @@ packages: peerDependencies: devtools-protocol: '*' + chromium-bidi@5.1.0: + resolution: {integrity: sha512-9MSRhWRVoRPDG0TgzkHrshFSJJNZzfY5UFqUMuksg7zL1yoZIZ3jLB0YAgHclbiAxPI86pBnwDX1tbzoiV8aFw==} + peerDependencies: + devtools-protocol: '*' + ci-info@2.0.0: resolution: {integrity: sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==} @@ -6459,6 +6422,10 @@ packages: resolution: {integrity: sha512-9HMlXtt/BNoYr8ooyjjNRdIilOTkVJXB+GhxMTtOKwk0R4j4lS4NpjuqmRxroBfnfTSHQIHQB7wryHhXarNjmQ==} engines: {node: '>=16'} + commander@12.1.0: + resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} + engines: {node: '>=18'} + commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} @@ -6596,9 +6563,6 @@ packages: core-js-compat@3.36.0: resolution: {integrity: sha512-iV9Pd/PsgjNWBXeq8XRtWVSgz2tKAfhfvBs7qxYty+RlRd+OCksaWmOnc4JKrTc1cToXL1N0s3l/vwlxPtdElw==} - core-js-compat@3.42.0: - resolution: {integrity: sha512-bQasjMfyDGyaeWKBIu33lHh9qlSR0MFE/Nmc6nMjf/iU9b3rSMdAYz1Baxrv4lPdGUsTqZudHA4jIGSJy0SWZQ==} - core-js-compat@3.9.0: resolution: {integrity: sha512-YK6fwFjCOKWwGnjFUR3c544YsnA/7DoLL0ysncuOJ4pwbriAtOpvM2bygdlcXbvQCQZ7bBU9CL4t7tGl7ETRpQ==} @@ -6616,6 +6580,10 @@ packages: resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} engines: {node: '>= 0.10'} + corser@2.0.1: + resolution: {integrity: sha512-utCYNzRSQIZNPIcGZdQc92UVJYAhtGAteCFg0yRaFm8f0P+CPtyGyHXJcGXnffjCybUCEx3FQ2G7U3/o9eIkVQ==} + engines: {node: '>= 0.4.0'} + cosmiconfig-toml-loader@1.0.0: resolution: {integrity: sha512-H/2gurFWVi7xXvCyvsWRLCMekl4tITJcX0QEsDMpzxtuxDyM59xLatYNg4s/k9AA/HdtCYfj2su8mgA0GSDLDA==} @@ -6718,9 +6686,6 @@ packages: css-select@4.3.0: resolution: {integrity: sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==} - css-select@5.1.0: - resolution: {integrity: sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==} - css-selector-parser@1.4.1: resolution: {integrity: sha512-HYPSb7y/Z7BNDCOrakL4raGO2zltZkbeXyAd6Tg9obzix6QhzxCotdBl6VT0Dv4vZfJGVz3WL/xaEI9Ly3ul0g==} @@ -6785,6 +6750,10 @@ packages: csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + cwd@0.10.0: + resolution: {integrity: sha512-YGZxdTTL9lmLkCUTpg4j0zQ7IhRB5ZmqNBbGCl3Tg6MP/d5/6sY7L5mmTjzbc6JKgVZYiqTQTNhPFsbXNGlRaA==} + engines: {node: '>=0.8'} + d@1.0.1: resolution: {integrity: sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==} @@ -7048,6 +7017,9 @@ packages: devtools-protocol@0.0.1299070: resolution: {integrity: sha512-+qtL3eX50qsJ7c+qVyagqi7AWMoQCBGNfoyJZMwm/NSXVqLYbuitrWEEIzxfUmTNy7//Xe8yhMmQ+elj3uAqSg==} + devtools-protocol@0.0.1452169: + resolution: {integrity: sha512-FOFDVMGrAUNp0dDKsAU1TorWJUx2JOU1k9xdgBKKJF3IBh/Uhl2yswG5r3TEAOrCiGY2QRp1e6LVDQrCsTKO4g==} + dezalgo@1.0.4: resolution: {integrity: sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==} @@ -7086,9 +7058,6 @@ packages: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} - discontinuous-range@1.0.0: - resolution: {integrity: sha512-c68LpLbO+7kP/b1Hr1qs8/BJ09F5khZGTxqxZuhzxpmwJKOgRFHJWIb9/KmqnqHhLdO55aOxFH/EGBvUQbL/RQ==} - doctrine@2.1.0: resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} engines: {node: '>=0.10.0'} @@ -7202,9 +7171,6 @@ packages: electron-to-chromium@1.4.685: resolution: {integrity: sha512-yDYeobbTEe4TNooEzOQO6xFqg9XnAkVy2Lod1C1B2it8u47JNLYvl9nLDWBamqUakWB8Jc1hhS1uHUNYTNQdfw==} - electron-to-chromium@1.5.161: - resolution: {integrity: sha512-hwtetwfKNZo/UlwHIVBlKZVdy7o8bIZxxKs0Mv/ROPiQQQmDgdm5a+KvKtBsxM8ZjFzTaCeLoodZ8jiBE3o9rA==} - elliptic@6.5.4: resolution: {integrity: sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==} @@ -7268,27 +7234,6 @@ packages: engines: {node: '>=4'} hasBin: true - enzyme-adapter-react-16@1.15.8: - resolution: {integrity: sha512-uYGC31eGZBp5nGsr4nKhZKvxGQjyHGjS06BJsUlWgE29/hvnpgCsT1BJvnnyny7N3GIIVyxZ4O9GChr6hy2WQA==} - peerDependencies: - enzyme: ^3.0.0 - react: ^16.0.0-0 - react-dom: ^16.0.0-0 - - enzyme-adapter-utils@1.14.2: - resolution: {integrity: sha512-1ZC++RlsYRaiOWE5NRaF5OgsMt7F5rn/VuaJIgc7eW/fmgg8eS1/Ut7EugSPPi7VMdWMLcymRnMF+mJUJ4B8KA==} - peerDependencies: - react: 0.13.x || 0.14.x || ^15.0.0-0 || ^16.0.0-0 - - enzyme-shallow-equal@1.0.5: - resolution: {integrity: sha512-i6cwm7hN630JXenxxJFBKzgLC3hMTafFQXflvzHgPmDhOBhxUWDe8AeRv1qp2/uWJ2Y8z5yLWMzmAfkTOiOCZg==} - - enzyme-shallow-equal@1.0.7: - resolution: {integrity: sha512-/um0GFqUXnpM9SvKtje+9Tjoz3f1fpBC3eXRFrNs8kpYn69JljciYP7KZTqM/YQbUY9KUjvKB4jo/q+L6WGGvg==} - - enzyme@3.11.0: - resolution: {integrity: sha512-Dw8/Gs4vRjxY6/6i9wU0V+utmQO9kvh9XLnz3LIudviOnVYDEe2ec+0k+NQoMamn1VrjKgCUOWj5jG/5M5M0Qw==} - eol@0.9.1: resolution: {integrity: sha512-Ds/TEoZjwggRoz/Q2O7SE3i4Jm66mqTDfmdHdq/7DKVk3bro9Q8h6WdXKdPqFLMoqxrDK5SVRzHVPOS6uuGtrg==} @@ -7306,9 +7251,6 @@ packages: resolution: {integrity: sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==} engines: {node: '>= 0.4'} - es-array-method-boxes-properly@1.0.0: - resolution: {integrity: sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==} - es-define-property@1.0.0: resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==} engines: {node: '>= 0.4'} @@ -7395,10 +7337,6 @@ packages: resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==} engines: {node: '>=6'} - escalade@3.2.0: - resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} - engines: {node: '>=6'} - escape-goat@2.1.1: resolution: {integrity: sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==} engines: {node: '>=8'} @@ -7764,6 +7702,14 @@ packages: resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} engines: {node: '>=6'} + expand-tilde@1.2.2: + resolution: {integrity: sha512-rtmc+cjLZqnu9dSYosX9EWmSJhTwpACgJQTfj4hgg2JjOD/6SIQalZrt4a3aQeh++oNxkazcaxrhPUj6+g5G/Q==} + engines: {node: '>=0.10.0'} + + expect-puppeteer@11.0.0: + resolution: {integrity: sha512-fgxsbOD+HqwOCMitYqEDzRoJM2fxKbCKPYfUoukK+qdZm/nC+cTOI74Au2MfmMZmF/5CgQGO4+1Ywq2GgD8zCQ==} + engines: {node: '>=18'} + expect@29.7.0: resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -7982,10 +7928,22 @@ packages: resolution: {integrity: sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==} engines: {node: '>=8'} + find-file-up@0.1.3: + resolution: {integrity: sha512-mBxmNbVyjg1LQIIpgO8hN+ybWBgDQK8qjht+EbrTCGmmPV/sc7RF1i9stPTD6bpvXZywBdrwRYxhSdJv867L6A==} + engines: {node: '>=0.10.0'} + find-my-way@9.3.0: resolution: {integrity: sha512-eRoFWQw+Yv2tuYlK2pjFS2jGXSxSppAs3hSQjfxVKxM5amECzIgYYc1FEI8ZmhSh/Ig+FrKEz43NLRKJjYCZVg==} engines: {node: '>=20'} + find-pkg@0.1.2: + resolution: {integrity: sha512-0rnQWcFwZr7eO0513HahrWafsc3CTFioEB7DRiEYCUM/70QXSY8f3mCST17HXLcPvEhzH/Ty/Bxd72ZZsr/yvw==} + engines: {node: '>=0.10.0'} + + find-process@1.4.10: + resolution: {integrity: sha512-ncYFnWEIwL7PzmrK1yZtaccN8GhethD37RzBHG6iOZoFYB4vSmLLXfeWJjeN5nMvCJMjOtBvBBF8OgxEcikiZg==} + hasBin: true + find-up@3.0.0: resolution: {integrity: sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==} engines: {node: '>=6'} @@ -8028,6 +7986,15 @@ packages: debug: optional: true + follow-redirects@1.15.9: + resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + for-each@0.3.3: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} @@ -8108,6 +8075,10 @@ packages: fs-exists-cached@1.0.0: resolution: {integrity: sha512-kSxoARUDn4F2RPXX48UXnaFKwVU7Ivd/6qpzZL29MCDmr9sTvybv4gFCp+qaI4fM9m0z9fgz/yJvi56GAz+BZg==} + fs-exists-sync@0.1.0: + resolution: {integrity: sha512-cR/vflFyPZtrN6b38ZyWxpWdhlXrzZEBawlpBQMq7033xVY7/kg0GDMBK5jg8lDYQckdJ5x/YC88lM3C7VMsLg==} + engines: {node: '>=0.10.0'} + fs-extra@10.1.0: resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} engines: {node: '>=12'} @@ -8397,10 +8368,18 @@ packages: resolution: {integrity: sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==} engines: {node: '>=10'} + global-modules@0.2.3: + resolution: {integrity: sha512-JeXuCbvYzYXcwE6acL9V2bAOeSIGl4dD+iwLY9iUx2VBJJ80R18HCn+JCwHM9Oegdfya3lEkGCdaRkSyc10hDA==} + engines: {node: '>=0.10.0'} + global-modules@2.0.0: resolution: {integrity: sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==} engines: {node: '>=6'} + global-prefix@0.1.5: + resolution: {integrity: sha512-gOPiyxcD9dJGCEArAhF4Hd0BAqvAe/JzERP7tYumE4yIkmIedPUVXcJFWbV3/p/ovIIvKjkrTk+f1UVkq7vvbw==} + engines: {node: '>=0.10.0'} + global-prefix@3.0.0: resolution: {integrity: sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==} engines: {node: '>=6'} @@ -8603,10 +8582,6 @@ packages: resolution: {integrity: sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==} engines: {node: '>=8'} - hasown@2.0.0: - resolution: {integrity: sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==} - engines: {node: '>= 0.4'} - hasown@2.0.1: resolution: {integrity: sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==} engines: {node: '>= 0.4'} @@ -8676,6 +8651,10 @@ packages: hoist-non-react-statics@3.3.2: resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} + homedir-polyfill@1.0.3: + resolution: {integrity: sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==} + engines: {node: '>=0.10.0'} + hookified@1.7.1: resolution: {integrity: sha512-OXcdHsXeOiD7OJ5zvWj8Oy/6RCdLwntAX+wUrfemNcMGn6sux4xbEHi2QXwqePYhjQ/yvxxq2MvCRirdlHscBw==} @@ -8689,9 +8668,6 @@ packages: htm@3.1.1: resolution: {integrity: sha512-983Vyg8NwUE7JkZ6NmOqpCZ+sh1bKv2iYTlUkzlWmA5JD2acKoxd4KVxbMmxX/85mtfdnDmTFoNKcg5DGAvxNQ==} - html-element-map@1.3.1: - resolution: {integrity: sha512-6XMlxrAFX4UEEGxctfFnmrFaaZFNf9i5fNuV5wZ3WWQ4FVaNP1aX1LkX9j2mfEx1NpjeE/rL3nmgEn23GdFmrg==} - html-encoding-sniffer@2.0.1: resolution: {integrity: sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==} engines: {node: '>=10'} @@ -8774,6 +8750,11 @@ packages: resolution: {integrity: sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==} engines: {node: '>=8.0.0'} + http-server@14.1.1: + resolution: {integrity: sha512-+cbxadF40UXd9T01zUHgA+rlo2Bg1Srer4+B4NwIHdaGxAGGv59nYRnGGDJ9LBk7alpS0US+J+bLLdQOOkJq4A==} + engines: {node: '>=12'} + hasBin: true + http2-wrapper@1.0.3: resolution: {integrity: sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==} engines: {node: '>=10.19.0'} @@ -8789,6 +8770,10 @@ packages: resolution: {integrity: sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==} engines: {node: '>= 14'} + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + human-signals@2.1.0: resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} engines: {node: '>=10.17.0'} @@ -9306,9 +9291,6 @@ packages: resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} engines: {node: '>= 0.4'} - is-subset@0.1.1: - resolution: {integrity: sha512-6Ybun0IkarhmEqxXCNw/C0bna6Zb/TkfUX9UbwJtK6ObwAVCxmAP308WWTHviM/zAqXk05cdhYsUsZeGQh99iw==} - is-symbol@1.0.4: resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==} engines: {node: '>= 0.4'} @@ -9370,6 +9352,10 @@ packages: is-whitespace-character@1.0.4: resolution: {integrity: sha512-SDweEzfIZM0SJV0EUga669UTKlmL0Pq8Lno0QDQsPnvECB3IM2aP0gdx5TrU0A01MAPfViaZiI2V1QMZLaKK5w==} + is-windows@0.2.0: + resolution: {integrity: sha512-n67eJYmXbniZB7RF4I/FTjK1s6RPOCTxhYrVYLRaCt3lF0mpWZPKr3T2LSZAqyjQsxR2qMmGYXXzK0YWwcPM1Q==} + engines: {node: '>=0.10.0'} + is-windows@1.0.2: resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} engines: {node: '>=0.10.0'} @@ -9626,6 +9612,9 @@ packages: joi@17.12.2: resolution: {integrity: sha512-RonXAIzCiHLc8ss3Ibuz45u28GOsWE1UpfDXLbN/9NKbL4tCJf8TWYVKsoYuuh+sAUt7fsSNpA+r2+TBA6Wjmw==} + joi@17.13.3: + resolution: {integrity: sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==} + joycon@3.1.1: resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} engines: {node: '>=10'} @@ -9895,9 +9884,6 @@ packages: lodash.deburr@4.1.0: resolution: {integrity: sha512-m/M1U1f3ddMCs6Hq2tAsYThTBDaAKFDX3dwDo97GEYzamXi9SqUpjWi/Rrj/gf3X2n8ktwgZrlP1z6E3v/IExQ==} - lodash.escape@4.0.1: - resolution: {integrity: sha512-nXEOnb/jK9g0DYMr1/Xvq6l5xMD7GDG55+GSYIYmS0G4tBk/hURD4JR9WCavs04t33WmJx9kCyp9vJ+mr4BOUw==} - lodash.every@4.6.0: resolution: {integrity: sha512-isF82d+65/sNvQ3aaQAW7LLHnnTxSN/2fm4rhYyuufLzA4VtHz6y6S5vFwe6PQVr2xdqUOyxBbTNKDpnmeu50w==} @@ -9983,6 +9969,10 @@ packages: resolution: {integrity: sha512-5UtUDQ/6edw4ofyljDNcOVJQ4c7OjDro4h3y8e1GQL5iYElYclVHJ3zeWchylvMaKnDbDilC8irOVyexnA/Slw==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + loglevel@1.9.2: + resolution: {integrity: sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==} + engines: {node: '>= 0.6.0'} + longest-streak@2.0.4: resolution: {integrity: sha512-vM6rUVCVUJJt33bnmHiZEvr7wPT78ztX7rojL+LW51bHtLh6HTjx84LA5W4+oa6aKEJA7jJu5LR6vQRBpA5DVg==} @@ -10641,9 +10631,6 @@ packages: socks: optional: true - moo@0.5.2: - resolution: {integrity: sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q==} - morgan@1.10.0: resolution: {integrity: sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==} engines: {node: '>= 0.8.0'} @@ -10727,10 +10714,6 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - nearley@2.20.1: - resolution: {integrity: sha512-+Mc8UaAebFzgV+KpI5n7DasuuQCHA89dmwm7JXw3TV43ukfNQ9DnBH3Mdb2g/I4Fdxc26pwimBWvjIw0UAILSQ==} - hasBin: true - negotiator@0.6.3: resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} engines: {node: '>= 0.6'} @@ -10806,9 +10789,6 @@ packages: node-releases@2.0.14: resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==} - node-releases@2.0.19: - resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} - nodemailer@6.9.10: resolution: {integrity: sha512-qtoKfGFhvIFW5kLfrkw2R6Nm6Ur4LNUMykyqu6n9BRKJuyQrqEGwdXXUAbwWEKt33dlWUGXb7rzmJP/p4+O+CA==} engines: {node: '>=6.0.0'} @@ -11008,6 +10988,10 @@ packages: os-browserify@0.3.0: resolution: {integrity: sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A==} + os-homedir@1.0.2: + resolution: {integrity: sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==} + engines: {node: '>=0.10.0'} + os-tmpdir@1.0.2: resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} engines: {node: '>=0.10.0'} @@ -11075,6 +11059,10 @@ packages: resolution: {integrity: sha512-BFi3vZnO9X5Qt6NRz7ZOaPja3ic0PhlsmCRYLOpN11+mWBCR6XJDqW5RF3j8jm4WGGQZtBA+bTfxYzeKW73eHg==} engines: {node: '>= 14'} + pac-proxy-agent@7.2.0: + resolution: {integrity: sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==} + engines: {node: '>= 14'} + pac-resolver@7.0.1: resolution: {integrity: sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==} engines: {node: '>= 14'} @@ -11118,6 +11106,10 @@ packages: parse-latin@4.3.0: resolution: {integrity: sha512-TYKL+K98dcAWoCw/Ac1yrPviU8Trk+/gmjQVaoWEFDZmVD4KRg6c/80xKqNNFQObo2mTONgF8trzAf2UTwKafw==} + parse-passwd@1.0.0: + resolution: {integrity: sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==} + engines: {node: '>=0.10.0'} + parse-path@4.0.4: resolution: {integrity: sha512-Z2lWUis7jlmXC1jeOG9giRO2+FsuyNipeQ43HAjqAZjwSe3SEf+q/84FGPHoso3kyntbxa4c4i77t3m6fGf8cw==} @@ -11127,9 +11119,6 @@ packages: parse-url@6.0.5: resolution: {integrity: sha512-e35AeLTSIlkw/5GFq70IN7po8fmDUjpDPY1rIK+VubRfsUvBonjQ+PBZG+vWMACnQSmNlvl524IucoDmcioMxA==} - parse5-htmlparser2-tree-adapter@7.0.0: - resolution: {integrity: sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==} - parse5@6.0.1: resolution: {integrity: sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==} @@ -11231,9 +11220,6 @@ packages: pend@1.2.0: resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} - performance-now@2.1.0: - resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==} - pg-int8@1.0.1: resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} engines: {node: '>=4.0.0'} @@ -11332,6 +11318,10 @@ packages: resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} engines: {node: '>=4'} + portfinder@1.0.37: + resolution: {integrity: sha512-yuGIEjDAYnnOex9ddMnKZEMFE0CcGo6zbfzDklkmT1m5z734ss6JMzN9rNB3+RR7iS+F10D4/BVIaXOyh8PQKw==} + engines: {node: '>= 10.12'} + posix-character-classes@0.1.1: resolution: {integrity: sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg==} engines: {node: '>=0.10.0'} @@ -11679,9 +11669,6 @@ packages: resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} engines: {node: '>= 6'} - prop-types-exact@1.2.0: - resolution: {integrity: sha512-K+Tk3Kd9V0odiXFP9fwDHUYRyvK3Nun3GVyPapSIs5OBkITAm15W0CPFD/YKTkMUAbc0b9CUwRQp2ybiBIq+eA==} - prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} @@ -11705,6 +11692,10 @@ packages: resolution: {integrity: sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ==} engines: {node: '>= 14'} + proxy-agent@6.5.0: + resolution: {integrity: sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==} + engines: {node: '>= 14'} + proxy-from-env@1.1.0: resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} @@ -11743,14 +11734,26 @@ packages: resolution: {integrity: sha512-XmqeDPVdC5/3nGJys1jbgeoZ02wP0WV1GBlPtr/ULRbGXJFuqgXMcKQ3eeNtFpBzGRbpeoCGWHge1ZWKWl0Exw==} engines: {node: '>=18'} + puppeteer-core@24.10.0: + resolution: {integrity: sha512-xX0QJRc8t19iAwRDsAOR38Q/Zx/W6WVzJCEhKCAwp2XMsaWqfNtQ+rBfQW9PlF+Op24d7c8Zlgq9YNmbnA7hdQ==} + engines: {node: '>=18'} + puppeteer@22.12.1: resolution: {integrity: sha512-1GxY8dnEnHr1SLzdSDr0FCjM6JQfAh2E2I/EqzeF8a58DbGVk9oVjj4lFdqNoVbpgFSpAbz7VER9St7S1wDpNg==} engines: {node: '>=18'} hasBin: true + puppeteer@24.10.0: + resolution: {integrity: sha512-Oua9VkGpj0S2psYu5e6mCer6W9AU9POEQh22wRgSXnLXASGH+MwLUVWgLCLeP9QPHHcJ7tySUlg4Sa9OJmaLpw==} + engines: {node: '>=18'} + hasBin: true + pure-rand@6.0.4: resolution: {integrity: sha512-LA0Y9kxMYv47GIPJy6MI84fqTd2HmYZI83W/kM/SkKfDlajnZYfmXFTxkbY+xSBPkLJxltMa9hIkmdc29eguMA==} + pyodide@0.23.3: + resolution: {integrity: sha512-t95Nu73ENjwoRhThmxvZIHMD7GXTJ3uOt/E0sJ1TxjBvoU/qPys4SV08FtZBMEnpMRKFzE4uecvx2c0qybSZhw==} + pyodide@0.23.4: resolution: {integrity: sha512-WpQUHaIXQ1xede5BMqPAjBcmopxN22s5hEsYOR8T7/UW/fkNLFUn07SaemUgthbtvedD5JGymMMj4VpD9sGMTg==} @@ -11816,16 +11819,6 @@ packages: resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} engines: {node: '>=10'} - raf@3.4.1: - resolution: {integrity: sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==} - - railroad-diagrams@1.0.0: - resolution: {integrity: sha512-cz93DjNeLY0idrCNOH6PviZGRN9GJhsdm9hpn1YCS879fj4W+x5IFJhhkRZcwVgMmFF7R82UA/7Oh+R8lLZg6A==} - - randexp@0.4.6: - resolution: {integrity: sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==} - engines: {node: '>=0.12'} - randombytes@2.1.0: resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} @@ -12029,11 +12022,6 @@ packages: react-spinkit@3.0.0: resolution: {integrity: sha512-RrfGRPjqxHQiy7quPqhjPynTu0zobgQaZu1QYBMpJJ6pCSRRRK16EZMaxdE6fLVYFRJWpX/eGATWLMoVFFT5uQ==} - react-test-renderer@16.14.0: - resolution: {integrity: sha512-L8yPjqPE5CZO6rKsKXRO/rVPiaCOy0tQQJbC+UjPNlobl5mad59lvPjwFsQHTvL03caVDIVr9x9/OSgDe6I5Eg==} - peerDependencies: - react: ^16.14.0 - react-test-renderer@17.0.2: resolution: {integrity: sha512-yaQ9cB89c17PUb0x6UfWRs7kQCorVdHlutU1boVPEsB8IDZH6n9tHxMacc3y0JoXOJUsZb/t/Mb8FUWMKaM7iQ==} peerDependencies: @@ -12157,9 +12145,6 @@ packages: resolution: {integrity: sha512-ECkTw8TmJwW60lOTR+ZkODISW6RQ8+2CL3COqtiJKLd6MmB45hN51HprHFziKLGkAuTGQhBb91V8cy+KHlaCjw==} engines: {node: '>= 0.4'} - reflect.ownkeys@0.2.0: - resolution: {integrity: sha512-qOLsBKHCpSOFKK1NUOCGC5VyeufB6lEsFe92AL2bhIJsacZS1qdoOZSbPk3MYKuT2cFlRDnulKXuuElIrMjGUg==} - regenerate-unicode-properties@10.1.1: resolution: {integrity: sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q==} engines: {node: '>=4'} @@ -12307,6 +12292,10 @@ packages: resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} engines: {node: '>=8'} + resolve-dir@0.1.1: + resolution: {integrity: sha512-QxMPqI6le2u0dCLyiGzgy92kjkkL6zO0XyvHzjdTNH3zM6e5Hz3BwG6+aEyNgiQ5Xz6PwTwgQEj3U50dByPKIA==} + engines: {node: '>=0.10.0'} + resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -12401,9 +12390,6 @@ packages: resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} engines: {node: '>= 18'} - rst-selector-parser@2.2.3: - resolution: {integrity: sha512-nDG1rZeP6oFTLN6yNDV/uiAvs1+FS/KlrEwh7+y7dpuApDBy6bI2HTBcc0/V8lv9OTqfyD34eF7au2pm8aBbhA==} - run-async@2.4.1: resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==} engines: {node: '>=0.12.0'} @@ -12418,6 +12404,9 @@ packages: rxjs@7.8.1: resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} + rxjs@7.8.2: + resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} + sade@1.8.1: resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} engines: {node: '>=6'} @@ -12498,6 +12487,9 @@ packages: resolution: {integrity: sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==} engines: {node: '>=4'} + secure-compare@3.0.1: + resolution: {integrity: sha512-AckIIV90rPDcBcglUwXPF3kg0P0qmPsPXAj6BBEENQE1p5yA1xfmDJzfi1Tappj37Pv2mVbKpL3Z1T+Nn7k1Qw==} + secure-json-parse@2.7.0: resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==} @@ -12765,6 +12757,10 @@ packages: resolution: {integrity: sha512-GNAq/eg8Udq2x0eNiFkr9gRg5bA7PXEWagQdeRX4cPSG+X/8V38v637gim9bjFptMk1QWsCTr0ttrJEiXbNnRw==} engines: {node: '>= 14'} + socks-proxy-agent@8.0.5: + resolution: {integrity: sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==} + engines: {node: '>= 14'} + socks@2.8.3: resolution: {integrity: sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==} engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} @@ -12822,6 +12818,10 @@ packages: sparse-bitfield@3.0.3: resolution: {integrity: sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==} + spawnd@11.0.0: + resolution: {integrity: sha512-brBHv9HYi8lwNvbI7X52NDZe4yAdsQwvr81b/r98LaN82LzeEnQ0L6YXBvG25zhgWRadTwB+4GsUu9NrNQcVzw==} + engines: {node: '>=18'} + spdx-correct@3.2.0: resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} @@ -12926,6 +12926,9 @@ packages: streamx@2.18.0: resolution: {integrity: sha512-LLUC1TWdjVdn1weXGcSxyTR3T4+acB6tVGXT95y0nGbca4t4o/ng1wKAGTljm9VicuCVLvRlqFYXYy5GwgM7sQ==} + streamx@2.22.1: + resolution: {integrity: sha512-znKXEBxfatz2GBNK02kRnCXjV+AA4kjZIUxeWSr3UGirZMJfTE9uiwKHobnbgxWyL/JWro8tTq+vOqAK1/qbSA==} + strict-event-emitter@0.5.1: resolution: {integrity: sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==} @@ -13187,6 +13190,9 @@ packages: tar-fs@3.0.5: resolution: {integrity: sha512-JOgGAmZyMgbqpLwct7ZV8VzkEB6pxXFBVErLtb+XCOqzc6w1xiWKI9GVd6bwk68EX7eJ4DWmfXVmq8K2ziZTGg==} + tar-fs@3.0.9: + resolution: {integrity: sha512-XF4w9Xp+ZQgifKakjZYmFdkLoSWd34VGKcsTCwlNWM7QG3ZbaxnTsaBwnjFZqHRf/rROxaR8rXnbtwdvaDI+lA==} + tar-stream@2.2.0: resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} engines: {node: '>=6'} @@ -13363,6 +13369,10 @@ packages: resolution: {integrity: sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==} engines: {node: '>=14'} + tree-kill@1.2.2: + resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} + hasBin: true + trim-trailing-lines@1.1.4: resolution: {integrity: sha512-rjUWSqnfTNrjbB9NQWfPMH/xRK1deHeGsHoVfpxJ++XeYXE0d6B1En37AHfw3jtfTU7dzMzZL2jjpe8Qb5gLIQ==} @@ -13555,6 +13565,9 @@ packages: resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} engines: {node: '>= 0.4'} + typed-query-selector@2.12.0: + resolution: {integrity: sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg==} + typedarray-to-buffer@3.1.5: resolution: {integrity: sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==} @@ -13655,6 +13668,10 @@ packages: resolution: {integrity: sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==} engines: {node: '>=0.10.0'} + union@0.5.0: + resolution: {integrity: sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==} + engines: {node: '>= 0.8.0'} + unique-string@2.0.0: resolution: {integrity: sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==} engines: {node: '>=8'} @@ -13779,12 +13796,6 @@ packages: peerDependencies: browserslist: '>= 4.21.0' - update-browserslist-db@1.1.3: - resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} - hasBin: true - peerDependencies: - browserslist: '>= 4.21.0' - update-check@1.5.2: resolution: {integrity: sha512-1TrmYLuLj/5ZovwUS7fFd1jMH3NnFDN1y1A8dboedIDt7zs/zJMo6TwwlhYKkSeEwzleeiSBV5/3c9ufAQWDaQ==} @@ -13799,6 +13810,9 @@ packages: resolution: {integrity: sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg==} deprecated: Please see https://github.com/lydell/urix#deprecated + url-join@4.0.1: + resolution: {integrity: sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==} + url-loader@4.1.1: resolution: {integrity: sha512-3BTV812+AVHHOJQO8O5MkWgZ5aosP7GnROJwvzLS9hWDj00lZ6Z0wNak423Lp9PBZN05N+Jk/N5Si8jRAlGyWA==} engines: {node: '>= 10.13.0'} @@ -13949,6 +13963,14 @@ packages: terser: optional: true + vitest-dev-server@11.0.3: + resolution: {integrity: sha512-aRZDLG+Q2T0fLWJSJpZaq3nN0HRzCcbp7ViPWpjIrdQ4ZojCuCxKHiRtV6K8oPgkFRTO/N+ST9lyxc0xtKbRsA==} + engines: {node: '>=18'} + + vitest-environment-puppeteer@11.0.3: + resolution: {integrity: sha512-Z74YeB1fSk1cF3NSLsK7Yr2q60wvtxHPTv7xj4yhZ4LTe5CZHRC6h6Rbf7gZKjXz6Z9bsM9WVbA3UyxAcPaYDw==} + engines: {node: '>=18'} + vm-browserify@1.1.2: resolution: {integrity: sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==} @@ -13968,6 +13990,11 @@ packages: resolution: {integrity: sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==} engines: {node: '>=14'} + wait-on@8.0.3: + resolution: {integrity: sha512-nQFqAFzZDeRxsu7S3C7LbuxslHhk+gnJZHyethuGKAn2IVleIbTB9I3vJSQiSR+DifUqmdzfPMoMPJfLqMF2vw==} + engines: {node: '>=12.0.0'} + hasBin: true + walker@1.0.8: resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} @@ -14235,6 +14262,18 @@ packages: utf-8-validate: optional: true + ws@8.18.2: + resolution: {integrity: sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + x-is-string@0.1.0: resolution: {integrity: sha512-GojqklwG8gpzOVEVki5KudKNoq7MbbjYZCbyWzEz7tyPA7eleiE0+ePwOWQQRb5fm86rD3S8Tc0tSFf3AOv50w==} @@ -14376,6 +14415,9 @@ packages: zod@3.23.8: resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} + zod@3.25.51: + resolution: {integrity: sha512-TQSnBldh+XSGL+opiSIq0575wvDPqu09AqWe1F7JhUMKY+M91/aGlK4MhpVNO7MgYfHcVCB1ffwAUTJzllKJqg==} + zwitch@1.0.5: resolution: {integrity: sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw==} @@ -15425,8 +15467,6 @@ snapshots: '@babel/compat-data@7.23.5': {} - '@babel/compat-data@7.27.3': {} - '@babel/core@7.10.5': dependencies: '@babel/code-frame': 7.22.13 @@ -15438,7 +15478,7 @@ snapshots: '@babel/traverse': 7.27.4 '@babel/types': 7.23.9 convert-source-map: 1.9.0 - debug: 4.4.1 + debug: 4.3.4(supports-color@8.1.1) gensync: 1.0.0-beta.2 json5: 2.2.3 lodash: 4.17.21 @@ -15554,14 +15594,6 @@ snapshots: lru-cache: 5.1.1 semver: 6.3.1 - '@babel/helper-compilation-targets@7.27.2': - dependencies: - '@babel/compat-data': 7.27.3 - '@babel/helper-validator-option': 7.27.1 - browserslist: 4.25.0 - lru-cache: 5.1.1 - semver: 6.3.1 - '@babel/helper-create-class-features-plugin@7.22.15(@babel/core@7.23.0)': dependencies: '@babel/core': 7.23.0 @@ -15646,28 +15678,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/helper-define-polyfill-provider@0.6.4(@babel/core@7.23.0)': - dependencies: - '@babel/core': 7.23.0 - '@babel/helper-compilation-targets': 7.27.2 - '@babel/helper-plugin-utils': 7.27.1 - debug: 4.4.1 - lodash.debounce: 4.0.8 - resolve: 1.22.10 - transitivePeerDependencies: - - supports-color - - '@babel/helper-define-polyfill-provider@0.6.4(@babel/core@7.23.7)': - dependencies: - '@babel/core': 7.23.7 - '@babel/helper-compilation-targets': 7.27.2 - '@babel/helper-plugin-utils': 7.27.1 - debug: 4.4.1 - lodash.debounce: 4.0.8 - resolve: 1.22.10 - transitivePeerDependencies: - - supports-color - '@babel/helper-environment-visitor@7.22.20': {} '@babel/helper-function-name@7.23.0': @@ -15808,8 +15818,6 @@ snapshots: '@babel/helper-validator-option@7.25.9': {} - '@babel/helper-validator-option@7.27.1': {} - '@babel/helper-wrap-function@7.22.20': dependencies: '@babel/helper-function-name': 7.23.0 @@ -16823,6 +16831,18 @@ snapshots: '@babel/core': 7.23.7 '@babel/helper-plugin-utils': 7.22.5 + '@babel/plugin-transform-runtime@7.23.7(@babel/core@7.23.0)': + dependencies: + '@babel/core': 7.23.0 + '@babel/helper-module-imports': 7.22.15 + '@babel/helper-plugin-utils': 7.22.5 + babel-plugin-polyfill-corejs2: 0.4.7(@babel/core@7.23.0) + babel-plugin-polyfill-corejs3: 0.8.7(@babel/core@7.23.0) + babel-plugin-polyfill-regenerator: 0.5.5(@babel/core@7.23.0) + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + '@babel/plugin-transform-runtime@7.23.7(@babel/core@7.23.7)': dependencies: '@babel/core': 7.23.7 @@ -16835,30 +16855,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/plugin-transform-runtime@7.27.4(@babel/core@7.23.0)': - dependencies: - '@babel/core': 7.23.0 - '@babel/helper-module-imports': 7.27.1 - '@babel/helper-plugin-utils': 7.27.1 - babel-plugin-polyfill-corejs2: 0.4.13(@babel/core@7.23.0) - babel-plugin-polyfill-corejs3: 0.11.1(@babel/core@7.23.0) - babel-plugin-polyfill-regenerator: 0.6.4(@babel/core@7.23.0) - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-runtime@7.27.4(@babel/core@7.23.7)': - dependencies: - '@babel/core': 7.23.7 - '@babel/helper-module-imports': 7.27.1 - '@babel/helper-plugin-utils': 7.27.1 - babel-plugin-polyfill-corejs2: 0.4.13(@babel/core@7.23.7) - babel-plugin-polyfill-corejs3: 0.11.1(@babel/core@7.23.7) - babel-plugin-polyfill-regenerator: 0.6.4(@babel/core@7.23.7) - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - '@babel/plugin-transform-shorthand-properties@7.23.3(@babel/core@7.23.0)': dependencies: '@babel/core': 7.23.0 @@ -17282,7 +17278,7 @@ snapshots: '@babel/parser': 7.26.9 '@babel/template': 7.26.9 '@babel/types': 7.26.9 - debug: 4.4.1 + debug: 4.3.4(supports-color@8.1.1) globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -17294,7 +17290,7 @@ snapshots: '@babel/parser': 7.27.4 '@babel/template': 7.27.2 '@babel/types': 7.27.3 - debug: 4.4.1 + debug: 4.3.4(supports-color@8.1.1) globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -17763,9 +17759,31 @@ snapshots: prop-types: 15.8.1 react: 17.0.2 - '@freecodecamp/curriculum-helpers@4.1.0': + '@freecodecamp/curriculum-helpers@4.4.0(typescript@5.8.2)': dependencies: + '@sinonjs/fake-timers': 14.0.0 + '@types/enzyme': 3.10.19 + '@types/enzyme-adapter-react-16': 1.0.9 + '@types/jquery': 3.5.32 + '@types/sinonjs__fake-timers': 8.1.5 browserify: 17.0.0 + chai: 4.4.1 + expect-puppeteer: 11.0.0 + http-server: 14.1.1 + jquery: 3.7.1 + process: 0.11.10 + puppeteer: 24.10.0(typescript@5.8.2) + pyodide: 0.23.3 + util: 0.12.5 + vitest-environment-puppeteer: 11.0.3(typescript@5.8.2) + transitivePeerDependencies: + - bare-buffer + - bufferutil + - debug + - encoding + - supports-color + - typescript + - utf-8-validate '@freecodecamp/loop-protect@3.0.0': {} @@ -18730,6 +18748,19 @@ snapshots: transitivePeerDependencies: - supports-color + '@puppeteer/browsers@2.10.5': + dependencies: + debug: 4.4.1 + extract-zip: 2.0.1 + progress: 2.0.3 + proxy-agent: 6.5.0 + semver: 7.7.2 + tar-fs: 3.0.9 + yargs: 17.7.2 + transitivePeerDependencies: + - bare-buffer + - supports-color + '@puppeteer/browsers@2.2.3': dependencies: debug: 4.3.4(supports-color@8.1.1) @@ -18976,10 +19007,18 @@ snapshots: dependencies: type-detect: 4.0.8 + '@sinonjs/commons@3.0.1': + dependencies: + type-detect: 4.0.8 + '@sinonjs/fake-timers@10.3.0': dependencies: '@sinonjs/commons': 3.0.0 + '@sinonjs/fake-timers@14.0.0': + dependencies: + '@sinonjs/commons': 3.0.1 + '@smithy/abort-controller@2.1.2': dependencies: '@smithy/types': 2.10.0 @@ -19996,8 +20035,6 @@ snapshots: '@types/canvas-confetti@1.9.0': {} - '@types/chai@4.3.12': {} - '@types/chai@4.3.20': {} '@types/cheerio@0.22.35': @@ -20049,14 +20086,9 @@ snapshots: '@types/enzyme-adapter-react-16@1.0.9': dependencies: - '@types/enzyme': 3.10.18 + '@types/enzyme': 3.10.19 - '@types/enzyme@3.10.16': - dependencies: - '@types/cheerio': 0.22.35 - '@types/react': 16.14.56 - - '@types/enzyme@3.10.18': + '@types/enzyme@3.10.19': dependencies: '@types/cheerio': 0.22.35 '@types/react': 16.14.56 @@ -20186,7 +20218,7 @@ snapshots: expect: 29.7.0 pretty-format: 29.7.0 - '@types/jquery@3.5.29': + '@types/jquery@3.5.32': dependencies: '@types/sizzle': 2.3.4 @@ -20380,6 +20412,8 @@ snapshots: '@types/shimmer@1.2.0': {} + '@types/sinonjs__fake-timers@8.1.5': {} + '@types/sizzle@2.3.4': {} '@types/stack-utils@2.0.1': {} @@ -20423,8 +20457,6 @@ snapshots: '@types/validator@13.11.2': {} - '@types/validator@13.15.1': {} - '@types/vfile-message@2.0.0': dependencies: vfile-message: 4.0.2 @@ -20579,7 +20611,7 @@ snapshots: dependencies: '@typescript-eslint/tsconfig-utils': 8.33.0(typescript@5.7.3) '@typescript-eslint/types': 8.33.0 - debug: 4.4.1 + debug: 4.3.4(supports-color@8.1.1) transitivePeerDependencies: - supports-color - typescript @@ -20652,7 +20684,7 @@ snapshots: dependencies: '@typescript-eslint/types': 3.10.1 '@typescript-eslint/visitor-keys': 3.10.1 - debug: 4.4.1 + debug: 4.3.4(supports-color@8.1.1) glob: 7.2.3 is-glob: 4.0.3 lodash: 4.17.21 @@ -21069,24 +21101,13 @@ snapshots: transitivePeerDependencies: - supports-color + agent-base@7.1.3: {} + aggregate-error@3.1.0: dependencies: clean-stack: 2.2.0 indent-string: 4.0.0 - airbnb-prop-types@2.16.0(react@16.14.0): - dependencies: - array.prototype.find: 2.2.2 - function.prototype.name: 1.1.6 - is-regex: 1.1.4 - object-is: 1.1.5 - object.assign: 4.1.5 - object.entries: 1.1.7 - prop-types: 15.8.1 - prop-types-exact: 1.2.0 - react: 16.14.0 - react-is: 16.13.1 - ajv-formats@2.1.1(ajv@8.12.0): optionalDependencies: ajv: 8.12.0 @@ -21257,21 +21278,6 @@ snapshots: array-unique@0.3.2: {} - array.prototype.filter@1.0.3: - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - es-abstract: 1.22.2 - es-array-method-boxes-properly: 1.0.0 - is-string: 1.0.7 - - array.prototype.find@2.2.2: - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - es-abstract: 1.22.2 - es-shim-unscopables: 1.0.0 - array.prototype.findlast@1.2.5: dependencies: call-bind: 1.0.7 @@ -21400,6 +21406,8 @@ snapshots: async@1.5.2: {} + async@3.2.6: {} + asynciterator.prototype@1.0.0: dependencies: has-symbols: 1.0.3 @@ -21452,6 +21460,14 @@ snapshots: transitivePeerDependencies: - debug + axios@1.9.0: + dependencies: + follow-redirects: 1.15.9 + form-data: 4.0.2 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + axobject-query@3.2.1: dependencies: dequal: 2.0.3 @@ -21540,7 +21556,7 @@ snapshots: dependencies: '@babel/runtime': 7.23.9 cosmiconfig: 6.0.0 - resolve: 1.22.8 + resolve: 1.22.10 babel-plugin-macros@3.1.0: dependencies: @@ -21548,20 +21564,11 @@ snapshots: cosmiconfig: 7.1.0 resolve: 1.22.8 - babel-plugin-polyfill-corejs2@0.4.13(@babel/core@7.23.0): + babel-plugin-polyfill-corejs2@0.4.7(@babel/core@7.23.0): dependencies: - '@babel/compat-data': 7.27.3 + '@babel/compat-data': 7.23.5 '@babel/core': 7.23.0 - '@babel/helper-define-polyfill-provider': 0.6.4(@babel/core@7.23.0) - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - - babel-plugin-polyfill-corejs2@0.4.13(@babel/core@7.23.7): - dependencies: - '@babel/compat-data': 7.27.3 - '@babel/core': 7.23.7 - '@babel/helper-define-polyfill-provider': 0.6.4(@babel/core@7.23.7) + '@babel/helper-define-polyfill-provider': 0.4.4(@babel/core@7.23.0) semver: 6.3.1 transitivePeerDependencies: - supports-color @@ -21593,22 +21600,6 @@ snapshots: transitivePeerDependencies: - supports-color - babel-plugin-polyfill-corejs3@0.11.1(@babel/core@7.23.0): - dependencies: - '@babel/core': 7.23.0 - '@babel/helper-define-polyfill-provider': 0.6.4(@babel/core@7.23.0) - core-js-compat: 3.42.0 - transitivePeerDependencies: - - supports-color - - babel-plugin-polyfill-corejs3@0.11.1(@babel/core@7.23.7): - dependencies: - '@babel/core': 7.23.7 - '@babel/helper-define-polyfill-provider': 0.6.4(@babel/core@7.23.7) - core-js-compat: 3.42.0 - transitivePeerDependencies: - - supports-color - babel-plugin-polyfill-corejs3@0.8.7(@babel/core@7.23.0): dependencies: '@babel/core': 7.23.0 @@ -21639,20 +21630,6 @@ snapshots: transitivePeerDependencies: - supports-color - babel-plugin-polyfill-regenerator@0.6.4(@babel/core@7.23.0): - dependencies: - '@babel/core': 7.23.0 - '@babel/helper-define-polyfill-provider': 0.6.4(@babel/core@7.23.0) - transitivePeerDependencies: - - supports-color - - babel-plugin-polyfill-regenerator@0.6.4(@babel/core@7.23.7): - dependencies: - '@babel/core': 7.23.7 - '@babel/helper-define-polyfill-provider': 0.6.4(@babel/core@7.23.7) - transitivePeerDependencies: - - supports-color - babel-plugin-preval@5.1.0: dependencies: '@babel/runtime': 7.23.9 @@ -21710,7 +21687,7 @@ snapshots: '@babel/plugin-proposal-optional-chaining': 7.17.12(@babel/core@7.23.0) '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.23.0) '@babel/plugin-transform-classes': 7.23.3(@babel/core@7.23.0) - '@babel/plugin-transform-runtime': 7.27.4(@babel/core@7.23.0) + '@babel/plugin-transform-runtime': 7.23.7(@babel/core@7.23.0) '@babel/plugin-transform-spread': 7.23.3(@babel/core@7.23.0) '@babel/preset-env': 7.23.7(@babel/core@7.23.0) '@babel/preset-react': 7.26.3(@babel/core@7.23.0) @@ -21741,6 +21718,9 @@ snapshots: bare-events@2.4.2: optional: true + bare-events@2.5.4: + optional: true + bare-fs@2.3.1: dependencies: bare-events: 2.4.2 @@ -21748,19 +21728,41 @@ snapshots: bare-stream: 2.1.3 optional: true + bare-fs@4.1.5: + dependencies: + bare-events: 2.5.4 + bare-path: 3.0.0 + bare-stream: 2.6.5(bare-events@2.5.4) + optional: true + bare-os@2.4.0: optional: true + bare-os@3.6.1: + optional: true + bare-path@2.1.3: dependencies: bare-os: 2.4.0 optional: true + bare-path@3.0.0: + dependencies: + bare-os: 3.6.1 + optional: true + bare-stream@2.1.3: dependencies: streamx: 2.18.0 optional: true + bare-stream@2.6.5(bare-events@2.5.4): + dependencies: + streamx: 2.22.1 + optionalDependencies: + bare-events: 2.5.4 + optional: true + base-64@1.0.0: {} base64-arraybuffer@0.1.4: {} @@ -21942,7 +21944,7 @@ snapshots: browser-resolve@2.0.0: dependencies: - resolve: 1.22.8 + resolve: 1.22.10 browser-stdout@1.3.1: {} @@ -22024,7 +22026,7 @@ snapshots: querystring-es3: 0.2.1 read-only-stream: 2.0.0 readable-stream: 2.3.8 - resolve: 1.22.8 + resolve: 1.22.10 shasum-object: 1.0.0 shell-quote: 1.8.1 stream-browserify: 3.0.0 @@ -22068,13 +22070,6 @@ snapshots: node-releases: 2.0.14 update-browserslist-db: 1.0.13(browserslist@4.23.0) - browserslist@4.25.0: - dependencies: - caniuse-lite: 1.0.30001720 - electron-to-chromium: 1.5.161 - node-releases: 2.0.19 - update-browserslist-db: 1.1.3(browserslist@4.25.0) - bs-logger@0.2.6: dependencies: fast-json-stable-stringify: 2.1.0 @@ -22283,25 +22278,6 @@ snapshots: dependencies: get-func-name: 2.0.2 - cheerio-select@2.1.0: - dependencies: - boolbase: 1.0.0 - css-select: 5.1.0 - css-what: 6.1.0 - domelementtype: 2.3.0 - domhandler: 5.0.3 - domutils: 3.1.0 - - cheerio@1.0.0-rc.12: - dependencies: - cheerio-select: 2.1.0 - dom-serializer: 2.0.0 - domhandler: 5.0.3 - domutils: 3.1.0 - htmlparser2: 8.0.2 - parse5: 7.1.2 - parse5-htmlparser2-tree-adapter: 7.0.0 - chokidar@3.5.3: dependencies: anymatch: 3.1.3 @@ -22337,6 +22313,12 @@ snapshots: urlpattern-polyfill: 10.0.0 zod: 3.23.8 + chromium-bidi@5.1.0(devtools-protocol@0.0.1452169): + dependencies: + devtools-protocol: 0.0.1452169 + mitt: 3.0.1 + zod: 3.25.51 + ci-info@2.0.0: {} ci-info@3.8.0: {} @@ -22478,6 +22460,8 @@ snapshots: commander@11.0.0: {} + commander@12.1.0: {} + commander@2.20.3: {} commander@7.2.0: {} @@ -22622,10 +22606,6 @@ snapshots: dependencies: browserslist: 4.23.0 - core-js-compat@3.42.0: - dependencies: - browserslist: 4.25.0 - core-js-compat@3.9.0: dependencies: browserslist: 4.23.0 @@ -22642,6 +22622,8 @@ snapshots: object-assign: 4.1.1 vary: 1.1.2 + corser@2.0.1: {} + cosmiconfig-toml-loader@1.0.0: dependencies: '@iarna/toml': 2.2.5 @@ -22827,14 +22809,6 @@ snapshots: domutils: 2.8.0 nth-check: 2.1.1 - css-select@5.1.0: - dependencies: - boolbase: 1.0.0 - css-what: 6.1.0 - domhandler: 5.0.3 - domutils: 3.1.0 - nth-check: 2.1.1 - css-selector-parser@1.4.1: {} css-tree@1.1.3: @@ -22915,6 +22889,11 @@ snapshots: csstype@3.1.3: {} + cwd@0.10.0: + dependencies: + find-pkg: 0.1.2 + fs-exists-sync: 0.1.0 + d@1.0.1: dependencies: es5-ext: 0.10.62 @@ -23188,6 +23167,8 @@ snapshots: devtools-protocol@0.0.1299070: {} + devtools-protocol@0.0.1452169: {} + dezalgo@1.0.4: dependencies: asap: 2.0.6 @@ -23220,8 +23201,6 @@ snapshots: dependencies: path-type: 4.0.0 - discontinuous-range@1.0.0: {} - doctrine@2.1.0: dependencies: esutils: 2.0.3 @@ -23336,8 +23315,6 @@ snapshots: electron-to-chromium@1.4.685: {} - electron-to-chromium@1.5.161: {} - elliptic@6.5.4: dependencies: bn.js: 4.12.0 @@ -23417,67 +23394,6 @@ snapshots: envinfo@7.10.0: {} - enzyme-adapter-react-16@1.15.8(enzyme@3.11.0)(react-dom@16.14.0(react@16.14.0))(react@16.14.0): - dependencies: - enzyme: 3.11.0 - enzyme-adapter-utils: 1.14.2(react@16.14.0) - enzyme-shallow-equal: 1.0.7 - hasown: 2.0.0 - object.assign: 4.1.5 - object.values: 1.1.7 - prop-types: 15.8.1 - react: 16.14.0 - react-dom: 16.14.0(react@16.14.0) - react-is: 16.13.1 - react-test-renderer: 16.14.0(react@16.14.0) - semver: 5.7.2 - - enzyme-adapter-utils@1.14.2(react@16.14.0): - dependencies: - airbnb-prop-types: 2.16.0(react@16.14.0) - function.prototype.name: 1.1.6 - hasown: 2.0.1 - object.assign: 4.1.5 - object.fromentries: 2.0.7 - prop-types: 15.8.1 - react: 16.14.0 - semver: 6.3.1 - - enzyme-shallow-equal@1.0.5: - dependencies: - has: 1.0.3 - object-is: 1.1.5 - - enzyme-shallow-equal@1.0.7: - dependencies: - hasown: 2.0.1 - object-is: 1.1.5 - - enzyme@3.11.0: - dependencies: - array.prototype.flat: 1.3.2 - cheerio: 1.0.0-rc.12 - enzyme-shallow-equal: 1.0.5 - function.prototype.name: 1.1.6 - has: 1.0.3 - html-element-map: 1.3.1 - is-boolean-object: 1.1.2 - is-callable: 1.2.7 - is-number-object: 1.0.7 - is-regex: 1.1.4 - is-string: 1.0.7 - is-subset: 0.1.1 - lodash.escape: 4.0.1 - lodash.isequal: 4.5.0 - object-inspect: 1.12.3 - object-is: 1.1.5 - object.assign: 4.1.5 - object.entries: 1.1.7 - object.values: 1.1.7 - raf: 3.4.1 - rst-selector-parser: 2.2.3 - string.prototype.trim: 1.2.8 - eol@0.9.1: {} error-ex@1.3.2: @@ -23584,8 +23500,6 @@ snapshots: unbox-primitive: 1.1.0 which-typed-array: 1.1.19 - es-array-method-boxes-properly@1.0.0: {} - es-define-property@1.0.0: dependencies: get-intrinsic: 1.2.4 @@ -23763,8 +23677,6 @@ snapshots: escalade@3.1.2: {} - escalade@3.2.0: {} - escape-goat@2.1.1: {} escape-html@1.0.3: {} @@ -24302,6 +24214,12 @@ snapshots: expand-template@2.0.3: {} + expand-tilde@1.2.2: + dependencies: + os-homedir: 1.0.2 + + expect-puppeteer@11.0.0: {} + expect@29.7.0: dependencies: '@jest/expect-utils': 29.7.0 @@ -24654,12 +24572,27 @@ snapshots: make-dir: 3.1.0 pkg-dir: 4.2.0 + find-file-up@0.1.3: + dependencies: + fs-exists-sync: 0.1.0 + resolve-dir: 0.1.1 + find-my-way@9.3.0: dependencies: fast-deep-equal: 3.1.3 fast-querystring: 1.1.2 safe-regex2: 5.0.0 + find-pkg@0.1.2: + dependencies: + find-file-up: 0.1.3 + + find-process@1.4.10: + dependencies: + chalk: 4.1.2 + commander: 12.1.0 + loglevel: 1.9.2 + find-up@3.0.0: dependencies: locate-path: 3.0.0 @@ -24705,6 +24638,8 @@ snapshots: optionalDependencies: debug: 4.4.1 + follow-redirects@1.15.9: {} + for-each@0.3.3: dependencies: is-callable: 1.2.7 @@ -24784,6 +24719,8 @@ snapshots: fs-exists-cached@1.0.0: {} + fs-exists-sync@0.1.0: {} + fs-extra@10.1.0: dependencies: graceful-fs: 4.2.11 @@ -25482,10 +25419,22 @@ snapshots: dependencies: ini: 2.0.0 + global-modules@0.2.3: + dependencies: + global-prefix: 0.1.5 + is-windows: 0.2.0 + global-modules@2.0.0: dependencies: global-prefix: 3.0.0 + global-prefix@0.1.5: + dependencies: + homedir-polyfill: 1.0.3 + ini: 1.3.8 + is-windows: 0.2.0 + which: 1.3.1 + global-prefix@3.0.0: dependencies: ini: 1.3.8 @@ -25733,10 +25682,6 @@ snapshots: is-stream: 2.0.1 type-fest: 0.8.1 - hasown@2.0.0: - dependencies: - function-bind: 1.1.2 - hasown@2.0.1: dependencies: function-bind: 1.1.2 @@ -25850,6 +25795,10 @@ snapshots: dependencies: react-is: 16.13.1 + homedir-polyfill@1.0.3: + dependencies: + parse-passwd: 1.0.0 + hookified@1.7.1: {} hosted-git-info@2.8.9: {} @@ -25860,11 +25809,6 @@ snapshots: htm@3.1.1: {} - html-element-map@1.3.1: - dependencies: - array.prototype.filter: 1.0.3 - call-bind: 1.0.7 - html-encoding-sniffer@2.0.1: dependencies: whatwg-encoding: 1.0.5 @@ -25969,6 +25913,14 @@ snapshots: transitivePeerDependencies: - supports-color + http-proxy@1.18.1: + dependencies: + eventemitter3: 4.0.7 + follow-redirects: 1.15.3(debug@4.4.1) + requires-port: 1.0.0 + transitivePeerDependencies: + - debug + http-proxy@1.18.1(debug@3.2.7): dependencies: eventemitter3: 4.0.7 @@ -25977,6 +25929,25 @@ snapshots: transitivePeerDependencies: - debug + http-server@14.1.1: + dependencies: + basic-auth: 2.0.1 + chalk: 4.1.2 + corser: 2.0.1 + he: 1.2.0 + html-encoding-sniffer: 3.0.0 + http-proxy: 1.18.1 + mime: 1.6.0 + minimist: 1.2.8 + opener: 1.5.2 + portfinder: 1.0.37 + secure-compare: 3.0.1 + union: 0.5.0 + url-join: 4.0.1 + transitivePeerDependencies: + - debug + - supports-color + http2-wrapper@1.0.3: dependencies: quick-lru: 5.1.1 @@ -25998,6 +25969,13 @@ snapshots: transitivePeerDependencies: - supports-color + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.3 + debug: 4.3.4(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + human-signals@2.1.0: {} human-signals@4.3.1: {} @@ -26488,8 +26466,6 @@ snapshots: call-bound: 1.0.4 has-tostringtag: 1.0.2 - is-subset@0.1.1: {} - is-symbol@1.0.4: dependencies: has-symbols: 1.0.3 @@ -26550,6 +26526,8 @@ snapshots: is-whitespace-character@1.0.4: {} + is-windows@0.2.0: {} + is-windows@1.0.2: {} is-word-character@1.0.4: {} @@ -26608,7 +26586,7 @@ snapshots: istanbul-lib-source-maps@4.0.1: dependencies: - debug: 4.4.1 + debug: 4.3.4(supports-color@8.1.1) istanbul-lib-coverage: 3.2.0 source-map: 0.6.1 transitivePeerDependencies: @@ -27085,6 +27063,14 @@ snapshots: '@sideway/formula': 3.0.1 '@sideway/pinpoint': 2.0.0 + joi@17.13.3: + dependencies: + '@hapi/hoek': 9.3.0 + '@hapi/topo': 5.1.0 + '@sideway/address': 4.1.5 + '@sideway/formula': 3.0.1 + '@sideway/pinpoint': 2.0.0 + joycon@3.1.1: {} jquery@3.7.1: {} @@ -27417,8 +27403,6 @@ snapshots: lodash.deburr@4.1.0: {} - lodash.escape@4.0.1: {} - lodash.every@4.6.0: {} lodash.flatten@4.4.0: {} @@ -27482,6 +27466,8 @@ snapshots: strip-ansi: 7.1.0 wrap-ansi: 8.1.0 + loglevel@1.9.2: {} + longest-streak@2.0.4: {} longest-streak@3.1.0: {} @@ -28225,7 +28211,7 @@ snapshots: micromark@3.2.0: dependencies: '@types/debug': 4.1.9 - debug: 4.4.1 + debug: 4.3.4(supports-color@8.1.1) decode-named-character-reference: 1.0.2 micromark-core-commonmark: 1.1.0 micromark-factory-space: 1.1.0 @@ -28431,7 +28417,7 @@ snapshots: inherits: 2.0.4 parents: 1.0.1 readable-stream: 2.3.8 - resolve: 1.22.8 + resolve: 1.22.10 stream-combiner2: 1.1.1 subarg: 1.0.0 through2: 2.0.5 @@ -28463,8 +28449,6 @@ snapshots: '@aws-sdk/credential-providers': 3.521.0 socks: 2.8.3 - moo@0.5.2: {} - morgan@1.10.0: dependencies: basic-auth: 2.0.1 @@ -28573,13 +28557,6 @@ snapshots: natural-compare@1.4.0: {} - nearley@2.20.1: - dependencies: - commander: 2.20.3 - moo: 0.5.2 - railroad-diagrams: 1.0.0 - randexp: 0.4.6 - negotiator@0.6.3: {} negotiator@1.0.0: {} @@ -28628,8 +28605,6 @@ snapshots: node-releases@2.0.14: {} - node-releases@2.0.19: {} - nodemailer@6.9.10: {} nopt@1.0.10: @@ -28858,6 +28833,8 @@ snapshots: os-browserify@0.3.0: {} + os-homedir@1.0.2: {} + os-tmpdir@1.0.2: {} outvariant@1.4.3: {} @@ -28919,6 +28896,19 @@ snapshots: transitivePeerDependencies: - supports-color + pac-proxy-agent@7.2.0: + dependencies: + '@tootallnate/quickjs-emscripten': 0.23.0 + agent-base: 7.1.3 + debug: 4.3.4(supports-color@8.1.1) + get-uri: 6.0.3 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + pac-resolver: 7.0.1 + socks-proxy-agent: 8.0.5 + transitivePeerDependencies: + - supports-color + pac-resolver@7.0.1: dependencies: degenerator: 5.0.1 @@ -29000,6 +28990,8 @@ snapshots: unist-util-modify-children: 2.0.0 unist-util-visit-children: 1.1.4 + parse-passwd@1.0.0: {} + parse-path@4.0.4: dependencies: is-ssh: 1.4.0 @@ -29016,11 +29008,6 @@ snapshots: parse-path: 4.0.4 protocols: 1.4.8 - parse5-htmlparser2-tree-adapter@7.0.0: - dependencies: - domhandler: 5.0.3 - parse5: 7.1.2 - parse5@6.0.1: {} parse5@7.1.2: @@ -29098,8 +29085,6 @@ snapshots: pend@1.2.0: {} - performance-now@2.1.0: {} - pg-int8@1.0.1: {} pg-protocol@1.8.0: {} @@ -29200,6 +29185,13 @@ snapshots: pluralize@8.0.0: {} + portfinder@1.0.37: + dependencies: + async: 3.2.6 + debug: 4.4.1 + transitivePeerDependencies: + - supports-color + posix-character-classes@0.1.1: {} possible-typed-array-names@1.1.0: {} @@ -29526,12 +29518,6 @@ snapshots: kleur: 3.0.3 sisteransi: 1.0.5 - prop-types-exact@1.2.0: - dependencies: - has: 1.0.3 - object.assign: 4.1.5 - reflect.ownkeys: 0.2.0 - prop-types@15.8.1: dependencies: loose-envify: 1.4.0 @@ -29570,6 +29556,19 @@ snapshots: transitivePeerDependencies: - supports-color + proxy-agent@6.5.0: + dependencies: + agent-base: 7.1.3 + debug: 4.3.4(supports-color@8.1.1) + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + lru-cache: 7.18.3 + pac-proxy-agent: 7.2.0 + proxy-from-env: 1.1.0 + socks-proxy-agent: 8.0.5 + transitivePeerDependencies: + - supports-color + proxy-from-env@1.1.0: {} proxy-middleware@0.15.0: {} @@ -29614,6 +29613,20 @@ snapshots: - supports-color - utf-8-validate + puppeteer-core@24.10.0: + dependencies: + '@puppeteer/browsers': 2.10.5 + chromium-bidi: 5.1.0(devtools-protocol@0.0.1452169) + debug: 4.4.1 + devtools-protocol: 0.0.1452169 + typed-query-selector: 2.12.0 + ws: 8.18.2 + transitivePeerDependencies: + - bare-buffer + - bufferutil + - supports-color + - utf-8-validate + puppeteer@22.12.1(typescript@5.8.2): dependencies: '@puppeteer/browsers': 2.2.3 @@ -29626,8 +29639,33 @@ snapshots: - typescript - utf-8-validate + puppeteer@24.10.0(typescript@5.8.2): + dependencies: + '@puppeteer/browsers': 2.10.5 + chromium-bidi: 5.1.0(devtools-protocol@0.0.1452169) + cosmiconfig: 9.0.0(typescript@5.8.2) + devtools-protocol: 0.0.1452169 + puppeteer-core: 24.10.0 + typed-query-selector: 2.12.0 + transitivePeerDependencies: + - bare-buffer + - bufferutil + - supports-color + - typescript + - utf-8-validate + pure-rand@6.0.4: {} + pyodide@0.23.3: + dependencies: + base-64: 1.0.0 + node-fetch: 2.7.0 + ws: 8.18.0 + transitivePeerDependencies: + - bufferutil + - encoding + - utf-8-validate + pyodide@0.23.4: dependencies: base-64: 1.0.0 @@ -29692,17 +29730,6 @@ snapshots: quick-lru@5.1.1: {} - raf@3.4.1: - dependencies: - performance-now: 2.1.0 - - railroad-diagrams@1.0.0: {} - - randexp@0.4.6: - dependencies: - discontinuous-range: 1.0.0 - ret: 0.1.15 - randombytes@2.1.0: dependencies: safe-buffer: 5.2.1 @@ -29953,14 +29980,6 @@ snapshots: object-assign: 4.1.1 prop-types: 15.8.1 - react-test-renderer@16.14.0(react@16.14.0): - dependencies: - object-assign: 4.1.1 - prop-types: 15.8.1 - react: 16.14.0 - react-is: 16.13.1 - scheduler: 0.19.1 - react-test-renderer@17.0.2(react@17.0.2): dependencies: object-assign: 4.1.1 @@ -30063,7 +30082,7 @@ snapshots: rechoir@0.6.2: dependencies: - resolve: 1.22.8 + resolve: 1.22.10 rechoir@0.7.1: dependencies: @@ -30134,8 +30153,6 @@ snapshots: globalthis: 1.0.3 which-builtin-type: 1.1.3 - reflect.ownkeys@0.2.0: {} - regenerate-unicode-properties@10.1.1: dependencies: regenerate: 1.4.2 @@ -30330,7 +30347,7 @@ snapshots: dependencies: debug: 4.4.1 module-details-from-path: 1.0.3 - resolve: 1.22.8 + resolve: 1.22.10 transitivePeerDependencies: - supports-color @@ -30352,6 +30369,11 @@ snapshots: dependencies: resolve-from: 5.0.0 + resolve-dir@0.1.1: + dependencies: + expand-tilde: 1.2.2 + global-modules: 0.2.3 + resolve-from@4.0.0: {} resolve-from@5.0.0: {} @@ -30446,11 +30468,6 @@ snapshots: transitivePeerDependencies: - supports-color - rst-selector-parser@2.2.3: - dependencies: - lodash.flattendeep: 4.4.0 - nearley: 2.20.1 - run-async@2.4.1: {} run-parallel@1.2.0: @@ -30465,6 +30482,10 @@ snapshots: dependencies: tslib: 2.6.2 + rxjs@7.8.2: + dependencies: + tslib: 2.8.1 + sade@1.8.1: dependencies: mri: 1.2.0 @@ -30572,6 +30593,8 @@ snapshots: extend-shallow: 2.0.1 kind-of: 6.0.3 + secure-compare@3.0.1: {} + secure-json-parse@2.7.0: {} secure-json-parse@3.0.2: {} @@ -30967,6 +30990,14 @@ snapshots: transitivePeerDependencies: - supports-color + socks-proxy-agent@8.0.5: + dependencies: + agent-base: 7.1.3 + debug: 4.3.4(supports-color@8.1.1) + socks: 2.8.3 + transitivePeerDependencies: + - supports-color + socks@2.8.3: dependencies: ip-address: 9.0.5 @@ -31020,6 +31051,11 @@ snapshots: dependencies: memory-pager: 1.5.0 + spawnd@11.0.0: + dependencies: + signal-exit: 4.1.0 + tree-kill: 1.2.2 + spdx-correct@3.2.0: dependencies: spdx-expression-parse: 3.0.1 @@ -31137,6 +31173,14 @@ snapshots: optionalDependencies: bare-events: 2.4.2 + streamx@2.22.1: + dependencies: + fast-fifo: 1.3.2 + text-decoder: 1.1.0 + optionalDependencies: + bare-events: 2.5.4 + optional: true + strict-event-emitter@0.5.1: {} strict-uri-encode@2.0.0: {} @@ -31508,6 +31552,16 @@ snapshots: bare-fs: 2.3.1 bare-path: 2.1.3 + tar-fs@3.0.9: + dependencies: + pump: 3.0.0 + tar-stream: 3.1.7 + optionalDependencies: + bare-fs: 4.1.5 + bare-path: 3.0.0 + transitivePeerDependencies: + - bare-buffer + tar-stream@2.2.0: dependencies: bl: 4.1.0 @@ -31688,6 +31742,8 @@ snapshots: dependencies: punycode: 2.3.0 + tree-kill@1.2.2: {} + trim-trailing-lines@1.1.4: {} trim@0.0.1: {} @@ -31910,6 +31966,8 @@ snapshots: possible-typed-array-names: 1.1.0 reflect.getprototypeof: 1.0.10 + typed-query-selector@2.12.0: {} + typedarray-to-buffer@3.1.5: dependencies: is-typedarray: 1.0.0 @@ -32035,6 +32093,10 @@ snapshots: is-extendable: 0.1.1 set-value: 2.0.1 + union@0.5.0: + dependencies: + qs: 6.14.0 + unique-string@2.0.0: dependencies: crypto-random-string: 2.0.0 @@ -32211,12 +32273,6 @@ snapshots: escalade: 3.1.2 picocolors: 1.1.0 - update-browserslist-db@1.1.3(browserslist@4.25.0): - dependencies: - browserslist: 4.25.0 - escalade: 3.2.0 - picocolors: 1.1.1 - update-check@1.5.2: dependencies: registry-auth-token: 3.3.2 @@ -32245,6 +32301,8 @@ snapshots: urix@0.1.0: {} + url-join@4.0.1: {} + url-loader@4.1.1(file-loader@6.2.0(webpack@5.90.3))(webpack@5.90.3): dependencies: loader-utils: 2.0.4 @@ -32380,6 +32438,28 @@ snapshots: fsevents: 2.3.3 terser: 5.28.1 + vitest-dev-server@11.0.3: + dependencies: + chalk: 4.1.2 + cwd: 0.10.0 + find-process: 1.4.10 + prompts: 2.4.2 + spawnd: 11.0.0 + tree-kill: 1.2.2 + wait-on: 8.0.3 + transitivePeerDependencies: + - debug + + vitest-environment-puppeteer@11.0.3(typescript@5.8.2): + dependencies: + chalk: 4.1.2 + cosmiconfig: 9.0.0(typescript@5.8.2) + deepmerge: 4.3.1 + vitest-dev-server: 11.0.3 + transitivePeerDependencies: + - debug + - typescript + vm-browserify@1.1.2: {} void-elements@3.1.0: {} @@ -32396,6 +32476,16 @@ snapshots: dependencies: xml-name-validator: 4.0.0 + wait-on@8.0.3: + dependencies: + axios: 1.9.0 + joi: 17.13.3 + lodash: 4.17.21 + minimist: 1.2.8 + rxjs: 7.8.2 + transitivePeerDependencies: + - debug + walker@1.0.8: dependencies: makeerror: 1.0.12 @@ -32710,6 +32800,8 @@ snapshots: ws@8.18.0: {} + ws@8.18.2: {} + x-is-string@0.1.0: {} xdg-basedir@4.0.0: {} @@ -32845,6 +32937,8 @@ snapshots: zod@3.23.8: {} + zod@3.25.51: {} + zwitch@1.0.5: {} zwitch@2.0.4: {} diff --git a/tools/client-plugins/browser-scripts/frame-runner.ts b/tools/client-plugins/browser-scripts/frame-runner.ts deleted file mode 100644 index 35fb91da292..00000000000 --- a/tools/client-plugins/browser-scripts/frame-runner.ts +++ /dev/null @@ -1,110 +0,0 @@ -import jQuery from 'jquery'; -import * as helpers from '@freecodecamp/curriculum-helpers'; - -import type { FrameDocument, FrameWindow, InitTestFrameArg } from '.'; - -(window as FrameWindow).$ = jQuery; - -const frameDocument = document as FrameDocument; - -frameDocument.__initTestFrame = initTestFrame; - -async function initTestFrame(e: InitTestFrameArg = { code: {} }) { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const code = (e.code.contents || '').slice(); - - const editableContents = (e.code.editableContents || '').slice(); - // __testEditable allows test authors to run tests against a transitory dom - // element built using only the code in the editable region. - const __testEditable = (cb: () => () => unknown) => { - const div = frameDocument.createElement('div'); - div.id = 'editable-only'; - div.innerHTML = editableContents; - frameDocument.body.appendChild(div); - const out = cb(); - frameDocument.body.removeChild(div); - return out; - }; - - /* eslint-disable @typescript-eslint/no-unused-vars */ - // Hardcode Deep Freeze dependency - const DeepFreeze = (o: Record) => { - Object.freeze(o); - Object.getOwnPropertyNames(o).forEach(function (prop) { - if ( - Object.prototype.hasOwnProperty.call(o, prop) && - o[prop] !== null && - (typeof o[prop] === 'object' || typeof o[prop] === 'function') && - !Object.isFrozen(o[prop]) - ) { - DeepFreeze(o[prop] as Record); - } - }); - return o; - }; - - const { default: chai } = await import(/* webpackChunkName: "chai" */ 'chai'); - const assert = chai.assert; - const __helpers = helpers; - const __checkForBrowserExtensions = true; - /* eslint-enable @typescript-eslint/no-unused-vars */ - - let Enzyme; - if (e.loadEnzyme) { - /* eslint-disable prefer-const */ - let Adapter16; - - [{ default: Enzyme }, { default: Adapter16 }] = await Promise.all([ - import(/* webpackChunkName: "enzyme" */ 'enzyme'), - import(/* webpackChunkName: "enzyme-adapter" */ 'enzyme-adapter-react-16') - ]); - - Enzyme.configure({ adapter: new Adapter16() }); - /* eslint-enable prefer-const */ - } - - frameDocument.__runTest = async function runTests(testString: string) { - // uncomment the following line to inspect - // the frame-runner as it runs tests - // make sure the dev tools console is open - // debugger; - try { - // eval test string to actual JavaScript - // This return can be a function - // i.e. function() { assert(true, 'happy coding'); } - const testPromise = new Promise((resolve, reject) => - // To avoid race conditions, we have to run the test in a final - // frameDocument ready: - $(() => { - try { - const test: unknown = eval(testString); - resolve(test); - } catch (err) { - reject(err as Error); - } - }) - ); - const test = await testPromise; - if (typeof test === 'function') { - // eslint-disable-next-line @typescript-eslint/no-unsafe-call - await test(); - } - return { pass: true }; - } catch (err) { - if (!(err instanceof chai.AssertionError)) { - console.error(err); - } - // to provide useful debugging information when debugging the tests, we - // have to extract the message, stack and, if they exist, expected and - // actual before returning - return { - err: { - message: (err as Error).message, - stack: (err as Error).stack, - expected: (err as { expected?: string }).expected, - actual: (err as { actual?: string }).actual - } - }; - } - }; -} diff --git a/tools/client-plugins/browser-scripts/package.json b/tools/client-plugins/browser-scripts/package.json index d7cfb57a530..ac66a4eb18c 100644 --- a/tools/client-plugins/browser-scripts/package.json +++ b/tools/client-plugins/browser-scripts/package.json @@ -29,20 +29,11 @@ "@babel/plugin-transform-runtime": "7.23.7", "@babel/preset-env": "7.23.7", "@babel/preset-typescript": "7.23.3", - "@freecodecamp/curriculum-helpers": "4.1.0", - "@types/chai": "4.3.12", "@types/copy-webpack-plugin": "^8.0.1", - "@types/enzyme": "3.10.16", - "@types/enzyme-adapter-react-16": "1.0.9", - "@types/jquery": "3.5.29", "@types/lodash-es": "4.17.12", "@typescript/vfs": "^1.6.0", "babel-loader": "8.3.0", - "chai": "4.4.1", "copy-webpack-plugin": "9.1.0", - "enzyme": "3.11.0", - "enzyme-adapter-react-16": "1.15.8", - "jquery": "3.7.1", "lodash-es": "4.17.21", "process": "0.11.10", "pyodide": "^0.23.3", @@ -52,6 +43,7 @@ "webpack-cli": "4.10.0" }, "dependencies": { + "@freecodecamp/curriculum-helpers": "^4.4.0", "react": "16", "react-dom": "16", "xterm": "^5.2.1" diff --git a/tools/client-plugins/browser-scripts/python-test-evaluator.ts b/tools/client-plugins/browser-scripts/python-test-evaluator.ts deleted file mode 100644 index f69c11c210e..00000000000 --- a/tools/client-plugins/browser-scripts/python-test-evaluator.ts +++ /dev/null @@ -1,192 +0,0 @@ -// We have to specify pyodide.js because we need to import that file (not .mjs) -// and 'import' defaults to .mjs -import { loadPyodide, type PyodideInterface } from 'pyodide/pyodide.js'; -import type { PyProxy, PythonError } from 'pyodide/ffi'; -import pkg from 'pyodide/package.json'; -import * as helpers from '@freecodecamp/curriculum-helpers'; -import chai from 'chai'; - -const ctx: Worker & typeof globalThis = self as unknown as Worker & - typeof globalThis; - -let pyodide: PyodideInterface; - -interface PythonRunEvent extends MessageEvent { - data: { - code: { - contents: string; - editableContents: string; - }; - firstTest: unknown; - testString: string; - build: string; - sources: { - [fileName: string]: unknown; - }; - }; -} - -type EvaluatedTeststring = { - input: string[]; - test: () => Promise; -}; - -async function setupPyodide() { - if (pyodide) return pyodide; - - pyodide = await loadPyodide({ - // TODO: host this ourselves - indexURL: `https://cdn.jsdelivr.net/pyodide/v${pkg.version}/full/` - }); - - // We freeze this to prevent learners from getting the worker into a - // weird state. NOTE: this has to come after pyodide is loaded, because - // pyodide modifies self while loading. - Object.freeze(self); - - // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access - pyodide.FS.writeFile( - '/home/pyodide/ast_helpers.py', - helpers.python.astHelpers, - { - encoding: 'utf8' - } - ); - - ctx.postMessage({ type: 'contentLoaded' }); - - return pyodide; -} - -void setupPyodide(); - -ctx.onmessage = async (e: PythonRunEvent) => { - const pyodide = await setupPyodide(); - /* eslint-disable @typescript-eslint/no-unused-vars */ - const code = (e.data.code.contents || '').slice(); - const editableContents = (e.data.code.editableContents || '').slice(); - const testString = e.data.testString; - - const assert = chai.assert; - const __helpers = helpers; - - // Create fresh globals for each test - // eslint-disable-next-line @typescript-eslint/no-unsafe-call - const __userGlobals = pyodide.globals.get('dict')() as PyProxy; - - /* eslint-enable @typescript-eslint/no-unused-vars */ - // uncomment the following line to inspect - // the frame-runner as it runs tests - // make sure the dev tools console is open - // debugger; - try { - // eval test string to get the dummy input and actual test - const evaluatedTestString = await new Promise( - (resolve, reject) => { - try { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const test: { input: string[]; test: () => Promise } = - eval(testString); - resolve(test); - } catch (err) { - reject(err as Error); - } - } - ); - - // If the test string does not evaluate to an object, then we assume that - // it's a standard JS test and any assertions have already passed. - if (typeof evaluatedTestString !== 'object') { - ctx.postMessage({ pass: true }); - return; - } - - if (!evaluatedTestString || !('test' in evaluatedTestString)) { - throw new Error( - 'Test string did not evaluate to an object with the test property' - ); - } - - const { input, test } = evaluatedTestString as EvaluatedTeststring; - - // Some tests rely on __name__ being set to __main__ and we new dicts do not - // have this set by default. - // eslint-disable-next-line @typescript-eslint/no-unsafe-call - __userGlobals.set('__name__', '__main__'); - - // The runPython helper is a shortcut for running python code with our - // custom globals. - const runPython = (pyCode: string) => - pyodide.runPython(pyCode, { globals: __userGlobals }) as unknown; - - runPython(` -def __inputGen(xs): - def gen(): - for x in xs: - yield x - iter = gen() - def input(arg=None): - return next(iter) - - return input - -input = __inputGen(${JSON.stringify(input ?? [])}) -`); - - runPython(`from ast_helpers import Node as _Node`); - - // The tests need the user's code as a string, so we write it to the virtual - // filesystem... - // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access - pyodide.FS.writeFile('/user_code.py', code, { encoding: 'utf8' }); - - // ...and then read it back into a variable so that they can evaluate it. - runPython(` -with open("/user_code.py", "r") as f: - _code = f.read() -`); - - try { - // Evaluates the learner's code so that any variables they define are - // available to the test. - runPython(code); - } catch (e) { - const err = e as PythonError; - - // Quite a lot of lessons can easily lead users to write code that has - // indentation errors. In these cases we want to provide a more helpful - // error message. For other errors, we can just provide the standard - // message. - const errorType = - err.type === 'IndentationError' ? 'indentation' : 'other'; - return ctx.postMessage({ - err: { - message: err.message, - stack: err.stack, - errorType - } - }); - } - - await test(); - - ctx.postMessage({ pass: true }); - } catch (err) { - if (!(err instanceof chai.AssertionError)) { - console.error(err); - } - // to provide useful debugging information when debugging the tests, we - // have to extract the message, stack and, if they exist, expected and - // actual before returning - ctx.postMessage({ - err: { - message: (err as Error).message, - stack: (err as Error).stack, - expected: (err as { expected?: string }).expected, - actual: (err as { actual?: string }).actual - } - }); - } finally { - __userGlobals.destroy(); - } -}; diff --git a/tools/client-plugins/browser-scripts/test-evaluator.ts b/tools/client-plugins/browser-scripts/test-evaluator.ts deleted file mode 100644 index 495904f9609..00000000000 --- a/tools/client-plugins/browser-scripts/test-evaluator.ts +++ /dev/null @@ -1,163 +0,0 @@ -import chai from 'chai'; -import { toString as __toString } from 'lodash-es'; -import * as curriculumHelpers from '@freecodecamp/curriculum-helpers'; -import { format as __format } from './utils/format'; - -const ctx: Worker & typeof globalThis = self as unknown as Worker & - typeof globalThis; - -const __utils = (() => { - const MAX_LOGS_SIZE = 64 * 1024; - - let logs: string[] = []; - - function flushLogs() { - if (logs.length) { - ctx.postMessage({ - type: 'LOG', - data: logs.join('\n') - }); - logs = []; - } - } - - function pushLogs(logs: string[], args: string[]) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - logs.push(args.map(arg => __format(arg)).join(' ')); - if (logs.join('\n').length > MAX_LOGS_SIZE) { - flushLogs(); - } - } - - const oldLog = ctx.console.log.bind(ctx.console); - function proxyLog(...args: string[]) { - pushLogs(logs, args); - return oldLog(...args); - } - - const oldInfo = ctx.console.info.bind(ctx.console); - function proxyInfo(...args: string[]) { - pushLogs(logs, args); - return oldInfo(...args); - } - - const oldWarn = ctx.console.warn.bind(ctx.console); - function proxyWarn(...args: string[]) { - pushLogs(logs, args); - return oldWarn(...args); - } - - const oldError = ctx.console.error.bind(ctx.console); - function proxyError(...args: string[]) { - pushLogs(logs, args); - return oldError(...args); - } - - function log(...msgs: Error[]) { - if (msgs && msgs[0] && !(msgs[0] instanceof chai.AssertionError)) { - // discards the stack trace via toString as it only useful to debug the - // site, not a specific challenge. - console.log(...msgs.map(msg => msg.toString())); - } - } - - const toggleProxyLogger = (on: unknown) => { - ctx.console.log = on ? proxyLog : oldLog; - ctx.console.info = on ? proxyInfo : oldInfo; - ctx.console.warn = on ? proxyWarn : oldWarn; - ctx.console.error = on ? proxyError : oldError; - }; - - return { - log, - toggleProxyLogger, - flushLogs - }; -})(); - -// We can't simply import these because of how webpack names them when building -// the bundle. Since both assert and __helpers have to exist in the global -// scope, we have to declare them. -const assert = chai.assert; -const __helpers = curriculumHelpers; - -// We freeze to prevent learners from getting the tester into a weird -// state by modifying these objects. -Object.freeze(self); -Object.freeze(__utils); -Object.freeze(assert); -Object.freeze(__helpers); - -interface TestEvaluatorEvent extends MessageEvent { - data: { - code: { - contents: string; - editableContents: string; - }; - firstTest: unknown; - testString: string; - build: string; - }; -} - -/* Run the test if there is one. If not just evaluate the user code */ -ctx.onmessage = async (e: TestEvaluatorEvent) => { - /* eslint-disable @typescript-eslint/no-unused-vars */ - const code = e.data?.code?.contents || ''; - const editableContents = e.data?.code?.editableContents || ''; - - // Build errors should be reported, but only once: - __utils.toggleProxyLogger(e.data.firstTest); - /* eslint-enable @typescript-eslint/no-unused-vars */ - try { - // This can be reassigned by the eval inside the try block, so it should be declared as a let - // eslint-disable-next-line prefer-const - let __userCodeWasExecuted = false; - try { - // Logging is proxyed after the build to catch console.log messages - // generated during testing. - await eval(`${e.data.build} -__utils.flushLogs(); -__userCodeWasExecuted = true; -__utils.toggleProxyLogger(true); -(async () => {${e.data.testString}})()`); - } catch (err) { - if (__userCodeWasExecuted) { - // rethrow error, since test failed. - throw err; - } - // log build errors unless they're related to import/export/require (there - // are challenges that use them and they should not trigger warnings) - if ( - (err as Error).name !== 'ReferenceError' || - ((err as Error).message !== 'require is not defined' && - (err as Error).message !== 'exports is not defined') - ) { - __utils.log(err as Error); - } - // the tests may not require working code, so they are evaluated even if - // the user code does not get executed. - eval(e.data.testString); - } - __utils.flushLogs(); - ctx.postMessage({ pass: true }); - } catch (err) { - // Errors from testing go to the browser console only. - __utils.toggleProxyLogger(false); - // Report execution errors in case user code has errors that are only - // uncovered during testing. - __utils.log(err as Error); - // Now that all logs have been created we can flush them. - __utils.flushLogs(); - ctx.postMessage({ - err: { - message: (err as Error).message, - stack: (err as Error).stack, - expected: (err as { expected?: string }).expected, - actual: (err as { actual?: string }).actual - } - }); - } -}; - -ctx.postMessage({ type: 'contentLoaded' }); diff --git a/tools/client-plugins/browser-scripts/test-runner.ts b/tools/client-plugins/browser-scripts/test-runner.ts new file mode 100644 index 00000000000..2a44a42b6fc --- /dev/null +++ b/tools/client-plugins/browser-scripts/test-runner.ts @@ -0,0 +1,3 @@ +export type { FCCTestRunner } from '@freecodecamp/curriculum-helpers/test-runner.js'; + +export { version } from '@freecodecamp/curriculum-helpers/package.json'; diff --git a/tools/client-plugins/browser-scripts/webpack.config.cjs b/tools/client-plugins/browser-scripts/webpack.config.cjs index f00689a9abe..89cf2dfb947 100644 --- a/tools/client-plugins/browser-scripts/webpack.config.cjs +++ b/tools/client-plugins/browser-scripts/webpack.config.cjs @@ -2,6 +2,9 @@ const { writeFileSync } = require('fs'); const path = require('path'); const CopyWebpackPlugin = require('copy-webpack-plugin'); const webpack = require('webpack'); +const { + version: helperVersion +} = require('@freecodecamp/curriculum-helpers/package.json'); module.exports = (env = {}) => { const __DEV__ = env.production !== true; @@ -14,11 +17,8 @@ module.exports = (env = {}) => { cache: __DEV__ ? { type: 'filesystem' } : false, mode: __DEV__ ? 'development' : 'production', entry: { - 'frame-runner': './frame-runner.ts', 'sass-compile': './sass-compile.ts', - 'test-evaluator': './test-evaluator.ts', 'python-worker': './python-worker.ts', - 'python-test-evaluator': './python-test-evaluator.ts', 'typescript-worker': './typescript-worker.ts' }, devtool: __DEV__ ? 'inline-source-map' : 'source-map', @@ -71,7 +71,11 @@ module.exports = (env = {}) => { patterns: [ './node_modules/sass.js/dist/sass.sync.js', // TODO: copy this into the css folder, not the js folder - './node_modules/xterm/css/xterm.css' + './node_modules/xterm/css/xterm.css', + { + from: './node_modules/@freecodecamp/curriculum-helpers/dist/test-runner', + to: `test-runner/${helperVersion}/` + } ] }), new webpack.ProvidePlugin({