fix(tools): use correct superblock paths (#45285)

* fix: use array of choices

* fix: capture traces from fs promise errors

* fix: use helper to get superblock subpath
This commit is contained in:
Oliver Eyton-Williams
2022-03-07 06:21:33 +01:00
committed by GitHub
parent 25c927e79b
commit 108d2460e7
3 changed files with 90 additions and 58 deletions

View File

@ -8,6 +8,7 @@ import ObjectID from 'bson-objectid';
import { SuperBlocks } from '../../config/certification-settings'; import { SuperBlocks } from '../../config/certification-settings';
import { blockNameify } from '../../utils/block-nameify'; import { blockNameify } from '../../utils/block-nameify';
import { createStepFile } from './utils'; import { createStepFile } from './utils';
import { getSuperBlockSubPath } from './fs-utils';
const helpCategories = ['HTML-CSS', 'JavaScript', 'Python'] as const; const helpCategories = ['HTML-CSS', 'JavaScript', 'Python'] as const;
@ -54,29 +55,15 @@ async function createProject(
if (!title) { if (!title) {
title = blockNameify(block); title = blockNameify(block);
} else if (title !== blockNameify(block)) { } else if (title !== blockNameify(block)) {
updateBlockNames(block, title).catch(reason => { void updateBlockNames(block, title);
throw reason;
});
} }
updateIntroJson(superBlock, block, title).catch(reason => { void updateIntroJson(superBlock, block, title);
throw reason; void updateHelpCategoryMap(block, helpCategory);
});
updateHelpCategoryMap(block, helpCategory).catch(reason => {
throw reason;
});
const challengeId = await createFirstChallenge(superBlock, block).catch( const challengeId = await createFirstChallenge(superBlock, block);
reason => { void createMetaJson(superBlock, block, title, order, challengeId);
throw reason;
}
);
createMetaJson(superBlock, block, title, order, challengeId).catch(reason => {
throw reason;
});
// TODO: remove once we stop relying on markdown in the client. // TODO: remove once we stop relying on markdown in the client.
createIntroMD(superBlock, block, title).catch(reason => { void createIntroMD(superBlock, block, title);
throw reason;
});
} }
async function updateIntroJson( async function updateIntroJson(
@ -93,12 +80,11 @@ async function updateIntroJson(
title, title,
intro: ['', ''] intro: ['', '']
}; };
fs.writeFile( void withTrace(
fs.writeFile,
introJsonPath, introJsonPath,
format(JSON.stringify(newIntro), { parser: 'json' }) format(JSON.stringify(newIntro), { parser: 'json' })
).catch(reason => { );
throw reason;
});
} }
async function updateHelpCategoryMap(block: string, helpCategory: string) { async function updateHelpCategoryMap(block: string, helpCategory: string) {
@ -108,12 +94,11 @@ async function updateHelpCategoryMap(block: string, helpCategory: string) {
); );
const helpMap = await parseJson<Record<string, string>>(helpCategoryPath); const helpMap = await parseJson<Record<string, string>>(helpCategoryPath);
helpMap[block] = helpCategory; helpMap[block] = helpCategory;
fs.writeFile( void withTrace(
fs.writeFile,
helpCategoryPath, helpCategoryPath,
format(JSON.stringify(helpMap), { parser: 'json' }) format(JSON.stringify(helpMap), { parser: 'json' })
).catch(reason => { );
throw reason;
});
} }
async function updateBlockNames(block: string, title: string) { async function updateBlockNames(block: string, title: string) {
@ -123,12 +108,11 @@ async function updateBlockNames(block: string, title: string) {
); );
const blockNames = await parseJson<Record<string, string>>(blockNamesPath); const blockNames = await parseJson<Record<string, string>>(blockNamesPath);
blockNames[block] = title; blockNames[block] = title;
fs.writeFile( void withTrace(
fs.writeFile,
blockNamesPath, blockNamesPath,
format(JSON.stringify(blockNames), { parser: 'json' }) format(JSON.stringify(blockNames), { parser: 'json' })
).catch(reason => { );
throw reason;
});
} }
async function createMetaJson( async function createMetaJson(
@ -148,14 +132,14 @@ async function createMetaJson(
newMeta.challengeOrder = [[challengeId.toString(), 'Step 1']]; newMeta.challengeOrder = [[challengeId.toString(), 'Step 1']];
const newMetaDir = path.resolve(metaDir, block); const newMetaDir = path.resolve(metaDir, block);
if (!existsSync(newMetaDir)) { if (!existsSync(newMetaDir)) {
await fs.mkdir(newMetaDir); await withTrace(fs.mkdir, newMetaDir);
} }
fs.writeFile(
void withTrace(
fs.writeFile,
path.resolve(metaDir, `${block}/meta.json`), path.resolve(metaDir, `${block}/meta.json`),
format(JSON.stringify(newMeta), { parser: 'json' }) format(JSON.stringify(newMeta), { parser: 'json' })
).catch(reason => { );
throw reason;
});
} }
async function createIntroMD(superBlock: string, block: string, title: string) { async function createIntroMD(superBlock: string, block: string, title: string) {
@ -176,26 +160,22 @@ This is a test for the new project-based curriculum.
); );
const filePath = path.resolve(dirPath, 'index.md'); const filePath = path.resolve(dirPath, 'index.md');
if (!existsSync(dirPath)) { if (!existsSync(dirPath)) {
await fs.mkdir(dirPath); await withTrace(fs.mkdir, dirPath);
} }
fs.writeFile(filePath, introMD, { encoding: 'utf8' }).catch(reason => { void withTrace(fs.writeFile, filePath, introMD, { encoding: 'utf8' });
throw reason;
});
} }
async function createFirstChallenge( async function createFirstChallenge(
superBlock: SuperBlocks, superBlock: SuperBlocks,
block: string block: string
): Promise<ObjectID> { ): Promise<ObjectID> {
const superBlockId = (Object.values(SuperBlocks).indexOf(superBlock) + 1) const superBlockSubPath = getSuperBlockSubPath(superBlock);
.toString()
.padStart(2, '0');
const newChallengeDir = path.resolve( const newChallengeDir = path.resolve(
__dirname, __dirname,
`../../curriculum/challenges/english/${superBlockId}-${superBlock}/${block}` `../../curriculum/challenges/english/${superBlockSubPath}/${block}`
); );
if (!existsSync(newChallengeDir)) { if (!existsSync(newChallengeDir)) {
await fs.mkdir(newChallengeDir); await withTrace(fs.mkdir, newChallengeDir);
} }
// TODO: would be nice if the extension made sense for the challenge, but, at // 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. // least until react I think they're all going to be html anyway.
@ -215,21 +195,32 @@ async function createFirstChallenge(
} }
function parseJson<JsonSchema>(filePath: string) { function parseJson<JsonSchema>(filePath: string) {
return fs return withTrace(fs.readFile, filePath, 'utf8').then(
.readFile(filePath, { encoding: 'utf8' }) // unfortunately, withTrace does not correctly infer that the third argument
.then(result => JSON.parse(result) as JsonSchema) // is a string, so it uses the (path, options?) overload and we have to cast
.catch(reason => { // result to string.
throw reason; result => JSON.parse(result as string) as JsonSchema
}); );
} }
prompt([ // fs Promise functions return errors, but no stack trace. This adds back in
// the stack trace.
function withTrace<Args extends unknown[], Result>(
fn: (...x: Args) => Promise<Result>,
...args: Args
): Promise<Result> {
return fn(...args).catch((reason: Error) => {
throw Error(reason.message);
});
}
void prompt([
{ {
name: 'superBlock', name: 'superBlock',
message: 'Which certification does this belong to?', message: 'Which certification does this belong to?',
default: SuperBlocks.RespWebDesign, default: SuperBlocks.RespWebDesign,
type: 'list', type: 'list',
choices: SuperBlocks choices: Object.values(SuperBlocks)
}, },
{ {
name: 'block', name: 'block',
@ -273,12 +264,17 @@ prompt([
} }
]) ])
.then( .then(
({ superBlock, block, title, helpCategory, order }: CreateProjectArgs) => async ({
createProject(superBlock, block, helpCategory, order, title) superBlock,
block,
title,
helpCategory,
order
}: CreateProjectArgs) =>
await createProject(superBlock, block, helpCategory, order, title)
) )
.then(() => .then(() =>
console.log( console.log(
'All set. Now use npm run clean:client in the root and it should be good to go.' 'All set. Now use npm run clean:client in the root and it should be good to go.'
) )
) );
.catch(console.error);

View File

@ -0,0 +1,15 @@
import fs from 'fs/promises';
import path from 'path';
import { SuperBlocks } from '../../config/certification-settings';
import { getSuperBlockSubPath } from './fs-utils';
describe('getSuperBlockSubPath', () => {
it('should return sub-paths that exist', async () => {
const subPaths = Object.values(SuperBlocks).map(getSuperBlockSubPath);
const paths = subPaths.map(sub =>
path.resolve(__dirname, '../../curriculum/challenges/english', sub)
);
await Promise.all(paths.map(path => fs.access(path)));
});
});

View File

@ -0,0 +1,21 @@
import { SuperBlocks } from '../../config/certification-settings';
export function getSuperBlockSubPath(superBlock: SuperBlocks): string {
const pathMap = {
[SuperBlocks.RespWebDesign]: '01-responsive-web-design',
[SuperBlocks.JsAlgoDataStruct]:
'02-javascript-algorithms-and-data-structures',
[SuperBlocks.FrontEndDevLibs]: '03-front-end-development-libraries',
[SuperBlocks.DataVis]: '04-data-visualization',
[SuperBlocks.BackEndDevApis]: '05-back-end-development-and-apis',
[SuperBlocks.QualityAssurance]: '06-quality-assurance',
[SuperBlocks.SciCompPy]: '07-scientific-computing-with-python',
[SuperBlocks.DataAnalysisPy]: '08-data-analysis-with-python',
[SuperBlocks.InfoSec]: '09-information-security',
[SuperBlocks.CodingInterviewPrep]: '10-coding-interview-prep',
[SuperBlocks.MachineLearningPy]: '11-machine-learning-with-python',
[SuperBlocks.RelationalDb]: '13-relational-databases',
[SuperBlocks.RespWebDesignNew]: '14-responsive-web-design-22'
};
return pathMap[superBlock];
}