diff --git a/guide/russian/react/context-api/index.md b/guide/russian/react/context-api/index.md index 4bdc671dcb..bfa59b1fa0 100644 --- a/guide/russian/react/context-api/index.md +++ b/guide/russian/react/context-api/index.md @@ -1,10 +1,11 @@ --- title: Context API +localeTitle: Context API --- # Context API -Новый Context API был реализован в версии React 16.3. +Новый Context API был реализован в версии React 16.3. Он существовал и раньше, однако находился в бета-версии, и, таким образом, не имел возможности предоставить инструменты для работы с его помощью. @@ -94,7 +95,7 @@ export default () => ( ```javascript
It’s 17:00 !
``` -### Динамическое изменение контекста +### Динамическое изменение контекста Чтобы изменить значение параметра время (time), которое мы передаем компоненту ShowTime, нам нужно предоставить нашему контексту функцию, которая может использоваться потребителем для обновления значения. diff --git a/package-lock.json b/package-lock.json index 2a79ff1828..de1f4a58c3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1984,6 +1984,12 @@ "restore-cursor": "^2.0.0" } }, + "cli-spinners": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-1.3.1.tgz", + "integrity": "sha512-1QL4544moEsDVH9T/l6Cemov/37iv1RtoKf7NJ04A60+4MREXNfx/QvavbH6QoGdsD4N4Mwy49cmaINR/o2mdg==", + "dev": true + }, "cli-width": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", @@ -6766,6 +6772,15 @@ "lodash._reinterpolate": "~3.0.0" } }, + "log-symbols": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", + "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", + "dev": true, + "requires": { + "chalk": "^2.0.1" + } + }, "loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -7649,6 +7664,20 @@ "wordwrap": "~1.0.0" } }, + "ora": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-3.0.0.tgz", + "integrity": "sha512-LBS97LFe2RV6GJmXBi6OKcETKyklHNMV0xw7BtsVn2MlsgsydyZetSCbCANr+PFLmDyv4KV88nn0eCKza665Mg==", + "dev": true, + "requires": { + "chalk": "^2.3.1", + "cli-cursor": "^2.1.0", + "cli-spinners": "^1.1.0", + "log-symbols": "^2.2.0", + "strip-ansi": "^4.0.0", + "wcwidth": "^1.0.1" + } + }, "os-homedir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", diff --git a/package.json b/package.json index 4ddaa4a7a2..21071bfc8b 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,8 @@ "test:lint": "echo 'Warning: TODO - Define Linting tests.'", "test:client": "cd ./client && npm test && cd ../", "test:curriculum": "cd ./curriculum && npm test && cd ../", - "test:guide-directories": "node ./tools/scripts/ci/ensure-guide-page-naming.js", + "test:challenge-formatting": "node ./tools/scripts/ci/ensure-challenge-formatting.js", + "test:guide-formatting": "node ./tools/scripts/ci/ensure-guide-formatting.js", "test:server": "cd ./api-server && npm test && cd ../", "test:tools": "jest ./tools" }, @@ -33,6 +34,7 @@ "lerna": "^3.4.0", "lodash": "^4.17.11", "npm-run-all": "^4.1.5", + "ora": "^3.0.0", "readdirp-walk": "^1.6.0", "shortid": "^2.2.14", "slugg": "^1.2.1", diff --git a/tools/scripts/ci/ensure-challenge-formatting.js b/tools/scripts/ci/ensure-challenge-formatting.js new file mode 100644 index 0000000000..0fd60d050f --- /dev/null +++ b/tools/scripts/ci/ensure-challenge-formatting.js @@ -0,0 +1,77 @@ +const readdirp = require('readdirp-walk'); +const { has, isEmpty, isNumber } = require('lodash'); +const ora = require('ora'); + +const { parseMarkdown } = require('../../challenge-md-parser'); +const { challengeRoot, checkFrontmatter } = require('./md-testing-utils'); + +const scrimbaUrlRE = /^https:\/\/scrimba\.com\//; +const requiredProps = ['title', 'id', 'challengeType']; + +const spinner = ora('Checking challenge markdown formatting').start(); + +readdirp({ root: challengeRoot, directoryFilter: '!_meta' }) + .on('data', file => + Promise.all([ + isChallengeParseable(file), + checkFrontmatter(file, { + validator: challengeFrontmatterValidator(file) + }) + ]).catch(err => { + console.info(` + the following error occured when testing + + ${file.fullPath} + + `); + console.error(err); + // eslint-disable-next-line no-process-exit + process.exit(1); + }) + ) + .on('end', () => spinner.stop()); + +const challengeFrontmatterValidator = file => frontmatter => { + const { fullPath } = file; + + const hasRequiredProperties = requiredProps + .map( + prop => + has(frontmatter, prop) && + (!isEmpty(frontmatter[prop]) || isNumber(frontmatter[prop])) + ) + .every(bool => bool); + + if (!hasRequiredProperties) { + console.log(`${fullPath} is missing required frontmatter + + ${JSON.stringify(frontmatter, null, 2)} + + Required properties are: ${JSON.stringify(requiredProps, null, 2)} + + `); + } + const { videoUrl } = frontmatter; + + let validVideoUrl = false; + if (isEmpty(videoUrl)) { + validVideoUrl = true; + } else { + validVideoUrl = scrimbaUrlRE.test(videoUrl); + + if (!validVideoUrl) { + console.log(` + ${fullPath} contains an invalid videoUrl +`); + } + } + return hasRequiredProperties && validVideoUrl; +}; + +function isChallengeParseable(file) { + const { stat, fullPath } = file; + if (!stat.isFile() || (/_meta/).test(fullPath)) { + return Promise.resolve(true); + } + return parseMarkdown(fullPath); +} diff --git a/tools/scripts/ci/ensure-guide-formatting.js b/tools/scripts/ci/ensure-guide-formatting.js new file mode 100644 index 0000000000..a1931550be --- /dev/null +++ b/tools/scripts/ci/ensure-guide-formatting.js @@ -0,0 +1,34 @@ +const readdirp = require('readdirp-walk'); +const { has } = require('lodash'); +const ora = require('ora'); + +const { + guideRoot, + checkGuideFile, + checkFrontmatter, + extractLangFromFileName +} = require('./md-testing-utils'); + +const spinner = ora('Checking guide markdown formatting').start(); + +const guideFrontmatterValidator = file => frontmatter => { + const hasLocale = + extractLangFromFileName(file) === 'english' + ? true + : has(frontmatter, 'localeTitle'); + const hasTitle = has(frontmatter, 'title'); + return hasLocale && hasTitle; +}; + +readdirp({ root: guideRoot }) + .on('data', file => + Promise.all([ + checkGuideFile(file), + checkFrontmatter(file, { validator: guideFrontmatterValidator(file) }) + ]).catch(err => { + console.error(err); + // eslint-disable-next-line no-process-exit + process.exit(1); + }) + ) + .on('end', () => spinner.stop()); diff --git a/tools/scripts/ci/ensure-guide-page-naming.js b/tools/scripts/ci/md-testing-utils.js similarity index 58% rename from tools/scripts/ci/ensure-guide-page-naming.js rename to tools/scripts/ci/md-testing-utils.js index ab285d461d..cf67d20102 100644 --- a/tools/scripts/ci/ensure-guide-page-naming.js +++ b/tools/scripts/ci/md-testing-utils.js @@ -1,10 +1,13 @@ const path = require('path'); const fs = require('fs'); -const readdirp = require('readdirp-walk'); const matter = require('gray-matter'); -const _ = require('lodash'); + +const pass = true; const guideRoot = path.resolve(__dirname, '../../../guide'); +const challengeRoot = path.resolve(__dirname, '../../../curriculum/challenges'); +exports.guideRoot = guideRoot; +exports.challengeRoot = challengeRoot; const allowedLangDirNames = [ 'arabic', @@ -15,7 +18,7 @@ const allowedLangDirNames = [ 'spanish' ]; -function checkFile(file) { +exports.checkGuideFile = function checkGuideFile(file) { const { stat, depth, name, fullPath } = file; if (depth === 1) { if (stat.isFile()) { @@ -26,43 +29,10 @@ function checkFile(file) { } } if (stat.isDirectory()) { - return checkDirName(name, fullPath).catch(err => { - throw err; - }); + return checkDirName(name, fullPath); } - return checkFileName(name, fullPath) - .then(() => checkFrontmatter(fullPath)) - .catch(err => { - console.log(` - - The below occured in: - - ${fullPath} - - `); - console.error(err); - // eslint-disable-next-line no-process-exit - process.exit(1); - }); -} - -readdirp({ root: guideRoot }) - .on('data', file => - checkFile(file).catch(err => { - console.error(err); - // eslint-disable-next-line no-process-exit - process.exit(1); - }) - ) - .on('end', () => { - console.log(` - - guide directory naming checks complete - -`); - // eslint-disable-next-line no-process-exit - process.exit(0); - }); + return checkGuideFileName(name, fullPath); +}; function checkDirName(dirName, fullPath) { return new Promise((resolve, reject) => { @@ -86,11 +56,11 @@ Upper case characters found in ${dirName}, all folder names must be lower case `) ); } - return resolve(); + return resolve(pass); }); } -function checkFileName(fileName, fullPath) { +function checkGuideFileName(fileName, fullPath) { return new Promise((resolve, reject) => { if (fileName !== 'index.md') { return reject( @@ -103,11 +73,21 @@ function checkFileName(fileName, fullPath) { ) ); } - return resolve(); + return resolve(pass); }); } -function checkFrontmatter(fullPath) { +exports.checkFrontmatter = function checkFrontmatter( + { fullPath, stat }, + options = { + validator() { + return true; + } + } +) { + if (!stat.isFile()) { + return Promise.resolve(pass); + } return new Promise((resolve, reject) => fs.readFile(fullPath, 'utf8', (err, content) => { if (err) { @@ -115,25 +95,15 @@ function checkFrontmatter(fullPath) { } try { const { data: frontmatter } = matter(content); - if (!frontmatter || _.isEmpty(frontmatter) || !frontmatter.title) { + const { validator } = options; + if (!validator(frontmatter)) { return reject( - new Error(` - The article at: ${fullPath} is missing frontmatter. - - Example: - - --- - title: The Article Title - localeTitle: The Translated Title # Only required for translations - --- - - < The Article Body > - - `) + new Error( + `The article at: ${fullPath} failed frontmatter validation.` + ) ); - // process.exit(1); } - return resolve(); + return resolve(pass); } catch (e) { console.log(` @@ -146,4 +116,10 @@ function checkFrontmatter(fullPath) { } }) ); +}; + +function extractLangFromFileName({ path: relativePath }) { + return relativePath.split(path.sep)[0]; } + +exports.extractLangFromFileName = extractLangFromFileName; diff --git a/tools/scripts/tests-pass.js b/tools/scripts/tests-pass.js new file mode 100644 index 0000000000..114c1ebd62 --- /dev/null +++ b/tools/scripts/tests-pass.js @@ -0,0 +1,49 @@ +/* eslint-disable */ +console.log(` + ,@@@@@@@@@@,,@@@@@@@% .#&@@@&&.,@@@@@@@@@@, .#&@@@&&. %@@@@@@%* ,@@@% .#&@@@&&. *&@@@@&( ,@@@@@@@% %@@@@@, ,@@, + ,@@, ,@@, ,@@/ ./. ,@@, ,@@/ ./. %@% ,&@# .&@&@@( .@@/ ./. #@&. .,/ ,@@, %@% *&@&. ,@@, + ,@@, ,@@&%%%%. .&@@/, ,@@, .&@@/, %@% ,&@# %@& /@@, .&@@/, (@@&%(*. ,@@&%%%%. %@% &@# ,@@, + ,@@, ,@@/,,,, ./#&@@@( ,@@, ./#&@@@( %@@@@@@%* /@@, #@&. ./#&@@@( *(%&@@&. ,@@/,,,, %@% &@# .&&. + ,@@, ,@@, ./, .&@# ,@@, ./, .&@# %@% ,@@@@@@@@@% ./. .&@# /*. /@@. ,@@, %@% *&@&. ,, + ,@@, ,@@@@@@@% .#&@@@@&/ ,@@, .#&@@@@&/ %@% .&@# ,@@/.#&@@@@&/ /%&@@@@. ,@@@@@@@% %@@@@@. ,@@, +,*************,,*/(((((//,,*(#%%%%%%%%%%%%%%%#(*,,,****************************************************,*/(((((((((/((((////****/((##%%%%%% +,*************,,//((((((//,,*(%%%%%%%%%%%%%%%%%##/*****************************************************,,*/(///(//////****//((##%%%%%%%%%%% +,************,,*/(((((((//***/#%%%%%%%%%%%%%%%%%%%#(/***************************************************,*//////////*//((#%%%%%%%%%%%%%%%%% +,***********,,*////////////***/##%%%%%%%%%%%%%%%%%%%##(*,***********************************************,,*////////(###%%%%%%%%%%%%%%%%%%%% +,**********,,,*/*******//////**/(#%%%%%%%%%%%%%%%%%%%%%#(/**********************************************,,,***/(##%%%%%%%%%%%%%%%%%%%%%%%%% +,*********,,,,*************///***/(#%%%%%%%%%%%%%%%%%%%%%%#(/***********************************,****,****/((#%%%%%%%%%%%%%%%%%%%%%%%%%%%%# +,*********,,,***************//****/(##%%%%%%%%%%%%%%%%%%%%%%##//**************//////////////////////((#####%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#( +,********,,,,***********************/(#%%%%%%%%%%%%%%%%%%%%%%%##################%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%##(/ +,*******,..,***********************,,*/##%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%###((// +,*******,.,,***********************,,,,*(#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%##(//**// +,******,.,,,************************,,,,*/(#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#(//******* +,*****,,,,,********,***,,,,,,,,,,,,*,,,,,,*/(######%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%##(/********** +,*****,..,*******,,,,,,,,,,,,,,,,,,,,,,*,,,,*///((#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%###(/************ +,*****,,,*******,,,,,*,,,,,,,,,,,,,,,,,****,,,*/(#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#######(//************** +,****,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,**,,,/(%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#((//****************** +,***,..,,,,,,,,,,,,,,,,,,,,,,,,,,,,,..,,,,,,,*(#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#(/******************* +,**,,.,,,,,,,,,,,,,,,,,,,,,,,,,,.......,,,,,,/#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#####%%%%%%%%%%%%%%%%#(/****************** +,**,..,,,,,,,,,,,,,,,,,,,,,,,,,......,,,*,,,*(#%%%%%%%%##(((/(##%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%##(((/*/((#%%%%%%%%%%%%%%#(/***************** +,*,..,,,,,,,,,,,,,,,,,,,,,,,,,,,.....,,**,,*/#%%%%%%%##((((*,**/#%%%%%%%%%%%%%%%%%%%%%%%%%%%%##((##/,,,*(#%%%%%%%%%%%%%%#(***************** +.*,.,,,**,,,,,,,,,,,,,,,,,,,,,,,,,,*****,,,/(%%%%%%%%#(//(#/,..*/#%%%%%%%%%%%%%%%%%%%%%%%%%%%#(//(#/,..,/(#%%%%%%%%%%%%%%#/*****/////////// +.,..,,,,,,,,,,,,,,,,,,,,,,,,,,*,,*******,,,(#%%%%%%%%#(*,,,....,/#%%%%%%%%%%%%%%%%%%%%%%%%%%%#(*,,,....,/(#%%%%%%%%%%%%%%#(*,**//////////// +.,..,,,,,,,,,...........,,,,,,*,********,,*(#%%%%%%%%%#(/*,,...,/#%%%%%%%%%%%%%%%%%%%%%%%%%%%%#(/*,,..,*/##%%%%%%%%%%%%%%%#(***//////////// +...,,,,,,,................,,*,**********,,/#%%%%%%%%%%%%#((////((#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%##((///(#%%%%%%%%%%%%%%%%%%(/**//////////// + ..,,,,,,.................,,,**********,,*(#%%%%%%%%%%%%%%%%%%#%%%%%%%%#((///((#%%%%%%%%%%%%%%%%%%%%%#%%%%%%%%%%%%%%%%%%%%%#/**//////////// +.,,,,,,,,.................,,***********,,/(####%%%%%%%%%%%%%%%%%%%%%%%%#(/*,,,*(#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#(/*//////////// +.,***,,,,,,..............,,,**********,..,***//((##%%%%%%%%%%%%%%%%%%%%%%%##((##%%%%%%%%%%%%%%%%%%%%%%%%%##(((((((((###%%%%%#/**/////////// +.*****,,,,,,,,,,,,,,,,,,,*************,..,*******/(#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%##///*//////((#%%%%%#(**/////////// +.****************/******/***////*****,.,*///////**/#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#(////////////(#%%%%%#/**////////// +.***********************/////*******,..,*//////////(#%%%%%%%%%%%%%%%%%%%%##########%%%%%%%%%%%%%%%%%%%%#(///////////*/(#%%%%%#(***///////// +.************************///********,..,*//////////#%%%%%%%%%%%%%%%%%%#(//*****///(((##%%%%%%%%%%%%%%%%#(///////////**/##%%%%##/***//////// +.***********************************,.,,***///////(#%%%%%%%%%%%%%%%%#(/*,,,*//((((////(#%%%%%%%%%%%%%%%#((////////////(#%%%%%%#(*********// +,***********,,,*,,*,,**************,,,*//******//(#%%%%%%%%%%%%%%%%%#(*,,*/(((#####(((((#%%%%%%%%%%%%%%%##///////////(#%%%%%%%%#(***/////// +,*************,,**,,,************,,,,,/(##((((####%%%%%%%%%%%%%%%%%%%(/**/(((#((((#((//(#%%%%%%%%%%%%%%%%%#(((((((((##%%%%%%%%%%#/**/////// +,******************************,,,,,,,*(#%#%%%%%%%%%%%%%%%%%%%%%%%%%%#(**/((#(#(((#((//(#%%%%%%%%%%%%%%%%%%%%%%%#%#%%%%%%%%%%%%%#(**/////// +,*************,**************,****,,,,,/(#%%%%%%%%%%%%%%%%%%%%%%%%%%%%#(/*/((((#((((///(#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%(/*/////// +,*************************************,*/#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%##(////////////(#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#/**/////* +,******////****///////////////////////***/#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%####(((((((###%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#(******** +.,*,****///////////////////////////////***/#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#(/******* +.,,,,*****//////////////////////////*******(#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%##(******* +.,,,,,,***********/////////////////********/(#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%(******* +`);