refactor(tools): migrate challenge editor to new curriculum structure (#61968)

This commit is contained in:
Anna
2025-09-04 08:04:54 -04:00
committed by GitHub
parent b47bc04c97
commit 5148ed2b82
11 changed files with 217 additions and 107 deletions
+22 -11
View File
@@ -1,5 +1,25 @@
import { join } from 'path';
export const SUPERBLOCK_META_DIR = join(
process.cwd(),
'..',
'..',
'..',
'curriculum',
'structure',
'superblocks'
);
export const BLOCK_META_DIR = join(
process.cwd(),
'..',
'..',
'..',
'curriculum',
'structure',
'blocks'
);
export const CHALLENGE_DIR = join(
process.cwd(),
'..',
@@ -7,15 +27,6 @@ export const CHALLENGE_DIR = join(
'..',
'curriculum',
'challenges',
'english'
);
export const META_DIR = join(
process.cwd(),
'..',
'..',
'..',
'curriculum',
'challenges',
'_meta'
'english',
'blocks'
);
@@ -1,114 +1,114 @@
export const superBlockList = [
{
name: 'Legacy Responsive Web Design',
path: '01-responsive-web-design'
path: 'responsive-web-design'
},
{
name: 'Legacy JavaScript Algorithms and Data Structures',
path: '02-javascript-algorithms-and-data-structures'
path: 'javascript-algorithms-and-data-structures'
},
{
name: 'Front End Development Libraries',
path: '03-front-end-development-libraries'
path: 'front-end-development-libraries'
},
{
name: 'Data Visualization',
path: '04-data-visualization'
path: 'data-visualization'
},
{
name: 'Back End Development and APIs',
path: '05-back-end-development-and-apis'
path: 'back-end-development-and-apis'
},
{
name: 'Quality Assurance',
path: '06-quality-assurance'
path: 'quality-assurance'
},
{
name: 'Scientific Computing with Python',
path: '07-scientific-computing-with-python'
path: 'scientific-computing-with-python'
},
{
name: 'Data Analysis with Python',
path: '08-data-analysis-with-python'
path: 'data-analysis-with-python'
},
{
name: 'Information Security',
path: '09-information-security'
path: 'information-security'
},
{
name: 'Coding Interview Prep',
path: '10-coding-interview-prep'
path: 'coding-interview-prep'
},
{
name: 'Machine Learning with Python',
path: '11-machine-learning-with-python'
path: 'machine-learning-with-python'
},
{
name: 'Relational Databases',
path: '13-relational-databases'
path: 'relational-databases'
},
{
name: 'Responsive Web Design',
path: '14-responsive-web-design-22'
path: 'responsive-web-design-22'
},
{
name: 'JavaScript Algorithms and Data Structures',
path: '15-javascript-algorithms-and-data-structures-22'
path: 'javascript-algorithms-and-data-structures-22'
},
{
name: 'The Odin Project',
path: '16-the-odin-project'
path: 'the-odin-project'
},
{
name: 'College Algebra with Python',
path: '17-college-algebra-with-python'
path: 'college-algebra-with-python'
},
{
name: 'Project Euler',
path: '18-project-euler'
path: 'project-euler'
},
{
name: '(New) Foundational C# with Microsoft',
path: '19-foundational-c-sharp-with-microsoft'
path: 'foundational-c-sharp-with-microsoft'
},
{
name: 'A2 English for Developers (Beta)',
path: '21-a2-english-for-developers'
path: 'a2-english-for-developers'
},
{
name: 'Rosetta Code',
path: '22-rosetta-code'
path: 'rosetta-code'
},
{
name: 'Python For Everybody',
path: '23-python-for-everybody'
path: 'python-for-everybody'
},
{
name: 'B1 English for Developers (Beta)',
path: '24-b1-english-for-developers'
path: 'b1-english-for-developers'
},
{
name: 'Certified Full Stack Developer',
path: '25-front-end-development'
path: 'full-stack-developer'
},
{
name: 'A2 Professional Spanish (Beta)',
path: '26-a2-professional-spanish'
path: 'a2-professional-spanish'
},
{
name: 'A2 Professional Chinese (Beta)',
path: '27-a2-professional-chinese'
path: 'a2-professional-chinese'
},
{
name: 'Basic HTML',
path: '28-basic-html'
path: 'basic-html'
},
{
name: 'Semantic HTML',
path: '29-semantic-html'
path: 'semantic-html'
},
{
name: 'A1 Professional Chinese (Beta)',
path: '30-a1-professional-chinese'
path: 'a1-professional-chinese'
}
];
@@ -0,0 +1,14 @@
export interface SuperBlockModule {
dashedName: string;
blocks?: string[];
}
export interface SuperBlockChapter {
dashedName: string;
modules: SuperBlockModule[];
}
export interface SuperBlockMeta {
blocks?: string[];
chapters?: SuperBlockChapter[];
}
+37 -19
View File
@@ -1,8 +1,8 @@
import { readdir, readFile } from 'fs/promises';
import { readFile } from 'fs/promises';
import { join } from 'path';
import { CHALLENGE_DIR, META_DIR } from '../configs/paths';
import { SUPERBLOCK_META_DIR, CHALLENGE_DIR } from '../configs/paths';
import { PartialMeta } from '../interfaces/partial-meta';
import { SuperBlockMeta } from '../interfaces/superblock-meta';
type Block = {
name: string;
@@ -10,23 +10,41 @@ type Block = {
};
export const getBlocks = async (sup: string): Promise<Block[]> => {
const filePath = join(CHALLENGE_DIR, sup);
const superBlockDataPath = join(SUPERBLOCK_META_DIR, sup + '.json');
const superBlockMetaFile = await readFile(superBlockDataPath, {
encoding: 'utf8'
});
const superBlockMeta = JSON.parse(superBlockMetaFile) as SuperBlockMeta;
let blocks: { name: string; path: string }[] = [];
const files = await readdir(filePath);
const blocks = await Promise.all(
files.map(async file => {
const metaPath = join(META_DIR, file, 'meta.json');
const metaData = JSON.parse(
await readFile(metaPath, 'utf8')
) as PartialMeta;
return {
name: metaData.name,
path: file
};
})
);
if (sup === 'full-stack-developer') {
const moduleBlockData = await Promise.all(
superBlockMeta.chapters!.flatMap(async chapter => {
return await Promise.all(
chapter.modules.flatMap(async module => {
return module.blocks!.flatMap(block => {
const filePath = join(CHALLENGE_DIR, block);
return {
name: block,
path: filePath
};
});
})
);
})
);
blocks = moduleBlockData.flat().flat();
} else {
blocks = await Promise.all(
superBlockMeta.blocks!.map(async block => {
const filePath = join(CHALLENGE_DIR, block);
return {
name: block,
path: filePath
};
})
);
}
return blocks;
};
@@ -0,0 +1,58 @@
import { readFile } from 'fs/promises';
import { join } from 'path';
import { SUPERBLOCK_META_DIR, CHALLENGE_DIR } from '../configs/paths';
import { SuperBlockMeta } from '../interfaces/superblock-meta';
type Block = {
name: string;
path: string;
};
export const getModules = async (chap: string): Promise<string[]> => {
const superBlockDataPath = join(
SUPERBLOCK_META_DIR,
'full-stack-developer' + '.json'
);
const superBlockMetaFile = await readFile(superBlockDataPath, {
encoding: 'utf8'
});
const superBlockMeta = JSON.parse(superBlockMetaFile) as SuperBlockMeta;
const chapter = superBlockMeta.chapters!.filter(
x => x.dashedName === chap
)[0];
return await Promise.all(
chapter.modules!.map(async module => module.dashedName)
);
};
export const getBlocks = async (module: string): Promise<Block[]> => {
const superBlockDataPath = join(
SUPERBLOCK_META_DIR,
'full-stack-developer' + '.json'
);
const superBlockMetaFile = await readFile(superBlockDataPath, {
encoding: 'utf8'
});
const superBlockMeta = JSON.parse(superBlockMetaFile) as SuperBlockMeta;
const foundModule = superBlockMeta
.chapters!.flatMap(x => x.modules)
.filter(x => x.dashedName === module)[0];
let blocks: { name: string; path: string }[] = [];
blocks = await Promise.all(
foundModule.blocks!.map(async block => {
const filePath = join(CHALLENGE_DIR, block);
return {
name: block,
path: filePath
};
})
);
return blocks;
};
@@ -8,7 +8,7 @@ export const getStepContent = async (
block: string,
step: string
): Promise<{ name: string; dashedName: string; fileData: string }> => {
const filePath = join(CHALLENGE_DIR, sup, block, step);
const filePath = join(CHALLENGE_DIR, block, step);
const fileData = await readFile(filePath, 'utf8');
const name = matter(fileData).data.title as string;
+19 -9
View File
@@ -1,10 +1,10 @@
import { readdir, readFile } from 'fs/promises';
import { readFile } from 'fs/promises';
import { join } from 'path';
import matter from 'gray-matter';
import { PartialMeta } from '../interfaces/partial-meta';
import { CHALLENGE_DIR, META_DIR } from '../configs/paths';
import { BLOCK_META_DIR, CHALLENGE_DIR } from '../configs/paths';
const getFileOrder = (id: string, meta: PartialMeta) => {
return meta.challengeOrder.findIndex(({ id: f }) => f === id);
@@ -17,16 +17,25 @@ type Step = {
};
export const getSteps = async (sup: string, block: string): Promise<Step[]> => {
const filePath = join(CHALLENGE_DIR, sup, block);
//const superMetaPath = join(SUPERBLOCK_META_DIR, sup + ".json");
const metaPath = join(META_DIR, block, 'meta.json');
//const superMetaData = JSON.parse(
// await readFile(superMetaPath, 'utf8')
//) as Partial;
const metaData = JSON.parse(await readFile(metaPath, 'utf8')) as PartialMeta;
const stepDirectory = join(CHALLENGE_DIR, block);
const blockFolderPath = join(BLOCK_META_DIR, block + '.json');
const blockMetaData = JSON.parse(
await readFile(blockFolderPath, { encoding: 'utf8' })
) as PartialMeta;
const stepFileNames = blockMetaData.challengeOrder.map(x => x.id + '.md');
const stepFilenames = await readdir(filePath);
const stepData = await Promise.all(
stepFilenames.map(async filename => {
const stepPath = join(filePath, filename);
stepFileNames.map(async filename => {
const stepPath = join(stepDirectory, filename);
const step = await readFile(stepPath, 'utf8');
const frontMatter = matter(step);
@@ -39,6 +48,7 @@ export const getSteps = async (sup: string, block: string): Promise<Step[]> => {
);
return stepData.sort(
(a, b) => getFileOrder(a.id, metaData) - getFileOrder(b.id, metaData)
(a, b) =>
getFileOrder(a.id, blockMetaData) - getFileOrder(b.id, blockMetaData)
);
};
@@ -9,7 +9,7 @@ export const saveStep = async (
content: string
): Promise<boolean> => {
try {
const filePath = join(CHALLENGE_DIR, sup, block, step);
const filePath = join(CHALLENGE_DIR, block, step);
await writeFile(filePath, content);
@@ -5,18 +5,18 @@ import { API_LOCATION } from '../../utils/handle-request';
import './block.css';
const stepBasedSuperblocks = [
'07-scientific-computing-with-python',
'14-responsive-web-design-22',
'15-javascript-algorithms-and-data-structures-22',
'25-front-end-development'
'scientific-computing-with-python',
'responsive-web-design-22',
'javascript-algorithms-and-data-structures-22',
'front-end-development'
];
const taskBasedSuperblocks = [
'21-a2-english-for-developers',
'24-b1-english-for-developers',
'26-a2-professional-spanish',
'27-a2-professional-chinese',
'30-a1-professional-chinese'
'a2-english-for-developers',
'b1-english-for-developers',
'a2-professional-spanish',
'a2-professional-chinese',
'a1-professional-chinese'
];
const Block = () => {
@@ -95,7 +95,7 @@ const Block = () => {
Looking to add or remove challenges? Navigate to <br />
<code>
freeCodeCamp/curriculum/challenges/english
{`/${params.superblock}/${params.block}/`}
{`/${params.block}/`}
</code>
<br />
in your terminal and run the following commands:
@@ -42,7 +42,7 @@ const SuperBlock = () => {
<ul>
{items.map(block => (
<li key={block.name}>
<Link to={`/${params.superblock}/${block.path}`}>{block.name}</Link>
<Link to={`/${params.superblock}/${block.name}`}>{block.name}</Link>
</li>
))}
</ul>
@@ -1,31 +1,30 @@
export const superBlockNameMap: { [key: string]: string } = {
'01-responsive-web-design': 'responsive-web-design',
'02-javascript-algorithms-and-data-structures':
'responsive-web-design': 'responsive-web-design',
'javascript-algorithms-and-data-structures':
'javascript-algorithms-and-data-structures',
'03-front-end-development-libraries': 'front-end-development-libraries',
'04-data-visualization': 'data-visualization',
'05-back-end-development-and-apis': 'back-end-development-and-apis',
'06-quality-assurance': 'quality-assurance',
'07-scientific-computing-with-python': 'scientific-computing-with-python',
'08-data-analysis-with-python': 'data-analysis-with-python',
'09-information-security': 'information-security',
'10-coding-interview-prep': 'coding-interview-prep',
'11-machine-learning-with-python': 'machine-learning-with-python',
'13-relational-databases': 'relational-databases',
'14-responsive-web-design-22': '2022/responsive-web-design',
'15-javascript-algorithms-and-data-structures-22':
'front-end-development-libraries': 'front-end-development-libraries',
'data-visualization': 'data-visualization',
'back-end-development-and-apis': 'back-end-development-and-apis',
'quality-assurance': 'quality-assurance',
'scientific-computing-with-python': 'scientific-computing-with-python',
'data-analysis-with-python': 'data-analysis-with-python',
'information-security': 'information-security',
'coding-interview-prep': 'coding-interview-prep',
'machine-learning-with-python': 'machine-learning-with-python',
'relational-databases': 'relational-databases',
'responsive-web-design-22': '2022/responsive-web-design',
'javascript-algorithms-and-data-structures-22':
'javascript-algorithms-and-data-structures-v8',
'16-the-odin-project': 'the-odin-project',
'17-college-algebra-with-python': 'college-algebra-with-python',
'18-project-euler': 'project-euler',
'19-foundational-c-sharp-with-microsoft':
'foundational-c-sharp-with-microsoft',
'21-a2-english-for-developers': 'a2-english-for-developers',
'22-rosetta-code': 'rosetta-code',
'23-python-for-everybody': 'python-for-everybody',
'24-b1-english-for-developers': 'b1-english-for-developers',
'25-front-end-development': 'full-stack-developer',
'26-a2-professional-spanish': 'a2-professional-spanish',
'27-a2-professional-chinese': 'a2-professional-chinese',
'30-a1-professional-chinese': 'a1-professional-chinese'
'the-odin-project': 'the-odin-project',
'college-algebra-with-python': 'college-algebra-with-python',
'project-euler': 'project-euler',
'foundational-c-sharp-with-microsoft': 'foundational-c-sharp-with-microsoft',
'a2-english-for-developers': 'a2-english-for-developers',
'rosetta-code': 'rosetta-code',
'python-for-everybody': 'python-for-everybody',
'b1-english-for-developers': 'b1-english-for-developers',
'front-end-development': 'full-stack-developer',
'a2-professional-spanish': 'a2-professional-spanish',
'a2-professional-chinese': 'a2-professional-chinese',
'a1-professional-chinese': 'a1-professional-chinese'
};