mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-05-28 18:26:54 +00:00
refactor(tools): migrate challenge editor to new curriculum structure (#61968)
This commit is contained in:
@@ -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[];
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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'
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user