2017-01-18 23:16:23 +00:00
|
|
|
const path = require('path');
|
2018-10-04 13:45:22 +00:00
|
|
|
const { findIndex } = require('lodash');
|
|
|
|
const readDirP = require('readdirp-walk');
|
2018-10-04 17:25:49 +00:00
|
|
|
const { parseMarkdown } = require('@freecodecamp/challenge-md-parser');
|
2020-04-23 17:01:15 +00:00
|
|
|
const fs = require('fs');
|
2018-01-19 19:03:17 +00:00
|
|
|
|
2019-09-25 18:16:08 +00:00
|
|
|
const { dasherize } = require('../utils/slugs');
|
2018-10-28 06:18:13 +00:00
|
|
|
|
2018-10-09 19:26:37 +00:00
|
|
|
const challengesDir = path.resolve(__dirname, './challenges');
|
2018-11-16 18:22:52 +00:00
|
|
|
const metaDir = path.resolve(challengesDir, '_meta');
|
|
|
|
exports.challengesDir = challengesDir;
|
|
|
|
exports.metaDir = metaDir;
|
2018-10-09 19:26:37 +00:00
|
|
|
|
2018-11-18 18:04:04 +00:00
|
|
|
function getChallengesDirForLang(lang) {
|
|
|
|
return path.resolve(challengesDir, `./${lang}`);
|
|
|
|
}
|
|
|
|
|
2020-04-23 17:01:15 +00:00
|
|
|
function getMetaForBlock(block) {
|
|
|
|
return JSON.parse(
|
|
|
|
fs.readFileSync(path.resolve(metaDir, `./${block}/meta.json`), '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
|
|
|
|
2018-10-04 13:45:22 +00:00
|
|
|
exports.getChallengesForLang = function getChallengesForLang(lang) {
|
|
|
|
let curriculum = {};
|
2019-03-15 21:12:56 +00:00
|
|
|
return new Promise(resolve => {
|
|
|
|
let running = 1;
|
|
|
|
function done() {
|
|
|
|
if (--running === 0) {
|
|
|
|
resolve(curriculum);
|
|
|
|
}
|
|
|
|
}
|
2018-11-18 18:04:04 +00:00
|
|
|
readDirP({ root: getChallengesDirForLang(lang) })
|
2019-03-15 21:12:56 +00:00
|
|
|
.on('data', file => {
|
|
|
|
running++;
|
2020-06-13 09:27:15 +00:00
|
|
|
buildCurriculum(file, curriculum).then(done);
|
2019-03-15 21:12:56 +00:00
|
|
|
})
|
|
|
|
.on('end', done);
|
|
|
|
});
|
2018-10-04 13:45:22 +00:00
|
|
|
};
|
|
|
|
|
2020-06-13 09:27:15 +00:00
|
|
|
async function buildCurriculum(file, curriculum) {
|
2018-11-18 18:04:04 +00:00
|
|
|
const { name, depth, path: filePath, fullPath, stat } = file;
|
2018-10-04 13:45:22 +00:00
|
|
|
if (depth === 1 && stat.isDirectory()) {
|
|
|
|
// extract the superBlock info
|
|
|
|
const { order, name: superBlock } = superBlockInfo(name);
|
|
|
|
curriculum[superBlock] = { superBlock, order, blocks: {} };
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (depth === 2 && stat.isDirectory()) {
|
2018-10-09 19:26:37 +00:00
|
|
|
const blockName = getBlockNameFromPath(filePath);
|
|
|
|
const metaPath = path.resolve(
|
|
|
|
__dirname,
|
|
|
|
`./challenges/_meta/${blockName}/meta.json`
|
|
|
|
);
|
|
|
|
const blockMeta = require(metaPath);
|
|
|
|
const { name: superBlock } = superBlockInfoFromPath(filePath);
|
2018-10-04 13:45:22 +00:00
|
|
|
const blockInfo = { meta: blockMeta, challenges: [] };
|
|
|
|
curriculum[superBlock].blocks[name] = blockInfo;
|
|
|
|
return;
|
|
|
|
}
|
2018-10-10 20:20:40 +00:00
|
|
|
if (name === 'meta.json' || name === '.DS_Store') {
|
2018-10-04 13:45:22 +00:00
|
|
|
return;
|
|
|
|
}
|
2018-11-16 18:22:52 +00:00
|
|
|
|
2018-10-09 19:26:37 +00:00
|
|
|
const block = getBlockNameFromPath(filePath);
|
|
|
|
const { name: superBlock } = superBlockInfoFromPath(filePath);
|
2018-10-10 20:20:40 +00:00
|
|
|
let challengeBlock;
|
|
|
|
try {
|
|
|
|
challengeBlock = curriculum[superBlock].blocks[block];
|
|
|
|
} catch (e) {
|
|
|
|
console.log(superBlock, block);
|
2018-10-25 09:54:57 +00:00
|
|
|
// eslint-disable-next-line no-process-exit
|
2018-10-10 20:20:40 +00:00
|
|
|
process.exit(0);
|
|
|
|
}
|
2018-10-04 13:45:22 +00:00
|
|
|
const { meta } = challengeBlock;
|
2018-11-16 18:22:52 +00:00
|
|
|
|
2020-06-13 09:27:15 +00:00
|
|
|
const challenge = await createChallenge(fullPath, meta);
|
2018-11-16 18:22:52 +00:00
|
|
|
|
|
|
|
challengeBlock.challenges = [...challengeBlock.challenges, challenge];
|
|
|
|
}
|
|
|
|
|
2020-06-13 09:27:15 +00:00
|
|
|
async function createChallenge(fullPath, maybeMeta) {
|
2018-11-16 18:22:52 +00:00
|
|
|
let meta;
|
|
|
|
if (maybeMeta) {
|
|
|
|
meta = maybeMeta;
|
|
|
|
} else {
|
2018-11-18 18:04:04 +00:00
|
|
|
const metaPath = path.resolve(
|
|
|
|
metaDir,
|
|
|
|
`./${getBlockNameFromFullPath(fullPath)}/meta.json`
|
|
|
|
);
|
2018-11-16 18:22:52 +00:00
|
|
|
meta = require(metaPath);
|
|
|
|
}
|
2018-11-18 18:04:04 +00:00
|
|
|
const { name: superBlock } = superBlockInfoFromFullPath(fullPath);
|
2018-11-16 18:22:52 +00:00
|
|
|
const challenge = await parseMarkdown(fullPath);
|
2018-10-04 13:45:22 +00:00
|
|
|
const challengeOrder = findIndex(
|
|
|
|
meta.challengeOrder,
|
|
|
|
([id]) => id === challenge.id
|
|
|
|
);
|
2018-11-18 18:16:09 +00:00
|
|
|
const {
|
|
|
|
name: blockName,
|
|
|
|
order,
|
|
|
|
superOrder,
|
|
|
|
isPrivate,
|
|
|
|
required = [],
|
|
|
|
template,
|
|
|
|
time
|
|
|
|
} = meta;
|
2018-10-04 13:45:22 +00:00
|
|
|
challenge.block = blockName;
|
2018-10-28 06:18:13 +00:00
|
|
|
challenge.dashedName = dasherize(challenge.title);
|
2018-10-04 13:45:22 +00:00
|
|
|
challenge.order = order;
|
|
|
|
challenge.superOrder = superOrder;
|
|
|
|
challenge.superBlock = superBlock;
|
|
|
|
challenge.challengeOrder = challengeOrder;
|
2018-11-18 18:16:09 +00:00
|
|
|
challenge.isPrivate = challenge.isPrivate || isPrivate;
|
|
|
|
challenge.required = required.concat(challenge.required || []);
|
|
|
|
challenge.template = template;
|
|
|
|
challenge.time = time;
|
2018-11-16 18:22:52 +00:00
|
|
|
|
2020-05-21 15:21:33 +00:00
|
|
|
// challenges can be hidden (so they do not appear in all environments e.g.
|
|
|
|
// production), SHOW_HIDDEN controls this.
|
|
|
|
if (process.env.SHOW_HIDDEN === 'true') {
|
|
|
|
challenge.isHidden = false;
|
|
|
|
}
|
|
|
|
|
2018-11-16 18:22:52 +00:00
|
|
|
return challenge;
|
2018-10-04 13:45:22 +00:00
|
|
|
}
|
|
|
|
|
2018-11-16 18:22:52 +00:00
|
|
|
exports.createChallenge = createChallenge;
|
|
|
|
|
2018-10-04 13:45:22 +00:00
|
|
|
function superBlockInfoFromPath(filePath) {
|
2018-10-25 09:54:57 +00:00
|
|
|
const [maybeSuper] = filePath.split(path.sep);
|
2018-10-04 13:45:22 +00:00
|
|
|
return superBlockInfo(maybeSuper);
|
2015-11-02 01:20:03 +00:00
|
|
|
}
|
|
|
|
|
2018-11-18 18:04:04 +00:00
|
|
|
function superBlockInfoFromFullPath(fullFilePath) {
|
2019-02-18 19:32:49 +00:00
|
|
|
const [, , maybeSuper] = fullFilePath.split(path.sep).reverse();
|
2018-11-18 18:04:04 +00:00
|
|
|
return superBlockInfo(maybeSuper);
|
|
|
|
}
|
|
|
|
|
2018-10-04 13:45:22 +00:00
|
|
|
function superBlockInfo(fileName) {
|
|
|
|
const [maybeOrder, ...superBlock] = fileName.split('-');
|
|
|
|
let order = parseInt(maybeOrder, 10);
|
2018-01-19 19:03:17 +00:00
|
|
|
if (isNaN(order)) {
|
2018-10-04 13:45:22 +00:00
|
|
|
return { order: 0, name: fileName };
|
2018-01-19 19:03:17 +00:00
|
|
|
} else {
|
|
|
|
return {
|
|
|
|
order: order,
|
2018-10-04 13:45:22 +00:00
|
|
|
name: superBlock.join('-')
|
2018-01-19 19:03:17 +00:00
|
|
|
};
|
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
|
|
|
|
|
|
|
function getBlockNameFromFullPath(fullFilePath) {
|
|
|
|
const [, block] = fullFilePath.split(path.sep).reverse();
|
|
|
|
return block;
|
|
|
|
}
|