feat(rechallenge): Retool challenge framework (#13666)
* feat(rechallenge): Retool challenge framework * fix(code-storage): should use setContent not updateContent * fix(rechallenge): fix context issue and temporal zone of death * fix(rechallenge): Fix frame sources for user code * fix(polyvinyl): Set should ignore source and transform should keep track of source * fix(rechallenge): Missing return statement causing issues
This commit is contained in:
committed by
Quincy Larson
parent
da52116860
commit
ee8ac7b453
@@ -1,3 +1,9 @@
|
||||
import cond from 'lodash/cond';
|
||||
import identity from 'lodash/identity';
|
||||
import matchesProperty from 'lodash/matchesProperty';
|
||||
import stubTrue from 'lodash/stubTrue';
|
||||
import conforms from 'lodash/conforms';
|
||||
|
||||
import * as babel from 'babel-core';
|
||||
import presetEs2015 from 'babel-preset-es2015';
|
||||
import presetReact from 'babel-preset-react';
|
||||
@@ -6,7 +12,11 @@ import { Observable } from 'rx';
|
||||
import loopProtect from 'loop-protect';
|
||||
/* eslint-enable import/no-unresolved */
|
||||
|
||||
import { updateContents } from '../../common/utils/polyvinyl';
|
||||
import {
|
||||
transformHeadTailAndContents,
|
||||
setContent
|
||||
} from '../../common/utils/polyvinyl.js';
|
||||
import castToObservable from '../../common/app/utils/cast-to-observable.js';
|
||||
|
||||
const babelOptions = { presets: [ presetEs2015, presetReact ] };
|
||||
loopProtect.hit = function hit(line) {
|
||||
@@ -18,67 +28,81 @@ loopProtect.hit = function hit(line) {
|
||||
throw new Error(err);
|
||||
};
|
||||
|
||||
const transformersForHtmlJS = {
|
||||
ext: /html|js/,
|
||||
transformers: [
|
||||
{
|
||||
name: 'add-loop-protect',
|
||||
transformer: function addLoopProtect(file) {
|
||||
const _contents = file.contents.toLowerCase();
|
||||
if (file.ext === 'html' && _contents.indexOf('<script>') === -1) {
|
||||
// No JavaScript in user code, so no need for loopProtect
|
||||
return updateContents(file.contents, file);
|
||||
}
|
||||
return updateContents(loopProtect(file.contents), file);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'replace-nbsp',
|
||||
nbspRegExp: new RegExp(String.fromCharCode(160), 'g'),
|
||||
transformer: function replaceNBSP(file) {
|
||||
return updateContents(
|
||||
file.contents.replace(this.nbspRegExp, ' '),
|
||||
file
|
||||
);
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
// const sourceReg =
|
||||
// /(<!-- fcc-start-source -->)([\s\S]*?)(?=<!-- fcc-end-source -->)/g;
|
||||
const HTML$JSReg = /html|js/;
|
||||
const console$logReg = /(?:\b)console(\.log\S+)/g;
|
||||
const NBSPReg = new RegExp(String.fromCharCode(160), 'g');
|
||||
|
||||
const transformersForJs = {
|
||||
ext: /js/,
|
||||
transformers: [
|
||||
{
|
||||
name: 'babel-transformer',
|
||||
transformer: function babelTransformer(file) {
|
||||
const result = babel.transform(file.contents, babelOptions);
|
||||
return updateContents(
|
||||
result.code,
|
||||
file
|
||||
);
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
const testHTMLJS = conforms({ ext: (ext) => HTML$JSReg.test(ext) });
|
||||
const testJS = matchesProperty('ext', 'js');
|
||||
|
||||
// Observable[Observable[File]]::addLoopProtect() => Observable[String]
|
||||
export default function transformers() {
|
||||
const source = this;
|
||||
return source.map(files$ => files$.flatMap(file => {
|
||||
if (!transformersForHtmlJS.ext.test(file.ext)) {
|
||||
return Observable.just(file);
|
||||
}
|
||||
if (
|
||||
transformersForJs.ext.test(file.ext) &&
|
||||
transformersForHtmlJS.ext.test(file.ext)
|
||||
) {
|
||||
return Observable.of(
|
||||
...transformersForHtmlJS.transformers,
|
||||
...transformersForJs.transformers
|
||||
)
|
||||
.reduce((file, context) => context.transformer(file), file);
|
||||
}
|
||||
return Observable.from(transformersForHtmlJS.transformers)
|
||||
.reduce((file, context) => context.transformer(file), file);
|
||||
}));
|
||||
// if shouldProxyConsole then we change instances of console log
|
||||
// to `window.__console.log`
|
||||
// this let's us tap into logging into the console.
|
||||
// currently we only do this to the main window and not the test window
|
||||
export function proxyLoggerTransformer(file) {
|
||||
return transformHeadTailAndContents(
|
||||
(source) => (
|
||||
source.replace(console$logReg, (match, methodCall) => {
|
||||
return 'window.__console' + methodCall;
|
||||
})),
|
||||
file
|
||||
);
|
||||
}
|
||||
|
||||
export const addLoopProtect = cond([
|
||||
[
|
||||
testHTMLJS,
|
||||
function(file) {
|
||||
const _contents = file.contents.toLowerCase();
|
||||
if (file.ext === 'html' && !_contents.indexOf('<script>') !== -1) {
|
||||
// No JavaScript in user code, so no need for loopProtect
|
||||
return file;
|
||||
}
|
||||
return setContent(loopProtect(file.contents), file);
|
||||
}
|
||||
],
|
||||
[ stubTrue, identity ]
|
||||
]);
|
||||
export const replaceNBSP = cond([
|
||||
[
|
||||
testHTMLJS,
|
||||
function(file) {
|
||||
return setContent(
|
||||
file.contents.replace(NBSPReg, ' '),
|
||||
file
|
||||
);
|
||||
}
|
||||
],
|
||||
[ stubTrue, identity ]
|
||||
]);
|
||||
|
||||
export const babelTransformer = cond([
|
||||
[
|
||||
testJS,
|
||||
function(file) {
|
||||
const result = babel.transform(file.contents, babelOptions);
|
||||
return setContent(
|
||||
result.code,
|
||||
file
|
||||
);
|
||||
}
|
||||
],
|
||||
[ stubTrue, identity ]
|
||||
]);
|
||||
|
||||
export const _transformers = [
|
||||
addLoopProtect,
|
||||
replaceNBSP,
|
||||
babelTransformer
|
||||
];
|
||||
|
||||
export function applyTransformers(file, transformers = _transformers) {
|
||||
return transformers.reduce(
|
||||
(obs, transformer) => {
|
||||
return obs.flatMap(file => castToObservable(transformer(file)));
|
||||
},
|
||||
Observable.of(file)
|
||||
);
|
||||
}
|
||||
|
Reference in New Issue
Block a user