Files
freeCodeCamp/client/src/templates/Challenges/utils/build.js

180 lines
5.1 KiB
JavaScript
Raw Normal View History

2019-01-09 03:23:17 +03:00
import { transformers } from '../rechallenge/transformers';
import { cssToHtml, jsToHtml, concatHtml } from '../rechallenge/builders.js';
2019-02-08 17:33:05 +03:00
import { challengeTypes } from '../../../../utils/challengeTypes';
import createWorker from './worker-executor';
import {
createTestFramer,
runTestInTestFrame,
createMainFramer
} from './frame';
// the config files are created during the build, but not before linting
// eslint-disable-next-line import/no-unresolved
import { filename as runner } from '../../../../config/frame-runner';
// eslint-disable-next-line import/no-unresolved
import { filename as testEvaluator } from '../../../../config/test-evaluator';
2018-12-29 11:34:03 +03:00
const frameRunner = [
{
src: `/js/${runner}.js`
2018-12-29 11:34:03 +03:00
}
];
const globalRequires = [
{
link:
'https://cdnjs.cloudflare.com/' +
'ajax/libs/normalize/4.2.0/normalize.min.css'
}
];
2018-12-29 16:42:09 +03:00
const applyFunction = fn =>
async function(file) {
try {
if (file.error) {
return file;
}
const newFile = await fn.call(this, file);
if (typeof newFile !== 'undefined') {
return newFile;
2018-10-06 02:36:38 +03:00
}
2018-12-29 16:42:09 +03:00
return file;
} catch (error) {
return { ...file, error };
2018-10-06 02:36:38 +03:00
}
2019-01-09 03:23:17 +03:00
};
2018-10-06 02:36:38 +03:00
2018-12-29 16:42:09 +03:00
const composeFunctions = (...fns) =>
2019-01-09 03:23:17 +03:00
fns.map(applyFunction).reduce((f, g) => x => f(x).then(g));
2018-12-29 11:34:03 +03:00
function buildSourceMap(files) {
return files.reduce((sources, file) => {
sources[file.name] = file.source || file.contents;
return sources;
}, {});
}
2019-01-09 03:23:17 +03:00
function checkFilesErrors(files) {
const errors = files.filter(({ error }) => error).map(({ error }) => error);
if (errors.length) {
throw errors;
}
return files;
}
2019-02-08 17:33:05 +03:00
const buildFunctions = {
[challengeTypes.js]: buildJSChallenge,
[challengeTypes.bonfire]: buildJSChallenge,
[challengeTypes.html]: buildDOMChallenge,
[challengeTypes.modern]: buildDOMChallenge,
[challengeTypes.backend]: buildBackendChallenge,
[challengeTypes.backEndProject]: buildBackendChallenge
2019-02-08 17:33:05 +03:00
};
export async function buildChallenge(challengeData) {
const { challengeType } = challengeData;
let build = buildFunctions[challengeType];
if (build) {
return build(challengeData);
}
throw new Error(`Cannot build challenge of type ${challengeType}`);
2019-02-08 17:33:05 +03:00
}
const testRunners = {
[challengeTypes.js]: getJSTestRunner,
[challengeTypes.html]: getDOMTestRunner,
[challengeTypes.backend]: getDOMTestRunner
};
export function getTestRunner(buildData, proxyLogger, document) {
const { challengeType } = buildData;
const testRunner = testRunners[challengeType];
if (testRunner) {
return testRunner(buildData, proxyLogger, document);
}
throw new Error(`Cannot get test runner for challenge type ${challengeType}`);
2019-02-08 17:33:05 +03:00
}
function getJSTestRunner({ build, sources }, proxyLogger) {
const code = sources && 'index' in sources ? sources['index'] : '';
const testWorker = createWorker(testEvaluator, { terminateWorker: true });
2019-02-08 17:33:05 +03:00
return (testString, testTimeout) => {
return testWorker
.execute({ build, testString, code, sources }, testTimeout)
.on('LOG', proxyLogger).done;
2019-02-08 17:33:05 +03:00
};
}
async function getDOMTestRunner(buildData, proxyLogger, document) {
await new Promise(resolve =>
createTestFramer(document, resolve, proxyLogger)(buildData)
);
return (testString, testTimeout) =>
runTestInTestFrame(document, testString, testTimeout);
}
export function buildDOMChallenge({ files, required = [], template = '' }) {
2018-12-29 11:34:03 +03:00
const finalRequires = [...globalRequires, ...required, ...frameRunner];
2019-02-08 17:33:05 +03:00
const loadEnzyme = Object.keys(files).some(key => files[key].ext === 'jsx');
2018-12-29 16:42:09 +03:00
const toHtml = [jsToHtml, cssToHtml];
const pipeLine = composeFunctions(...transformers, ...toHtml);
2018-12-29 11:34:03 +03:00
const finalFiles = Object.keys(files)
.map(key => files[key])
2018-12-29 11:34:03 +03:00
.map(pipeLine);
2019-01-09 03:23:17 +03:00
return Promise.all(finalFiles)
.then(checkFilesErrors)
.then(files => ({
2019-02-08 17:33:05 +03:00
challengeType: challengeTypes.html,
build: concatHtml({ required: finalRequires, template, files }),
2019-02-08 17:33:05 +03:00
sources: buildSourceMap(files),
loadEnzyme
2019-01-09 03:23:17 +03:00
}));
}
2019-02-08 17:33:05 +03:00
export function buildJSChallenge({ files }) {
const pipeLine = composeFunctions(...transformers);
2018-11-26 02:17:38 +03:00
const finalFiles = Object.keys(files)
.map(key => files[key])
.map(pipeLine);
2019-01-09 03:23:17 +03:00
return Promise.all(finalFiles)
.then(checkFilesErrors)
.then(files => ({
2019-02-08 17:33:05 +03:00
challengeType: challengeTypes.js,
2019-01-09 03:23:17 +03:00
build: files
.reduce(
(body, file) => [...body, file.head, file.contents, file.tail],
[]
)
.join('\n'),
sources: buildSourceMap(files)
}));
2018-11-26 02:17:38 +03:00
}
2019-02-08 17:33:05 +03:00
export function buildBackendChallenge({ url }) {
return {
2019-02-08 17:33:05 +03:00
challengeType: challengeTypes.backend,
2019-01-09 03:23:17 +03:00
build: concatHtml({ required: frameRunner }),
sources: { url }
};
}
export async function updatePreview(buildData, document, proxyLogger) {
const { challengeType } = buildData;
if (challengeType === challengeTypes.html) {
await new Promise(resolve =>
createMainFramer(document, resolve, proxyLogger)(buildData)
);
} else {
throw new Error(`Cannot show preview for challenge type ${challengeType}`);
}
}
export function challengeHasPreview({ challengeType }) {
return (
challengeType === challengeTypes.html ||
challengeType === challengeTypes.modern
);
}