diff --git a/curriculum/getChallenges.js b/curriculum/getChallenges.js index 47d456c452..3340c91968 100644 --- a/curriculum/getChallenges.js +++ b/curriculum/getChallenges.js @@ -1,76 +1,90 @@ -/* eslint-disable no-self-compare */ -// no import here as this runs without babel -const fs = require('fs'); const path = require('path'); -const omit = require('lodash/omit'); +const { findIndex } = require('lodash'); +const invariant = require('invariant'); +const readDirP = require('readdirp-walk'); -const hiddenFile = /(^(\.|\/\.))|(.md$)/g; +const { parseMarkdown } = require('../tools/challenge-md-parser'); -function getFilesFor(dir) { - let targetDir = path.join(__dirname, dir); - return fs - .readdirSync(targetDir) - .filter(file => !hiddenFile.test(file)) - .map(function(file) { - let superBlock; - if (fs.statSync(path.join(targetDir, file)).isFile()) { - return { file: file }; - } - superBlock = file; - return getFilesFor(path.join(dir, superBlock)).map(function(data) { - return { - file: path.join(superBlock, data.file), - superBlock: superBlock - }; - }); - }) - .reduce(function(files, entry) { - return files.concat(entry); - }, []); +const supportedLangs = ['english']; + +function validateLang(lang) { + invariant(lang, 'Please provide a language'); + invariant( + supportedLangs.includes(lang), + `${lang} is not supported + + Supported languages: ${JSON.stringify(supportedLangs, null, 2)} + + ` + ); } -function superblockInfo(filePath) { - let parts = (filePath || '').split('-'); - let order = parseInt(parts[0], 10); +exports.getChallengesForLang = function getChallengesForLang(lang) { + validateLang(lang); + let curriculum = {}; + return new Promise(resolve => + readDirP({ root: path.resolve(__dirname, `./challenges/${lang}`) }) + .on('data', file => buildCurriculum(file, curriculum)) + .on('end', () => resolve(curriculum)) + ); +}; + +async function buildCurriculum(file, curriculum) { + const { name, depth, path, fullPath, stat } = file; + 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()) { + const blockMeta = require(`${fullPath}/meta.json`); + const { name: superBlock } = superBlockInfoFromPath(path); + const blockInfo = { meta: blockMeta, challenges: [] }; + curriculum[superBlock].blocks[name] = blockInfo; + return; + } + if (name === 'meta.json') { + return; + } + const block = getBlockNameFromPath(path); + const { name: superBlock } = superBlockInfoFromPath(path); + const challenge = await parseMarkdown(fullPath); + const challengeBlock = curriculum[superBlock].blocks[block]; + const { meta } = challengeBlock; + const challengeOrder = findIndex( + meta.challengeOrder, + ([id]) => id === challenge.id + ); + const { name: blockName, order, superOrder } = meta; + challenge.block = blockName; + challenge.order = order; + challenge.superOrder = superOrder; + challenge.superBlock = superBlock; + challenge.challengeOrder = challengeOrder; + challengeBlock.challenges = [...challengeBlock.challenges, challenge]; +} + +function superBlockInfoFromPath(filePath) { + const [maybeSuper] = filePath.split('/'); + return superBlockInfo(maybeSuper); +} + +function superBlockInfo(fileName) { + const [maybeOrder, ...superBlock] = fileName.split('-'); + let order = parseInt(maybeOrder, 10); if (isNaN(order)) { - return { order: 0, name: filePath }; + return { order: 0, name: fileName }; } else { return { order: order, - name: parts.splice(1).join('-') + name: superBlock.join('-') }; } } -// unpackFlag is an argument passed by the unpack script in unpack.js -// which allows us to conditionall omit translations when running -// the test suite and prevent schema related errors in the main fCC branch -module.exports = function getChallenges(challengesDir, unpackFlag) { - if (!challengesDir) { - challengesDir = 'challenges'; - } - return getFilesFor(challengesDir).map(function(data) { - const challengeSpec = require('./' + challengesDir + '/' + data.file); - let superInfo = superblockInfo(data.superBlock); - challengeSpec.fileName = data.file; - challengeSpec.superBlock = superInfo.name; - challengeSpec.superOrder = superInfo.order; - challengeSpec.challenges = challengeSpec.challenges.map(challenge => - omit(challenge, [ - 'betaSolutions', - 'betaTests', - 'hints', - 'MDNlinks', - 'null', - 'rawSolutions', - 'react', - 'reactRedux', - 'redux', - 'releasedOn', - unpackFlag ? undefined : 'translations', - 'type' - ]) - ); - return challengeSpec; - }); -}; +function getBlockNameFromPath(filePath) { + const [, block] = filePath.split('/'); + return block; +} + diff --git a/curriculum/gulpfile.js b/curriculum/gulpfile.js index b6a7593bc8..0685eca58a 100644 --- a/curriculum/gulpfile.js +++ b/curriculum/gulpfile.js @@ -1,16 +1,7 @@ const gulp = require('gulp'); -const util = require('gulp-util'); -const jsonMinify = require('gulp-json-minify'); const babel = require('gulp-babel'); const rename = require('gulp-rename'); -gulp.task('json:minify', function() { - return gulp.src('./challenges/**/*.json') - .pipe(jsonMinify()) - .pipe(gulp.dest('dist/challenges/')) - .on('error', util.log); -}); - gulp.task('babel-getChallenges', () => gulp.src('./getChallenges.js') .pipe(babel({ diff --git a/curriculum/md-conversion.js b/curriculum/md-conversion.js index 5e1f17679a..16bac759fa 100644 --- a/curriculum/md-conversion.js +++ b/curriculum/md-conversion.js @@ -4,7 +4,7 @@ const { omit, findLastIndex } = require('lodash'); const YAML = require('js-yaml'); const { dasherize } = require('./utils'); -const getChallenges = require('./getChallenges'); +const { getChallenges } = require('./getChallenges'); const blackListedFieldNames = [ 'betaSolutions', @@ -22,21 +22,34 @@ const blackListedFieldNames = [ ]; getChallenges().forEach(block => { - const { name, order, challenges, time = '', superBlock, superOrder } = block; + const { + name, + order, + challenges, + time = '', + superBlock, + superOrder, + template = '', + required = [], + ...restBlock + } = block; const blockDashedName = dasherize(name); const blockMeta = { name, dashedName: blockDashedName, order, time, + template, + required, superBlock, superOrder, - challengeOrder: challenges.map(({id, title}) => [id, title]) + challengeOrder: challenges.map(({ id, title }) => [id, title]), + ...restBlock }; const superOrderPrefix = `0${superOrder}`; const outputDir = path.resolve( __dirname, - `./challenges/en/${superOrderPrefix}-${superBlock}/${blockDashedName}` + `./challenges/english/${superOrderPrefix}-${superBlock}/${blockDashedName}` ); fs.ensureDirSync(outputDir); @@ -49,7 +62,7 @@ getChallenges().forEach(block => { ...restChallenge } = challenge; const challengeMeta = omit(restChallenge, blackListedFieldNames); - const challengeFileName = `${dasherize(challenge.title)}.en.md`; + const challengeFileName = `${dasherize(challenge.title)}.english.md`; let description = ''; let instructions = ''; @@ -81,7 +94,7 @@ ${instructions}
\`\`\`yml -${YAML.dump(tests, { lineWidth: 10000 })} +${YAML.dump({ tests }, { lineWidth: 10000 })} \`\`\`
diff --git a/curriculum/package-lock.json b/curriculum/package-lock.json index f469087ea3..502a8d93e0 100644 --- a/curriculum/package-lock.json +++ b/curriculum/package-lock.json @@ -4600,6 +4600,12 @@ "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", "dev": true }, + "each-limit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/each-limit/-/each-limit-1.0.0.tgz", + "integrity": "sha1-OAFACDNnqK9kKZvKwV/dWxqXcZY=", + "dev": true + }, "editor": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/editor/-/editor-1.0.0.tgz", @@ -5090,6 +5096,12 @@ "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", "dev": true }, + "eventemitter3": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-1.2.0.tgz", + "integrity": "sha1-HIaZHYFq0eUEdQ5zh0Ik7PO+xQg=", + "dev": true + }, "events": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/events/-/events-2.1.0.tgz", @@ -7225,16 +7237,6 @@ "vinyl-sourcemaps-apply": "0.2.1" } }, - "gulp-json-minify": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/gulp-json-minify/-/gulp-json-minify-1.2.2.tgz", - "integrity": "sha512-8acCNnGiDELUh9qqZVm57eu4KVclibiVM7hXxeMn3nsdcQtEWfHUSpcxvrqTVCL5MchErGMDDDSxmcF6RTFcJQ==", - "dev": true, - "requires": { - "node-json-minify": "1.0.0", - "through2": "2.0.3" - } - }, "gulp-rename": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/gulp-rename/-/gulp-rename-1.3.0.tgz", @@ -7701,7 +7703,6 @@ "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "dev": true, "requires": { "loose-envify": "1.3.1" } @@ -8253,8 +8254,7 @@ "js-tokens": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", - "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", - "dev": true + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=" }, "js-yaml": { "version": "3.12.0", @@ -9138,6 +9138,18 @@ "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", "dev": true }, + "lodash.isobject": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-3.0.2.tgz", + "integrity": "sha1-PI+41bW/S/kK4G4U8qUwpO2TXh0=", + "dev": true + }, + "lodash.isundefined": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash.isundefined/-/lodash.isundefined-3.0.1.tgz", + "integrity": "sha1-I+89lTVWUgOmbO/VuDD4SJEa+0g=", + "dev": true + }, "lodash.kebabcase": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz", @@ -9347,7 +9359,6 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz", "integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=", - "dev": true, "requires": { "js-tokens": "3.0.2" } @@ -9826,12 +9837,6 @@ "is-stream": "1.1.0" } }, - "node-json-minify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-json-minify/-/node-json-minify-1.0.0.tgz", - "integrity": "sha1-e7NDL5ZYtr6x2ZP9XVOzyinQ15w=", - "dev": true - }, "normalize-package-data": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", @@ -11039,6 +11044,17 @@ "set-immediate-shim": "1.0.1" } }, + "readdirp-walk": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/readdirp-walk/-/readdirp-walk-1.6.0.tgz", + "integrity": "sha1-B1ZfV6uTqWfW3mRPRmPAE1b1oLo=", + "dev": true, + "requires": { + "eventemitter3": "1.2.0", + "micromatch": "2.3.11", + "walk-filtered": "0.8.0" + } + }, "rechoir": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", @@ -12926,6 +12942,19 @@ "integrity": "sha512-EqzLchIMYLBjRPoqVsEkZOa/4Vr2RfOWbd58F+I/Gj79AYTrsseMunxbbSkbYfrqZaXSuPBBXNSOhtJgg0PpmA==", "dev": true }, + "walk-filtered": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/walk-filtered/-/walk-filtered-0.8.0.tgz", + "integrity": "sha1-RsMaJG7HTO9QwOtLSxVjvHYaAKU=", + "dev": true, + "requires": { + "each-limit": "1.0.0", + "graceful-fs": "4.1.11", + "lodash.isobject": "3.0.2", + "lodash.isundefined": "3.0.1", + "object-assign": "4.1.1" + } + }, "whatwg-fetch": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz", diff --git a/curriculum/package.json b/curriculum/package.json index 2576f30ce9..bc2676d06a 100644 --- a/curriculum/package.json +++ b/curriculum/package.json @@ -12,11 +12,9 @@ "url": "https://github.com/freeCodeCamp/curriculum/issues" }, "version": "3.2.1", - "main": "dist/index.js", + "main": "getChallenges.js", "scripts": { - "build": "npm run build:js && npm run build:json", - "build:js": "gulp babel", - "build:json": "gulp json:minify", + "build": "gulp babel", "precommit": "lint-staged", "commit": "git-cz", "commitmsg": "commitlint -e", @@ -36,7 +34,9 @@ "config": "commitizen.config.js" } }, - "dependencies": {}, + "dependencies": { + "invariant": "^2.2.4" + }, "devDependencies": { "@commitlint/cli": "^7.0.0", "@commitlint/config-conventional": "^7.0.1", @@ -64,9 +64,7 @@ "fs-extra": "^6.0.1", "gulp": "^3.9.1", "gulp-babel": "^7.0.1", - "gulp-json-minify": "^1.2.2", "gulp-rename": "^1.3.0", - "gulp-util": "^3.0.8", "husky": "^0.14.3", "joi": "^13.3.0", "joi-objectid": "^2.0.0", @@ -76,6 +74,7 @@ "lodash": "^4.17.10", "prettier": "^1.13.5", "prettier-package-json": "^1.6.0", + "readdirp-walk": "^1.6.0", "rx": "^4.1.0", "semantic-release": "^15.6.0", "tap-spec": "^5.0.0",