feat: handle python input synchronously (#52526)

Co-authored-by: Shaun Hamilton <shauhami020@gmail.com>
This commit is contained in:
Oliver Eyton-Williams
2023-12-18 20:22:26 +01:00
committed by GitHub
parent 8e457e7789
commit 583745e6ca
24 changed files with 760 additions and 660 deletions
+3 -1
View File
@@ -136,7 +136,9 @@
"typescript": "5.2.2", "typescript": "5.2.2",
"util": "0.12.5", "util": "0.12.5",
"uuid": "8.3.2", "uuid": "8.3.2",
"validator": "13.11.0" "validator": "13.11.0",
"xterm": "^5.2.1",
"xterm-addon-fit": "^0.8.0"
}, },
"devDependencies": { "devDependencies": {
"@babel/plugin-syntax-dynamic-import": "7.8.3", "@babel/plugin-syntax-dynamic-import": "7.8.3",
+1 -1
View File
@@ -20,7 +20,7 @@
] ]
}, },
{ {
"source": "{misc/*.js,sw.js}", "source": "{misc/*.js,sw.js,python-input-sw.js}",
"headers": [ "headers": [
{ {
"key": "Cache-Control", "key": "Cache-Control",
@@ -45,6 +45,7 @@ interface DesktopLayoutProps {
testsPane: Pane; testsPane: Pane;
}; };
notes: ReactElement; notes: ReactElement;
onPreviewResize: () => void;
preview: ReactElement; preview: ReactElement;
resizeProps: ResizeProps; resizeProps: ResizeProps;
testOutput: ReactElement; testOutput: ReactElement;
@@ -149,6 +150,7 @@ const DesktopLayout = (props: DesktopLayoutProps): JSX.Element => {
isFirstStep, isFirstStep,
layoutState, layoutState,
notes, notes,
onPreviewResize,
preview, preview,
hasEditableBoundaries, hasEditableBoundaries,
windowTitle windowTitle
@@ -285,7 +287,9 @@ const DesktopLayout = (props: DesktopLayoutProps): JSX.Element => {
)} )}
</ReflexContainer> </ReflexContainer>
{displayPreviewPortal && ( {displayPreviewPortal && (
<PreviewPortal windowTitle={windowTitle}>{preview}</PreviewPortal> <PreviewPortal onResize={onPreviewResize} windowTitle={windowTitle}>
{preview}
</PreviewPortal>
)} )}
</div> </div>
); );
@@ -30,6 +30,7 @@ interface MobileLayoutProps {
instructions: JSX.Element; instructions: JSX.Element;
notes: ReactElement; notes: ReactElement;
preview: JSX.Element; preview: JSX.Element;
onPreviewResize: () => void;
windowTitle: string; windowTitle: string;
showPreviewPortal: boolean; showPreviewPortal: boolean;
showPreviewPane: boolean; showPreviewPane: boolean;
@@ -157,6 +158,7 @@ class MobileLayout extends Component<MobileLayoutProps, MobileLayoutState> {
hasPreview, hasPreview,
notes, notes,
preview, preview,
onPreviewResize,
showPreviewPane, showPreviewPane,
showPreviewPortal, showPreviewPortal,
removePortalWindow, removePortalWindow,
@@ -328,7 +330,9 @@ class MobileLayout extends Component<MobileLayoutProps, MobileLayoutState> {
)} )}
</Tabs> </Tabs>
{displayPreviewPortal && ( {displayPreviewPortal && (
<PreviewPortal windowTitle={windowTitle}>{preview}</PreviewPortal> <PreviewPortal onResize={onPreviewResize} windowTitle={windowTitle}>
{preview}
</PreviewPortal>
)} )}
</> </>
); );
@@ -9,6 +9,8 @@ import { bindActionCreators, Dispatch } from 'redux';
import { createStructuredSelector } from 'reselect'; import { createStructuredSelector } from 'reselect';
import store from 'store'; import store from 'store';
import { editor } from 'monaco-editor'; import { editor } from 'monaco-editor';
import type { FitAddon } from 'xterm-addon-fit';
import { challengeTypes } from '../../../../../shared/config/challenge-types'; import { challengeTypes } from '../../../../../shared/config/challenge-types';
import LearnLayout from '../../../components/layouts/learn'; import LearnLayout from '../../../components/layouts/learn';
import { MAX_MOBILE_WIDTH } from '../../../../config/misc'; import { MAX_MOBILE_WIDTH } from '../../../../config/misc';
@@ -56,6 +58,7 @@ import {
} from '../redux/selectors'; } from '../redux/selectors';
import { savedChallengesSelector } from '../../../redux/selectors'; import { savedChallengesSelector } from '../../../redux/selectors';
import { getGuideUrl } from '../utils'; import { getGuideUrl } from '../utils';
import { XtermTerminal } from './xterm';
import MultifileEditor from './multifile-editor'; import MultifileEditor from './multifile-editor';
import DesktopLayout from './desktop-layout'; import DesktopLayout from './desktop-layout';
import MobileLayout from './mobile-layout'; import MobileLayout from './mobile-layout';
@@ -148,9 +151,16 @@ const handleContentWidgetEvents = (e: MouseEvent | TouchEvent): void => {
const StepPreview = ({ const StepPreview = ({
disableIframe, disableIframe,
previewMounted previewMounted,
}: Pick<PreviewProps, 'disableIframe' | 'previewMounted'>) => { challengeType,
return ( xtermFitRef
}: Pick<PreviewProps, 'disableIframe' | 'previewMounted'> & {
challengeType: number;
xtermFitRef: React.MutableRefObject<FitAddon | null>;
}) => {
return challengeType === challengeTypes.python ? (
<XtermTerminal xtermFitRef={xtermFitRef} />
) : (
<Preview <Preview
className='full-height' className='full-height'
disableIframe={disableIframe} disableIframe={disableIframe}
@@ -214,6 +224,7 @@ function ShowClassic({
const containerRef = useRef<HTMLElement>(null); const containerRef = useRef<HTMLElement>(null);
const editorRef = useRef<editor.IStandaloneCodeEditor>(); const editorRef = useRef<editor.IStandaloneCodeEditor>();
const instructionsPanelRef = useRef<HTMLDivElement>(null); const instructionsPanelRef = useRef<HTMLDivElement>(null);
const xtermFitRef = useRef<FitAddon | null>(null);
const isMobile = useMediaQuery({ const isMobile = useMediaQuery({
query: `(max-width: ${MAX_MOBILE_WIDTH}px)` query: `(max-width: ${MAX_MOBILE_WIDTH}px)`
}); });
@@ -248,6 +259,8 @@ function ShowClassic({
return isValidLayout ? reflexLayout : BASE_LAYOUT; return isValidLayout ? reflexLayout : BASE_LAYOUT;
}; };
const onPreviewResize = () => xtermFitRef.current?.fit();
// layout: Holds the information of the panes sizes for desktop view // layout: Holds the information of the panes sizes for desktop view
const [layout, setLayout] = useState(getLayoutState()); const [layout, setLayout] = useState(getLayoutState());
@@ -438,10 +451,13 @@ function ShowClassic({
showToolPanel: false showToolPanel: false
})} })}
notes={<Notes notes={notes} />} notes={<Notes notes={notes} />}
onPreviewResize={onPreviewResize}
preview={ preview={
<StepPreview <StepPreview
challengeType={challengeType}
disableIframe={resizing} disableIframe={resizing}
previewMounted={previewMounted} previewMounted={previewMounted}
xtermFitRef={xtermFitRef}
/> />
} }
windowTitle={windowTitle} windowTitle={windowTitle}
@@ -470,10 +486,13 @@ function ShowClassic({
isFirstStep={isFirstStep} isFirstStep={isFirstStep}
layoutState={layout} layoutState={layout}
notes={<Notes notes={notes} />} notes={<Notes notes={notes} />}
onPreviewResize={onPreviewResize}
preview={ preview={
<StepPreview <StepPreview
challengeType={challengeType}
disableIframe={resizing} disableIframe={resizing}
previewMounted={previewMounted} previewMounted={previewMounted}
xtermFitRef={xtermFitRef}
/> />
} }
resizeProps={resizeProps} resizeProps={resizeProps}
@@ -0,0 +1,102 @@
import React, { MutableRefObject, useEffect, useRef } from 'react';
import type { IDisposable, Terminal } from 'xterm';
import type { FitAddon } from 'xterm-addon-fit';
import { registerTerminal } from '../utils/python-worker-handler';
const registerServiceWorker = async () => {
if ('serviceWorker' in navigator) {
try {
await navigator.serviceWorker.register('/python-input-sw.js');
} catch (error) {
console.error(`Registration failed`);
console.error(error);
}
}
};
export const XtermTerminal = ({
xtermFitRef
}: {
xtermFitRef: MutableRefObject<FitAddon | null>;
}) => {
const termContainerRef = useRef<HTMLDivElement | null>(null);
useEffect(() => {
void registerServiceWorker();
let term: Terminal | null;
async function createTerminal() {
const disposables: IDisposable[] = [];
const { Terminal } = await import('xterm');
const { FitAddon } = await import('xterm-addon-fit');
// Setting convertEol so that \n is converted to \r\n. Otherwise the terminal
// will interpret \n as line feed and just move the cursor to the next line.
// convertEol makes every \n a \r\n.
term = new Terminal({ convertEol: true });
const fitAddon = new FitAddon();
xtermFitRef.current = fitAddon;
term.loadAddon(fitAddon);
if (termContainerRef.current) term.open(termContainerRef.current);
fitAddon.fit();
const print = (text: string) => term?.writeln(`>>> ${text}`);
// TODO: prevent user from moving cursor outside the current input line and
// handle insertion and deletion properly. While backspace and delete don't
// seem to work, we can use "\x1b[0K" to clear from the cursor to the end.
// Also, we should not add special characters to the userinput string.
const input = (text: string) => {
print(text);
let userinput = '';
// Eslint is correct that this only gets assigned once, but we can't use
// const because the declaration (before keyListener is defined) and
// assignment (after keyListener is defined) must be separate.
// eslint-disable-next-line prefer-const
let disposable: IDisposable | undefined;
const done = () => {
disposable?.dispose();
navigator.serviceWorker.controller?.postMessage(userinput);
};
const keyListener = (key: string) => {
if (key === '\u007F' || key === '\b') {
// Backspace or delete key
term?.write('\b \b'); // Move cursor back, replace character with space, then move cursor back again
userinput = userinput.slice(0, -1); // Remove the last character from userinput
}
if (key == '\r') {
term?.write('\r\n');
done();
} else {
userinput += key;
term?.write(key);
}
};
disposable = term?.onData(keyListener); // Listen for key events and store the disposable
if (disposable) disposables.push(disposable);
};
const reset = () => {
term?.reset();
disposables.forEach(disposable => disposable.dispose());
disposables.length = 0;
};
registerTerminal({ print, input }, reset);
}
void createTerminal();
return () => {
term?.dispose();
};
}, [xtermFitRef]);
return (
<div ref={termContainerRef}>
<link rel='stylesheet' href='/js/xterm.css' />
</div>
);
};
@@ -34,6 +34,7 @@ interface PreviewPortalProps {
isAdvancing: boolean; isAdvancing: boolean;
setChapterSlug: (arg: string) => void; setChapterSlug: (arg: string) => void;
chapterSlug: string; chapterSlug: string;
onResize: () => void;
} }
const mapDispatchToProps = { const mapDispatchToProps = {
@@ -136,6 +137,10 @@ class PreviewPortal extends Component<PreviewPortalProps> {
this.props.removePortalWindow(); this.props.removePortalWindow();
}); });
this.externalWindow?.addEventListener('resize', () => {
this.props.onResize();
});
this.props.storePortalWindow(this.externalWindow); this.props.storePortalWindow(this.externalWindow);
// close the portal if the main window closes // close the portal if the main window closes
@@ -1,120 +0,0 @@
export const indent = (code, spaces) => {
const lines = code.split('\n');
return lines.map(line => `${' '.repeat(spaces)}${line}`).join('\n');
};
// Requirements:
// - run in a single instance of pyodide (because loadPyodide is slow)
// - be able to stop execution of learner code
//
// This wrapper lets us meet the second requirement, since tasks are
// cancellable. This creates a second issue: the learner code no longer modifies
// the global scope, so we need to copy the locals to globals.
//
// Finally, we have to await the task, or there's no way for the JavaScript
// context to know when the task is complete.
export const makeCancellable = code => `import asyncio
async def cancellable_coroutine():
try:
${indent(code, 8)}
globals()['__locals'] = locals()
except asyncio.CancelledError:
pass
__task = asyncio.create_task(cancellable_coroutine())
def __cancel():
__task.cancel()
await __task`;
export function modifyInputStatements(line) {
// Use a regular expression to match input statements with chained methods
const inputRegex = /(.*=\s*)input\((["'].*?["']\))(\.\w+\([^)]*\))*/;
const match = line.match(inputRegex);
if (match) {
const inputStatement = match[0];
const varAssignment = match[1];
const inputCall =
'input' +
inputStatement
.slice(varAssignment.length)
.split('input')[1]
.split('.')[0];
const methods = inputStatement
.slice(varAssignment.length + inputCall.length)
.split('.')
.slice(1);
const tempVar = '_temp_input_var';
const newStatements = [
`${tempVar} = ${inputCall}`,
...methods.map(method => `${tempVar} = ${tempVar}.${method}`),
`${varAssignment.trim()} ${tempVar}`
];
// Get the indentation of the original line
const indentation = line.match(/^\s*/)[0];
// Apply the same indentation to each new statement
const indentedStatements = newStatements.map(stmt => indentation + stmt);
// Replace the original input statement in the line with the temporary variable
const updatedLine = line.replace(
inputStatement,
indentedStatements.join('\n')
);
return updatedLine.split('\n');
}
return [line];
}
export function makeInputAwaitable(code) {
const lines = code.split('\n');
const asyncFunctions = new Set();
const modifiedLines = [];
for (let i = 0; i < lines.length; i++) {
let line = lines[i];
// Modify input statements with chained methods
const updatedLines = modifyInputStatements(line);
// If the line contains an input statement, update it to use "await"
if (updatedLines.some(updatedLine => updatedLine.includes('input('))) {
updatedLines.forEach((updatedLine, index) => {
if (updatedLine.includes('input(')) {
updatedLines[index] = updatedLine.replace('input(', 'await input(');
}
});
// Find the outer function definition and make it async
for (let j = i - 1; j >= 0; j--) {
if (lines[j].includes('def ')) {
if (!modifiedLines[j].includes('async def ')) {
const functionName = lines[j].match(
/def\s+([a-zA-Z_][a-zA-Z_0-9]*)/
)[1];
asyncFunctions.add(functionName);
modifiedLines[j] = modifiedLines[j].replace('def ', 'async def ');
}
break;
}
}
}
// Update function calls to include 'await' for async functions
asyncFunctions.forEach(funcName => {
updatedLines.forEach((updatedLine, index) => {
if (
updatedLine.includes(` ${funcName}(`) &&
!updatedLine.includes(`await ${funcName}(`)
) {
updatedLines[index] = updatedLine.replace(
`${funcName}(`,
`await ${funcName}(`
);
}
});
});
modifiedLines.push(...updatedLines);
}
return modifiedLines.join('\n');
}
@@ -1,41 +0,0 @@
/* eslint-disable @typescript-eslint/no-unsafe-call */
import { indent, makeCancellable } from './transform-python';
describe('transform-python', () => {
describe('indent', () => {
it('should indent n spaces', () => {
const inputCode = `def foo():
print('bar')`;
const fourSpaces = ` def foo():
print('bar')`;
const eightSpaces = ` def foo():
print('bar')`;
expect(indent(inputCode, 4)).toEqual(fourSpaces);
expect(indent(inputCode, 8)).toEqual(eightSpaces);
});
});
describe('makeCancellable', () => {
it('should wrap a code string in a cancellable coroutine', () => {
const inputCode = `def foo():
print('bar')`;
const wrappedCode = `import asyncio
async def cancellable_coroutine():
try:
def foo():
print('bar')
globals()['__locals'] = locals()
except asyncio.CancelledError:
pass
__task = asyncio.create_task(cancellable_coroutine())
def __cancel():
__task.cancel()
await __task`;
expect(makeCancellable(inputCode)).toEqual(wrappedCode);
});
});
});
@@ -19,7 +19,6 @@ import {
compileHeadTail compileHeadTail
} from '../../../../../shared/utils/polyvinyl'; } from '../../../../../shared/utils/polyvinyl';
import createWorker from '../utils/worker-executor'; import createWorker from '../utils/worker-executor';
import { makeCancellable, makeInputAwaitable } from './transform-python';
const { filename: sassCompile } = sassData; const { filename: sassCompile } = sassData;
@@ -100,7 +99,6 @@ const NBSPReg = new RegExp(String.fromCharCode(160), 'g');
const testJS = matchesProperty('ext', 'js'); const testJS = matchesProperty('ext', 'js');
const testJSX = matchesProperty('ext', 'jsx'); const testJSX = matchesProperty('ext', 'jsx');
const testHTML = matchesProperty('ext', 'html'); const testHTML = matchesProperty('ext', 'html');
const testPython = matchesProperty('ext', 'py');
const testHTML$JS$JSX = overSome(testHTML, testJS, testJSX); const testHTML$JS$JSX = overSome(testHTML, testJS, testJSX);
const replaceNBSP = cond([ const replaceNBSP = cond([
@@ -306,17 +304,6 @@ const htmlTransformer = cond([
[stubTrue, identity] [stubTrue, identity]
]); ]);
const transformPython = async function (file) {
const awaitableCode = makeInputAwaitable(file.contents);
const cancellableCode = makeCancellable(awaitableCode);
return transformContents(() => cancellableCode, file);
};
const pythonTransformer = cond([
[testPython, transformPython],
[stubTrue, identity]
]);
export const getTransformers = loopProtectOptions => [ export const getTransformers = loopProtectOptions => [
replaceNBSP, replaceNBSP,
babelTransformer(loopProtectOptions), babelTransformer(loopProtectOptions),
@@ -326,6 +313,5 @@ export const getTransformers = loopProtectOptions => [
export const getPythonTransformers = () => [ export const getPythonTransformers = () => [
replaceNBSP, replaceNBSP,
partial(compileHeadTail, ''), partial(compileHeadTail, '')
pythonTransformer
]; ];
@@ -33,7 +33,10 @@ import {
updatePreview, updatePreview,
updateProjectPreview updateProjectPreview
} from '../utils/build'; } from '../utils/build';
import { runPythonInFrame, mainPreviewId } from '../utils/frame'; import {
getPythonWorker,
resetPythonWorker
} from '../utils/python-worker-handler';
import { executeGA } from '../../../redux/actions'; import { executeGA } from '../../../redux/actions';
import { fireConfetti } from '../../../utils/fire-confetti'; import { fireConfetti } from '../../../utils/fire-confetti';
import { actionTypes } from './action-types'; import { actionTypes } from './action-types';
@@ -264,15 +267,13 @@ function* previewChallengeSaga({ flushLogs = true } = {}) {
const portalDocument = yield select(portalDocumentSelector); const portalDocument = yield select(portalDocumentSelector);
const finalDocument = portalDocument || document; const finalDocument = portalDocument || document;
yield call(updatePreview, buildData, finalDocument, proxyLogger); // Python challenges do not use the preview frame, they use a web worker
// to run the code. The UI is handled by the xterm component, so there
// Python challenges need to be created in two steps: // is no need to update the preview frame.
// 1) build the frame
// 2) evaluate the code in the frame. This is necessary to avoid
// recreating the frame (which is slow since loadPyodide takes a long
// time)on every change.
if (challengeData.challengeType === challengeTypes.python) { if (challengeData.challengeType === challengeTypes.python) {
yield updatePython(challengeData); yield updatePython(challengeData);
} else {
yield call(updatePreview, buildData, finalDocument, proxyLogger);
} }
} else if (isJavaScriptChallenge(challengeData)) { } else if (isJavaScriptChallenge(challengeData)) {
const runUserCode = getTestRunner(buildData, { const runUserCode = getTestRunner(buildData, {
@@ -306,19 +307,19 @@ function* updatePreviewSaga() {
} }
function* updatePython(challengeData) { function* updatePython(challengeData) {
const document = yield getContext('document');
// TODO: refactor the build pipeline so that we have discrete, composable // TODO: refactor the build pipeline so that we have discrete, composable
// functions to handle transforming code, embedding it and building the // functions to handle transforming code, embedding it and building the
// final html. Then we can just use the transformation function here. // final html. Then we can just use the transformation function here.
const buildData = yield buildChallengeData(challengeData); const buildData = yield buildChallengeData(challengeData);
const code = buildData.transformedPython; resetPythonWorker();
const worker = getPythonWorker();
const code = {
contents: buildData.sources.index,
editableContents: buildData.sources.editableContents,
original: buildData.sources.original
};
worker.postMessage({ code });
// TODO: proxy errors to the console // TODO: proxy errors to the console
try {
yield call(runPythonInFrame, document, code, mainPreviewId);
} catch (err) {
console.log('Error evaluating python code', code);
console.log('Message:', err.message);
}
} }
function* previewProjectSolutionSaga({ payload }) { function* previewProjectSolutionSaga({ payload }) {
+40 -21
View File
@@ -1,13 +1,13 @@
import { challengeTypes } from '../../../../../shared/config/challenge-types'; import { challengeTypes } from '../../../../../shared/config/challenge-types';
import frameRunnerData from '../../../../../client/config/browser-scripts/frame-runner.json'; import frameRunnerData from '../../../../../client/config/browser-scripts/frame-runner.json';
import testEvaluatorData from '../../../../../client/config/browser-scripts/test-evaluator.json'; import jsTestEvaluatorData from '../../../../../client/config/browser-scripts/test-evaluator.json';
import pythonRunnerData from '../../../../../client/config/browser-scripts/python-runner.json'; import pyTestEvaluatorData from '../../../../../client/config/browser-scripts/python-test-evaluator.json';
import { import {
ChallengeFile as PropTypesChallengeFile, ChallengeFile as PropTypesChallengeFile,
ChallengeMeta ChallengeMeta
} from '../../../redux/prop-types'; } from '../../../redux/prop-types';
import { concatHtml, createPythonTerminal } from '../rechallenge/builders'; import { concatHtml } from '../rechallenge/builders';
import { import {
getTransformers, getTransformers,
embedFilesInHtml, embedFilesInHtml,
@@ -48,12 +48,16 @@ interface BuildOptions {
usesTestRunner?: boolean; usesTestRunner?: boolean;
} }
const { filename: testEvaluator } = testEvaluatorData; interface WorkerConfig {
terminateWorker: boolean;
testEvaluator: string;
}
const { filename: jsTestEvaluator } = jsTestEvaluatorData;
const { filename: pyTestEvaluator } = pyTestEvaluatorData;
const frameRunnerSrc = `/js/${frameRunnerData.filename}.js`; const frameRunnerSrc = `/js/${frameRunnerData.filename}.js`;
const pythonRunnerSrc = `/js/${pythonRunnerData.filename}.js`;
type ApplyFunctionProps = (file: ChallengeFile) => Promise<ChallengeFile>; type ApplyFunctionProps = (file: ChallengeFile) => Promise<ChallengeFile>;
const applyFunction = const applyFunction =
@@ -143,6 +147,7 @@ const testRunners = {
[challengeTypes.html]: getDOMTestRunner, [challengeTypes.html]: getDOMTestRunner,
[challengeTypes.backend]: getDOMTestRunner, [challengeTypes.backend]: getDOMTestRunner,
[challengeTypes.pythonProject]: getDOMTestRunner, [challengeTypes.pythonProject]: getDOMTestRunner,
[challengeTypes.python]: getPyTestRunner,
[challengeTypes.multifileCertProject]: getDOMTestRunner [challengeTypes.multifileCertProject]: getDOMTestRunner
}; };
// TODO: Figure out and (hopefully) simplify the return type. // TODO: Figure out and (hopefully) simplify the return type.
@@ -163,13 +168,37 @@ export function getTestRunner(
function getJSTestRunner( function getJSTestRunner(
{ build, sources }: BuildChallengeData, { build, sources }: BuildChallengeData,
{ proxyLogger, removeComments }: TestRunnerConfig { proxyLogger, removeComments }: TestRunnerConfig
) {
return getWorkerTestRunner(
{ build, sources },
{ proxyLogger, removeComments },
{ testEvaluator: jsTestEvaluator, terminateWorker: true }
);
}
function getPyTestRunner(
{ build, sources }: BuildChallengeData,
{ proxyLogger, removeComments }: TestRunnerConfig
) {
return getWorkerTestRunner(
{ build, sources },
{ proxyLogger, removeComments },
{ testEvaluator: pyTestEvaluator, terminateWorker: false }
);
}
function getWorkerTestRunner(
{ build, sources }: Pick<BuildChallengeData, 'build' | 'sources'>,
{ proxyLogger, removeComments }: TestRunnerConfig,
{ testEvaluator, terminateWorker }: WorkerConfig
) { ) {
const code = { const code = {
contents: sources.index, contents: sources.index,
editableContents: sources.editableContents editableContents: sources.editableContents,
original: sources.original
}; };
const testWorker = createWorker(testEvaluator, { terminateWorker: true }); const testWorker = createWorker(testEvaluator, { terminateWorker });
type CreateWorker = ReturnType<typeof createWorker>; type CreateWorker = ReturnType<typeof createWorker>;
@@ -203,7 +232,7 @@ async function getDOMTestRunner(
type BuildResult = { type BuildResult = {
challengeType: number; challengeType: number;
build: string; build?: string;
sources: Source | undefined; sources: Source | undefined;
}; };
@@ -282,10 +311,6 @@ function buildBackendChallenge({ url }: BuildChallengeData) {
}; };
} }
function getTransformedPython(challengeFiles: ChallengeFiles) {
return challengeFiles[0].contents;
}
export function buildPythonChallenge({ export function buildPythonChallenge({
challengeFiles challengeFiles
}: BuildChallengeData): Promise<BuildResult> | undefined { }: BuildChallengeData): Promise<BuildResult> | undefined {
@@ -298,14 +323,8 @@ export function buildPythonChallenge({
.then(checkFilesErrors) .then(checkFilesErrors)
// Unlike the DOM challenges, there's no need to embed the files in HTML // Unlike the DOM challenges, there's no need to embed the files in HTML
.then(challengeFiles => ({ .then(challengeFiles => ({
// TODO: Stop overwriting challengeType with 'html'. Figure out why it's challengeType: challengeTypes.python,
// necessary at the moment. sources: buildSourceMap(challengeFiles)
challengeType: challengeTypes.html,
// Both the terminal and pyodide are loaded into the browser, so we
// still need to build the HTML.
build: createPythonTerminal(pythonRunnerSrc),
sources: buildSourceMap(challengeFiles),
transformedPython: getTransformedPython(challengeFiles)
})) }))
); );
} }
@@ -24,7 +24,6 @@ export interface Context {
build: string; build: string;
sources: Source; sources: Source;
loadEnzyme?: () => void; loadEnzyme?: () => void;
transformedPython?: string;
} }
export interface TestRunnerConfig { export interface TestRunnerConfig {
@@ -268,15 +267,14 @@ const updateWindowI18next = () => (frameContext: Context) => {
const initTestFrame = (frameReady?: () => void) => (frameContext: Context) => { const initTestFrame = (frameReady?: () => void) => (frameContext: Context) => {
waitForFrame(frameContext) waitForFrame(frameContext)
.then(async () => { .then(async () => {
const { sources, loadEnzyme, transformedPython } = frameContext; const { sources, loadEnzyme } = frameContext;
// provide the file name and get the original source // provide the file name and get the original source
const getUserInput = (fileName: string) => const getUserInput = (fileName: string) =>
toString(sources[fileName as keyof typeof sources]); toString(sources[fileName as keyof typeof sources]);
await frameContext.document?.__initTestFrame({ await frameContext.document?.__initTestFrame({
code: sources, code: sources,
getUserInput, getUserInput,
loadEnzyme, loadEnzyme
transformedPython
}); });
if (frameReady) frameReady(); if (frameReady) frameReady();
@@ -0,0 +1,65 @@
import pythonWorkerData from '../../../../config/browser-scripts/python-worker.json';
const pythonWorkerSrc = `/js/${pythonWorkerData.filename}.js`;
let worker: Worker | null = null;
let testWorker: Worker | null = null;
let listener: ((event: MessageEvent) => void) | null = null;
let resetTerminal: (() => void) | null = null;
export function getPythonWorker(): Worker {
if (!worker) {
worker = new Worker(pythonWorkerSrc);
}
return worker;
}
export function getPythonTestWorker(): Worker {
if (!testWorker) {
testWorker = new Worker(pythonWorkerSrc);
}
return testWorker;
}
type PythonWorkerEvent = {
data: {
type: 'print' | 'input' | 'contentLoaded';
text: string;
};
};
/**
* Registers a terminal to receive print and input messages from the python worker.
* @param handlers
* @param handlers.print - A function that handles print messages from the python worker
* @param handlers.input - A function that handles input messages from the python worker
* @param reset - A function that resets the terminal
*/
export function registerTerminal(
handlers: {
print: (text: string) => void;
input: (text: string) => void;
},
reset: () => void
): void {
const pythonWorker = getPythonWorker();
if (listener) pythonWorker.removeEventListener('message', listener);
listener = (event: PythonWorkerEvent) => {
const { type, text } = event.data;
// Ignore contentLoaded messages for now.
if (type === 'contentLoaded') return;
handlers[type](text);
};
pythonWorker.addEventListener('message', listener);
resetTerminal = reset;
}
/**
* Terminates the existing python worker and creates a new one.
*/
export function resetPythonWorker(): void {
if (resetTerminal) resetTerminal();
worker?.terminate();
worker = new Worker(pythonWorkerSrc);
if (listener) worker.addEventListener('message', listener);
}
+23
View File
@@ -0,0 +1,23 @@
self.addEventListener('install', function() {
self.skipWaiting();
});
self.addEventListener('activate', function() {
self.clients.claim();
})
let resolver;
self.onmessage = function(event) {
resolver(event.data);
}
self.addEventListener('fetch', (event) => {
const url = new URL(event.request.url);
if (url.pathname === '/python/intercept-input/') {
const response = new Promise((resolve) => {
resolver = (data) => resolve(new Response(data));
});
event.respondWith(response);
}
});
@@ -13,11 +13,42 @@ Create a function to add two numbers together.
Adding 3 and 5 should return 8. Adding 3 and 5 should return 8.
```js
({
test: () => assert.equal(__userGlobals.get("add")(3,5), 8)
})
```
Test value of `__name__`
```js
({
test: () => assert(__pyodide.runPython(`__name__ == '__main__'`))
})
```
Test __locals and __pyodide
```js
({
test: () => assert(__pyodide.runPython(`__locals.get('add')(4,5) == 9`))
})
```
Test cleaner syntax
```js
({
test: () => assert(runPython(`add(4,5) == 9`))
})
```
Testing getDef
```js ```js
({ ({
test: () => { test: () => {
assert.equal(__userGlobals.get("add")(3,5), 8); const add = __helpers.python.getDef(code, "add");
const add = __helpers.python.getDef(e.code.original["main.py"].replaceAll(/\r/g, ""), "add");
assert.deepEqual(add, { assert.deepEqual(add, {
def: "def add(a, b):\n return a + b\n", def: "def add(a, b):\n return a + b\n",
function_parameters: "a, b", function_parameters: "a, b",
@@ -28,7 +59,6 @@ Adding 3 and 5 should return 8.
}) })
``` ```
# --seed-- # --seed--
## --seed-contents-- ## --seed-contents--
+45 -45
View File
@@ -33,8 +33,10 @@ const {
} = require('../../client/src/templates/Challenges/utils/worker-executor'); } = require('../../client/src/templates/Challenges/utils/worker-executor');
const { challengeTypes } = require('../../shared/config/challenge-types'); const { challengeTypes } = require('../../shared/config/challenge-types');
// the config files are created during the build, but not before linting // the config files are created during the build, but not before linting
const testEvaluator = const javaScriptTestEvaluator =
require('../../client/config/browser-scripts/test-evaluator.json').filename; 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 { getLines } = require('../../shared/utils/get-lines');
@@ -531,28 +533,39 @@ async function createTestRunner(
solutionFiles solutionFiles
); );
const { build, sources, loadEnzyme, transformedPython } = const { build, sources, loadEnzyme } = await buildChallenge(
await buildChallenge( {
{ challengeFiles,
challengeFiles, required,
required, template
template },
}, { usesTestRunner: true }
{ usesTestRunner: true } );
);
const code = { const code = {
contents: sources.index, contents: sources.index,
editableContents: sources.editableContents editableContents: sources.editableContents,
original: sources.original
}; };
const runsInBrowser = const runsInBrowser = buildChallenge === buildDOMChallenge;
buildChallenge === buildDOMChallenge || const runsInPythonWorker = buildChallenge === buildPythonChallenge;
buildChallenge === buildPythonChallenge;
const testEvaluator = runsInPythonWorker
? pythonTestEvaluator
: javaScriptTestEvaluator;
// 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 workerConfig = {
testEvaluator,
options: { terminateWorker: !runsInPythonWorker }
};
const evaluator = await (runsInBrowser const evaluator = await (runsInBrowser
? getContextEvaluator(build, sources, code, loadEnzyme, transformedPython) ? getContextEvaluator(build, sources, code, loadEnzyme)
: getWorkerEvaluator(build, sources, code, removeComments)); : getWorkerEvaluator(build, sources, code, removeComments, workerConfig));
return async ({ text, testString }) => { return async ({ text, testString }) => {
try { try {
@@ -596,20 +609,8 @@ function replaceChallengeFilesContentsWithSolutions(
}); });
} }
async function getContextEvaluator( async function getContextEvaluator(build, sources, code, loadEnzyme) {
build, await initializeTestRunner(build, sources, code, loadEnzyme);
sources,
code,
loadEnzyme,
transformedPython
) {
await initializeTestRunner(
build,
sources,
code,
loadEnzyme,
transformedPython
);
return { return {
evaluate: async (testString, timeout) => evaluate: async (testString, timeout) =>
@@ -624,8 +625,15 @@ async function getContextEvaluator(
}; };
} }
async function getWorkerEvaluator(build, sources, code, removeComments) { async function getWorkerEvaluator(
const testWorker = createWorker(testEvaluator, { terminateWorker: true }); build,
sources,
code,
removeComments,
workerConfig
) {
const { testEvaluator, options } = workerConfig;
const testWorker = createWorker(testEvaluator, options);
return { return {
evaluate: async (testString, timeout) => evaluate: async (testString, timeout) =>
await testWorker.execute( await testWorker.execute(
@@ -635,30 +643,22 @@ async function getWorkerEvaluator(build, sources, code, removeComments) {
}; };
} }
async function initializeTestRunner( async function initializeTestRunner(build, sources, code, loadEnzyme) {
build,
sources,
code,
loadEnzyme,
transformedPython
) {
await page.reload(); await page.reload();
await page.setContent(build); await page.setContent(build);
await page.evaluate( await page.evaluate(
async (code, sources, loadEnzyme, transformedPython) => { async (code, sources, loadEnzyme) => {
const getUserInput = fileName => sources[fileName]; const getUserInput = fileName => sources[fileName];
// TODO: use frame's functions directly, so it behaves more like the // TODO: use frame's functions directly, so it behaves more like the
// client. Also, keep an eye on performance - loading pyodide is slow. // client.
await document.__initTestFrame({ await document.__initTestFrame({
code: sources, code: sources,
getUserInput, getUserInput,
loadEnzyme, loadEnzyme
transformedPython
}); });
}, },
code, code,
sources, sources,
loadEnzyme, loadEnzyme
transformedPython
); );
} }
+136 -81
View File
@@ -595,7 +595,7 @@ importers:
version: 4.20.10 version: 4.20.10
gatsby: gatsby:
specifier: 3.15.0 specifier: 3.15.0
version: 3.15.0(@types/node@20.8.2)(babel-eslint@10.1.0)(eslint-import-resolver-typescript@3.5.5)(eslint-plugin-testing-library@3.9.0)(react-dom@16.14.0)(react@16.14.0)(typescript@5.2.2)(webpack-cli@4.10.0) version: 3.15.0(@types/node@18.18.9)(babel-eslint@10.1.0)(eslint-import-resolver-typescript@3.5.5)(eslint-plugin-testing-library@3.9.0)(react-dom@16.14.0)(react@16.14.0)(typescript@5.2.2)(webpack-cli@4.10.0)
gatsby-cli: gatsby-cli:
specifier: 3.15.0 specifier: 3.15.0
version: 3.15.0 version: 3.15.0
@@ -782,6 +782,12 @@ importers:
validator: validator:
specifier: 13.11.0 specifier: 13.11.0
version: 13.11.0 version: 13.11.0
xterm:
specifier: ^5.2.1
version: 5.2.1
xterm-addon-fit:
specifier: ^0.8.0
version: 0.8.0(xterm@5.2.1)
devDependencies: devDependencies:
'@babel/plugin-syntax-dynamic-import': '@babel/plugin-syntax-dynamic-import':
specifier: 7.8.3 specifier: 7.8.3
@@ -947,7 +953,7 @@ importers:
version: 13.0.4 version: 13.0.4
ts-node: ts-node:
specifier: 10.9.1 specifier: 10.9.1
version: 10.9.1(@types/node@20.8.2)(typescript@5.2.2) version: 10.9.1(@types/node@18.18.9)(typescript@5.2.2)
webpack: webpack:
specifier: 5.89.0 specifier: 5.89.0
version: 5.89.0(webpack-cli@4.10.0) version: 5.89.0(webpack-cli@4.10.0)
@@ -2650,7 +2656,7 @@ packages:
'@babel/traverse': 7.23.2 '@babel/traverse': 7.23.2
'@babel/types': 7.23.0 '@babel/types': 7.23.0
convert-source-map: 1.9.0 convert-source-map: 1.9.0
debug: 4.3.4(supports-color@8.1.1) debug: 4.3.4
gensync: 1.0.0-beta.2 gensync: 1.0.0-beta.2
json5: 2.2.3 json5: 2.2.3
lodash: 4.17.21 lodash: 4.17.21
@@ -2722,7 +2728,7 @@ packages:
'@babel/traverse': 7.23.2 '@babel/traverse': 7.23.2
'@babel/types': 7.23.0 '@babel/types': 7.23.0
convert-source-map: 2.0.0 convert-source-map: 2.0.0
debug: 4.3.4(supports-color@8.1.1) debug: 4.3.4
gensync: 1.0.0-beta.2 gensync: 1.0.0-beta.2
json5: 2.2.3 json5: 2.2.3
semver: 6.3.1 semver: 6.3.1
@@ -2744,7 +2750,7 @@ packages:
'@babel/traverse': 7.23.3 '@babel/traverse': 7.23.3
'@babel/types': 7.23.3 '@babel/types': 7.23.3
convert-source-map: 2.0.0 convert-source-map: 2.0.0
debug: 4.3.4(supports-color@8.1.1) debug: 4.3.4
gensync: 1.0.0-beta.2 gensync: 1.0.0-beta.2
json5: 2.2.3 json5: 2.2.3
semver: 6.3.1 semver: 6.3.1
@@ -2946,7 +2952,7 @@ packages:
'@babel/core': 7.23.3 '@babel/core': 7.23.3
'@babel/helper-compilation-targets': 7.22.15 '@babel/helper-compilation-targets': 7.22.15
'@babel/helper-plugin-utils': 7.22.5 '@babel/helper-plugin-utils': 7.22.5
debug: 4.3.4(supports-color@8.1.1) debug: 4.3.4
lodash.debounce: 4.0.8 lodash.debounce: 4.0.8
resolve: 1.22.6 resolve: 1.22.6
transitivePeerDependencies: transitivePeerDependencies:
@@ -2961,7 +2967,7 @@ packages:
'@babel/core': 7.23.0 '@babel/core': 7.23.0
'@babel/helper-compilation-targets': 7.22.15 '@babel/helper-compilation-targets': 7.22.15
'@babel/helper-plugin-utils': 7.22.5 '@babel/helper-plugin-utils': 7.22.5
debug: 4.3.4(supports-color@8.1.1) debug: 4.3.4
lodash.debounce: 4.0.8 lodash.debounce: 4.0.8
resolve: 1.22.6 resolve: 1.22.6
transitivePeerDependencies: transitivePeerDependencies:
@@ -2975,7 +2981,7 @@ packages:
'@babel/core': 7.23.3 '@babel/core': 7.23.3
'@babel/helper-compilation-targets': 7.22.15 '@babel/helper-compilation-targets': 7.22.15
'@babel/helper-plugin-utils': 7.22.5 '@babel/helper-plugin-utils': 7.22.5
debug: 4.3.4(supports-color@8.1.1) debug: 4.3.4
lodash.debounce: 4.0.8 lodash.debounce: 4.0.8
resolve: 1.22.6 resolve: 1.22.6
transitivePeerDependencies: transitivePeerDependencies:
@@ -6408,7 +6414,7 @@ packages:
'@babel/helper-split-export-declaration': 7.22.6 '@babel/helper-split-export-declaration': 7.22.6
'@babel/parser': 7.23.0 '@babel/parser': 7.23.0
'@babel/types': 7.23.0 '@babel/types': 7.23.0
debug: 4.3.4(supports-color@8.1.1) debug: 4.3.4
globals: 11.12.0 globals: 11.12.0
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@@ -6425,7 +6431,7 @@ packages:
'@babel/helper-split-export-declaration': 7.22.6 '@babel/helper-split-export-declaration': 7.22.6
'@babel/parser': 7.23.0 '@babel/parser': 7.23.0
'@babel/types': 7.23.0 '@babel/types': 7.23.0
debug: 4.3.4(supports-color@8.1.1) debug: 4.3.4
globals: 11.12.0 globals: 11.12.0
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@@ -6442,7 +6448,7 @@ packages:
'@babel/helper-split-export-declaration': 7.22.6 '@babel/helper-split-export-declaration': 7.22.6
'@babel/parser': 7.23.3 '@babel/parser': 7.23.3
'@babel/types': 7.23.3 '@babel/types': 7.23.3
debug: 4.3.4(supports-color@8.1.1) debug: 4.3.4
globals: 11.12.0 globals: 11.12.0
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@@ -6812,7 +6818,7 @@ packages:
engines: {node: ^10.12.0 || >=12.0.0} engines: {node: ^10.12.0 || >=12.0.0}
dependencies: dependencies:
ajv: 6.12.6 ajv: 6.12.6
debug: 4.3.4(supports-color@8.1.1) debug: 4.3.4
espree: 7.3.1 espree: 7.3.1
globals: 13.22.0 globals: 13.22.0
ignore: 4.0.6 ignore: 4.0.6
@@ -6828,7 +6834,7 @@ packages:
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dependencies: dependencies:
ajv: 6.12.6 ajv: 6.12.6
debug: 4.3.4(supports-color@8.1.1) debug: 4.3.4
espree: 9.6.1 espree: 9.6.1
globals: 13.22.0 globals: 13.22.0
ignore: 5.2.4 ignore: 5.2.4
@@ -7197,7 +7203,7 @@ packages:
tslib: 2.2.0 tslib: 2.2.0
value-or-promise: 1.0.6 value-or-promise: 1.0.6
/@graphql-tools/url-loader@6.10.1(@types/node@20.8.2)(graphql@15.8.0): /@graphql-tools/url-loader@6.10.1(@types/node@18.18.9)(graphql@15.8.0):
resolution: {integrity: sha512-DSDrbhQIv7fheQ60pfDpGD256ixUQIR6Hhf9Z5bRjVkXOCvO5XrkwoWLiU7iHL81GB1r0Ba31bf+sl+D4nyyfw==} resolution: {integrity: sha512-DSDrbhQIv7fheQ60pfDpGD256ixUQIR6Hhf9Z5bRjVkXOCvO5XrkwoWLiU7iHL81GB1r0Ba31bf+sl+D4nyyfw==}
peerDependencies: peerDependencies:
graphql: ^14.0.0 || ^15.0.0 graphql: ^14.0.0 || ^15.0.0
@@ -7216,7 +7222,7 @@ packages:
is-promise: 4.0.0 is-promise: 4.0.0
isomorphic-ws: 4.0.1(ws@7.4.5) isomorphic-ws: 4.0.1(ws@7.4.5)
lodash: 4.17.21 lodash: 4.17.21
meros: 1.1.4(@types/node@20.8.2) meros: 1.1.4(@types/node@18.18.9)
subscriptions-transport-ws: 0.9.19(graphql@15.8.0) subscriptions-transport-ws: 0.9.19(graphql@15.8.0)
sync-fetch: 0.3.0 sync-fetch: 0.3.0
tslib: 2.2.0 tslib: 2.2.0
@@ -7335,7 +7341,7 @@ packages:
engines: {node: '>=10.10.0'} engines: {node: '>=10.10.0'}
dependencies: dependencies:
'@humanwhocodes/object-schema': 2.0.1 '@humanwhocodes/object-schema': 2.0.1
debug: 4.3.4(supports-color@8.1.1) debug: 4.3.4
minimatch: 3.1.2 minimatch: 3.1.2
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@@ -7345,7 +7351,7 @@ packages:
engines: {node: '>=10.10.0'} engines: {node: '>=10.10.0'}
dependencies: dependencies:
'@humanwhocodes/object-schema': 1.2.1 '@humanwhocodes/object-schema': 1.2.1
debug: 4.3.4(supports-color@8.1.1) debug: 4.3.4
minimatch: 3.1.2 minimatch: 3.1.2
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@@ -11251,7 +11257,7 @@ packages:
'@typescript-eslint/experimental-utils': 4.33.0(eslint@7.32.0)(typescript@5.2.2) '@typescript-eslint/experimental-utils': 4.33.0(eslint@7.32.0)(typescript@5.2.2)
'@typescript-eslint/parser': 4.33.0(eslint@7.32.0)(typescript@5.2.2) '@typescript-eslint/parser': 4.33.0(eslint@7.32.0)(typescript@5.2.2)
'@typescript-eslint/scope-manager': 4.33.0 '@typescript-eslint/scope-manager': 4.33.0
debug: 4.3.4(supports-color@8.1.1) debug: 4.3.4
eslint: 7.32.0 eslint: 7.32.0
functional-red-black-tree: 1.0.1 functional-red-black-tree: 1.0.1
ignore: 5.2.4 ignore: 5.2.4
@@ -11279,7 +11285,7 @@ packages:
'@typescript-eslint/type-utils': 6.10.0(eslint@8.53.0)(typescript@5.2.2) '@typescript-eslint/type-utils': 6.10.0(eslint@8.53.0)(typescript@5.2.2)
'@typescript-eslint/utils': 6.10.0(eslint@8.53.0)(typescript@5.2.2) '@typescript-eslint/utils': 6.10.0(eslint@8.53.0)(typescript@5.2.2)
'@typescript-eslint/visitor-keys': 6.10.0 '@typescript-eslint/visitor-keys': 6.10.0
debug: 4.3.4(supports-color@8.1.1) debug: 4.3.4
eslint: 8.53.0 eslint: 8.53.0
graphemer: 1.4.0 graphemer: 1.4.0
ignore: 5.2.4 ignore: 5.2.4
@@ -11337,7 +11343,7 @@ packages:
'@typescript-eslint/scope-manager': 4.33.0 '@typescript-eslint/scope-manager': 4.33.0
'@typescript-eslint/types': 4.33.0 '@typescript-eslint/types': 4.33.0
'@typescript-eslint/typescript-estree': 4.33.0(typescript@5.2.2) '@typescript-eslint/typescript-estree': 4.33.0(typescript@5.2.2)
debug: 4.3.4(supports-color@8.1.1) debug: 4.3.4
eslint: 7.32.0 eslint: 7.32.0
typescript: 5.2.2 typescript: 5.2.2
transitivePeerDependencies: transitivePeerDependencies:
@@ -11357,7 +11363,7 @@ packages:
'@typescript-eslint/types': 6.10.0 '@typescript-eslint/types': 6.10.0
'@typescript-eslint/typescript-estree': 6.10.0(typescript@5.2.2) '@typescript-eslint/typescript-estree': 6.10.0(typescript@5.2.2)
'@typescript-eslint/visitor-keys': 6.10.0 '@typescript-eslint/visitor-keys': 6.10.0
debug: 4.3.4(supports-color@8.1.1) debug: 4.3.4
eslint: 8.53.0 eslint: 8.53.0
typescript: 5.2.2 typescript: 5.2.2
transitivePeerDependencies: transitivePeerDependencies:
@@ -11397,7 +11403,7 @@ packages:
dependencies: dependencies:
'@typescript-eslint/typescript-estree': 6.10.0(typescript@5.2.2) '@typescript-eslint/typescript-estree': 6.10.0(typescript@5.2.2)
'@typescript-eslint/utils': 6.10.0(eslint@8.53.0)(typescript@5.2.2) '@typescript-eslint/utils': 6.10.0(eslint@8.53.0)(typescript@5.2.2)
debug: 4.3.4(supports-color@8.1.1) debug: 4.3.4
eslint: 8.53.0 eslint: 8.53.0
ts-api-utils: 1.0.3(typescript@5.2.2) ts-api-utils: 1.0.3(typescript@5.2.2)
typescript: 5.2.2 typescript: 5.2.2
@@ -11433,7 +11439,7 @@ packages:
dependencies: dependencies:
'@typescript-eslint/types': 3.10.1 '@typescript-eslint/types': 3.10.1
'@typescript-eslint/visitor-keys': 3.10.1 '@typescript-eslint/visitor-keys': 3.10.1
debug: 4.3.4(supports-color@8.1.1) debug: 4.3.4
glob: 7.2.3 glob: 7.2.3
is-glob: 4.0.3 is-glob: 4.0.3
lodash: 4.17.21 lodash: 4.17.21
@@ -11454,7 +11460,7 @@ packages:
dependencies: dependencies:
'@typescript-eslint/types': 4.33.0 '@typescript-eslint/types': 4.33.0
'@typescript-eslint/visitor-keys': 4.33.0 '@typescript-eslint/visitor-keys': 4.33.0
debug: 4.3.4(supports-color@8.1.1) debug: 4.3.4
globby: 11.1.0 globby: 11.1.0
is-glob: 4.0.3 is-glob: 4.0.3
semver: 7.5.4 semver: 7.5.4
@@ -11474,7 +11480,7 @@ packages:
dependencies: dependencies:
'@typescript-eslint/types': 5.62.0 '@typescript-eslint/types': 5.62.0
'@typescript-eslint/visitor-keys': 5.62.0 '@typescript-eslint/visitor-keys': 5.62.0
debug: 4.3.4(supports-color@8.1.1) debug: 4.3.4
globby: 11.1.0 globby: 11.1.0
is-glob: 4.0.3 is-glob: 4.0.3
semver: 7.5.4 semver: 7.5.4
@@ -11495,7 +11501,7 @@ packages:
dependencies: dependencies:
'@typescript-eslint/types': 6.10.0 '@typescript-eslint/types': 6.10.0
'@typescript-eslint/visitor-keys': 6.10.0 '@typescript-eslint/visitor-keys': 6.10.0
debug: 4.3.4(supports-color@8.1.1) debug: 4.3.4
globby: 11.1.0 globby: 11.1.0
is-glob: 4.0.3 is-glob: 4.0.3
semver: 7.5.4 semver: 7.5.4
@@ -11978,7 +11984,7 @@ packages:
resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==}
engines: {node: '>= 6.0.0'} engines: {node: '>= 6.0.0'}
dependencies: dependencies:
debug: 4.3.4(supports-color@8.1.1) debug: 4.3.4
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@@ -13201,7 +13207,7 @@ packages:
'@babel/core': 7.23.0 '@babel/core': 7.23.0
'@babel/runtime': 7.23.1 '@babel/runtime': 7.23.1
'@babel/types': 7.23.0 '@babel/types': 7.23.0
gatsby: 3.15.0(@types/node@20.8.2)(babel-eslint@10.1.0)(eslint-import-resolver-typescript@3.5.5)(eslint-plugin-testing-library@3.9.0)(react-dom@16.14.0)(react@16.14.0)(typescript@5.2.2)(webpack-cli@4.10.0) gatsby: 3.15.0(@types/node@18.18.9)(babel-eslint@10.1.0)(eslint-import-resolver-typescript@3.5.5)(eslint-plugin-testing-library@3.9.0)(react-dom@16.14.0)(react@16.14.0)(typescript@5.2.2)(webpack-cli@4.10.0)
gatsby-core-utils: 2.15.0 gatsby-core-utils: 2.15.0
/babel-plugin-remove-graphql-queries@3.15.0(@babel/core@7.23.3)(gatsby@3.15.0): /babel-plugin-remove-graphql-queries@3.15.0(@babel/core@7.23.3)(gatsby@3.15.0):
@@ -13214,7 +13220,7 @@ packages:
'@babel/core': 7.23.3 '@babel/core': 7.23.3
'@babel/runtime': 7.23.1 '@babel/runtime': 7.23.1
'@babel/types': 7.23.0 '@babel/types': 7.23.0
gatsby: 3.15.0(@types/node@20.8.2)(babel-eslint@10.1.0)(eslint-import-resolver-typescript@3.5.5)(eslint-plugin-testing-library@3.9.0)(react-dom@16.14.0)(react@16.14.0)(typescript@5.2.2)(webpack-cli@4.10.0) gatsby: 3.15.0(@types/node@18.18.9)(babel-eslint@10.1.0)(eslint-import-resolver-typescript@3.5.5)(eslint-plugin-testing-library@3.9.0)(react-dom@16.14.0)(react@16.14.0)(typescript@5.2.2)(webpack-cli@4.10.0)
gatsby-core-utils: 2.15.0 gatsby-core-utils: 2.15.0
/babel-plugin-syntax-async-functions@6.13.0: /babel-plugin-syntax-async-functions@6.13.0:
@@ -15977,6 +15983,16 @@ packages:
ms: 2.0.0 ms: 2.0.0
dev: false dev: false
/debug@3.2.7:
resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==}
peerDependencies:
supports-color: '*'
peerDependenciesMeta:
supports-color:
optional: true
dependencies:
ms: 2.1.3
/debug@3.2.7(supports-color@5.5.0): /debug@3.2.7(supports-color@5.5.0):
resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==}
peerDependencies: peerDependencies:
@@ -16011,6 +16027,17 @@ packages:
ms: 2.1.2 ms: 2.1.2
dev: true dev: true
/debug@4.3.4:
resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
engines: {node: '>=6.0'}
peerDependencies:
supports-color: '*'
peerDependenciesMeta:
supports-color:
optional: true
dependencies:
ms: 2.1.2
/debug@4.3.4(supports-color@8.1.1): /debug@4.3.4(supports-color@8.1.1):
resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
engines: {node: '>=6.0'} engines: {node: '>=6.0'}
@@ -16308,7 +16335,7 @@ packages:
hasBin: true hasBin: true
dependencies: dependencies:
address: 1.1.2 address: 1.1.2
debug: 4.3.4(supports-color@8.1.1) debug: 4.3.4
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@@ -16336,7 +16363,7 @@ packages:
'@types/tmp': 0.0.33 '@types/tmp': 0.0.33
application-config-path: 0.1.1 application-config-path: 0.1.1
command-exists: 1.2.9 command-exists: 1.2.9
debug: 3.2.7(supports-color@8.1.1) debug: 3.2.7
eol: 0.9.1 eol: 0.9.1
get-port: 3.2.0 get-port: 3.2.0
glob: 7.2.3 glob: 7.2.3
@@ -16464,7 +16491,7 @@ packages:
/docsify-server-renderer@4.13.1: /docsify-server-renderer@4.13.1:
resolution: {integrity: sha512-XNJeCK3zp+mVO7JZFn0bH4hNBAMMC1MbuCU7CBsjLHYn4NHrjIgCBGmylzEan3/4Qm6kbSzQx8XzUK5T7GQxHw==} resolution: {integrity: sha512-XNJeCK3zp+mVO7JZFn0bH4hNBAMMC1MbuCU7CBsjLHYn4NHrjIgCBGmylzEan3/4Qm6kbSzQx8XzUK5T7GQxHw==}
dependencies: dependencies:
debug: 4.3.4(supports-color@8.1.1) debug: 4.3.4
docsify: 4.13.1 docsify: 4.13.1
node-fetch: 2.7.0 node-fetch: 2.7.0
resolve-pathname: 3.0.0 resolve-pathname: 3.0.0
@@ -16765,7 +16792,7 @@ packages:
dependencies: dependencies:
base64-arraybuffer: 0.1.4 base64-arraybuffer: 0.1.4
component-emitter: 1.3.0 component-emitter: 1.3.0
debug: 4.3.4(supports-color@8.1.1) debug: 4.3.4
engine.io-parser: 4.0.3 engine.io-parser: 4.0.3
has-cors: 1.1.0 has-cors: 1.1.0
parseqs: 0.0.6 parseqs: 0.0.6
@@ -16792,7 +16819,7 @@ packages:
base64id: 2.0.0 base64id: 2.0.0
cookie: 0.4.2 cookie: 0.4.2
cors: 2.8.5 cors: 2.8.5
debug: 4.3.4(supports-color@8.1.1) debug: 4.3.4
engine.io-parser: 4.0.3 engine.io-parser: 4.0.3
ws: 7.4.6 ws: 7.4.6
transitivePeerDependencies: transitivePeerDependencies:
@@ -17198,7 +17225,7 @@ packages:
/eslint-import-resolver-node@0.3.9: /eslint-import-resolver-node@0.3.9:
resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==}
dependencies: dependencies:
debug: 3.2.7(supports-color@8.1.1) debug: 3.2.7
is-core-module: 2.13.1 is-core-module: 2.13.1
resolve: 1.22.6 resolve: 1.22.6
transitivePeerDependencies: transitivePeerDependencies:
@@ -17211,10 +17238,10 @@ packages:
eslint: '*' eslint: '*'
eslint-plugin-import: '*' eslint-plugin-import: '*'
dependencies: dependencies:
debug: 4.3.4(supports-color@8.1.1) debug: 4.3.4
enhanced-resolve: 5.15.0 enhanced-resolve: 5.15.0
eslint: 8.53.0 eslint: 8.53.0
eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.10.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.5.5)(eslint@8.53.0) eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.10.0)(eslint-import-resolver-typescript@3.5.5)(eslint@8.53.0)
eslint-plugin-import: 2.29.0(@typescript-eslint/parser@6.10.0)(eslint-import-resolver-typescript@3.5.5)(eslint@8.53.0) eslint-plugin-import: 2.29.0(@typescript-eslint/parser@6.10.0)(eslint-import-resolver-typescript@3.5.5)(eslint@8.53.0)
get-tsconfig: 4.7.2 get-tsconfig: 4.7.2
globby: 13.2.2 globby: 13.2.2
@@ -17249,7 +17276,7 @@ packages:
optional: true optional: true
dependencies: dependencies:
'@typescript-eslint/parser': 4.33.0(eslint@7.32.0)(typescript@5.2.2) '@typescript-eslint/parser': 4.33.0(eslint@7.32.0)(typescript@5.2.2)
debug: 3.2.7(supports-color@8.1.1) debug: 3.2.7
eslint: 7.32.0 eslint: 7.32.0
eslint-import-resolver-node: 0.3.9 eslint-import-resolver-node: 0.3.9
eslint-import-resolver-typescript: 3.5.5(@typescript-eslint/parser@6.10.0)(eslint-plugin-import@2.29.0)(eslint@8.53.0) eslint-import-resolver-typescript: 3.5.5(@typescript-eslint/parser@6.10.0)(eslint-plugin-import@2.29.0)(eslint@8.53.0)
@@ -17278,13 +17305,41 @@ packages:
optional: true optional: true
dependencies: dependencies:
'@typescript-eslint/parser': 6.10.0(eslint@8.53.0)(typescript@5.2.2) '@typescript-eslint/parser': 6.10.0(eslint@8.53.0)(typescript@5.2.2)
debug: 3.2.7(supports-color@8.1.1) debug: 3.2.7
eslint: 8.53.0 eslint: 8.53.0
eslint-import-resolver-node: 0.3.9 eslint-import-resolver-node: 0.3.9
eslint-import-resolver-typescript: 3.5.5(@typescript-eslint/parser@6.10.0)(eslint-plugin-import@2.29.0)(eslint@8.53.0) eslint-import-resolver-typescript: 3.5.5(@typescript-eslint/parser@6.10.0)(eslint-plugin-import@2.29.0)(eslint@8.53.0)
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
/eslint-module-utils@2.8.0(@typescript-eslint/parser@6.10.0)(eslint-import-resolver-typescript@3.5.5)(eslint@8.53.0):
resolution: {integrity: sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==}
engines: {node: '>=4'}
peerDependencies:
'@typescript-eslint/parser': '*'
eslint: '*'
eslint-import-resolver-node: '*'
eslint-import-resolver-typescript: '*'
eslint-import-resolver-webpack: '*'
peerDependenciesMeta:
'@typescript-eslint/parser':
optional: true
eslint:
optional: true
eslint-import-resolver-node:
optional: true
eslint-import-resolver-typescript:
optional: true
eslint-import-resolver-webpack:
optional: true
dependencies:
'@typescript-eslint/parser': 6.10.0(eslint@8.53.0)(typescript@5.2.2)
debug: 3.2.7
eslint: 8.53.0
eslint-import-resolver-typescript: 3.5.5(@typescript-eslint/parser@6.10.0)(eslint-plugin-import@2.29.0)(eslint@8.53.0)
transitivePeerDependencies:
- supports-color
/eslint-plugin-filenames-simple@0.8.0(eslint@8.53.0): /eslint-plugin-filenames-simple@0.8.0(eslint@8.53.0):
resolution: {integrity: sha512-8+uBzNBE5gSUMQv7bmMBiOD26eKzD4/5flPtD5Vl3dzZLXotSwXK3W7ZZqKQfU0Qyoborh+LqbN76EfmbBcU8A==} resolution: {integrity: sha512-8+uBzNBE5gSUMQv7bmMBiOD26eKzD4/5flPtD5Vl3dzZLXotSwXK3W7ZZqKQfU0Qyoborh+LqbN76EfmbBcU8A==}
engines: {node: ^14.17.0 || ^16.0.0 || ^18.0.0} engines: {node: ^14.17.0 || ^16.0.0 || ^18.0.0}
@@ -17305,7 +17360,7 @@ packages:
lodash: 4.17.21 lodash: 4.17.21
string-natural-compare: 3.0.1 string-natural-compare: 3.0.1
/eslint-plugin-graphql@4.0.0(@types/node@20.8.2)(graphql@15.8.0)(typescript@5.2.2): /eslint-plugin-graphql@4.0.0(@types/node@18.18.9)(graphql@15.8.0)(typescript@5.2.2):
resolution: {integrity: sha512-d5tQm24YkVvCEk29ZR5ScsgXqAGCjKlMS8lx3mS7FS/EKsWbkvXQImpvic03EpMIvNTBW5e+2xnHzXB/VHNZJw==} resolution: {integrity: sha512-d5tQm24YkVvCEk29ZR5ScsgXqAGCjKlMS8lx3mS7FS/EKsWbkvXQImpvic03EpMIvNTBW5e+2xnHzXB/VHNZJw==}
engines: {node: '>=10.0'} engines: {node: '>=10.0'}
peerDependencies: peerDependencies:
@@ -17313,7 +17368,7 @@ packages:
dependencies: dependencies:
'@babel/runtime': 7.23.1 '@babel/runtime': 7.23.1
graphql: 15.8.0 graphql: 15.8.0
graphql-config: 3.4.1(@types/node@20.8.2)(graphql@15.8.0)(typescript@5.2.2) graphql-config: 3.4.1(@types/node@18.18.9)(graphql@15.8.0)(typescript@5.2.2)
lodash.flatten: 4.4.0 lodash.flatten: 4.4.0
lodash.without: 4.4.0 lodash.without: 4.4.0
transitivePeerDependencies: transitivePeerDependencies:
@@ -17338,7 +17393,7 @@ packages:
array.prototype.findlastindex: 1.2.3 array.prototype.findlastindex: 1.2.3
array.prototype.flat: 1.3.2 array.prototype.flat: 1.3.2
array.prototype.flatmap: 1.3.2 array.prototype.flatmap: 1.3.2
debug: 3.2.7(supports-color@8.1.1) debug: 3.2.7
doctrine: 2.1.0 doctrine: 2.1.0
eslint: 7.32.0 eslint: 7.32.0
eslint-import-resolver-node: 0.3.9 eslint-import-resolver-node: 0.3.9
@@ -17372,7 +17427,7 @@ packages:
array.prototype.findlastindex: 1.2.3 array.prototype.findlastindex: 1.2.3
array.prototype.flat: 1.3.2 array.prototype.flat: 1.3.2
array.prototype.flatmap: 1.3.2 array.prototype.flatmap: 1.3.2
debug: 3.2.7(supports-color@8.1.1) debug: 3.2.7
doctrine: 2.1.0 doctrine: 2.1.0
eslint: 8.53.0 eslint: 8.53.0
eslint-import-resolver-node: 0.3.9 eslint-import-resolver-node: 0.3.9
@@ -17412,7 +17467,7 @@ packages:
'@es-joy/jsdoccomment': 0.39.4 '@es-joy/jsdoccomment': 0.39.4
are-docs-informative: 0.0.2 are-docs-informative: 0.0.2
comment-parser: 1.3.1 comment-parser: 1.3.1
debug: 4.3.4(supports-color@8.1.1) debug: 4.3.4
escape-string-regexp: 4.0.0 escape-string-regexp: 4.0.0
eslint: 8.53.0 eslint: 8.53.0
esquery: 1.5.0 esquery: 1.5.0
@@ -17643,7 +17698,7 @@ packages:
ajv: 6.12.6 ajv: 6.12.6
chalk: 4.1.2 chalk: 4.1.2
cross-spawn: 7.0.3 cross-spawn: 7.0.3
debug: 4.3.4(supports-color@8.1.1) debug: 4.3.4
doctrine: 3.0.0 doctrine: 3.0.0
enquirer: 2.4.1 enquirer: 2.4.1
escape-string-regexp: 4.0.0 escape-string-regexp: 4.0.0
@@ -17696,7 +17751,7 @@ packages:
ajv: 6.12.6 ajv: 6.12.6
chalk: 4.1.2 chalk: 4.1.2
cross-spawn: 7.0.3 cross-spawn: 7.0.3
debug: 4.3.4(supports-color@8.1.1) debug: 4.3.4
doctrine: 3.0.0 doctrine: 3.0.0
escape-string-regexp: 4.0.0 escape-string-regexp: 4.0.0
eslint-scope: 7.2.2 eslint-scope: 7.2.2
@@ -18616,7 +18671,7 @@ packages:
debug: debug:
optional: true optional: true
dependencies: dependencies:
debug: 3.2.7(supports-color@8.1.1) debug: 3.2.7
/follow-redirects@1.15.3(debug@4.3.4): /follow-redirects@1.15.3(debug@4.3.4):
resolution: {integrity: sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==} resolution: {integrity: sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==}
@@ -18627,7 +18682,7 @@ packages:
debug: debug:
optional: true optional: true
dependencies: dependencies:
debug: 4.3.4(supports-color@8.1.1) debug: 4.3.4
/for-each@0.3.3: /for-each@0.3.3:
resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==}
@@ -19091,7 +19146,7 @@ packages:
dependencies: dependencies:
'@babel/runtime': 7.14.0 '@babel/runtime': 7.14.0
fs-extra: 10.0.1 fs-extra: 10.0.1
gatsby: 3.15.0(@types/node@20.8.2)(babel-eslint@10.1.0)(eslint-import-resolver-typescript@3.5.5)(eslint-plugin-testing-library@3.9.0)(react-dom@16.14.0)(react@16.14.0)(typescript@5.2.2)(webpack-cli@4.10.0) gatsby: 3.15.0(@types/node@18.18.9)(babel-eslint@10.1.0)(eslint-import-resolver-typescript@3.5.5)(eslint-plugin-testing-library@3.9.0)(react-dom@16.14.0)(react@16.14.0)(typescript@5.2.2)(webpack-cli@4.10.0)
lodash: 4.17.21 lodash: 4.17.21
moment: 2.29.1 moment: 2.29.1
pify: 5.0.0 pify: 5.0.0
@@ -19105,7 +19160,7 @@ packages:
gatsby: ^3.0.0-next.0 gatsby: ^3.0.0-next.0
dependencies: dependencies:
'@babel/runtime': 7.20.13 '@babel/runtime': 7.20.13
gatsby: 3.15.0(@types/node@20.8.2)(babel-eslint@10.1.0)(eslint-import-resolver-typescript@3.5.5)(eslint-plugin-testing-library@3.9.0)(react-dom@16.14.0)(react@16.14.0)(typescript@5.2.2)(webpack-cli@4.10.0) gatsby: 3.15.0(@types/node@18.18.9)(babel-eslint@10.1.0)(eslint-import-resolver-typescript@3.5.5)(eslint-plugin-testing-library@3.9.0)(react-dom@16.14.0)(react@16.14.0)(typescript@5.2.2)(webpack-cli@4.10.0)
dev: false dev: false
/gatsby-plugin-manifest@3.15.0(gatsby@3.15.0)(graphql@15.8.0): /gatsby-plugin-manifest@3.15.0(gatsby@3.15.0)(graphql@15.8.0):
@@ -19115,7 +19170,7 @@ packages:
gatsby: ^3.0.0-next.0 gatsby: ^3.0.0-next.0
dependencies: dependencies:
'@babel/runtime': 7.20.13 '@babel/runtime': 7.20.13
gatsby: 3.15.0(@types/node@20.8.2)(babel-eslint@10.1.0)(eslint-import-resolver-typescript@3.5.5)(eslint-plugin-testing-library@3.9.0)(react-dom@16.14.0)(react@16.14.0)(typescript@5.2.2)(webpack-cli@4.10.0) gatsby: 3.15.0(@types/node@18.18.9)(babel-eslint@10.1.0)(eslint-import-resolver-typescript@3.5.5)(eslint-plugin-testing-library@3.9.0)(react-dom@16.14.0)(react@16.14.0)(typescript@5.2.2)(webpack-cli@4.10.0)
gatsby-core-utils: 2.15.0 gatsby-core-utils: 2.15.0
gatsby-plugin-utils: 1.15.0(gatsby@3.15.0)(graphql@15.8.0) gatsby-plugin-utils: 1.15.0(gatsby@3.15.0)(graphql@15.8.0)
semver: 7.5.4 semver: 7.5.4
@@ -19136,7 +19191,7 @@ packages:
chokidar: 3.5.3 chokidar: 3.5.3
fs-exists-cached: 1.0.0 fs-exists-cached: 1.0.0
fs-extra: 10.1.0 fs-extra: 10.1.0
gatsby: 3.15.0(@types/node@20.8.2)(babel-eslint@10.1.0)(eslint-import-resolver-typescript@3.5.5)(eslint-plugin-testing-library@3.9.0)(react-dom@16.14.0)(react@16.14.0)(typescript@5.2.2)(webpack-cli@4.10.0) gatsby: 3.15.0(@types/node@18.18.9)(babel-eslint@10.1.0)(eslint-import-resolver-typescript@3.5.5)(eslint-plugin-testing-library@3.9.0)(react-dom@16.14.0)(react@16.14.0)(typescript@5.2.2)(webpack-cli@4.10.0)
gatsby-core-utils: 2.15.0 gatsby-core-utils: 2.15.0
gatsby-page-utils: 1.15.0 gatsby-page-utils: 1.15.0
gatsby-plugin-utils: 1.15.0(gatsby@3.15.0)(graphql@15.8.0) gatsby-plugin-utils: 1.15.0(gatsby@3.15.0)(graphql@15.8.0)
@@ -19153,7 +19208,7 @@ packages:
peerDependencies: peerDependencies:
gatsby: ~2.x.x || ~3.x.x || ~4.x.x gatsby: ~2.x.x || ~3.x.x || ~4.x.x
dependencies: dependencies:
gatsby: 3.15.0(@types/node@20.8.2)(babel-eslint@10.1.0)(eslint-import-resolver-typescript@3.5.5)(eslint-plugin-testing-library@3.9.0)(react-dom@16.14.0)(react@16.14.0)(typescript@5.2.2)(webpack-cli@4.10.0) gatsby: 3.15.0(@types/node@18.18.9)(babel-eslint@10.1.0)(eslint-import-resolver-typescript@3.5.5)(eslint-plugin-testing-library@3.9.0)(react-dom@16.14.0)(react@16.14.0)(typescript@5.2.2)(webpack-cli@4.10.0)
lodash.get: 4.4.2 lodash.get: 4.4.2
lodash.uniq: 4.5.0 lodash.uniq: 4.5.0
dev: false dev: false
@@ -19166,7 +19221,7 @@ packages:
postcss: ^8.0.5 postcss: ^8.0.5
dependencies: dependencies:
'@babel/runtime': 7.20.13 '@babel/runtime': 7.20.13
gatsby: 3.15.0(@types/node@20.8.2)(babel-eslint@10.1.0)(eslint-import-resolver-typescript@3.5.5)(eslint-plugin-testing-library@3.9.0)(react-dom@16.14.0)(react@16.14.0)(typescript@5.2.2)(webpack-cli@4.10.0) gatsby: 3.15.0(@types/node@18.18.9)(babel-eslint@10.1.0)(eslint-import-resolver-typescript@3.5.5)(eslint-plugin-testing-library@3.9.0)(react-dom@16.14.0)(react@16.14.0)(typescript@5.2.2)(webpack-cli@4.10.0)
postcss: 8.4.31 postcss: 8.4.31
postcss-loader: 4.3.0(postcss@8.4.31)(webpack@5.89.0) postcss-loader: 4.3.0(postcss@8.4.31)(webpack@5.89.0)
transitivePeerDependencies: transitivePeerDependencies:
@@ -19181,7 +19236,7 @@ packages:
react-helmet: ^5.1.3 || ^6.0.0 react-helmet: ^5.1.3 || ^6.0.0
dependencies: dependencies:
'@babel/runtime': 7.20.13 '@babel/runtime': 7.20.13
gatsby: 3.15.0(@types/node@20.8.2)(babel-eslint@10.1.0)(eslint-import-resolver-typescript@3.5.5)(eslint-plugin-testing-library@3.9.0)(react-dom@16.14.0)(react@16.14.0)(typescript@5.2.2)(webpack-cli@4.10.0) gatsby: 3.15.0(@types/node@18.18.9)(babel-eslint@10.1.0)(eslint-import-resolver-typescript@3.5.5)(eslint-plugin-testing-library@3.9.0)(react-dom@16.14.0)(react@16.14.0)(typescript@5.2.2)(webpack-cli@4.10.0)
react-helmet: 6.1.0(react@16.14.0) react-helmet: 6.1.0(react@16.14.0)
dev: false dev: false
@@ -19202,7 +19257,7 @@ packages:
'@babel/preset-typescript': 7.23.3(@babel/core@7.23.3) '@babel/preset-typescript': 7.23.3(@babel/core@7.23.3)
'@babel/runtime': 7.23.1 '@babel/runtime': 7.23.1
babel-plugin-remove-graphql-queries: 3.15.0(@babel/core@7.23.3)(gatsby@3.15.0) babel-plugin-remove-graphql-queries: 3.15.0(@babel/core@7.23.3)(gatsby@3.15.0)
gatsby: 3.15.0(@types/node@20.8.2)(babel-eslint@10.1.0)(eslint-import-resolver-typescript@3.5.5)(eslint-plugin-testing-library@3.9.0)(react-dom@16.14.0)(react@16.14.0)(typescript@5.2.2)(webpack-cli@4.10.0) gatsby: 3.15.0(@types/node@18.18.9)(babel-eslint@10.1.0)(eslint-import-resolver-typescript@3.5.5)(eslint-plugin-testing-library@3.9.0)(react-dom@16.14.0)(react@16.14.0)(typescript@5.2.2)(webpack-cli@4.10.0)
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@@ -19215,7 +19270,7 @@ packages:
dependencies: dependencies:
'@babel/runtime': 7.23.1 '@babel/runtime': 7.23.1
fastq: 1.15.0 fastq: 1.15.0
gatsby: 3.15.0(@types/node@20.8.2)(babel-eslint@10.1.0)(eslint-import-resolver-typescript@3.5.5)(eslint-plugin-testing-library@3.9.0)(react-dom@16.14.0)(react@16.14.0)(typescript@5.2.2)(webpack-cli@4.10.0) gatsby: 3.15.0(@types/node@18.18.9)(babel-eslint@10.1.0)(eslint-import-resolver-typescript@3.5.5)(eslint-plugin-testing-library@3.9.0)(react-dom@16.14.0)(react@16.14.0)(typescript@5.2.2)(webpack-cli@4.10.0)
graphql: 15.8.0 graphql: 15.8.0
joi: 17.11.0 joi: 17.11.0
@@ -19225,7 +19280,7 @@ packages:
gatsby: ^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 gatsby: ^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0
dependencies: dependencies:
'@babel/runtime': 7.23.1 '@babel/runtime': 7.23.1
gatsby: 3.15.0(@types/node@20.8.2)(babel-eslint@10.1.0)(eslint-import-resolver-typescript@3.5.5)(eslint-plugin-testing-library@3.9.0)(react-dom@16.14.0)(react@16.14.0)(typescript@5.2.2)(webpack-cli@4.10.0) gatsby: 3.15.0(@types/node@18.18.9)(babel-eslint@10.1.0)(eslint-import-resolver-typescript@3.5.5)(eslint-plugin-testing-library@3.9.0)(react-dom@16.14.0)(react@16.14.0)(typescript@5.2.2)(webpack-cli@4.10.0)
webpack-bundle-analyzer: 4.9.1 webpack-bundle-analyzer: 4.9.1
transitivePeerDependencies: transitivePeerDependencies:
- bufferutil - bufferutil
@@ -19266,7 +19321,7 @@ packages:
chokidar: 3.5.3 chokidar: 3.5.3
contentful-management: 7.54.2(debug@4.3.4) contentful-management: 7.54.2(debug@4.3.4)
cors: 2.8.5 cors: 2.8.5
debug: 4.3.4(supports-color@8.1.1) debug: 4.3.4
detect-port: 1.5.1 detect-port: 1.5.1
dotenv: 8.6.0 dotenv: 8.6.0
execa: 5.1.1 execa: 5.1.1
@@ -19322,7 +19377,7 @@ packages:
prismjs: ^1.15.0 prismjs: ^1.15.0
dependencies: dependencies:
'@babel/runtime': 7.20.13 '@babel/runtime': 7.20.13
gatsby: 3.15.0(@types/node@20.8.2)(babel-eslint@10.1.0)(eslint-import-resolver-typescript@3.5.5)(eslint-plugin-testing-library@3.9.0)(react-dom@16.14.0)(react@16.14.0)(typescript@5.2.2)(webpack-cli@4.10.0) gatsby: 3.15.0(@types/node@18.18.9)(babel-eslint@10.1.0)(eslint-import-resolver-typescript@3.5.5)(eslint-plugin-testing-library@3.9.0)(react-dom@16.14.0)(react@16.14.0)(typescript@5.2.2)(webpack-cli@4.10.0)
parse-numeric-range: 1.3.0 parse-numeric-range: 1.3.0
prismjs: 1.29.0 prismjs: 1.29.0
unist-util-visit: 2.0.3 unist-util-visit: 2.0.3
@@ -19339,7 +19394,7 @@ packages:
fastq: 1.15.0 fastq: 1.15.0
file-type: 16.5.4 file-type: 16.5.4
fs-extra: 10.1.0 fs-extra: 10.1.0
gatsby: 3.15.0(@types/node@20.8.2)(babel-eslint@10.1.0)(eslint-import-resolver-typescript@3.5.5)(eslint-plugin-testing-library@3.9.0)(react-dom@16.14.0)(react@16.14.0)(typescript@5.2.2)(webpack-cli@4.10.0) gatsby: 3.15.0(@types/node@18.18.9)(babel-eslint@10.1.0)(eslint-import-resolver-typescript@3.5.5)(eslint-plugin-testing-library@3.9.0)(react-dom@16.14.0)(react@16.14.0)(typescript@5.2.2)(webpack-cli@4.10.0)
gatsby-core-utils: 2.15.0 gatsby-core-utils: 2.15.0
got: 9.6.0 got: 9.6.0
md5-file: 5.0.0 md5-file: 5.0.0
@@ -19379,7 +19434,7 @@ packages:
gatsby: ^4.0.0-next gatsby: ^4.0.0-next
dependencies: dependencies:
'@babel/runtime': 7.23.1 '@babel/runtime': 7.23.1
gatsby: 3.15.0(@types/node@20.8.2)(babel-eslint@10.1.0)(eslint-import-resolver-typescript@3.5.5)(eslint-plugin-testing-library@3.9.0)(react-dom@16.14.0)(react@16.14.0)(typescript@5.2.2)(webpack-cli@4.10.0) gatsby: 3.15.0(@types/node@18.18.9)(babel-eslint@10.1.0)(eslint-import-resolver-typescript@3.5.5)(eslint-plugin-testing-library@3.9.0)(react-dom@16.14.0)(react@16.14.0)(typescript@5.2.2)(webpack-cli@4.10.0)
gatsby-core-utils: 3.25.0 gatsby-core-utils: 3.25.0
gray-matter: 4.0.3 gray-matter: 4.0.3
hast-util-raw: 6.1.0 hast-util-raw: 6.1.0
@@ -19414,7 +19469,7 @@ packages:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
/gatsby@3.15.0(@types/node@20.8.2)(babel-eslint@10.1.0)(eslint-import-resolver-typescript@3.5.5)(eslint-plugin-testing-library@3.9.0)(react-dom@16.14.0)(react@16.14.0)(typescript@5.2.2)(webpack-cli@4.10.0): /gatsby@3.15.0(@types/node@18.18.9)(babel-eslint@10.1.0)(eslint-import-resolver-typescript@3.5.5)(eslint-plugin-testing-library@3.9.0)(react-dom@16.14.0)(react@16.14.0)(typescript@5.2.2)(webpack-cli@4.10.0):
resolution: {integrity: sha512-zZrHYZtBksrWkOvIJIsaOdfT6rTd5g+HclsWO25H3kTecaPGm5wiKrTtEDPePHWNqEM1V0rLJ/I97/N5tS+7Lw==} resolution: {integrity: sha512-zZrHYZtBksrWkOvIJIsaOdfT6rTd5g+HclsWO25H3kTecaPGm5wiKrTtEDPePHWNqEM1V0rLJ/I97/N5tS+7Lw==}
engines: {node: '>=12.13.0'} engines: {node: '>=12.13.0'}
hasBin: true hasBin: true
@@ -19465,7 +19520,7 @@ packages:
css-minimizer-webpack-plugin: 2.0.0(webpack@5.89.0) css-minimizer-webpack-plugin: 2.0.0(webpack@5.89.0)
css.escape: 1.5.1 css.escape: 1.5.1
date-fns: 2.30.0 date-fns: 2.30.0
debug: 3.2.7(supports-color@8.1.1) debug: 3.2.7
deepmerge: 4.3.1 deepmerge: 4.3.1
del: 5.1.0 del: 5.1.0
detect-port: 1.5.1 detect-port: 1.5.1
@@ -19474,7 +19529,7 @@ packages:
eslint: 7.32.0 eslint: 7.32.0
eslint-config-react-app: 6.0.0(@typescript-eslint/eslint-plugin@4.33.0)(@typescript-eslint/parser@4.33.0)(babel-eslint@10.1.0)(eslint-plugin-flowtype@5.10.0)(eslint-plugin-import@2.28.1)(eslint-plugin-jsx-a11y@6.7.1)(eslint-plugin-react-hooks@4.6.0)(eslint-plugin-react@7.33.2)(eslint-plugin-testing-library@3.9.0)(eslint@7.32.0)(typescript@5.2.2) eslint-config-react-app: 6.0.0(@typescript-eslint/eslint-plugin@4.33.0)(@typescript-eslint/parser@4.33.0)(babel-eslint@10.1.0)(eslint-plugin-flowtype@5.10.0)(eslint-plugin-import@2.28.1)(eslint-plugin-jsx-a11y@6.7.1)(eslint-plugin-react-hooks@4.6.0)(eslint-plugin-react@7.33.2)(eslint-plugin-testing-library@3.9.0)(eslint@7.32.0)(typescript@5.2.2)
eslint-plugin-flowtype: 5.10.0(eslint@7.32.0) eslint-plugin-flowtype: 5.10.0(eslint@7.32.0)
eslint-plugin-graphql: 4.0.0(@types/node@20.8.2)(graphql@15.8.0)(typescript@5.2.2) eslint-plugin-graphql: 4.0.0(@types/node@18.18.9)(graphql@15.8.0)(typescript@5.2.2)
eslint-plugin-import: 2.28.1(@typescript-eslint/parser@4.33.0)(eslint-import-resolver-typescript@3.5.5)(eslint@7.32.0) eslint-plugin-import: 2.28.1(@typescript-eslint/parser@4.33.0)(eslint-import-resolver-typescript@3.5.5)(eslint@7.32.0)
eslint-plugin-jsx-a11y: 6.7.1(eslint@7.32.0) eslint-plugin-jsx-a11y: 6.7.1(eslint@7.32.0)
eslint-plugin-react: 7.33.2(eslint@7.32.0) eslint-plugin-react: 7.33.2(eslint@7.32.0)
@@ -19992,7 +20047,7 @@ packages:
graphql-type-json: 0.3.2(graphql@15.8.0) graphql-type-json: 0.3.2(graphql@15.8.0)
object-path: 0.11.5 object-path: 0.11.5
/graphql-config@3.4.1(@types/node@20.8.2)(graphql@15.8.0)(typescript@5.2.2): /graphql-config@3.4.1(@types/node@18.18.9)(graphql@15.8.0)(typescript@5.2.2):
resolution: {integrity: sha512-g9WyK4JZl1Ko++FSyE5Ir2g66njfxGzrDDhBOwnkoWf/t3TnnZG6BBkWP+pkqVJ5pqMJGPKHNrbew8jRxStjhw==} resolution: {integrity: sha512-g9WyK4JZl1Ko++FSyE5Ir2g66njfxGzrDDhBOwnkoWf/t3TnnZG6BBkWP+pkqVJ5pqMJGPKHNrbew8jRxStjhw==}
engines: {node: '>= 10.0.0'} engines: {node: '>= 10.0.0'}
peerDependencies: peerDependencies:
@@ -20003,7 +20058,7 @@ packages:
'@graphql-tools/json-file-loader': 6.2.6(graphql@15.8.0) '@graphql-tools/json-file-loader': 6.2.6(graphql@15.8.0)
'@graphql-tools/load': 6.2.8(graphql@15.8.0) '@graphql-tools/load': 6.2.8(graphql@15.8.0)
'@graphql-tools/merge': 6.2.14(graphql@15.8.0) '@graphql-tools/merge': 6.2.14(graphql@15.8.0)
'@graphql-tools/url-loader': 6.10.1(@types/node@20.8.2)(graphql@15.8.0) '@graphql-tools/url-loader': 6.10.1(@types/node@18.18.9)(graphql@15.8.0)
'@graphql-tools/utils': 7.10.0(graphql@15.8.0) '@graphql-tools/utils': 7.10.0(graphql@15.8.0)
cosmiconfig: 7.0.0 cosmiconfig: 7.0.0
cosmiconfig-toml-loader: 1.0.0 cosmiconfig-toml-loader: 1.0.0
@@ -20648,7 +20703,7 @@ packages:
dependencies: dependencies:
'@tootallnate/once': 2.0.0 '@tootallnate/once': 2.0.0
agent-base: 6.0.2 agent-base: 6.0.2
debug: 4.3.4(supports-color@8.1.1) debug: 4.3.4
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
dev: true dev: true
@@ -20725,7 +20780,7 @@ packages:
engines: {node: '>= 6'} engines: {node: '>= 6'}
dependencies: dependencies:
agent-base: 6.0.2 agent-base: 6.0.2
debug: 4.3.4(supports-color@8.1.1) debug: 4.3.4
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@@ -21657,7 +21712,7 @@ packages:
resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==} resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==}
engines: {node: '>=10'} engines: {node: '>=10'}
dependencies: dependencies:
debug: 4.3.4(supports-color@8.1.1) debug: 4.3.4
istanbul-lib-coverage: 3.2.0 istanbul-lib-coverage: 3.2.0
source-map: 0.6.1 source-map: 0.6.1
transitivePeerDependencies: transitivePeerDependencies:
@@ -22816,7 +22871,7 @@ packages:
cli-truncate: 3.1.0 cli-truncate: 3.1.0
colorette: 2.0.20 colorette: 2.0.20
commander: 9.5.0 commander: 9.5.0
debug: 4.3.4(supports-color@8.1.1) debug: 4.3.4
execa: 6.1.0 execa: 6.1.0
lilconfig: 2.0.6 lilconfig: 2.0.6
listr2: 5.0.8 listr2: 5.0.8
@@ -23908,7 +23963,7 @@ packages:
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
engines: {node: '>= 8'} engines: {node: '>= 8'}
/meros@1.1.4(@types/node@20.8.2): /meros@1.1.4(@types/node@18.18.9):
resolution: {integrity: sha512-E9ZXfK9iQfG9s73ars9qvvvbSIkJZF5yOo9j4tcwM5tN8mUKfj/EKN5PzOr3ZH0y5wL7dLAHw3RVEfpQV9Q7VQ==} resolution: {integrity: sha512-E9ZXfK9iQfG9s73ars9qvvvbSIkJZF5yOo9j4tcwM5tN8mUKfj/EKN5PzOr3ZH0y5wL7dLAHw3RVEfpQV9Q7VQ==}
engines: {node: '>=12'} engines: {node: '>=12'}
peerDependencies: peerDependencies:
@@ -23917,7 +23972,7 @@ packages:
'@types/node': '@types/node':
optional: true optional: true
dependencies: dependencies:
'@types/node': 20.8.2 '@types/node': 18.18.9
/method-override@3.0.0: /method-override@3.0.0:
resolution: {integrity: sha512-IJ2NNN/mSl9w3kzWB92rcdHpz+HjkxhDJWNDBqSlas+zQdP8wBiJzITPg08M/k2uVvMow7Sk41atndNtt/PHSA==} resolution: {integrity: sha512-IJ2NNN/mSl9w3kzWB92rcdHpz+HjkxhDJWNDBqSlas+zQdP8wBiJzITPg08M/k2uVvMow7Sk41atndNtt/PHSA==}
@@ -24366,7 +24421,7 @@ packages:
/micromark@2.11.4: /micromark@2.11.4:
resolution: {integrity: sha512-+WoovN/ppKolQOFIAajxi7Lu9kInbPxFuTBVEavFcL8eAfVstoc5MocPmqBeAdBOJV00uaVjegzH4+MA0DN/uA==} resolution: {integrity: sha512-+WoovN/ppKolQOFIAajxi7Lu9kInbPxFuTBVEavFcL8eAfVstoc5MocPmqBeAdBOJV00uaVjegzH4+MA0DN/uA==}
dependencies: dependencies:
debug: 4.3.4(supports-color@8.1.1) debug: 4.3.4
parse-entities: 2.0.0 parse-entities: 2.0.0
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@@ -24376,7 +24431,7 @@ packages:
resolution: {integrity: sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA==} resolution: {integrity: sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA==}
dependencies: dependencies:
'@types/debug': 4.1.9 '@types/debug': 4.1.9
debug: 4.3.4(supports-color@8.1.1) debug: 4.3.4
decode-named-character-reference: 1.0.2 decode-named-character-reference: 1.0.2
micromark-core-commonmark: 1.1.0 micromark-core-commonmark: 1.1.0
micromark-factory-space: 1.1.0 micromark-factory-space: 1.1.0
@@ -24399,7 +24454,7 @@ packages:
resolution: {integrity: sha512-o/sd0nMof8kYff+TqcDx3VSrgBTcZpSvYcAHIfHhv5VAuNmisCxjhx6YmxS8PFEpb9z5WKWKPdzf0jM23ro3RQ==} resolution: {integrity: sha512-o/sd0nMof8kYff+TqcDx3VSrgBTcZpSvYcAHIfHhv5VAuNmisCxjhx6YmxS8PFEpb9z5WKWKPdzf0jM23ro3RQ==}
dependencies: dependencies:
'@types/debug': 4.1.9 '@types/debug': 4.1.9
debug: 4.3.4(supports-color@8.1.1) debug: 4.3.4
decode-named-character-reference: 1.0.2 decode-named-character-reference: 1.0.2
devlop: 1.1.0 devlop: 1.1.0
micromark-core-commonmark: 2.0.0 micromark-core-commonmark: 2.0.0
@@ -29383,7 +29438,7 @@ packages:
'@types/component-emitter': 1.2.12 '@types/component-emitter': 1.2.12
backo2: 1.0.2 backo2: 1.0.2
component-emitter: 1.3.0 component-emitter: 1.3.0
debug: 4.3.4(supports-color@8.1.1) debug: 4.3.4
engine.io-client: 4.1.4 engine.io-client: 4.1.4
parseuri: 0.0.6 parseuri: 0.0.6
socket.io-parser: 4.0.5 socket.io-parser: 4.0.5
@@ -29398,7 +29453,7 @@ packages:
dependencies: dependencies:
'@types/component-emitter': 1.2.12 '@types/component-emitter': 1.2.12
component-emitter: 1.3.0 component-emitter: 1.3.0
debug: 4.3.4(supports-color@8.1.1) debug: 4.3.4
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@@ -29411,7 +29466,7 @@ packages:
'@types/node': 14.18.63 '@types/node': 14.18.63
accepts: 1.3.8 accepts: 1.3.8
base64id: 2.0.0 base64id: 2.0.0
debug: 4.3.4(supports-color@8.1.1) debug: 4.3.4
engine.io: 4.1.2 engine.io: 4.1.2
socket.io-adapter: 2.1.0 socket.io-adapter: 2.1.0
socket.io-parser: 4.0.5 socket.io-parser: 4.0.5
@@ -29649,7 +29704,7 @@ packages:
arg: 5.0.2 arg: 5.0.2
bluebird: 3.7.2 bluebird: 3.7.2
check-more-types: 2.24.0 check-more-types: 2.24.0
debug: 4.3.4(supports-color@8.1.1) debug: 4.3.4
execa: 5.1.1 execa: 5.1.1
lazy-ass: 1.6.0 lazy-ass: 1.6.0
ps-tree: 1.2.0 ps-tree: 1.2.0
@@ -32189,7 +32244,7 @@ packages:
/webpack-virtual-modules@0.3.2: /webpack-virtual-modules@0.3.2:
resolution: {integrity: sha512-RXQXioY6MhzM4CNQwmBwKXYgBs6ulaiQ8bkNQEl2J6Z+V+s7lgl/wGvaI/I0dLnYKB8cKsxQc17QOAVIphPLDw==} resolution: {integrity: sha512-RXQXioY6MhzM4CNQwmBwKXYgBs6ulaiQ8bkNQEl2J6Z+V+s7lgl/wGvaI/I0dLnYKB8cKsxQc17QOAVIphPLDw==}
dependencies: dependencies:
debug: 3.2.7(supports-color@8.1.1) debug: 3.2.7
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
-1
View File
@@ -22,7 +22,6 @@ export interface InitTestFrameArg {
}; };
getUserInput?: (fileName: string) => string; getUserInput?: (fileName: string) => string;
loadEnzyme?: () => void; loadEnzyme?: () => void;
transformedPython?: string;
} }
export type FrameWindow = Window & export type FrameWindow = Window &
@@ -1,299 +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 pkg from 'pyodide/package.json';
import { IDisposable, Terminal } from 'xterm';
import { FitAddon } from 'xterm-addon-fit';
import jQuery from 'jquery'; // TODO: is jQuery needed for the python runner?
import * as helpers from '@freecodecamp/curriculum-helpers';
import type { PythonDocument, FrameWindow, InitTestFrameArg } from '.';
import 'xterm/css/xterm.css';
(window as FrameWindow).$ = jQuery;
// This will be running in an iframe, so document will be
// element.contentDocument. This declaration is just to add properties we know
// exist on this document (but not on the parent)
const contentDocument = document as PythonDocument;
function createTerminal(disposables: IDisposable[]) {
const terminalContainer = document.getElementById('terminal');
if (!terminalContainer) throw Error('Could not find terminal container');
// Setting convertEol so that \n is converted to \r\n. Otherwise the terminal
// will interpret \n as line feed and just move the cursor to the next line.
// convertEol makes every \n a \r\n.
const term = new Terminal({ convertEol: true });
const fitAddon = new FitAddon();
term.loadAddon(fitAddon);
term.open(terminalContainer);
fitAddon.fit();
const resetTerminal = () => {
term.reset();
disposables.forEach(disposable => disposable.dispose());
disposables.length = 0;
};
return { term, resetTerminal };
}
async function setupPyodide() {
// I tried setting jsglobals here, to provide 'input' and 'print' to python,
// without having to modify the global window object. However, it didn't work
// because pyodide needs access to that object. Instead, I used
// registerJsModule when setting up runPython.
return await loadPyodide({
indexURL: `https://cdn.jsdelivr.net/pyodide/v${pkg.version}/full/`
});
}
type Input = (text: string) => Promise<string>;
type Print = (...args: unknown[]) => void;
type ResetTerminal = () => void;
type EvaluatedTeststring = {
input: string[];
test: () => Promise<unknown>;
};
function createJSFunctionsForPython(
term: Terminal,
disposables: IDisposable[],
pyodide: PyodideInterface
) {
const writeLine = (text: string) => term.writeln(`>>> ${text}`);
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
const str = pyodide.globals.get('str') as (x: unknown) => string;
function print(...args: unknown[]) {
const text = args.map(x => str(x)).join(' ');
writeLine(text);
}
// TODO: prevent user from moving cursor outside the current input line and
// handle insertion and deletion properly. While backspace and delete don't
// seem to work, we can use "\x1b[0K" to clear from the cursor to the end.
// Also, we should not add special characters to the userinput string.
const waitForInput = (): Promise<string> =>
new Promise(resolve => {
let userinput = '';
// Eslint is correct that this only gets assigned once, but we can't use
// const because the declaration (before keyListener is defined) and
// assignment (after keyListener is defined) must be separate.
// eslint-disable-next-line prefer-const
let disposable: IDisposable | undefined;
const done = () => {
disposable?.dispose();
resolve(userinput);
};
const keyListener = (key: string) => {
if (key === '\u007F' || key === '\b') {
// Backspace or delete key
term.write('\b \b'); // Move cursor back, replace character with space, then move cursor back again
userinput = userinput.slice(0, -1); // Remove the last character from userinput
}
if (key == '\r') {
term.write('\r\n');
done();
} else {
userinput += key;
term.write(key);
}
};
disposable = term.onData(keyListener); // Listen for key events and store the disposable
disposables.push(disposable);
});
const input = async (text: string) => {
writeLine(text);
return await waitForInput();
};
return { print, input };
}
function setupRunPython(
pyodide: PyodideInterface,
{
input,
print,
resetTerminal
}: { input: Input; print: Print; resetTerminal: ResetTerminal }
) {
// Make print and input available to python
pyodide.registerJsModule('jscustom', {
input,
print
});
pyodide.runPython(`
import jscustom
from jscustom import print
from jscustom import input
`);
async function runPython(code: string) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
pyodide.globals.get('__cancel')?.();
resetTerminal();
// There's no need to clear out globals between runs, because the user's
// code is always run in a coroutine and shouldn't pollute them. If we
// subsequently want to run code that does interact with globals, we can
// revisit this.
await pyodide.runPythonAsync(code);
return pyodide;
}
contentDocument.__runPython = runPython;
}
async function initPythonFrame() {
const disposables: IDisposable[] = [];
const { term, resetTerminal } = createTerminal(disposables);
const pyodide = await setupPyodide();
const { print, input } = createJSFunctionsForPython(
term,
disposables,
pyodide
);
setupRunPython(pyodide, { input, print, resetTerminal });
}
contentDocument.__initPythonFrame = initPythonFrame;
contentDocument.__initTestFrame = initTestFrame;
// TODO: DRY this and frame-runner.ts's initTestFrame
async function initTestFrame(e: InitTestFrameArg) {
const pyodide = await setupPyodide();
// transformedPython is used here not because it's necessary (it's not since
// the transformation converts `input` into `await input` and the tests
// provide a synchronous `input` function), but because we want to run the
// tests against exactly the same code that runs in the preview.
const code = (e.transformedPython || '').slice();
const __file = (id?: string) => {
if (id && e.code.original) {
return e.code.original[id];
} else {
return code;
}
};
if (!e.getUserInput) {
e.getUserInput = () => code;
}
/* eslint-disable @typescript-eslint/no-unused-vars */
// Fake Deep Equal dependency
const DeepEqual = (a: Record<string, unknown>, b: Record<string, unknown>) =>
JSON.stringify(a) === JSON.stringify(b);
// Hardcode Deep Freeze dependency
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const DeepFreeze = (o: Record<string, any>) => {
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])
) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
DeepFreeze(o[prop]);
}
});
return o;
};
const { default: chai } = await import(/* webpackChunkName: "chai" */ 'chai');
const assert = chai.assert;
const __helpers = helpers;
/* eslint-enable @typescript-eslint/no-unused-vars */
contentDocument.__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 get the dummy input and actual test
const evaluatedTestString = await new Promise<unknown>(
(resolve, reject) =>
// To avoid race conditions, we have to run the test in a final
// frameDocument ready:
$(() => {
try {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const test: { input: string[]; test: () => Promise<unknown> } =
eval(testString);
resolve(test);
} catch (err) {
reject(err);
}
})
);
// 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') {
return { pass: true };
}
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;
// TODO: throw helpful error if we run out of input values, since it's likely
// that the user added too many input statements.
const inputIterator = input ? input.values() : null;
setupRunPython(pyodide, {
input: () => {
return Promise.resolve(
inputIterator ? inputIterator.next().value : ''
);
},
// We don't, currently, care what print is called with, but it does need
// to exist
print: () => void 0,
// resetTerminal is only necessary when calling __runPython more than
// once, which we don't do in the test frame
resetTerminal: () => void 0
});
// We have to declare these variables in the scope of 'eval', so that they
// exist when the `testString` is evaluated. Otherwise, they will be
// undefined when `test` is called and the tests will not be able to use
// __pyodide or __userGlobals.
const __pyodide = await this.__runPython(code);
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
const __userGlobals = __pyodide.globals.get('__locals') as unknown;
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
}
};
}
};
}
@@ -0,0 +1,166 @@
// 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 } 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;
original: { [id: string]: string };
};
removeComments: boolean;
firstTest: unknown;
testString: string;
build: string;
sources: {
[fileName: string]: unknown;
};
};
}
type EvaluatedTeststring = {
input: string[];
test: () => Promise<unknown>;
};
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);
ctx.postMessage({ type: 'contentLoaded' });
return pyodide;
}
void setupPyodide();
ctx.onmessage = async (e: PythonRunEvent) => {
const pyodide = await setupPyodide();
// TODO: Use removeComments when we have it
/* 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;
/* 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<unknown>(
(resolve, reject) => {
try {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const test: { input: string[]; test: () => Promise<unknown> } =
eval(testString);
resolve(test);
} catch (err) {
reject(err);
}
}
);
// 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;
const inputIterator = (input ?? []).values();
const testInput = () => {
const next = inputIterator.next();
if (next.done) {
// TODO: handle this error in the UI
throw new Error('Too many input calls');
} else {
return next.value;
}
};
// Make input available to python (print is not used yet)
pyodide.registerJsModule('jscustom', {
input: testInput
// print: () => {}
});
// Create fresh globals for each test
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
const __userGlobals = pyodide.globals.get('dict')() as PyProxy;
// 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;
// TODO: remove __pyodide once all the test use runPython.
const __pyodide = {
runPython
};
runPython(
`
import jscustom
from jscustom import input
`
);
// Evaluates the learner's code so that any variables they define are
// available to the test.
runPython(code);
// TODO: remove the next line, creating __locals, once all the tests access
// variables directly.
runPython('__locals = globals()');
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
}
});
}
};
@@ -0,0 +1,81 @@
// 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 pkg from 'pyodide/package.json';
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;
original: { [id: string]: string };
};
};
}
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
const str = pyodide.globals.get('str') as (x: unknown) => string;
function print(...args: unknown[]) {
const text = args.map(x => str(x)).join(' ');
postMessage({ type: 'print', text });
}
function input(text: string) {
// TODO: send unique ids to the main thread and the service worker, so we
// can have multiple concurrent input requests.
postMessage({ type: 'input', text });
const request = new XMLHttpRequest();
request.open('POST', '/python/intercept-input/', false);
request.send(null);
return request.responseText;
}
// I tried setting jsglobals here, to provide 'input' and 'print' to python,
// without having to modify the global window object. However, it didn't work
// because pyodide needs access to that object. Instead, I used
// registerJsModule when setting up runPython.
// Make print available to python
pyodide.registerJsModule('jscustom', {
print,
input
});
// TODO: use a fresh global object for each runPython call if we stop terminating
// the worker when the user input changes. (See python-test-evaluator.ts)
pyodide.runPython(`
import jscustom
from jscustom import print
from jscustom import input
`);
return pyodide;
}
void setupPyodide();
ctx.onmessage = async (e: PythonRunEvent) => {
const code = (e.data.code.contents || '').slice();
const pyodide = await setupPyodide();
// use pyodide.runPythonAsync if we want top-level await
pyodide.runPython(code);
};
@@ -2,6 +2,7 @@
"compilerOptions": { "compilerOptions": {
"target": "es2022", "target": "es2022",
"module": "CommonJS", "module": "CommonJS",
"lib": ["WebWorker", "DOM"],
"allowJs": true, "allowJs": true,
"strict": true, "strict": true,
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,
@@ -17,7 +17,8 @@ module.exports = (env = {}) => {
'frame-runner': './frame-runner.ts', 'frame-runner': './frame-runner.ts',
'sass-compile': './sass-compile.ts', 'sass-compile': './sass-compile.ts',
'test-evaluator': './test-evaluator.ts', 'test-evaluator': './test-evaluator.ts',
'python-runner': './python-runner.ts' 'python-worker': './python-worker.ts',
'python-test-evaluator': './python-test-evaluator.ts'
}, },
devtool: __DEV__ ? 'inline-source-map' : 'source-map', devtool: __DEV__ ? 'inline-source-map' : 'source-map',
output: { output: {
@@ -61,17 +62,16 @@ module.exports = (env = {}) => {
] ]
} }
} }
},
// xterm doesn't bundle its css, so we need to load it ourselves
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
} }
] ]
}, },
plugins: [ plugins: [
new CopyWebpackPlugin({ new CopyWebpackPlugin({
patterns: ['./node_modules/sass.js/dist/sass.sync.js'] 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'
]
}), }),
new webpack.ProvidePlugin({ new webpack.ProvidePlugin({
process: 'process/browser' process: 'process/browser'