* refactor: extract help category map as JSON * refactor: extract block names as json * feat: add create-project script * fix: correct case for tsconfig target Co-authored-by: Shaun Hamilton <shauhami020@gmail.com> Co-authored-by: Shaun Hamilton <shauhami020@gmail.com>
		
			
				
	
	
		
			251 lines
		
	
	
		
			5.8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			251 lines
		
	
	
		
			5.8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
const fs = require('fs');
 | 
						|
const path = require('path');
 | 
						|
const matter = require('gray-matter');
 | 
						|
const ObjectID = require('bson-objectid');
 | 
						|
const { parseMDSync } = require('../challenge-parser/parser');
 | 
						|
 | 
						|
const padWithLeadingZeros = originalNum => {
 | 
						|
  /* always want file step numbers 3 digits */
 | 
						|
  return ('' + originalNum).padStart(3, '0');
 | 
						|
};
 | 
						|
 | 
						|
const insertErms = (seedCode, erms) => {
 | 
						|
  const lines = seedCode.split('\n');
 | 
						|
  if (Number.isInteger(erms[0])) {
 | 
						|
    lines.splice(erms[0], 0, '--fcc-editable-region--');
 | 
						|
  }
 | 
						|
  if (Number.isInteger(erms[1])) {
 | 
						|
    lines.splice(erms[1], 0, '--fcc-editable-region--');
 | 
						|
  }
 | 
						|
  return lines.join('\n');
 | 
						|
};
 | 
						|
 | 
						|
const createStepFile = ({
 | 
						|
  projectPath,
 | 
						|
  stepNum,
 | 
						|
  challengeSeeds = {},
 | 
						|
  stepBetween = false
 | 
						|
}) => {
 | 
						|
  const seedTexts = Object.values(challengeSeeds).map(
 | 
						|
    ({ contents, ext, editableRegionBoundaries }) => {
 | 
						|
      const fullContents = insertErms(contents, editableRegionBoundaries);
 | 
						|
      return `\`\`\`${ext}
 | 
						|
${fullContents}
 | 
						|
\`\`\``;
 | 
						|
    }
 | 
						|
  );
 | 
						|
 | 
						|
  const seedHeads = Object.values(challengeSeeds)
 | 
						|
    .filter(({ head }) => head)
 | 
						|
    .map(
 | 
						|
      ({ ext, head }) => `\`\`\`${ext}
 | 
						|
${head}
 | 
						|
\`\`\``
 | 
						|
    )
 | 
						|
    .join('\n');
 | 
						|
 | 
						|
  const seedTails = Object.values(challengeSeeds)
 | 
						|
    .filter(({ tail }) => tail)
 | 
						|
    .map(
 | 
						|
      ({ ext, tail }) => `\`\`\`${ext}
 | 
						|
${tail}
 | 
						|
\`\`\``
 | 
						|
    )
 | 
						|
    .join('\n');
 | 
						|
 | 
						|
  const descStepNum = stepBetween ? stepNum + 1 : stepNum;
 | 
						|
  const stepDescription = `${
 | 
						|
    stepBetween ? 'new' : ''
 | 
						|
  } step ${descStepNum} instructions`;
 | 
						|
  const challengeSeedSection = `
 | 
						|
# --seed--
 | 
						|
 | 
						|
## --seed-contents--
 | 
						|
 | 
						|
${seedTexts.join('\n')}`;
 | 
						|
 | 
						|
  const seedHeadSection = seedHeads
 | 
						|
    ? `
 | 
						|
 | 
						|
## --before-user-code--
 | 
						|
 | 
						|
${seedHeads}`
 | 
						|
    : '';
 | 
						|
 | 
						|
  const seedTailSection = seedTails
 | 
						|
    ? `
 | 
						|
 | 
						|
## --after-user-code--
 | 
						|
 | 
						|
${seedTails}`
 | 
						|
    : '';
 | 
						|
  const challengeId = ObjectID();
 | 
						|
 | 
						|
  const template =
 | 
						|
    `---
 | 
						|
id: ${challengeId}
 | 
						|
title: Part ${stepNum}
 | 
						|
challengeType: 0
 | 
						|
dashedName: part-${stepNum}
 | 
						|
---
 | 
						|
 | 
						|
# --description--
 | 
						|
 | 
						|
${stepDescription}
 | 
						|
 | 
						|
# --hints--
 | 
						|
 | 
						|
Test 1
 | 
						|
 | 
						|
\`\`\`js
 | 
						|
 | 
						|
\`\`\`
 | 
						|
` +
 | 
						|
    challengeSeedSection +
 | 
						|
    seedHeadSection +
 | 
						|
    seedTailSection;
 | 
						|
 | 
						|
  let finalStepNum = padWithLeadingZeros(stepNum);
 | 
						|
  finalStepNum += stepBetween ? 'a' : '';
 | 
						|
  fs.writeFileSync(`${projectPath}part-${finalStepNum}.md`, template);
 | 
						|
  return challengeId;
 | 
						|
};
 | 
						|
 | 
						|
const reorderSteps = () => {
 | 
						|
  const projectPath = getProjectPath();
 | 
						|
 | 
						|
  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
 | 
						|
    ? ''
 | 
						|
    : path.join(__dirname, '../');
 | 
						|
 | 
						|
  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;
 | 
						|
      }
 | 
						|
    }
 | 
						|
  });
 | 
						|
 | 
						|
  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();
 | 
						|
    const title =
 | 
						|
      newFileName === 'final.md' ? 'Final Prototype' : `Part ${newStepNum}`;
 | 
						|
    const dashedName = `part-${newStepNum}`;
 | 
						|
    challengeOrder.push(['' + challengeID, title]);
 | 
						|
    const newData = {
 | 
						|
      ...frontMatter.data,
 | 
						|
      id: challengeID,
 | 
						|
      title,
 | 
						|
      dashedName
 | 
						|
    };
 | 
						|
    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');
 | 
						|
};
 | 
						|
 | 
						|
const getChallengeSeeds = challengeFilePath => {
 | 
						|
  return parseMDSync(challengeFilePath).files;
 | 
						|
};
 | 
						|
 | 
						|
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;
 | 
						|
 | 
						|
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 };
 | 
						|
  }, {});
 | 
						|
};
 | 
						|
 | 
						|
module.exports = {
 | 
						|
  createStepFile,
 | 
						|
  getChallengeSeeds,
 | 
						|
  padWithLeadingZeros,
 | 
						|
  reorderSteps,
 | 
						|
  getExistingStepNums,
 | 
						|
  getProjectPath,
 | 
						|
  getArgValues
 | 
						|
};
 |