mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-05-28 18:26:54 +00:00
refactor: move challenge build outside client (#65513)
This commit is contained in:
committed by
GitHub
parent
c8b21dfc4a
commit
5ff971687c
+3
-4
@@ -47,14 +47,13 @@
|
||||
"@babel/preset-env": "7.23.7",
|
||||
"@babel/preset-react": "7.23.3",
|
||||
"@babel/preset-typescript": "7.23.3",
|
||||
"@babel/standalone": "7.23.7",
|
||||
"@codesandbox/sandpack-react": "2.6.9",
|
||||
"@codesandbox/sandpack-themes": "2.0.21",
|
||||
"@fortawesome/fontawesome-svg-core": "6.7.1",
|
||||
"@fortawesome/free-brands-svg-icons": "6.7.1",
|
||||
"@fortawesome/free-solid-svg-icons": "6.7.1",
|
||||
"@fortawesome/react-fontawesome": "0.2.2",
|
||||
"@freecodecamp/loop-protect": "3.0.0",
|
||||
"@freecodecamp/challenge-builder": "workspace:*",
|
||||
"@freecodecamp/ui": "5.0.1",
|
||||
"@gatsbyjs/reach-router": "1.3.9",
|
||||
"@growthbook/growthbook-react": "1.6.0",
|
||||
@@ -141,10 +140,10 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/plugin-syntax-dynamic-import": "7.8.3",
|
||||
"@freecodecamp/browser-scripts": "workspace:*",
|
||||
"@freecodecamp/curriculum": "workspace:*",
|
||||
"@freecodecamp/eslint-config": "workspace:*",
|
||||
"@freecodecamp/shared": "workspace:*",
|
||||
"@freecodecamp/curriculum": "workspace:*",
|
||||
"@freecodecamp/browser-scripts": "workspace:*",
|
||||
"@testing-library/jest-dom": "^6.8.0",
|
||||
"@testing-library/react": "12.1.5",
|
||||
"@testing-library/react-hooks": "^8.0.1",
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import React from 'react';
|
||||
import { runSaga } from 'redux-saga';
|
||||
import { describe, test, it, expect, beforeEach, vi, Mock } from 'vitest';
|
||||
import { buildChallenge } from '@freecodecamp/challenge-builder/build';
|
||||
|
||||
import { render } from '../../../../utils/test-utils';
|
||||
|
||||
import { getCompletedPercentage } from '../../../utils/get-completion-percentage';
|
||||
@@ -14,7 +16,7 @@ import {
|
||||
isBuildEnabledSelector,
|
||||
isBlockNewlyCompletedSelector
|
||||
} from '../redux/selectors';
|
||||
import { buildChallenge, getTestRunner } from '../utils/build';
|
||||
import { getTestRunner } from '../utils/build';
|
||||
import CompletionModal, { combineFileData } from './completion-modal';
|
||||
vi.mock('../../../analytics');
|
||||
vi.mock('../../../utils/fire-confetti');
|
||||
@@ -22,6 +24,7 @@ vi.mock('../../../components/Progress');
|
||||
vi.mock('../redux/selectors');
|
||||
vi.mock('../utils/build');
|
||||
vi.mock('../../../utils/get-words');
|
||||
vi.mock('@freecodecamp/challenge-builder/build');
|
||||
const mockFireConfetti = fireConfetti as Mock;
|
||||
const mockTestRunner = vi.fn().mockReturnValue({ pass: true });
|
||||
const mockBuildEnabledSelector = isBuildEnabledSelector as Mock;
|
||||
|
||||
@@ -15,6 +15,8 @@ import {
|
||||
} from 'redux-saga/effects';
|
||||
|
||||
import { challengeTypes } from '@freecodecamp/shared/config/challenge-types';
|
||||
import { buildChallenge } from '@freecodecamp/challenge-builder/build';
|
||||
|
||||
import { createFlashMessage } from '../../../components/Flash/redux';
|
||||
import { FlashMessages } from '../../../components/Flash/redux/flash-messages';
|
||||
import {
|
||||
@@ -25,7 +27,6 @@ import {
|
||||
} from '../../../utils/challenge-request-helpers';
|
||||
import { playTone } from '../../../utils/tone';
|
||||
import {
|
||||
buildChallenge,
|
||||
canBuildChallenge,
|
||||
challengeHasPreview,
|
||||
getTestRunner,
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import { challengeTypes } from '@freecodecamp/shared/config/challenge-types';
|
||||
import type { ChallengeFile } from '@freecodecamp/shared/utils/polyvinyl';
|
||||
|
||||
import type { ChallengeFile } from '../../../redux/prop-types';
|
||||
import { concatHtml } from '../rechallenge/builders';
|
||||
import {
|
||||
getTransformers,
|
||||
embedFilesInHtml,
|
||||
getPythonTransformers,
|
||||
getMultifileJSXTransformers
|
||||
} from '../rechallenge/transformers';
|
||||
} from '@freecodecamp/challenge-builder/transformers';
|
||||
import { concatHtml } from '@freecodecamp/challenge-builder/builders';
|
||||
import { runnerTypes } from '@freecodecamp/challenge-builder/build';
|
||||
|
||||
import {
|
||||
runTestsInTestFrame,
|
||||
createMainPreviewFramer,
|
||||
@@ -97,56 +99,6 @@ export function canBuildChallenge(challengeData: BuildChallengeData): boolean {
|
||||
return Object.prototype.hasOwnProperty.call(buildFunctions, challengeType);
|
||||
}
|
||||
|
||||
export async function buildChallenge(
|
||||
challengeData: BuildChallengeData,
|
||||
options: BuildOptions
|
||||
) {
|
||||
const { challengeType } = challengeData;
|
||||
const build = buildFunctions[challengeType];
|
||||
if (build) {
|
||||
return build(challengeData, options);
|
||||
}
|
||||
throw new Error(`Cannot build challenge of type ${challengeType}`);
|
||||
}
|
||||
|
||||
export const runnerTypes: Record<
|
||||
(typeof challengeTypes)[keyof typeof challengeTypes],
|
||||
'javascript' | 'dom' | 'python'
|
||||
> = {
|
||||
[challengeTypes.html]: 'dom',
|
||||
[challengeTypes.js]: 'javascript',
|
||||
[challengeTypes.backend]: 'dom',
|
||||
[challengeTypes.zipline]: 'dom',
|
||||
[challengeTypes.frontEndProject]: 'dom',
|
||||
[challengeTypes.backEndProject]: 'dom',
|
||||
[challengeTypes.pythonProject]: 'python',
|
||||
[challengeTypes.jsProject]: 'javascript',
|
||||
[challengeTypes.modern]: 'dom',
|
||||
[challengeTypes.step]: 'dom',
|
||||
[challengeTypes.quiz]: 'dom',
|
||||
[challengeTypes.invalid]: 'dom',
|
||||
[challengeTypes.video]: 'dom',
|
||||
[challengeTypes.codeAllyPractice]: 'dom',
|
||||
[challengeTypes.codeAllyCert]: 'dom',
|
||||
[challengeTypes.multifileCertProject]: 'dom',
|
||||
[challengeTypes.theOdinProject]: 'dom',
|
||||
[challengeTypes.colab]: 'dom',
|
||||
[challengeTypes.exam]: 'dom',
|
||||
[challengeTypes.msTrophy]: 'dom',
|
||||
[challengeTypes.multipleChoice]: 'dom',
|
||||
[challengeTypes.python]: 'python',
|
||||
[challengeTypes.dialogue]: 'dom',
|
||||
[challengeTypes.fillInTheBlank]: 'dom',
|
||||
[challengeTypes.multifilePythonCertProject]: 'python',
|
||||
[challengeTypes.generic]: 'dom',
|
||||
[challengeTypes.lab]: 'dom',
|
||||
[challengeTypes.jsLab]: 'javascript',
|
||||
[challengeTypes.pyLab]: 'python',
|
||||
[challengeTypes.dailyChallengeJs]: 'javascript',
|
||||
[challengeTypes.dailyChallengePy]: 'python',
|
||||
[challengeTypes.review]: 'dom'
|
||||
};
|
||||
|
||||
export async function getTestRunner(buildData: BuildChallengeData) {
|
||||
const { challengeType } = buildData;
|
||||
// TODO: Fully type BuildChallengeData
|
||||
|
||||
@@ -48,9 +48,10 @@
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.23.7",
|
||||
"@babel/register": "7.23.7",
|
||||
"@freecodecamp/browser-scripts": "workspace:*",
|
||||
"@freecodecamp/challenge-builder": "workspace:*",
|
||||
"@freecodecamp/eslint-config": "workspace:*",
|
||||
"@freecodecamp/shared": "workspace:*",
|
||||
"@freecodecamp/browser-scripts": "workspace:*",
|
||||
"@total-typescript/ts-reset": "^0.6.1",
|
||||
"@types/debug": "^4.1.12",
|
||||
"@types/js-yaml": "4.0.5",
|
||||
|
||||
@@ -26,16 +26,16 @@ import { sortChallenges } from './utils/sort-challenges.js';
|
||||
const { flatten, isEmpty, cloneDeep } = lodash;
|
||||
|
||||
vi.mock(
|
||||
'../../../client/src/templates/Challenges/utils/typescript-worker-handler',
|
||||
'@freecodecamp/challenge-builder/typescript-worker-handler',
|
||||
async importOriginal => {
|
||||
const actual = await importOriginal();
|
||||
|
||||
// ts and tsvfs must match the versions used in the typescript-worker.
|
||||
const tsvfs = await import('@typescript/vfs-1.6.1');
|
||||
const ts = await import('typescript-5.9.2');
|
||||
// use the same TS compiler as the client
|
||||
// use the same TS compiler as the challenge-builder
|
||||
const tsCompiler = await import(
|
||||
'../../../tools/client-plugins/browser-scripts/modules/typescript-compiler'
|
||||
'@freecodecamp/browser-scripts/ts-compiler'
|
||||
);
|
||||
const compiler = new tsCompiler.Compiler(ts, tsvfs);
|
||||
await compiler.setup({ useNodeModules: true });
|
||||
@@ -151,7 +151,7 @@ async function populateTestsForLang({ lang, challenges, meta }) {
|
||||
// Presumably this is because we import from_this file in the generated block
|
||||
// test files and that happens before the mock is applied.
|
||||
const { buildChallenge } = await import(
|
||||
'../../../client/src/templates/Challenges/utils/build'
|
||||
'@freecodecamp/challenge-builder/build'
|
||||
);
|
||||
const validateChallenge = challengeSchemaValidator();
|
||||
|
||||
@@ -370,9 +370,7 @@ async function createTestRunner(
|
||||
buildChallenge,
|
||||
solutionFromNext
|
||||
) {
|
||||
const { runnerTypes } = await import(
|
||||
'../../../client/src/templates/Challenges/utils/build'
|
||||
);
|
||||
const { runnerTypes } = await import('@freecodecamp/challenge-builder/build');
|
||||
|
||||
const challengeFiles = replaceChallengeFilesContentsWithSolutions(
|
||||
challenge.challengeFiles,
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
dist
|
||||
@@ -0,0 +1,4 @@
|
||||
/* eslint-disable filenames-simple/naming-convention */
|
||||
import { createLintStagedConfig } from '@freecodecamp/eslint-config/lintstaged';
|
||||
|
||||
export default createLintStagedConfig(import.meta.dirname);
|
||||
@@ -0,0 +1,18 @@
|
||||
import { configTypeChecked } from '@freecodecamp/eslint-config/base';
|
||||
import globals from 'globals';
|
||||
|
||||
import { defineConfig } from 'eslint/config';
|
||||
|
||||
const baseLanguageOptions = {
|
||||
globals: {
|
||||
...globals.browser,
|
||||
...globals.node // TODO: necessary?
|
||||
}
|
||||
};
|
||||
|
||||
export default defineConfig({
|
||||
extends: [configTypeChecked],
|
||||
languageOptions: {
|
||||
...baseLanguageOptions
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"name": "@freecodecamp/challenge-builder",
|
||||
"version": "0.0.1",
|
||||
"author": "freeCodeCamp <team@freecodecamp.org>",
|
||||
"license": "BSD-3-Clause",
|
||||
"description": "Builds challenges for testing and rendering",
|
||||
"private": false,
|
||||
"engines": {
|
||||
"node": ">=24",
|
||||
"pnpm": ">=10"
|
||||
},
|
||||
"exports": {
|
||||
"./build": "./dist/build.js",
|
||||
"./transformers": "./dist/transformers.js",
|
||||
"./typescript-worker-handler": "./dist/typescript-worker-handler.js",
|
||||
"./builders": "./dist/builders.js"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest",
|
||||
"test:ui": "vitest --ui",
|
||||
"type-check": "tsc --noEmit",
|
||||
"compile": "tsc",
|
||||
"lint": "eslint --max-warnings 0"
|
||||
},
|
||||
"type": "module",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/freeCodeCamp/freeCodeCamp.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/freeCodeCamp/freeCodeCamp/issues"
|
||||
},
|
||||
"homepage": "https://github.com/freeCodeCamp/freeCodeCamp#readme",
|
||||
"devDependencies": {
|
||||
"@freecodecamp/eslint-config": "workspace:*",
|
||||
"@types/lodash-es": "4.17.12",
|
||||
"@vitest/ui": "^3.2.4",
|
||||
"eslint": "^9.39.1",
|
||||
"vitest": "^3.2.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/preset-env": "7.23.7",
|
||||
"@babel/preset-react": "7.28.5",
|
||||
"@babel/standalone": "7.23.7",
|
||||
"@freecodecamp/browser-scripts": "workspace:*",
|
||||
"@freecodecamp/loop-protect": "3.0.0",
|
||||
"@freecodecamp/shared": "workspace:*",
|
||||
"lodash-es": "4.17.23"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,269 @@
|
||||
import { challengeTypes } from '@freecodecamp/shared/config/challenge-types';
|
||||
import type { ChallengeFile } from '@freecodecamp/shared/utils/polyvinyl';
|
||||
|
||||
import { concatHtml } from './builders.js';
|
||||
import {
|
||||
getTransformers,
|
||||
embedFilesInHtml,
|
||||
getPythonTransformers,
|
||||
getMultifileJSXTransformers
|
||||
} from './transformers.js';
|
||||
|
||||
interface Source {
|
||||
index: string;
|
||||
contents?: string;
|
||||
editableContents: string;
|
||||
}
|
||||
|
||||
interface BuildChallengeData {
|
||||
challengeType: number;
|
||||
challengeFiles?: ChallengeFile[];
|
||||
required: { src?: string }[];
|
||||
template: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
interface BuildOptions {
|
||||
preview: boolean;
|
||||
disableLoopProtectTests: boolean;
|
||||
disableLoopProtectPreview: boolean;
|
||||
usesTestRunner?: boolean;
|
||||
}
|
||||
|
||||
type ApplyFunctionProps = (
|
||||
file: ChallengeFile
|
||||
) => Promise<ChallengeFile> | ChallengeFile;
|
||||
|
||||
const applyFunction =
|
||||
(fn: ApplyFunctionProps) => async (file: ChallengeFile) => {
|
||||
try {
|
||||
if (file.error) {
|
||||
return file;
|
||||
}
|
||||
const newFile = await fn.call(this, file);
|
||||
if (typeof newFile !== 'undefined') {
|
||||
return newFile;
|
||||
}
|
||||
return file;
|
||||
} catch (error) {
|
||||
return { ...file, error };
|
||||
}
|
||||
};
|
||||
|
||||
const composeFunctions = (...fns: ApplyFunctionProps[]) =>
|
||||
fns.map(applyFunction).reduce((f, g) => x => f(x).then(g));
|
||||
|
||||
function buildSourceMap(challengeFiles: ChallengeFile[]): Source | undefined {
|
||||
// TODO: rename sources.index to sources.contents.
|
||||
const source: Source | undefined = challengeFiles?.reduce(
|
||||
(sources, challengeFile) => {
|
||||
sources.index += challengeFile.source || '';
|
||||
sources.contents = sources.index;
|
||||
sources.editableContents += challengeFile.editableContents || '';
|
||||
return sources;
|
||||
},
|
||||
{
|
||||
index: '',
|
||||
editableContents: ''
|
||||
} as Source
|
||||
);
|
||||
return source;
|
||||
}
|
||||
|
||||
export const buildFunctions = {
|
||||
[challengeTypes.js]: buildJSChallenge,
|
||||
[challengeTypes.jsProject]: buildJSChallenge,
|
||||
[challengeTypes.html]: buildDOMChallenge,
|
||||
[challengeTypes.modern]: buildDOMChallenge,
|
||||
[challengeTypes.backend]: buildBackendChallenge,
|
||||
[challengeTypes.backEndProject]: buildBackendChallenge,
|
||||
[challengeTypes.pythonProject]: buildBackendChallenge,
|
||||
[challengeTypes.multifileCertProject]: buildDOMChallenge,
|
||||
[challengeTypes.colab]: buildBackendChallenge,
|
||||
[challengeTypes.python]: buildPythonChallenge,
|
||||
[challengeTypes.multifilePythonCertProject]: buildPythonChallenge,
|
||||
[challengeTypes.lab]: buildDOMChallenge,
|
||||
[challengeTypes.jsLab]: buildJSChallenge,
|
||||
[challengeTypes.pyLab]: buildPythonChallenge,
|
||||
[challengeTypes.dailyChallengeJs]: buildJSChallenge,
|
||||
[challengeTypes.dailyChallengePy]: buildPythonChallenge
|
||||
};
|
||||
|
||||
export function canBuildChallenge(challengeData: BuildChallengeData): boolean {
|
||||
const { challengeType } = challengeData;
|
||||
return Object.prototype.hasOwnProperty.call(buildFunctions, challengeType);
|
||||
}
|
||||
|
||||
export async function buildChallenge(
|
||||
challengeData: BuildChallengeData,
|
||||
options: BuildOptions
|
||||
) {
|
||||
const { challengeType } = challengeData;
|
||||
const build = buildFunctions[challengeType];
|
||||
if (build) {
|
||||
return build(challengeData, options);
|
||||
}
|
||||
throw new Error(`Cannot build challenge of type ${challengeType}`);
|
||||
}
|
||||
|
||||
export const runnerTypes: Record<
|
||||
(typeof challengeTypes)[keyof typeof challengeTypes],
|
||||
'javascript' | 'dom' | 'python'
|
||||
> = {
|
||||
[challengeTypes.html]: 'dom',
|
||||
[challengeTypes.js]: 'javascript',
|
||||
[challengeTypes.backend]: 'dom',
|
||||
[challengeTypes.zipline]: 'dom',
|
||||
[challengeTypes.frontEndProject]: 'dom',
|
||||
[challengeTypes.backEndProject]: 'dom',
|
||||
[challengeTypes.pythonProject]: 'python',
|
||||
[challengeTypes.jsProject]: 'javascript',
|
||||
[challengeTypes.modern]: 'dom',
|
||||
[challengeTypes.step]: 'dom',
|
||||
[challengeTypes.quiz]: 'dom',
|
||||
[challengeTypes.invalid]: 'dom',
|
||||
[challengeTypes.video]: 'dom',
|
||||
[challengeTypes.codeAllyPractice]: 'dom',
|
||||
[challengeTypes.codeAllyCert]: 'dom',
|
||||
[challengeTypes.multifileCertProject]: 'dom',
|
||||
[challengeTypes.theOdinProject]: 'dom',
|
||||
[challengeTypes.colab]: 'dom',
|
||||
[challengeTypes.exam]: 'dom',
|
||||
[challengeTypes.msTrophy]: 'dom',
|
||||
[challengeTypes.multipleChoice]: 'dom',
|
||||
[challengeTypes.python]: 'python',
|
||||
[challengeTypes.dialogue]: 'dom',
|
||||
[challengeTypes.fillInTheBlank]: 'dom',
|
||||
[challengeTypes.multifilePythonCertProject]: 'python',
|
||||
[challengeTypes.generic]: 'dom',
|
||||
[challengeTypes.lab]: 'dom',
|
||||
[challengeTypes.jsLab]: 'javascript',
|
||||
[challengeTypes.pyLab]: 'python',
|
||||
[challengeTypes.dailyChallengeJs]: 'javascript',
|
||||
[challengeTypes.dailyChallengePy]: 'python',
|
||||
[challengeTypes.review]: 'dom'
|
||||
};
|
||||
|
||||
type BuildResult = {
|
||||
challengeType: number;
|
||||
build?: string;
|
||||
sources: Source | undefined;
|
||||
loadEnzyme?: boolean;
|
||||
error?: unknown;
|
||||
};
|
||||
|
||||
// TODO: All the buildXChallenge files have a similar structure, so make that
|
||||
// abstraction (function, class, whatever) and then create the various functions
|
||||
// out of it.
|
||||
export async function buildDOMChallenge(
|
||||
{
|
||||
challengeFiles,
|
||||
required = [],
|
||||
template = '',
|
||||
challengeType
|
||||
}: BuildChallengeData,
|
||||
options?: BuildOptions
|
||||
): Promise<BuildResult> {
|
||||
// TODO: make this required in the schema.
|
||||
if (!challengeFiles) throw Error('No challenge files provided');
|
||||
const hasJsx = challengeFiles.some(
|
||||
challengeFile => challengeFile.ext === 'jsx' || challengeFile.ext === 'tsx'
|
||||
);
|
||||
const isMultifile = challengeFiles.length > 1;
|
||||
|
||||
const requiresReact16 = required.some(({ src }) =>
|
||||
src?.includes('https://cdnjs.cloudflare.com/ajax/libs/react/16.')
|
||||
);
|
||||
|
||||
// I'm reasonably sure this is fine, but we need to migrate transformers to
|
||||
// TypeScript to be sure.
|
||||
const transformers: ApplyFunctionProps[] = (isMultifile && hasJsx
|
||||
? getMultifileJSXTransformers(options)
|
||||
: getTransformers(options)) as unknown as ApplyFunctionProps[];
|
||||
|
||||
const pipeLine = composeFunctions(...transformers);
|
||||
const finalFiles = await Promise.all(challengeFiles.map(pipeLine));
|
||||
const error = finalFiles.find(({ error }) => error)?.error;
|
||||
const contents = (await embedFilesInHtml(finalFiles)) as string;
|
||||
|
||||
// if there is an error, we just build the test runner so that it can be
|
||||
// used to run tests against the code without actually running the code.
|
||||
const toBuild = error
|
||||
? {}
|
||||
: {
|
||||
required,
|
||||
template,
|
||||
contents
|
||||
};
|
||||
|
||||
return {
|
||||
challengeType,
|
||||
build: concatHtml(toBuild),
|
||||
sources: buildSourceMap(finalFiles),
|
||||
loadEnzyme: requiresReact16,
|
||||
error
|
||||
};
|
||||
}
|
||||
|
||||
export async function buildJSChallenge(
|
||||
{
|
||||
challengeFiles,
|
||||
challengeType
|
||||
}: { challengeFiles?: ChallengeFile[]; challengeType: number },
|
||||
options: BuildOptions
|
||||
): Promise<BuildResult> {
|
||||
if (!challengeFiles) throw Error('No challenge files provided');
|
||||
const pipeLine = composeFunctions(
|
||||
...(getTransformers(options) as unknown as ApplyFunctionProps[])
|
||||
);
|
||||
|
||||
const finalFiles = await Promise.all(challengeFiles?.map(pipeLine));
|
||||
const error = finalFiles.find(({ error }) => error)?.error;
|
||||
|
||||
const toBuild = error ? [] : finalFiles;
|
||||
|
||||
return {
|
||||
challengeType,
|
||||
build: toBuild
|
||||
.reduce(
|
||||
(body, challengeFile) => [
|
||||
...body,
|
||||
challengeFile.head,
|
||||
challengeFile.contents,
|
||||
challengeFile.tail
|
||||
],
|
||||
[] as string[]
|
||||
)
|
||||
.join('\n'),
|
||||
sources: buildSourceMap(finalFiles),
|
||||
error
|
||||
};
|
||||
}
|
||||
|
||||
function buildBackendChallenge({ url, challengeType }: BuildChallengeData) {
|
||||
return {
|
||||
challengeType,
|
||||
build: '',
|
||||
sources: { contents: url }
|
||||
};
|
||||
}
|
||||
|
||||
export async function buildPythonChallenge({
|
||||
challengeFiles,
|
||||
challengeType
|
||||
}: BuildChallengeData): Promise<BuildResult> {
|
||||
if (!challengeFiles) throw new Error('No challenge files provided');
|
||||
const pipeLine = composeFunctions(
|
||||
...(getPythonTransformers() as unknown as ApplyFunctionProps[])
|
||||
);
|
||||
const finalFiles = await Promise.all(challengeFiles.map(pipeLine));
|
||||
const error = finalFiles.find(({ error }) => error)?.error;
|
||||
const sources = buildSourceMap(finalFiles);
|
||||
|
||||
return {
|
||||
challengeType,
|
||||
sources,
|
||||
build: sources?.contents,
|
||||
error
|
||||
};
|
||||
}
|
||||
+2
-2
@@ -17,11 +17,11 @@ import {
|
||||
} from '@freecodecamp/shared/utils/polyvinyl';
|
||||
import { version } from '@freecodecamp/browser-scripts/package.json';
|
||||
|
||||
import { WorkerExecutor } from '../utils/worker-executor';
|
||||
import { WorkerExecutor } from './worker-executor';
|
||||
import {
|
||||
compileTypeScriptCode,
|
||||
checkTSServiceIsReady
|
||||
} from '../utils/typescript-worker-handler';
|
||||
} from './typescript-worker-handler';
|
||||
|
||||
const protectTimeout = 100;
|
||||
const testProtectTimeout = 1500;
|
||||
+3
-3
@@ -1,8 +1,8 @@
|
||||
import { version } from '@freecodecamp/browser-scripts/package.json';
|
||||
import browserScripts from '@freecodecamp/browser-scripts/package.json';
|
||||
|
||||
import { awaitResponse } from './awaitable-messenger';
|
||||
import { awaitResponse } from './awaitable-messenger.js';
|
||||
|
||||
const typeScriptWorkerSrc = `/js/workers/${version}/typescript-worker.js`;
|
||||
const typeScriptWorkerSrc = `/js/workers/${browserScripts.version}/typescript-worker.js`;
|
||||
|
||||
let worker: Worker | null = null;
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"include": ["src"],
|
||||
"extends": "../../tsconfig-base.json",
|
||||
"compilerOptions": {
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"module": "es2020",
|
||||
"moduleResolution": "bundler",
|
||||
"outDir": "dist",
|
||||
"noEmit": false
|
||||
}
|
||||
}
|
||||
Generated
+383
-257
File diff suppressed because it is too large
Load Diff
@@ -12,6 +12,8 @@
|
||||
"dist"
|
||||
],
|
||||
"exports": {
|
||||
".": "./index.d.ts",
|
||||
"./ts-compiler": "./modules/typescript-compiler.ts",
|
||||
"./test-runner": "./test-runner.ts",
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user