From bd4d46915aa8d888fcccb3d7a785bfc52ac648ab Mon Sep 17 00:00:00 2001 From: Oliver Eyton-Williams Date: Tue, 8 Jun 2021 21:27:45 +0200 Subject: [PATCH] feat(curriculum): add project creation tool (#42410) * 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 Co-authored-by: Shaun Hamilton --- .eslintrc.json | 3 +- client/utils/challengeTypes.js | 64 +-- client/utils/help-category-map.json | 63 ++ tools/challenge-helper-scripts/base-meta.json | 13 + .../create-project.ts | 288 ++++++++++ .../package-lock.json | 542 ++++++++++++++++++ tools/challenge-helper-scripts/package.json | 10 +- tools/challenge-helper-scripts/tsconfig.json | 73 +++ tools/challenge-helper-scripts/utils.d.ts | 10 + tools/challenge-helper-scripts/utils.js | 4 +- utils/block-nameify.d.ts | 1 + utils/block-nameify.js | 29 +- utils/preformatted-block-names.json | 27 + 13 files changed, 1031 insertions(+), 96 deletions(-) create mode 100644 client/utils/help-category-map.json create mode 100644 tools/challenge-helper-scripts/base-meta.json create mode 100644 tools/challenge-helper-scripts/create-project.ts create mode 100644 tools/challenge-helper-scripts/tsconfig.json create mode 100644 tools/challenge-helper-scripts/utils.d.ts create mode 100644 utils/block-nameify.d.ts create mode 100644 utils/preformatted-block-names.json diff --git a/.eslintrc.json b/.eslintrc.json index 0033ace6dc..35a7bb1700 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -42,8 +42,7 @@ "files": ["**/*.ts?(x)"], "parser": "@typescript-eslint/parser", "parserOptions": { - "tsconfigRootDir": "./client", - "project": ["./tsconfig.json"] + "project": ["./client/tsconfig.json", "./tools/challenge-helper-scripts/tsconfig.json"] }, "extends": [ "plugin:@typescript-eslint/recommended", diff --git a/client/utils/challengeTypes.js b/client/utils/challengeTypes.js index 45c92dd524..ca80ca6364 100644 --- a/client/utils/challengeTypes.js +++ b/client/utils/challengeTypes.js @@ -76,66 +76,4 @@ exports.submitTypes = { }; // determine which help forum questions should be posted to -exports.helpCategoryMap = { - 'basic-html-and-html5': 'HTML-CSS', - 'basic-css': 'HTML-CSS', - 'applied-visual-design': 'HTML-CSS', - 'applied-accessibility': 'HTML-CSS', - 'responsive-web-design-principles': 'HTML-CSS', - 'css-flexbox': 'HTML-CSS', - 'css-grid': 'HTML-CSS', - 'responsive-web-design-projects': 'HTML-CSS', - 'basic-javascript': 'JavaScript', - es6: 'JavaScript', - 'regular-expressions': 'JavaScript', - debugging: 'JavaScript', - 'basic-data-structures': 'JavaScript', - 'basic-algorithm-scripting': 'JavaScript', - 'object-oriented-programming': 'JavaScript', - 'functional-programming': 'JavaScript', - 'intermediate-algorithm-scripting': 'JavaScript', - 'javascript-algorithms-and-data-structures-projects': 'JavaScript', - bootstrap: 'HTML-CSS', - jquery: 'JavaScript', - sass: 'HTML-CSS', - react: 'JavaScript', - redux: 'JavaScript', - 'react-and-redux': 'JavaScript', - 'front-end-libraries-projects': 'JavaScript', - 'data-visualization-with-d3': 'JavaScript', - 'json-apis-and-ajax': 'JavaScript', - 'data-visualization-projects': 'JavaScript', - 'managing-packages-with-npm': 'JavaScript', - 'basic-node-and-express': 'JavaScript', - 'mongodb-and-mongoose': 'JavaScript', - 'apis-and-microservices-projects': 'JavaScript', - 'information-security-with-helmetjs': 'JavaScript', - 'quality-assurance-and-testing-with-chai': 'JavaScript', - 'advanced-node-and-express': 'JavaScript', - 'quality-assurance-projects': 'JavaScript', - 'information-security-projects': 'JavaScript', - algorithms: 'JavaScript', - 'data-structures': 'JavaScript', - 'take-home-projects': 'JavaScript', - 'rosetta-code': 'JavaScript', - 'project-euler': 'JavaScript', - 'scientific-computing-with-python': 'Python', - 'scientific-computing-with-python-projects': 'Python', - 'data-analysis-with-python': 'Python', - 'data-analysis-with-python-projects': 'Python', - 'machine-learning-with-python': 'Python', - 'machine-learning-with-python-projects': 'Python', - 'python-for-everybody': 'Python', - tensorflow: 'Python', - 'how-neural-networks-work': 'Python', - numpy: 'Python', - 'data-analysis-with-python-course': 'Python', - 'python-for-penetration-testing': 'Python', - 'basic-html-cat-photo-app': 'HTML-CSS', - 'basic-css-cafe-menu': 'HTML-CSS', - 'css-variables-skyline': 'HTML-CSS', - 'basic-javascript-rpg-game': 'JavaScript', - 'functional-programming-spreadsheet': 'JavaScript', - 'intermediate-javascript-calorie-counter': 'JavaScript', - 'd3-dashboard': 'JavaScript' -}; +exports.helpCategoryMap = require('./help-category-map.json'); diff --git a/client/utils/help-category-map.json b/client/utils/help-category-map.json new file mode 100644 index 0000000000..572952d3bd --- /dev/null +++ b/client/utils/help-category-map.json @@ -0,0 +1,63 @@ +{ + "basic-html-and-html5": "HTML-CSS", + "basic-css": "HTML-CSS", + "applied-visual-design": "HTML-CSS", + "applied-accessibility": "HTML-CSS", + "responsive-web-design-principles": "HTML-CSS", + "css-flexbox": "HTML-CSS", + "css-grid": "HTML-CSS", + "responsive-web-design-projects": "HTML-CSS", + "basic-javascript": "JavaScript", + "es6": "JavaScript", + "regular-expressions": "JavaScript", + "debugging": "JavaScript", + "basic-data-structures": "JavaScript", + "basic-algorithm-scripting": "JavaScript", + "object-oriented-programming": "JavaScript", + "functional-programming": "JavaScript", + "intermediate-algorithm-scripting": "JavaScript", + "javascript-algorithms-and-data-structures-projects": "JavaScript", + "bootstrap": "HTML-CSS", + "jquery": "JavaScript", + "sass": "HTML-CSS", + "react": "JavaScript", + "redux": "JavaScript", + "react-and-redux": "JavaScript", + "front-end-libraries-projects": "JavaScript", + "data-visualization-with-d3": "JavaScript", + "json-apis-and-ajax": "JavaScript", + "data-visualization-projects": "JavaScript", + "managing-packages-with-npm": "JavaScript", + "basic-node-and-express": "JavaScript", + "mongodb-and-mongoose": "JavaScript", + "apis-and-microservices-projects": "JavaScript", + "information-security-with-helmetjs": "JavaScript", + "quality-assurance-and-testing-with-chai": "JavaScript", + "advanced-node-and-express": "JavaScript", + "quality-assurance-projects": "JavaScript", + "information-security-projects": "JavaScript", + "algorithms": "JavaScript", + "data-structures": "JavaScript", + "take-home-projects": "JavaScript", + "rosetta-code": "JavaScript", + "project-euler": "JavaScript", + "scientific-computing-with-python": "Python", + "scientific-computing-with-python-projects": "Python", + "data-analysis-with-python": "Python", + "data-analysis-with-python-projects": "Python", + "machine-learning-with-python": "Python", + "machine-learning-with-python-projects": "Python", + "python-for-everybody": "Python", + "tensorflow": "Python", + "how-neural-networks-work": "Python", + "numpy": "Python", + "data-analysis-with-python-course": "Python", + "python-for-penetration-testing": "Python", + "basic-html-cat-photo-app": "HTML-CSS", + "basic-css-cafe-menu": "HTML-CSS", + "css-variables-skyline": "HTML-CSS", + "basic-javascript-rpg-game": "JavaScript", + "functional-programming-spreadsheet": "JavaScript", + "intermediate-javascript-calorie-counter": "JavaScript", + "d3-dashboard": "JavaScript" +} diff --git a/tools/challenge-helper-scripts/base-meta.json b/tools/challenge-helper-scripts/base-meta.json new file mode 100644 index 0000000000..a0775e1618 --- /dev/null +++ b/tools/challenge-helper-scripts/base-meta.json @@ -0,0 +1,13 @@ +{ + "name": "", + "isUpcomingChange": true, + "dashedName": "", + "order": 42, + "time": "5 hours", + "template": "", + "required": [], + "superBlock": "", + "superOrder": 1, + "isBeta": true, + "challengeOrder": [["", ""]] +} diff --git a/tools/challenge-helper-scripts/create-project.ts b/tools/challenge-helper-scripts/create-project.ts new file mode 100644 index 0000000000..a8e01c73f5 --- /dev/null +++ b/tools/challenge-helper-scripts/create-project.ts @@ -0,0 +1,288 @@ +import fs from 'fs/promises'; +import { existsSync } from 'fs'; +import path from 'path'; +import { format } from 'prettier'; +import { prompt } from 'inquirer'; + +import { createStepFile } from './utils.js'; +import { blockNameify } from '../../utils/block-nameify'; + +const superBlocks = [ + 'responsive-web-design', + 'javascript-algorithms-and-data-structures', + 'front-end-libraries', + 'data-visualization', + 'apis-and-microservices', + 'quality-assurance', + 'scientific-computing-with-python', + 'data-analysis-with-python', + 'information-security', + 'machine-learning-with-python', + 'coding-interview-prep' +] as const; + +type SuperBlock = typeof superBlocks[number]; + +const helpCategories = ['HTML-CSS', 'JavaScript', 'Python'] as const; + +type BlockInfo = { + title: string; + intro: string[]; +}; + +type SuperBlockInfo = { + blocks: Record; +}; + +type IntroJson = Record; + +type Meta = { + name: string; + isUpcomingChange: boolean; + dashedName: string; + order: number; + time: string; + template: string; + required: string[]; + superBlock: string; + superOrder: number; + isBeta: boolean; + challengeOrder: string[][]; +}; + +async function createProject( + superBlock: SuperBlock, + block: string, + helpCategory: string, + order: number, + title?: string +) { + if (!title) { + title = blockNameify(block); + } else if (title !== blockNameify(block)) { + updateBlockNames(block, title).catch(reason => { + throw reason; + }); + } + updateIntroJson(superBlock, block, title).catch(reason => { + throw reason; + }); + updateHelpCategoryMap(block, helpCategory).catch(reason => { + throw reason; + }); + + const challengeId = await createFirstChallenge(superBlock, block).catch( + reason => { + throw reason; + } + ); + createMetaJson(superBlock, block, title, order, challengeId).catch(reason => { + throw reason; + }); + // TODO: remove once we stop relying on markdown in the client. + createIntroMD(superBlock, block, title).catch(reason => { + throw reason; + }); +} + +async function updateIntroJson( + superBlock: SuperBlock, + block: string, + title: string +) { + const introJsonPath = path.resolve( + __dirname, + '../../client/i18n/locales/english/intro.json' + ); + const newIntro = await parseJson(introJsonPath); + newIntro[superBlock].blocks[block] = { + title, + intro: ['', ''] + }; + fs.writeFile( + introJsonPath, + format(JSON.stringify(newIntro), { parser: 'json' }) + ).catch(reason => { + throw reason; + }); +} + +async function updateHelpCategoryMap(block: string, helpCategory: string) { + const helpCategoryPath = path.resolve( + __dirname, + '../../client/utils/help-category-map.json' + ); + const helpMap = await parseJson>(helpCategoryPath); + helpMap[block] = helpCategory; + fs.writeFile( + helpCategoryPath, + format(JSON.stringify(helpMap), { parser: 'json' }) + ).catch(reason => { + throw reason; + }); +} + +async function updateBlockNames(block: string, title: string) { + const blockNamesPath = path.resolve( + __dirname, + '../../utils/preformatted-block-names.json' + ); + const blockNames = await parseJson>(blockNamesPath); + blockNames[block] = title; + fs.writeFile( + blockNamesPath, + format(JSON.stringify(blockNames), { parser: 'json' }) + ).catch(reason => { + throw reason; + }); +} + +async function createMetaJson( + superBlock: SuperBlock, + block: string, + title: string, + order: number, + challengeId: string +) { + const metaDir = path.resolve(__dirname, '../../curriculum/challenges/_meta'); + const newMeta = await parseJson('./base-meta.json'); + newMeta.name = title; + newMeta.dashedName = block; + newMeta.order = order; + newMeta.superOrder = superBlocks.indexOf(superBlock) + 1; + newMeta.superBlock = superBlock; + newMeta.challengeOrder = [[challengeId, 'Part 1']]; + const newMetaDir = path.resolve(metaDir, block); + if (!existsSync(newMetaDir)) { + await fs.mkdir(newMetaDir); + } + fs.writeFile( + path.resolve(metaDir, `${block}/meta.json`), + format(JSON.stringify(newMeta), { parser: 'json' }) + ).catch(reason => { + throw reason; + }); +} + +async function createIntroMD(superBlock: string, block: string, title: string) { + const introMD = `--- +title: Introduction to the ${title} +block: ${block} +superBlock: Responsive Web Design +isBeta: true +--- +## Introduction to the ${title} + +This is a test for the new project-based curriculum.`; + const dirPath = path.resolve( + __dirname, + `../../client/src/pages/learn/${superBlock}/${block}/` + ); + const filePath = path.resolve(dirPath, 'index.md'); + if (!existsSync(dirPath)) { + await fs.mkdir(dirPath); + } + fs.writeFile(filePath, introMD, { encoding: 'utf8' }).catch(reason => { + throw reason; + }); +} + +async function createFirstChallenge( + superBlock: SuperBlock, + block: string +): Promise { + const superBlockId = (superBlocks.indexOf(superBlock) + 1) + .toString() + .padStart(2, '0'); + const newChallengeDir = path.resolve( + __dirname, + `../../curriculum/challenges/english/${superBlockId}-${superBlock}/${block}` + ); + if (!existsSync(newChallengeDir)) { + await fs.mkdir(newChallengeDir); + } + // TODO: would be nice if the extension made sense for the challenge, but, at + // least until react I think they're all going to be html anyway. + const challengeSeeds = { + indexhtml: { + contents: '', + ext: 'html', + editableRegionBoundaries: [0, 2] + } + }; + // including trailing slash for compatibility with createStepFile + return createStepFile({ + projectPath: newChallengeDir + '/', + stepNum: 1, + challengeSeeds, + stepBetween: false + }); +} + +function parseJson(filePath: string) { + return fs + .readFile(filePath, { encoding: 'utf8' }) + .then(result => JSON.parse(result) as JsonSchema) + .catch(reason => { + throw reason; + }); +} + +prompt([ + { + name: 'superBlock', + message: 'Which certification does this belong to?', + default: 'responsive-web-design', + type: 'list', + choices: superBlocks + }, + { + name: 'block', + message: 'What is the short name (in kebab-case) for this project?', + validate: (block: string) => { + if (!block.length) { + return 'please enter a short name'; + } + if (/[^a-z0-9\-]/.test(block)) { + return 'please use alphanumerical characters and kebab case'; + } + return true; + }, + filter: (block: string) => { + return block.toLowerCase(); + } + }, + { + name: 'title', + default: ({ block }: { block: string }) => blockNameify(block) + }, + { + name: 'helpCategory', + message: 'Choose a help category', + default: 'HTML-CSS', + type: 'list', + choices: helpCategories + }, + { + name: 'order', + message: 'Which position does this appear in the certificate?', + default: 42, + validate: (order: string) => { + return parseInt(order, 10) > 0 + ? true + : 'Order must be an number greater than zero.'; + }, + filter: (order: string) => { + return parseInt(order, 10); + } + } +]) + .then(({ superBlock, block, title, helpCategory, order }) => + createProject(superBlock, block, helpCategory, order, title) + ) + .then(() => + console.log( + 'All set. Now use npm run clean:client in the root and it should be good to go.' + ) + ) + .catch(console.error); diff --git a/tools/challenge-helper-scripts/package-lock.json b/tools/challenge-helper-scripts/package-lock.json index ec49ddeef6..08ed229318 100644 --- a/tools/challenge-helper-scripts/package-lock.json +++ b/tools/challenge-helper-scripts/package-lock.json @@ -4,6 +4,85 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@tsconfig/node10": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.7.tgz", + "integrity": "sha512-aBvUmXLQbayM4w3A8TrjwrXs4DZ8iduJnuJLLRGdkWlyakCf1q6uHZJBzXoRA/huAEknG5tcUyQxN3A+In5euQ==", + "dev": true + }, + "@tsconfig/node12": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.7.tgz", + "integrity": "sha512-dgasobK/Y0wVMswcipr3k0HpevxFJLijN03A8mYfEPvWvOs14v0ZlYTR4kIgMx8g4+fTyTFv8/jLCIfRqLDJ4A==", + "dev": true + }, + "@tsconfig/node14": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.0.tgz", + "integrity": "sha512-RKkL8eTdPv6t5EHgFKIVQgsDapugbuOptNd9OOunN/HAkzmmTnZELx1kNCK0rSdUYGmiFMM3rRQMAWiyp023LQ==", + "dev": true + }, + "@tsconfig/node16": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.1.tgz", + "integrity": "sha512-FTgBI767POY/lKNDNbIzgAX6miIDBs6NTCbdlDb8TrWovHsSvaVIZDlTqym29C6UqhzwcJx4CYr+AlrMywA0cA==", + "dev": true + }, + "@types/inquirer": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-7.3.1.tgz", + "integrity": "sha512-osD38QVIfcdgsPCT0V3lD7eH0OFurX71Jft18bZrsVQWVRt6TuxRzlr0GJLrxoHZR2V5ph7/qP8se/dcnI7o0g==", + "dev": true, + "requires": { + "@types/through": "*", + "rxjs": "^6.4.0" + } + }, + "@types/node": { + "version": "15.12.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-15.12.1.tgz", + "integrity": "sha512-zyxJM8I1c9q5sRMtVF+zdd13Jt6RU4r4qfhTd7lQubyThvLfx6yYekWSQjGCGV2Tkecgxnlpl/DNlb6Hg+dmEw==", + "dev": true + }, + "@types/through": { + "version": "0.0.30", + "resolved": "https://registry.npmjs.org/@types/through/-/through-0.0.30.tgz", + "integrity": "sha512-FvnCJljyxhPM3gkRgWmxmDZyAQSiBQQWLI0A0VFL0K7W1oRUrPJSqNO0NvTnLkBcotdlp3lKvaT0JrnyRDkzOg==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "requires": { + "type-fest": "^0.21.3" + } + }, + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -13,12 +92,109 @@ "sprintf-js": "~1.0.2" } }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true + }, + "bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "requires": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, "bson-objectid": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/bson-objectid/-/bson-objectid-2.0.1.tgz", "integrity": "sha512-b4D1/G4uP9Yks4rv+nDVsZ4ybT1W5nQYw4lfpfaRP2Q18azlR6Oe2BAuirG1lzrwQFtHnJ0nrK5kWKKZVEMUng==", "dev": true }, + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, + "cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "requires": { + "restore-cursor": "^3.1.0" + } + }, + "cli-spinners": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.6.0.tgz", + "integrity": "sha512-t+4/y50K/+4xcCRosKkA7W4gTr1MySvLV0q+PxmG7FJ5g+66ChKurYjxBCjHggHH3HA5Hh9cy+lcUGWDqVH+4Q==", + "dev": true + }, + "cli-width": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", + "dev": true + }, + "clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", + "dev": true + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, "cross-env": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", @@ -39,6 +215,33 @@ "which": "^2.0.1" } }, + "defaults": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", + "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", + "dev": true, + "requires": { + "clone": "^1.0.2" + } + }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, "esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", @@ -54,6 +257,26 @@ "is-extendable": "^0.1.0" } }, + "external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dev": true, + "requires": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + } + }, + "figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, "gray-matter": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz", @@ -66,12 +289,79 @@ "strip-bom-string": "^1.0.0" } }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "inquirer": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.1.0.tgz", + "integrity": "sha512-1nKYPoalt1vMBfCMtpomsUc32wmOoWXAoq3kM/5iTfxyQ2f/BxjixQpC+mbZ7BI0JUXHED4/XPXekDVtJNpXYw==", + "dev": true, + "requires": { + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.1", + "cli-cursor": "^3.1.0", + "cli-width": "^3.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.21", + "mute-stream": "0.0.8", + "ora": "^5.3.0", + "run-async": "^2.4.0", + "rxjs": "^6.6.6", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "through": "^2.3.6" + } + }, "is-extendable": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", "dev": true }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true + }, + "is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true + }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -94,12 +384,132 @@ "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "dev": true }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "requires": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + } + }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, + "mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true + }, + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "ora": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.0.tgz", + "integrity": "sha512-1StwyXQGoU6gdjYkyVcqOLnVlbKj+6yPNNOxJVgpt9t4eksKjiriiHuxktLYkgllwk+D6MbC4ihH84L1udRXPg==", + "dev": true, + "requires": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + } + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true + }, "path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true }, + "prettier": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.1.tgz", + "integrity": "sha512-p+vNbgpLjif/+D+DwAZAbndtRrR0md0MwfmOVN9N+2RgyACMT+7tfaRnT+WDPkqnuVwleyuBIG2XBxKDme3hPA==", + "dev": true + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "requires": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + } + }, + "run-async": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "dev": true + }, + "rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, "section-matter": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", @@ -125,18 +535,144 @@ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, + "signal-exit": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", + "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", "dev": true }, + "string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "requires": { + "safe-buffer": "~5.2.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + }, "strip-bom-string": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", "integrity": "sha1-5SEekiQ2n7uB1jOi8ABE3IztrZI=", "dev": true }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.2" + } + }, + "ts-node": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.0.0.tgz", + "integrity": "sha512-ROWeOIUvfFbPZkoDis0L/55Fk+6gFQNZwwKPLinacRl6tsxstTF1DbAcLKkovwnpKMVvOMHP1TIbnwXwtLg1gg==", + "dev": true, + "requires": { + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "source-map-support": "^0.5.17", + "yn": "3.1.1" + } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true + }, + "typescript": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.2.tgz", + "integrity": "sha512-zZ4hShnmnoVnAHpVHWpTcxdv7dWP60S2FsydQLV8V5PbS3FifjWFFRiHSWpDJahly88PRyV5teTSLoq4eG7mKw==", + "dev": true + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=", + "dev": true, + "requires": { + "defaults": "^1.0.3" + } + }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -145,6 +681,12 @@ "requires": { "isexe": "^2.0.0" } + }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true } } } diff --git a/tools/challenge-helper-scripts/package.json b/tools/challenge-helper-scripts/package.json index c74b251db4..91074dcd21 100644 --- a/tools/challenge-helper-scripts/package.json +++ b/tools/challenge-helper-scripts/package.json @@ -19,11 +19,17 @@ "author": "freeCodeCamp ", "main": "utils.js", "scripts": { - "test": "mocha --delay --reporter progress --bail" + "test": "mocha --delay --reporter progress --bail", + "create-project": "ts-node create-project" }, "devDependencies": { + "@types/inquirer": "^7.3.1", "bson-objectid": "2.0.1", "cross-env": "7.0.3", - "gray-matter": "4.0.3" + "gray-matter": "4.0.3", + "inquirer": "^8.1.0", + "prettier": "^2.3.1", + "ts-node": "^10.0.0", + "typescript": "^4.3.2" } } diff --git a/tools/challenge-helper-scripts/tsconfig.json b/tools/challenge-helper-scripts/tsconfig.json new file mode 100644 index 0000000000..71b74e58de --- /dev/null +++ b/tools/challenge-helper-scripts/tsconfig.json @@ -0,0 +1,73 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig.json to read more about this file */ + + /* Basic Options */ + // "incremental": true, /* Enable incremental compilation */ + "target": "ES2020", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */ + // "module": "es2020", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ + // "lib": [], /* Specify library files to be included in the compilation. */ + // "allowJs": true, /* Allow javascript files to be compiled. */ + // "checkJs": true, /* Report errors in .js files. */ + // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */ + // "declaration": true, /* Generates corresponding '.d.ts' file. */ + // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ + // "sourceMap": true, /* Generates corresponding '.map' file. */ + // "outFile": "./", /* Concatenate and emit output to single file. */ + // "outDir": "./", /* Redirect output structure to the directory. */ + // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ + // "composite": true, /* Enable project compilation */ + // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ + // "removeComments": true, /* Do not emit comments to output. */ + "noEmit": true, /* Do not emit outputs. */ + // "importHelpers": true, /* Import emit helpers from 'tslib'. */ + // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ + // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ + + /* Strict Type-Checking Options */ + "strict": true, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* Enable strict null checks. */ + // "strictFunctionTypes": true, /* Enable strict checking of function types. */ + // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ + // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ + // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ + // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ + + /* Additional Checks */ + // "noUnusedLocals": true, /* Report errors on unused locals. */ + // "noUnusedParameters": true, /* Report errors on unused parameters. */ + // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ + // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ + // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an 'override' modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */ + + /* Module Resolution Options */ + "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ + // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ + // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ + // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ + // "typeRoots": [], /* List of folders to include type definitions from. */ + // "types": [], /* Type declaration files to be included in compilation. */ + // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ + "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ + "resolveJsonModule": true, /* Enables direct import of json files */ + // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + + /* Source Map Options */ + // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ + // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ + + /* Experimental Options */ + // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ + // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ + + /* Advanced Options */ + "skipLibCheck": true, /* Skip type checking of declaration files. */ + "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ + } +} diff --git a/tools/challenge-helper-scripts/utils.d.ts b/tools/challenge-helper-scripts/utils.d.ts new file mode 100644 index 0000000000..39eefcdd0e --- /dev/null +++ b/tools/challenge-helper-scripts/utils.d.ts @@ -0,0 +1,10 @@ +type CreateStepOptions = { + projectPath: string; + stepNum: number; + challengeSeeds: Record; + stepBetween: boolean; +}; + +export declare function createStepFile(options: CreateStepOptions): string; + +// TODO: the rest of the functions diff --git a/tools/challenge-helper-scripts/utils.js b/tools/challenge-helper-scripts/utils.js index 159975dcba..975e37ea17 100644 --- a/tools/challenge-helper-scripts/utils.js +++ b/tools/challenge-helper-scripts/utils.js @@ -79,10 +79,11 @@ ${seedHeads}` ${seedTails}` : ''; + const challengeId = ObjectID(); const template = `--- -id: ${ObjectID()} +id: ${challengeId} title: Part ${stepNum} challengeType: 0 dashedName: part-${stepNum} @@ -107,6 +108,7 @@ Test 1 let finalStepNum = padWithLeadingZeros(stepNum); finalStepNum += stepBetween ? 'a' : ''; fs.writeFileSync(`${projectPath}part-${finalStepNum}.md`, template); + return challengeId; }; const reorderSteps = () => { diff --git a/utils/block-nameify.d.ts b/utils/block-nameify.d.ts new file mode 100644 index 0000000000..9e73aad654 --- /dev/null +++ b/utils/block-nameify.d.ts @@ -0,0 +1 @@ +export declare function blockNameify(phrase: string): string; diff --git a/utils/block-nameify.js b/utils/block-nameify.js index bd97d50b3f..3467cd8c24 100644 --- a/utils/block-nameify.js +++ b/utils/block-nameify.js @@ -1,31 +1,4 @@ -const preFormattedBlockNames = { - 'api-projects': 'API Projects', - 'basic-css': 'Basic CSS', - 'basic-html-and-html5': 'Basic HTML and HTML5', - 'css-flexbox': 'CSS Flexbox', - 'css-grid': 'CSS Grid', - devops: 'DevOps', - es6: 'ES6', - 'information-security-with-helmetjs': 'Information Security with HelmetJS', - jquery: 'jQuery', - 'json-apis-and-ajax': 'JSON APIs and Ajax', - 'mongodb-and-mongoose': 'MongoDB and Mongoose', - 'the-dom': 'The DOM', - 'apis-and-microservices': 'APIs and Microservices', - 'apis-and-microservices-projects': 'APIs and Microservices Projects', - 'scientific-computing-with-python': 'Scientific Computing with Python', - 'data-analysis-with-python': 'Data Analysis with Python', - 'machine-learning-with-python': 'Machine Learning with Python', - tensorflow: 'TensorFlow', - 'basic-javascript-rpg-game': 'Basic JavaScript RPG Game', - 'basic-html-cat-photo-app': 'HTML Cat Photo App', - 'basic-css-cafe-menu': 'CSS Cafe Menu', - 'css-variables-skyline': 'CSS Variables Skyline', - 'javascript-spreadsheet': 'JavaScript Spreadsheet', - 'intermediate-javascript-calorie-counter': - 'Intermediate JavaScript Calorie Counter', - 'd3-dashboard': 'D3 Dashboard' -}; +const preFormattedBlockNames = require('./preformatted-block-names.json'); const noFormatting = ['and', 'for', 'of', 'the', 'up', 'with']; diff --git a/utils/preformatted-block-names.json b/utils/preformatted-block-names.json new file mode 100644 index 0000000000..9757c4fb75 --- /dev/null +++ b/utils/preformatted-block-names.json @@ -0,0 +1,27 @@ +{ + "api-projects": "API Projects", + "basic-css": "Basic CSS", + "basic-html-and-html5": "Basic HTML and HTML5", + "css-flexbox": "CSS Flexbox", + "css-grid": "CSS Grid", + "devops": "DevOps", + "es6": "ES6", + "information-security-with-helmetjs": "Information Security with HelmetJS", + "jquery": "jQuery", + "json-apis-and-ajax": "JSON APIs and Ajax", + "mongodb-and-mongoose": "MongoDB and Mongoose", + "the-dom": "The DOM", + "apis-and-microservices": "APIs and Microservices", + "apis-and-microservices-projects": "APIs and Microservices Projects", + "scientific-computing-with-python": "Scientific Computing with Python", + "data-analysis-with-python": "Data Analysis with Python", + "machine-learning-with-python": "Machine Learning with Python", + "tensorflow": "TensorFlow", + "basic-javascript-rpg-game": "Basic JavaScript RPG Game", + "basic-html-cat-photo-app": "HTML Cat Photo App", + "basic-css-cafe-menu": "CSS Cafe Menu", + "css-variables-skyline": "CSS Variables Skyline", + "javascript-spreadsheet": "JavaScript Spreadsheet", + "intermediate-javascript-calorie-counter": "Intermediate JavaScript Calorie Counter", + "d3-dashboard": "D3 Dashboard" +}