2021-08-02 13:39:40 +00:00
|
|
|
const fs = require('fs');
|
2017-01-18 23:16:23 +00:00
|
|
|
const path = require('path');
|
2021-08-02 13:39:40 +00:00
|
|
|
const util = require('util');
|
2022-04-15 14:17:49 +00:00
|
|
|
const assert = require('assert');
|
2021-08-02 13:39:40 +00:00
|
|
|
const yaml = require('js-yaml');
|
2022-04-15 14:17:49 +00:00
|
|
|
const { findIndex, isEmpty } = require('lodash');
|
2020-12-16 15:17:44 +00:00
|
|
|
const readDirP = require('readdirp');
|
2021-08-09 08:30:31 +00:00
|
|
|
const { helpCategoryMap } = require('../client/utils/challenge-types');
|
2021-08-02 13:39:40 +00:00
|
|
|
const { showUpcomingChanges } = require('../config/env.json');
|
|
|
|
const { curriculum: curriculumLangs } =
|
|
|
|
require('../config/i18n/all-langs').availableLangs;
|
2021-02-01 18:31:39 +00:00
|
|
|
const { parseMD } = require('../tools/challenge-parser/parser');
|
2020-02-24 14:05:18 +00:00
|
|
|
/* eslint-disable max-len */
|
|
|
|
const {
|
|
|
|
translateCommentsInChallenge
|
2021-02-03 09:46:27 +00:00
|
|
|
} = require('../tools/challenge-parser/translation-parser');
|
2020-02-24 14:05:18 +00:00
|
|
|
/* eslint-enable max-len*/
|
2018-01-19 19:03:17 +00:00
|
|
|
|
2021-06-15 17:37:52 +00:00
|
|
|
const { isAuditedCert } = require('../utils/is-audited');
|
2020-06-08 13:01:48 +00:00
|
|
|
const { createPoly } = require('../utils/polyvinyl');
|
2021-08-02 13:39:40 +00:00
|
|
|
const { dasherize } = require('../utils/slugs');
|
2022-03-10 20:41:33 +00:00
|
|
|
const { getSuperOrder, getSuperBlockFromDir } = require('./utils');
|
2021-06-11 14:40:37 +00:00
|
|
|
|
2020-08-28 15:10:37 +00:00
|
|
|
const access = util.promisify(fs.access);
|
|
|
|
|
2022-04-15 14:17:49 +00:00
|
|
|
const CHALLENGES_DIR = path.resolve(__dirname, 'challenges');
|
|
|
|
const META_DIR = path.resolve(CHALLENGES_DIR, '_meta');
|
|
|
|
exports.CHALLENGES_DIR = CHALLENGES_DIR;
|
|
|
|
exports.META_DIR = META_DIR;
|
2018-10-09 19:26:37 +00:00
|
|
|
|
2020-09-28 13:16:30 +00:00
|
|
|
const COMMENT_TRANSLATIONS = createCommentMap(
|
2022-04-15 14:17:49 +00:00
|
|
|
path.resolve(__dirname, 'dictionaries')
|
2020-09-28 13:16:30 +00:00
|
|
|
);
|
|
|
|
|
2020-09-23 14:38:20 +00:00
|
|
|
function getTranslatableComments(dictionariesDir) {
|
2021-01-31 16:06:57 +00:00
|
|
|
const COMMENTS_TO_TRANSLATE = require(path.resolve(
|
2020-09-23 14:38:20 +00:00
|
|
|
dictionariesDir,
|
|
|
|
'english',
|
2021-01-31 16:06:57 +00:00
|
|
|
'comments.json'
|
2020-09-23 14:38:20 +00:00
|
|
|
));
|
2021-02-02 19:14:29 +00:00
|
|
|
return Object.values(COMMENTS_TO_TRANSLATE);
|
2020-09-23 14:38:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
exports.getTranslatableComments = getTranslatableComments;
|
|
|
|
|
2020-09-28 13:16:30 +00:00
|
|
|
function createCommentMap(dictionariesDir) {
|
|
|
|
// get all the languages for which there are dictionaries.
|
|
|
|
const languages = fs
|
|
|
|
.readdirSync(dictionariesDir)
|
|
|
|
.filter(x => x !== 'english');
|
|
|
|
|
|
|
|
// get all their dictionaries
|
|
|
|
const dictionaries = languages.reduce(
|
|
|
|
(acc, lang) => ({
|
|
|
|
...acc,
|
2021-01-31 16:06:57 +00:00
|
|
|
[lang]: require(path.resolve(dictionariesDir, lang, 'comments.json'))
|
2020-09-28 13:16:30 +00:00
|
|
|
}),
|
|
|
|
{}
|
|
|
|
);
|
|
|
|
|
|
|
|
// get the english dicts
|
2021-01-31 16:06:57 +00:00
|
|
|
const COMMENTS_TO_TRANSLATE = require(path.resolve(
|
|
|
|
dictionariesDir,
|
|
|
|
'english',
|
|
|
|
'comments.json'
|
|
|
|
));
|
|
|
|
|
2021-02-02 19:14:29 +00:00
|
|
|
const COMMENTS_TO_NOT_TRANSLATE = require(path.resolve(
|
2021-01-31 16:06:57 +00:00
|
|
|
dictionariesDir,
|
|
|
|
'english',
|
|
|
|
'comments-to-not-translate'
|
|
|
|
));
|
2020-09-28 13:16:30 +00:00
|
|
|
|
|
|
|
// map from english comment text to translations
|
2021-02-02 19:14:29 +00:00
|
|
|
const translatedCommentMap = Object.entries(COMMENTS_TO_TRANSLATE).reduce(
|
|
|
|
(acc, [id, text]) => {
|
2020-09-28 13:16:30 +00:00
|
|
|
return {
|
|
|
|
...acc,
|
|
|
|
[text]: getTranslationEntry(dictionaries, { engId: id, text })
|
|
|
|
};
|
|
|
|
},
|
|
|
|
{}
|
|
|
|
);
|
|
|
|
|
|
|
|
// map from english comment text to itself
|
2021-02-02 19:14:29 +00:00
|
|
|
const untranslatableCommentMap = Object.values(
|
|
|
|
COMMENTS_TO_NOT_TRANSLATE
|
|
|
|
).reduce((acc, text) => {
|
|
|
|
const englishEntry = languages.reduce(
|
|
|
|
(acc, lang) => ({
|
2020-09-28 13:16:30 +00:00
|
|
|
...acc,
|
2021-02-02 19:14:29 +00:00
|
|
|
[lang]: text
|
|
|
|
}),
|
|
|
|
{}
|
|
|
|
);
|
|
|
|
return {
|
|
|
|
...acc,
|
|
|
|
[text]: englishEntry
|
|
|
|
};
|
|
|
|
}, {});
|
2020-09-28 13:16:30 +00:00
|
|
|
|
|
|
|
return { ...translatedCommentMap, ...untranslatableCommentMap };
|
|
|
|
}
|
|
|
|
|
|
|
|
exports.createCommentMap = createCommentMap;
|
|
|
|
|
|
|
|
function getTranslationEntry(dicts, { engId, text }) {
|
|
|
|
return Object.keys(dicts).reduce((acc, lang) => {
|
2021-02-02 19:14:29 +00:00
|
|
|
const entry = dicts[lang][engId];
|
2020-09-28 13:16:30 +00:00
|
|
|
if (entry) {
|
2021-02-02 19:14:29 +00:00
|
|
|
return { ...acc, [lang]: entry };
|
2020-09-28 13:16:30 +00:00
|
|
|
} else {
|
2022-04-15 14:17:49 +00:00
|
|
|
// default to english
|
|
|
|
return { ...acc, [lang]: text };
|
2020-09-28 13:16:30 +00:00
|
|
|
}
|
|
|
|
}, {});
|
|
|
|
}
|
|
|
|
|
2018-11-18 18:04:04 +00:00
|
|
|
function getChallengesDirForLang(lang) {
|
2022-04-15 14:17:49 +00:00
|
|
|
return path.resolve(CHALLENGES_DIR, `${lang}`);
|
2018-11-18 18:04:04 +00:00
|
|
|
}
|
|
|
|
|
2020-04-23 17:01:15 +00:00
|
|
|
function getMetaForBlock(block) {
|
|
|
|
return JSON.parse(
|
2022-04-15 14:17:49 +00:00
|
|
|
fs.readFileSync(path.resolve(META_DIR, `${block}/meta.json`), 'utf8')
|
2020-04-23 17:01:15 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-02-01 16:01:31 +00:00
|
|
|
function parseCert(filePath) {
|
|
|
|
return yaml.load(fs.readFileSync(filePath, 'utf8'));
|
|
|
|
}
|
|
|
|
|
2018-11-18 18:04:04 +00:00
|
|
|
exports.getChallengesDirForLang = getChallengesDirForLang;
|
2020-04-23 17:01:15 +00:00
|
|
|
exports.getMetaForBlock = getMetaForBlock;
|
2018-11-18 18:04:04 +00:00
|
|
|
|
2020-12-16 15:17:44 +00:00
|
|
|
// This recursively walks the directories starting at root, and calls cb for
|
|
|
|
// each file/directory and only resolves once all the callbacks do.
|
|
|
|
const walk = (root, target, options, cb) => {
|
2019-03-15 21:12:56 +00:00
|
|
|
return new Promise(resolve => {
|
|
|
|
let running = 1;
|
|
|
|
function done() {
|
|
|
|
if (--running === 0) {
|
2020-12-16 15:17:44 +00:00
|
|
|
resolve(target);
|
2019-03-15 21:12:56 +00:00
|
|
|
}
|
|
|
|
}
|
2020-12-16 15:17:44 +00:00
|
|
|
readDirP(root, options)
|
2019-03-15 21:12:56 +00:00
|
|
|
.on('data', file => {
|
|
|
|
running++;
|
2020-12-16 15:17:44 +00:00
|
|
|
cb(file, target).then(done);
|
2019-03-15 21:12:56 +00:00
|
|
|
})
|
|
|
|
.on('end', done);
|
|
|
|
});
|
2018-10-04 13:45:22 +00:00
|
|
|
};
|
|
|
|
|
2020-12-16 15:17:44 +00:00
|
|
|
exports.getChallengesForLang = async function getChallengesForLang(lang) {
|
2022-04-15 14:17:49 +00:00
|
|
|
// english determines the shape of the curriculum, all other languages mirror
|
|
|
|
// it.
|
|
|
|
const root = getChallengesDirForLang('english');
|
2020-12-16 15:17:44 +00:00
|
|
|
// scaffold the curriculum, first set up the superblocks, then recurse into
|
|
|
|
// the blocks
|
|
|
|
const curriculum = await walk(
|
|
|
|
root,
|
|
|
|
{},
|
2021-12-21 18:35:51 +00:00
|
|
|
{ type: 'directories', depth: 0 },
|
2020-12-16 15:17:44 +00:00
|
|
|
buildSuperBlocks
|
|
|
|
);
|
2022-04-15 14:17:49 +00:00
|
|
|
Object.entries(curriculum).forEach(([name, superBlock]) => {
|
|
|
|
assert(!isEmpty(superBlock.blocks), `superblock ${name} has no blocks`);
|
|
|
|
});
|
2020-12-16 15:17:44 +00:00
|
|
|
const cb = (file, curriculum) => buildChallenges(file, curriculum, lang);
|
|
|
|
// fill the scaffold with the challenges
|
|
|
|
return walk(
|
|
|
|
root,
|
|
|
|
curriculum,
|
2021-02-01 16:01:31 +00:00
|
|
|
{ type: 'files', fileFilter: ['*.md', '*.yml'] },
|
2020-12-16 15:17:44 +00:00
|
|
|
cb
|
|
|
|
);
|
|
|
|
};
|
2020-09-03 22:07:40 +00:00
|
|
|
|
2022-03-10 20:41:33 +00:00
|
|
|
async function buildBlocks({ basename: blockName }, curriculum, superBlock) {
|
2022-04-15 14:17:49 +00:00
|
|
|
const metaPath = path.resolve(META_DIR, `${blockName}/meta.json`);
|
2022-03-22 19:11:44 +00:00
|
|
|
|
|
|
|
if (fs.existsSync(metaPath)) {
|
|
|
|
// try to read the file, if the meta path does not exist it should be a certification.
|
|
|
|
// As they do not have meta files.
|
|
|
|
|
|
|
|
const blockMeta = JSON.parse(fs.readFileSync(metaPath));
|
|
|
|
|
2021-12-21 18:35:51 +00:00
|
|
|
const { isUpcomingChange } = blockMeta;
|
2022-03-22 19:11:44 +00:00
|
|
|
|
2021-12-21 18:35:51 +00:00
|
|
|
if (typeof isUpcomingChange !== 'boolean') {
|
|
|
|
throw Error(
|
|
|
|
`meta file at ${metaPath} is missing 'isUpcomingChange', it must be 'true' or 'false'`
|
|
|
|
);
|
|
|
|
}
|
2020-12-16 15:17:44 +00:00
|
|
|
|
2021-12-21 18:35:51 +00:00
|
|
|
if (!isUpcomingChange || showUpcomingChanges) {
|
|
|
|
// add the block to the superBlock
|
|
|
|
const blockInfo = { meta: blockMeta, challenges: [] };
|
2022-03-10 20:41:33 +00:00
|
|
|
curriculum[superBlock].blocks[blockName] = blockInfo;
|
2021-12-21 18:35:51 +00:00
|
|
|
}
|
2022-03-22 19:11:44 +00:00
|
|
|
} else {
|
2022-03-10 20:41:33 +00:00
|
|
|
curriculum['certifications'].blocks[blockName] = { challenges: [] };
|
2018-10-04 13:45:22 +00:00
|
|
|
}
|
2020-12-16 15:17:44 +00:00
|
|
|
}
|
2018-11-16 18:22:52 +00:00
|
|
|
|
2020-12-16 15:17:44 +00:00
|
|
|
async function buildSuperBlocks({ path, fullPath }, curriculum) {
|
2022-03-10 20:41:33 +00:00
|
|
|
const superBlock = getSuperBlockFromDir(getBaseDir(path));
|
|
|
|
curriculum[superBlock] = { blocks: {} };
|
2020-12-16 15:17:44 +00:00
|
|
|
|
2022-03-10 20:41:33 +00:00
|
|
|
const cb = (file, curriculum) => buildBlocks(file, curriculum, superBlock);
|
2020-12-16 15:17:44 +00:00
|
|
|
return walk(fullPath, curriculum, { depth: 1, type: 'directories' }, cb);
|
|
|
|
}
|
|
|
|
|
2021-12-09 19:53:12 +00:00
|
|
|
async function buildChallenges({ path: filePath }, curriculum, lang) {
|
2020-12-16 15:17:44 +00:00
|
|
|
// path is relative to getChallengesDirForLang(lang)
|
2021-12-09 19:53:12 +00:00
|
|
|
const block = getBlockNameFromPath(filePath);
|
2022-03-10 20:41:33 +00:00
|
|
|
const superBlockDir = getBaseDir(filePath);
|
|
|
|
const superBlock = getSuperBlockFromDir(superBlockDir);
|
2018-10-10 20:20:40 +00:00
|
|
|
let challengeBlock;
|
2020-09-03 22:07:40 +00:00
|
|
|
|
|
|
|
// TODO: this try block and process exit can all go once errors terminate the
|
|
|
|
// tests correctly.
|
2018-10-10 20:20:40 +00:00
|
|
|
try {
|
2022-03-10 20:41:33 +00:00
|
|
|
challengeBlock = curriculum[superBlock].blocks[block];
|
2020-09-03 22:07:40 +00:00
|
|
|
if (!challengeBlock) {
|
|
|
|
// this should only happen when a isUpcomingChange block is skipped
|
|
|
|
return;
|
|
|
|
}
|
2018-10-10 20:20:40 +00:00
|
|
|
} catch (e) {
|
2022-03-10 20:41:33 +00:00
|
|
|
console.log(`failed to create superBlock from ${superBlockDir}`);
|
2018-10-25 09:54:57 +00:00
|
|
|
// eslint-disable-next-line no-process-exit
|
2020-09-03 22:07:40 +00:00
|
|
|
process.exit(1);
|
2018-10-10 20:20:40 +00:00
|
|
|
}
|
2018-10-04 13:45:22 +00:00
|
|
|
const { meta } = challengeBlock;
|
2021-12-09 19:53:12 +00:00
|
|
|
const isCert = path.extname(filePath) === '.yml';
|
2021-12-20 18:36:31 +00:00
|
|
|
// TODO: there's probably a better way, but this makes sure we don't build any
|
|
|
|
// of the new curriculum when we don't want it.
|
2022-05-16 09:53:45 +00:00
|
|
|
if (
|
|
|
|
!showUpcomingChanges &&
|
|
|
|
meta?.superBlock === '2022/javascript-algorithms-and-data-structures'
|
|
|
|
) {
|
|
|
|
return;
|
|
|
|
}
|
2022-04-15 14:17:49 +00:00
|
|
|
const createChallenge = generateChallengeCreator(CHALLENGES_DIR, lang);
|
2021-12-09 19:53:12 +00:00
|
|
|
const challenge = isCert
|
2022-04-15 14:17:49 +00:00
|
|
|
? await createCertification(CHALLENGES_DIR, filePath, lang)
|
|
|
|
: await createChallenge(filePath, meta);
|
2018-11-16 18:22:52 +00:00
|
|
|
|
|
|
|
challengeBlock.challenges = [...challengeBlock.challenges, challenge];
|
|
|
|
}
|
|
|
|
|
2021-01-13 02:31:00 +00:00
|
|
|
async function parseTranslation(transPath, dict, lang, parse = parseMD) {
|
2020-11-27 18:02:05 +00:00
|
|
|
const translatedChal = await parse(transPath);
|
2020-02-24 14:05:18 +00:00
|
|
|
|
2021-03-16 15:22:39 +00:00
|
|
|
const { challengeType } = translatedChal;
|
|
|
|
// challengeType 11 is for video challenges and 3 is for front-end projects
|
|
|
|
// neither of which have seeds.
|
|
|
|
return challengeType !== 11 && challengeType !== 3
|
2021-01-13 02:31:00 +00:00
|
|
|
? translateCommentsInChallenge(translatedChal, lang, dict)
|
|
|
|
: translatedChal;
|
2020-02-24 14:05:18 +00:00
|
|
|
}
|
|
|
|
|
2022-04-15 14:17:49 +00:00
|
|
|
async function createCertification(basePath, filePath) {
|
2021-12-09 19:53:12 +00:00
|
|
|
function getFullPath(pathLang) {
|
|
|
|
return path.resolve(__dirname, basePath, pathLang, filePath);
|
|
|
|
}
|
2021-12-21 18:35:51 +00:00
|
|
|
// TODO: restart using isAudited() once we can determine a) the superBlocks
|
|
|
|
// (plural) a certification belongs to and b) get that info from the parsed
|
|
|
|
// certification, rather than the path. ASSUMING that this is used by the
|
|
|
|
// client. If not, delete this comment and the lang param.
|
|
|
|
return parseCert(getFullPath('english'));
|
2021-12-09 19:53:12 +00:00
|
|
|
}
|
|
|
|
|
2022-04-15 14:17:49 +00:00
|
|
|
// This is a slightly weird abstraction, but it lets us define helper functions
|
|
|
|
// without passing around a ton of arguments.
|
|
|
|
function generateChallengeCreator(basePath, lang) {
|
|
|
|
function getFullPath(pathLang, filePath) {
|
2020-12-17 14:02:56 +00:00
|
|
|
return path.resolve(__dirname, basePath, pathLang, filePath);
|
|
|
|
}
|
2022-04-15 14:17:49 +00:00
|
|
|
|
|
|
|
async function validate(filePath, superBlock) {
|
|
|
|
const invalidLang = !curriculumLangs.includes(lang);
|
|
|
|
if (invalidLang)
|
|
|
|
throw Error(`${lang} is not a accepted language.
|
|
|
|
Trying to parse ${filePath}`);
|
|
|
|
|
|
|
|
const missingEnglish =
|
|
|
|
lang !== 'english' && !(await hasEnglishSource(basePath, filePath));
|
|
|
|
if (missingEnglish)
|
|
|
|
throw Error(`Missing English challenge for
|
2020-08-28 15:10:37 +00:00
|
|
|
${filePath}
|
|
|
|
It should be in
|
2022-04-15 14:17:49 +00:00
|
|
|
${getFullPath('english', filePath)}
|
2020-08-28 15:10:37 +00:00
|
|
|
`);
|
2022-04-15 14:17:49 +00:00
|
|
|
|
|
|
|
const missingAuditedChallenge =
|
|
|
|
isAuditedCert(lang, superBlock) &&
|
|
|
|
!fs.existsSync(getFullPath(lang, filePath));
|
|
|
|
if (missingAuditedChallenge)
|
|
|
|
throw Error(`Missing ${lang} audited challenge for
|
|
|
|
${filePath}
|
|
|
|
No audited challenges should fallback to English.
|
|
|
|
`);
|
|
|
|
}
|
|
|
|
|
|
|
|
function addMetaToChallenge(challenge, meta) {
|
|
|
|
const challengeOrder = findIndex(
|
|
|
|
meta.challengeOrder,
|
|
|
|
([id]) => id === challenge.id
|
|
|
|
);
|
|
|
|
|
|
|
|
challenge.block = meta.name ? dasherize(meta.name) : null;
|
|
|
|
challenge.hasEditableBoundaries = !!meta.hasEditableBoundaries;
|
|
|
|
challenge.order = meta.order;
|
2022-05-16 09:53:45 +00:00
|
|
|
// const superOrder = getSuperOrder(meta.superBlock);
|
2022-04-25 18:00:27 +00:00
|
|
|
// NOTE: Use this version when a super block is in beta.
|
2022-05-16 09:53:45 +00:00
|
|
|
const superOrder = getSuperOrder(meta.superBlock, {
|
|
|
|
// switch this back to SHOW_NEW_CURRICULUM when we're ready to beta the JS superblock
|
|
|
|
showNewCurriculum: process.env.SHOW_UPCOMING_CHANGES === 'true'
|
|
|
|
});
|
2022-04-15 14:17:49 +00:00
|
|
|
if (superOrder !== null) challenge.superOrder = superOrder;
|
|
|
|
/* Since there can be more than one way to complete a certification (using the
|
2021-12-20 18:36:31 +00:00
|
|
|
legacy curriculum or the new one, for instance), we need a certification
|
|
|
|
field to track which certification this belongs to. */
|
2022-05-16 09:53:45 +00:00
|
|
|
const dupeCertifications = [
|
|
|
|
{
|
|
|
|
certification: 'responsive-web-design',
|
|
|
|
dupe: '2022/responsive-web-design'
|
|
|
|
},
|
|
|
|
{
|
|
|
|
certification: 'javascript-algorithms-and-data-structures',
|
|
|
|
dupe: '2022/javascript-algorithms-and-data-structures'
|
|
|
|
}
|
|
|
|
];
|
|
|
|
const hasDupe = dupeCertifications.find(
|
|
|
|
cert => cert.dupe === meta.superBlock
|
|
|
|
);
|
|
|
|
challenge.certification = hasDupe ? hasDupe.certification : meta.superBlock;
|
2022-04-15 14:17:49 +00:00
|
|
|
challenge.superBlock = meta.superBlock;
|
|
|
|
challenge.challengeOrder = challengeOrder;
|
|
|
|
challenge.isPrivate = challenge.isPrivate || meta.isPrivate;
|
|
|
|
challenge.required = (meta.required || []).concat(challenge.required || []);
|
|
|
|
challenge.template = meta.template;
|
|
|
|
challenge.time = meta.time;
|
|
|
|
challenge.helpCategory =
|
|
|
|
challenge.helpCategory || helpCategoryMap[challenge.block];
|
|
|
|
challenge.translationPending =
|
|
|
|
lang !== 'english' && !isAuditedCert(lang, meta.superBlock);
|
|
|
|
challenge.usesMultifileEditor = !!meta.usesMultifileEditor;
|
|
|
|
if (challenge.challengeFiles) {
|
|
|
|
// The client expects the challengeFiles to be an array of polyvinyls
|
|
|
|
challenge.challengeFiles = challengeFilesToPolys(
|
|
|
|
challenge.challengeFiles
|
|
|
|
);
|
|
|
|
}
|
|
|
|
if (challenge.solutions?.length) {
|
|
|
|
// The test runner needs the solutions to be arrays of polyvinyls so it
|
|
|
|
// can sort them correctly.
|
|
|
|
challenge.solutions = challenge.solutions.map(challengeFilesToPolys);
|
|
|
|
}
|
2020-06-12 12:47:58 +00:00
|
|
|
}
|
2022-01-24 18:42:27 +00:00
|
|
|
|
2022-04-15 14:17:49 +00:00
|
|
|
async function createChallenge(filePath, maybeMeta) {
|
|
|
|
const meta = maybeMeta
|
|
|
|
? maybeMeta
|
|
|
|
: require(path.resolve(
|
|
|
|
META_DIR,
|
|
|
|
`${getBlockNameFromPath(filePath)}/meta.json`
|
|
|
|
));
|
|
|
|
|
|
|
|
await validate(filePath, meta.superBlock);
|
|
|
|
|
|
|
|
const useEnglish =
|
|
|
|
lang === 'english' ||
|
|
|
|
!isAuditedCert(lang, meta.superBlock) ||
|
|
|
|
!fs.existsSync(getFullPath(lang, filePath));
|
|
|
|
|
|
|
|
const challenge = await (useEnglish
|
|
|
|
? parseMD(getFullPath('english', filePath))
|
|
|
|
: parseTranslation(
|
|
|
|
getFullPath(lang, filePath),
|
|
|
|
COMMENT_TRANSLATIONS,
|
|
|
|
lang
|
|
|
|
));
|
|
|
|
|
|
|
|
addMetaToChallenge(challenge, meta);
|
|
|
|
|
|
|
|
return challenge;
|
|
|
|
}
|
|
|
|
return createChallenge;
|
2018-10-04 13:45:22 +00:00
|
|
|
}
|
|
|
|
|
2022-01-24 18:42:27 +00:00
|
|
|
function challengeFilesToPolys(files) {
|
|
|
|
return files.reduce((challengeFiles, challengeFile) => {
|
|
|
|
return [
|
|
|
|
...challengeFiles,
|
|
|
|
{
|
|
|
|
...createPoly(challengeFile),
|
|
|
|
seed: challengeFile.contents.slice(0)
|
|
|
|
}
|
|
|
|
];
|
|
|
|
}, []);
|
|
|
|
}
|
|
|
|
|
2020-12-17 14:02:56 +00:00
|
|
|
async function hasEnglishSource(basePath, translationPath) {
|
2020-08-28 15:10:37 +00:00
|
|
|
const englishRoot = path.resolve(__dirname, basePath, 'english');
|
2020-12-17 14:02:56 +00:00
|
|
|
return await access(
|
|
|
|
path.join(englishRoot, translationPath),
|
|
|
|
fs.constants.F_OK
|
|
|
|
)
|
|
|
|
.then(() => true)
|
|
|
|
.catch(() => false);
|
2020-02-24 14:05:18 +00:00
|
|
|
}
|
|
|
|
|
2021-12-21 18:35:51 +00:00
|
|
|
function getBaseDir(filePath) {
|
|
|
|
const [baseDir] = filePath.split(path.sep);
|
|
|
|
return baseDir;
|
2015-12-07 05:44:34 +00:00
|
|
|
}
|
|
|
|
|
2018-10-04 13:45:22 +00:00
|
|
|
function getBlockNameFromPath(filePath) {
|
2018-10-25 09:54:57 +00:00
|
|
|
const [, block] = filePath.split(path.sep);
|
2018-10-04 13:45:22 +00:00
|
|
|
return block;
|
|
|
|
}
|
2018-11-16 18:22:52 +00:00
|
|
|
|
2020-12-17 14:02:56 +00:00
|
|
|
exports.hasEnglishSource = hasEnglishSource;
|
2020-08-28 15:10:37 +00:00
|
|
|
exports.parseTranslation = parseTranslation;
|
2022-04-15 14:17:49 +00:00
|
|
|
exports.generateChallengeCreator = generateChallengeCreator;
|