| 
									
										
										
										
											2020-08-25 02:35:46 -07:00
										 |  |  | const fs = require('fs'); | 
					
						
							|  |  |  | const path = require('path'); | 
					
						
							|  |  |  | const matter = require('gray-matter'); | 
					
						
							|  |  |  | const ObjectID = require('bson-objectid'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const padWithLeadingZeros = originalNum => { | 
					
						
							|  |  |  |   /* always want file step numbers 3 digits */ | 
					
						
							| 
									
										
										
										
											2020-09-17 14:52:55 +02:00
										 |  |  |   return ('' + originalNum).padStart(3, '0'); | 
					
						
							| 
									
										
										
										
											2020-08-25 02:35:46 -07:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const removeErms = seedCode => { | 
					
						
							|  |  |  |   const lines = seedCode.split('\n'); | 
					
						
							|  |  |  |   return lines | 
					
						
							|  |  |  |     .filter(line => !line.includes('--fcc-editable-region--')) | 
					
						
							|  |  |  |     .join('\n'); | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-27 18:57:51 -07:00
										 |  |  | const createStepFile = ({ | 
					
						
							|  |  |  |   projectPath, | 
					
						
							|  |  |  |   stepNum, | 
					
						
							|  |  |  |   challengeSeed = '', | 
					
						
							|  |  |  |   stepBetween = false | 
					
						
							|  |  |  | }) => { | 
					
						
							| 
									
										
										
										
											2020-08-25 02:35:46 -07:00
										 |  |  |   if (challengeSeed) { | 
					
						
							|  |  |  |     challengeSeed = removeErms(challengeSeed); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-27 18:57:51 -07:00
										 |  |  |   const descStepNum = stepBetween ? stepNum + 1 : stepNum; | 
					
						
							|  |  |  |   const stepDescription = `${ | 
					
						
							|  |  |  |     stepBetween ? 'new' : '' | 
					
						
							|  |  |  |   } step ${descStepNum} instructions`;
 | 
					
						
							| 
									
										
										
										
											2020-08-25 02:35:46 -07:00
										 |  |  |   const challengeSeedSection = `<section id='challengeSeed'>
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ${challengeSeed.trim()} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | </section>`; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const template = `---
 | 
					
						
							|  |  |  | id: ${ObjectID.generate()} | 
					
						
							|  |  |  | title: Part ${stepNum} | 
					
						
							|  |  |  | challengeType: 0 | 
					
						
							|  |  |  | --- | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ## Description | 
					
						
							|  |  |  | <section id='description'> | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-27 18:57:51 -07:00
										 |  |  | ${stepDescription} | 
					
						
							| 
									
										
										
										
											2020-08-25 02:35:46 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | </section> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ## Tests | 
					
						
							|  |  |  | <section id='tests'> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | \`\`\`yml
 | 
					
						
							|  |  |  | tests: | 
					
						
							|  |  |  |   - text: Test 1 | 
					
						
							|  |  |  |     testString: '' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | \`\`\`
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | </section> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ## Challenge Seed | 
					
						
							|  |  |  | ${challengeSeedSection} | 
					
						
							|  |  |  | `;
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-27 18:57:51 -07:00
										 |  |  |   let finalStepNum = padWithLeadingZeros(stepNum); | 
					
						
							|  |  |  |   finalStepNum += stepBetween ? 'a' : ''; | 
					
						
							|  |  |  |   fs.writeFileSync(`${projectPath}part-${finalStepNum}.md`, template); | 
					
						
							| 
									
										
										
										
											2020-08-25 02:35:46 -07:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const reorderSteps = () => { | 
					
						
							| 
									
										
										
										
											2020-08-27 18:57:51 -07:00
										 |  |  |   const projectPath = getProjectPath(); | 
					
						
							| 
									
										
										
										
											2020-08-25 02:35:46 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |   const projectName = process.env.CALLING_DIR | 
					
						
							|  |  |  |     ? process.env.CALLING_DIR.split(path.sep) | 
					
						
							|  |  |  |         .slice(-1) | 
					
						
							|  |  |  |         .toString() | 
					
						
							|  |  |  |     : process | 
					
						
							|  |  |  |         .cwd() | 
					
						
							|  |  |  |         .split(path.sep) | 
					
						
							|  |  |  |         .slice(-1) | 
					
						
							|  |  |  |         .toString(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const curriculumPath = process.env.CALLING_DIR | 
					
						
							|  |  |  |     ? '' | 
					
						
							| 
									
										
										
										
											2020-09-11 08:47:34 -07:00
										 |  |  |     : path.join(__dirname, '../'); | 
					
						
							| 
									
										
										
										
											2020-08-25 02:35:46 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |   const projectMetaPath = path.resolve( | 
					
						
							|  |  |  |     curriculumPath, | 
					
						
							|  |  |  |     'challenges', | 
					
						
							|  |  |  |     '_meta', | 
					
						
							|  |  |  |     projectName, | 
					
						
							|  |  |  |     'meta.json' | 
					
						
							|  |  |  |   ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   let metaData; | 
					
						
							|  |  |  |   try { | 
					
						
							|  |  |  |     metaData = fs.readFileSync(projectMetaPath); | 
					
						
							|  |  |  |   } catch (err) { | 
					
						
							|  |  |  |     throw `No _meta.json file exists at ${projectMetaPath}`; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   let foundFinal = false; | 
					
						
							|  |  |  |   const filesArr = []; | 
					
						
							|  |  |  |   fs.readdirSync(projectPath).forEach(fileName => { | 
					
						
							|  |  |  |     if (path.extname(fileName).toLowerCase() === '.md') { | 
					
						
							|  |  |  |       if (!fileName.endsWith('final.md')) { | 
					
						
							|  |  |  |         filesArr.push(fileName); | 
					
						
							|  |  |  |       } else { | 
					
						
							|  |  |  |         foundFinal = true; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   }); | 
					
						
							| 
									
										
										
										
											2020-08-27 18:57:51 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-25 02:35:46 -07:00
										 |  |  |   if (foundFinal) { | 
					
						
							|  |  |  |     filesArr.push('final.md'); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const filesToReorder = filesArr.map((fileName, i) => { | 
					
						
							|  |  |  |     const newStepNum = i + 1; | 
					
						
							|  |  |  |     const newFileName = | 
					
						
							|  |  |  |       fileName !== 'final.md' | 
					
						
							|  |  |  |         ? `part-${padWithLeadingZeros(newStepNum)}.md` | 
					
						
							|  |  |  |         : 'final.md'; | 
					
						
							|  |  |  |     return { | 
					
						
							|  |  |  |       oldFileName: fileName, | 
					
						
							|  |  |  |       newFileName, | 
					
						
							|  |  |  |       newStepNum | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const challengeOrder = []; | 
					
						
							|  |  |  |   const parsedData = JSON.parse(metaData); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   filesToReorder.forEach(({ oldFileName, newFileName, newStepNum }) => { | 
					
						
							|  |  |  |     fs.renameSync( | 
					
						
							|  |  |  |       `${projectPath}${oldFileName}`, | 
					
						
							|  |  |  |       `${projectPath}${newFileName}.tmp` | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |     const filePath = `${projectPath}${newFileName}.tmp`; | 
					
						
							|  |  |  |     const frontMatter = matter.read(filePath); | 
					
						
							|  |  |  |     const challengeID = frontMatter.data.id || ObjectID.generate(); | 
					
						
							|  |  |  |     const title = | 
					
						
							|  |  |  |       newFileName === 'final.md' ? 'Final Prototype' : `Part ${newStepNum}`; | 
					
						
							|  |  |  |     challengeOrder.push(['' + challengeID, title]); | 
					
						
							|  |  |  |     const newData = { | 
					
						
							|  |  |  |       ...frontMatter.data, | 
					
						
							|  |  |  |       id: challengeID, | 
					
						
							|  |  |  |       title | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  |     fs.writeFileSync(filePath, frontMatter.stringify(newData)); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   filesToReorder.forEach(({ newFileName }) => { | 
					
						
							|  |  |  |     fs.renameSync( | 
					
						
							|  |  |  |       `${projectPath}${newFileName}.tmp`, | 
					
						
							|  |  |  |       `${projectPath}${newFileName}` | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const newMeta = { ...parsedData, challengeOrder }; | 
					
						
							|  |  |  |   fs.writeFileSync(projectMetaPath, JSON.stringify(newMeta, null, 2)); | 
					
						
							|  |  |  |   console.log('Reordered steps'); | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-27 18:57:51 -07:00
										 |  |  | const getChallengeSeed = challengeFilePath => { | 
					
						
							|  |  |  |   const fileContent = fs.readFileSync(challengeFilePath, 'utf8'); | 
					
						
							|  |  |  |   const matchedSection = fileContent | 
					
						
							|  |  |  |     .toString() | 
					
						
							|  |  |  |     .match(/<section id='challengeSeed'>(?<challengeSeed>[\s\S]+)<\/section>/); | 
					
						
							|  |  |  |   let finalChallengeSeed = ''; | 
					
						
							|  |  |  |   if (matchedSection) { | 
					
						
							|  |  |  |     let { | 
					
						
							|  |  |  |       groups: { challengeSeed } | 
					
						
							|  |  |  |     } = matchedSection; | 
					
						
							|  |  |  |     finalChallengeSeed = challengeSeed ? challengeSeed : ''; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   return finalChallengeSeed; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const getExistingStepNums = projectPath => { | 
					
						
							|  |  |  |   return fs.readdirSync(projectPath).reduce((stepNums, fileName) => { | 
					
						
							|  |  |  |     if ( | 
					
						
							|  |  |  |       path.extname(fileName).toLowerCase() === '.md' && | 
					
						
							|  |  |  |       !fileName.endsWith('final.md') | 
					
						
							|  |  |  |     ) { | 
					
						
							|  |  |  |       let stepNum = fileName.split('.')[0].split('-')[1]; | 
					
						
							|  |  |  |       if (!/^\d{3}$/.test(stepNum)) { | 
					
						
							|  |  |  |         throw `Step not created. File ${fileName} has a step number containing non-digits.` + | 
					
						
							|  |  |  |           ' Please run reorder-steps script first.'; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       stepNum = parseInt(stepNum, 10); | 
					
						
							|  |  |  |       stepNums.push(stepNum); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return stepNums; | 
					
						
							|  |  |  |   }, []); | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const getProjectPath = () => | 
					
						
							|  |  |  |   (process.env.CALLING_DIR || process.cwd()) + path.sep; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-03 02:54:19 -07:00
										 |  |  | const getArgValues = argv => { | 
					
						
							|  |  |  |   return argv.slice(2).reduce((argsObj, arg) => { | 
					
						
							|  |  |  |     const [argument, value] = arg.replace(/\s/g, '').split('='); | 
					
						
							|  |  |  |     if (!argument || !value) { | 
					
						
							|  |  |  |       throw `Invalid argument/value specified: ${arg}`; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return { ...argsObj, [argument]: value }; | 
					
						
							|  |  |  |   }, {}); | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-25 02:35:46 -07:00
										 |  |  | module.exports = { | 
					
						
							|  |  |  |   createStepFile, | 
					
						
							| 
									
										
										
										
											2020-08-27 18:57:51 -07:00
										 |  |  |   getChallengeSeed, | 
					
						
							|  |  |  |   padWithLeadingZeros, | 
					
						
							|  |  |  |   reorderSteps, | 
					
						
							|  |  |  |   getExistingStepNums, | 
					
						
							| 
									
										
										
										
											2020-10-03 02:54:19 -07:00
										 |  |  |   getProjectPath, | 
					
						
							|  |  |  |   getArgValues | 
					
						
							| 
									
										
										
										
											2020-08-25 02:35:46 -07:00
										 |  |  | }; |