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:
committed by
GitHub
parent
25c927e79b
commit
108d2460e7
@ -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);
|
|
||||||
|
15
tools/challenge-helper-scripts/fs-utils.test.ts
Normal file
15
tools/challenge-helper-scripts/fs-utils.test.ts
Normal 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)));
|
||||||
|
});
|
||||||
|
});
|
21
tools/challenge-helper-scripts/fs-utils.ts
Normal file
21
tools/challenge-helper-scripts/fs-utils.ts
Normal 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];
|
||||||
|
}
|
Reference in New Issue
Block a user