mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-05-28 18:26:54 +00:00
feat: handle python input synchronously (#52526)
Co-authored-by: Shaun Hamilton <shauhami020@gmail.com>
This commit is contained in:
committed by
GitHub
parent
8e457e7789
commit
583745e6ca
+3
-1
@@ -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",
|
||||||
|
|||||||
@@ -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 }) {
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
});
|
||||||
+33
-3
@@ -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--
|
||||||
|
|||||||
@@ -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
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+136
-81
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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'
|
||||||
|
|||||||
Reference in New Issue
Block a user