mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-05-28 18:26:54 +00:00
feat(tools): add language block specific properties in helper scripts (#63711)
Co-authored-by: Huyen Nguyen <25715018+huyenltnguyen@users.noreply.github.com>
This commit is contained in:
@@ -30,6 +30,7 @@ import {
|
||||
updateSimpleSuperblockStructure,
|
||||
updateChapterModuleSuperblockStructure
|
||||
} from './helpers/create-project';
|
||||
import { getLangFromSuperBlock } from './helpers/get-lang-from-superblock';
|
||||
|
||||
const helpCategories = [
|
||||
'English',
|
||||
@@ -78,13 +79,23 @@ async function createLanguageBlock(
|
||||
}
|
||||
await updateIntroJson(superBlock, block, title);
|
||||
|
||||
const challengeLang = getLangFromSuperBlock(superBlock);
|
||||
let challengeId: ObjectID;
|
||||
|
||||
if (blockLabel === BlockLabel.quiz) {
|
||||
challengeId = await createQuizChallenge(block, title, questionCount!);
|
||||
challengeId = await createQuizChallenge(
|
||||
block,
|
||||
title,
|
||||
questionCount!,
|
||||
challengeLang
|
||||
);
|
||||
blockLayout = BlockLayouts.Link;
|
||||
} else {
|
||||
challengeId = await createDialogueChallenge(superBlock, block);
|
||||
challengeId = await createDialogueChallenge(
|
||||
superBlock,
|
||||
block,
|
||||
challengeLang
|
||||
);
|
||||
}
|
||||
|
||||
await createMetaJson(
|
||||
@@ -178,7 +189,8 @@ async function createMetaJson(
|
||||
|
||||
async function createDialogueChallenge(
|
||||
superBlock: SuperBlocks,
|
||||
block: string
|
||||
block: string,
|
||||
challengeLang: string
|
||||
): Promise<ObjectID> {
|
||||
const { blockContentDir } = getContentConfig('english') as {
|
||||
blockContentDir: string;
|
||||
@@ -188,14 +200,16 @@ async function createDialogueChallenge(
|
||||
await fs.mkdir(newChallengeDir, { recursive: true });
|
||||
|
||||
return createDialogueFile({
|
||||
projectPath: newChallengeDir + '/'
|
||||
projectPath: newChallengeDir + '/',
|
||||
challengeLang: challengeLang
|
||||
});
|
||||
}
|
||||
|
||||
async function createQuizChallenge(
|
||||
block: string,
|
||||
title: string,
|
||||
questionCount: number
|
||||
questionCount: number,
|
||||
challengeLang: string
|
||||
): Promise<ObjectID> {
|
||||
const newChallengeDir = path.resolve(
|
||||
__dirname,
|
||||
@@ -208,7 +222,8 @@ async function createQuizChallenge(
|
||||
projectPath: newChallengeDir + '/',
|
||||
title: title,
|
||||
dashedName: block,
|
||||
questionCount: questionCount
|
||||
questionCount: questionCount,
|
||||
challengeLang
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -5,18 +5,29 @@ import { getProjectPath } from './helpers/get-project-info';
|
||||
import { getMetaData, updateMetaData } from './helpers/project-metadata';
|
||||
import {
|
||||
createChallengeFile,
|
||||
getChallenge,
|
||||
updateTaskMeta,
|
||||
updateTaskMarkdownFiles
|
||||
} from './utils';
|
||||
import { getInputType } from './helpers/get-input-type';
|
||||
|
||||
const createNextTask = async () => {
|
||||
const { challengeType } = await newTaskPrompts();
|
||||
const meta = getMetaData();
|
||||
|
||||
const prevChallengeId =
|
||||
meta.challengeOrder[meta.challengeOrder.length - 1]?.id;
|
||||
const challengeLang = prevChallengeId && getChallenge(prevChallengeId)?.lang;
|
||||
|
||||
const inputType = await getInputType(challengeType, challengeLang);
|
||||
|
||||
// Placeholder title, to be replaced by updateTaskMarkdownFiles
|
||||
const options = {
|
||||
title: `Task 0`,
|
||||
dashedName: 'task-0',
|
||||
challengeType
|
||||
challengeType,
|
||||
...{ ...(challengeLang && { challengeLang }) },
|
||||
...{ ...(inputType && { inputType }) }
|
||||
};
|
||||
|
||||
const path = getProjectPath();
|
||||
@@ -29,7 +40,6 @@ const createNextTask = async () => {
|
||||
createChallengeFile(challengeIdString, challengeText, path);
|
||||
console.log('Finished creating new task markdown file.');
|
||||
|
||||
const meta = getMetaData();
|
||||
meta.challengeOrder.push({
|
||||
id: challengeIdString,
|
||||
title: options.title
|
||||
|
||||
@@ -11,19 +11,34 @@ interface ChallengeOptions {
|
||||
dashedName: string;
|
||||
challengeType: string;
|
||||
questionCount?: number;
|
||||
challengeLang?: string;
|
||||
inputType?: string;
|
||||
}
|
||||
|
||||
const buildFrontMatter = ({
|
||||
challengeId,
|
||||
title,
|
||||
dashedName,
|
||||
challengeType
|
||||
}: ChallengeOptions) => `---
|
||||
challengeType,
|
||||
challengeLang,
|
||||
inputType
|
||||
}: ChallengeOptions) => {
|
||||
const langString = challengeLang
|
||||
? `
|
||||
lang: ${challengeLang}`
|
||||
: '';
|
||||
const inputTypeString = inputType
|
||||
? `
|
||||
inputType: ${inputType}`
|
||||
: '';
|
||||
|
||||
return `---
|
||||
id: ${challengeId.toString()}
|
||||
title: ${sanitizeTitle(title)}
|
||||
challengeType: ${challengeType}
|
||||
dashedName: ${dashedName}
|
||||
dashedName: ${dashedName}${langString}${inputTypeString}
|
||||
---`;
|
||||
};
|
||||
|
||||
const buildFrontMatterWithVideo = ({
|
||||
challengeId,
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import { prompt } from 'inquirer';
|
||||
import { ChallengeLang } from '../../../shared/config/curriculum';
|
||||
import { challengeTypes } from '../../../shared/config/challenge-types';
|
||||
|
||||
export const getInputType = async (
|
||||
challengeType: string,
|
||||
challengeLang?: string
|
||||
): Promise<string | undefined> => {
|
||||
const isRequired =
|
||||
parseInt(challengeType) === challengeTypes.fillInTheBlank &&
|
||||
challengeLang === ChallengeLang.Chinese;
|
||||
|
||||
if (!isRequired) {
|
||||
return;
|
||||
}
|
||||
|
||||
const inputType = await prompt<{ value: string }>({
|
||||
name: 'value',
|
||||
message: 'What input type is challenge using?',
|
||||
type: 'list',
|
||||
choices: ['pinyin-tone', 'pinyin-to-hanzi']
|
||||
});
|
||||
|
||||
return inputType.value;
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
import {
|
||||
ChallengeLang,
|
||||
SuperBlocks,
|
||||
superBlockToSpeechLang
|
||||
} from '../../../shared/config/curriculum';
|
||||
|
||||
export const getLangFromSuperBlock = (
|
||||
superBlock: SuperBlocks
|
||||
): ChallengeLang => {
|
||||
const lang = superBlockToSpeechLang[superBlock];
|
||||
if (!lang) {
|
||||
throw new Error(`Missing lang mapping for superBlock: ${superBlock}`);
|
||||
}
|
||||
return lang;
|
||||
};
|
||||
@@ -2,6 +2,24 @@ import { describe, it, expect } from 'vitest';
|
||||
import ObjectID from 'bson-objectid';
|
||||
import { getStepTemplate } from './get-step-template';
|
||||
|
||||
const props = {
|
||||
challengeId: new ObjectID('60d4ebe4801158d1abe1b18f'),
|
||||
challengeSeeds: [
|
||||
{
|
||||
contents: '',
|
||||
editableRegionBoundaries: [0, 2],
|
||||
ext: 'html',
|
||||
head: '',
|
||||
id: '',
|
||||
key: 'indexhtml',
|
||||
name: 'index',
|
||||
tail: ''
|
||||
}
|
||||
],
|
||||
stepNum: 5,
|
||||
challengeType: 0
|
||||
};
|
||||
|
||||
// Note: evaluates at highlevel the process, but seedHeads and seedTails could
|
||||
// be tested if more specifics are needed.
|
||||
describe('getStepTemplate util', () => {
|
||||
@@ -35,24 +53,20 @@ Test 1
|
||||
--fcc-editable-region--
|
||||
\`\`\`\n`;
|
||||
|
||||
const props = {
|
||||
challengeId: new ObjectID('60d4ebe4801158d1abe1b18f'),
|
||||
challengeSeeds: [
|
||||
{
|
||||
contents: '',
|
||||
editableRegionBoundaries: [0, 2],
|
||||
ext: 'html',
|
||||
head: '',
|
||||
id: '',
|
||||
key: 'indexhtml',
|
||||
name: 'index',
|
||||
tail: ''
|
||||
}
|
||||
],
|
||||
stepNum: 5,
|
||||
challengeType: 0
|
||||
};
|
||||
|
||||
expect(getStepTemplate(props)).toEqual(baseOutput);
|
||||
});
|
||||
|
||||
it('should add lang property when challengeLang is passed', () => {
|
||||
const frontMatter = `---
|
||||
id: 60d4ebe4801158d1abe1b18f
|
||||
title: Step 5
|
||||
challengeType: 0
|
||||
dashedName: step-5
|
||||
lang: es
|
||||
---`;
|
||||
|
||||
expect(getStepTemplate({ ...props, challengeLang: 'es' })).match(
|
||||
new RegExp(`^${frontMatter}`)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -26,6 +26,7 @@ type StepOptions = {
|
||||
stepNum: number;
|
||||
challengeType?: number;
|
||||
isFirstChallenge?: boolean;
|
||||
challengeLang?: string;
|
||||
};
|
||||
|
||||
export interface ChallengeSeed {
|
||||
@@ -42,7 +43,8 @@ function getStepTemplate({
|
||||
challengeSeeds,
|
||||
stepNum,
|
||||
challengeType,
|
||||
isFirstChallenge = false
|
||||
isFirstChallenge = false,
|
||||
challengeLang
|
||||
}: StepOptions): string {
|
||||
const seedTexts = challengeSeeds
|
||||
.map(({ contents, ext, editableRegionBoundaries }) => {
|
||||
@@ -75,12 +77,17 @@ function getStepTemplate({
|
||||
demoType: onClick`
|
||||
: '';
|
||||
|
||||
const langString = challengeLang
|
||||
? `
|
||||
lang: ${challengeLang}`
|
||||
: '';
|
||||
|
||||
return (
|
||||
`---
|
||||
id: ${challengeId.toString()}
|
||||
title: Step ${stepNum}
|
||||
challengeType: ${challengeType ?? 'placeholder'}
|
||||
dashedName: step-${stepNum}${demoString}
|
||||
dashedName: step-${stepNum}${langString}${demoString}
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
@@ -5,11 +5,13 @@ import { newTaskPrompts } from './helpers/new-task-prompts';
|
||||
import { getProjectPath } from './helpers/get-project-info';
|
||||
import {
|
||||
createChallengeFile,
|
||||
getChallenge,
|
||||
insertChallengeIntoMeta,
|
||||
updateTaskMeta,
|
||||
updateTaskMarkdownFiles
|
||||
} from './utils';
|
||||
import { getMetaData } from './helpers/project-metadata';
|
||||
import { getInputType } from './helpers/get-input-type';
|
||||
|
||||
const insertChallenge = async () => {
|
||||
const challenges = getMetaData().challengeOrder;
|
||||
@@ -22,6 +24,7 @@ const insertChallenge = async () => {
|
||||
value: id
|
||||
}))
|
||||
});
|
||||
const challengeLang = getChallenge(challengeAfter.id)?.lang;
|
||||
|
||||
const indexToInsert = challenges.findIndex(
|
||||
({ id }) => id === challengeAfter.id
|
||||
@@ -31,10 +34,13 @@ const insertChallenge = async () => {
|
||||
|
||||
const { challengeType } = await newTaskPrompts();
|
||||
|
||||
const inputType = await getInputType(challengeType, challengeLang);
|
||||
const options = {
|
||||
title: newTaskTitle,
|
||||
dashedName: 'task-0',
|
||||
challengeType
|
||||
challengeType,
|
||||
...{ ...(challengeLang && { challengeLang }) },
|
||||
...{ ...(inputType && { inputType }) }
|
||||
};
|
||||
|
||||
const path = getProjectPath();
|
||||
|
||||
@@ -22,6 +22,7 @@ interface Options {
|
||||
projectPath?: string;
|
||||
challengeSeeds?: ChallengeSeed[];
|
||||
isFirstChallenge?: boolean;
|
||||
challengeLang?: string;
|
||||
}
|
||||
|
||||
interface QuizOptions {
|
||||
@@ -29,6 +30,7 @@ interface QuizOptions {
|
||||
title: string;
|
||||
dashedName: string;
|
||||
questionCount: number;
|
||||
challengeLang?: string;
|
||||
}
|
||||
|
||||
export async function getAllBlocks() {
|
||||
@@ -49,7 +51,8 @@ const createStepFile = ({
|
||||
challengeType,
|
||||
projectPath = getProjectPath(),
|
||||
challengeSeeds = [],
|
||||
isFirstChallenge = false
|
||||
isFirstChallenge = false,
|
||||
challengeLang
|
||||
}: Options): ObjectID => {
|
||||
const challengeId = new ObjectID();
|
||||
|
||||
@@ -58,7 +61,8 @@ const createStepFile = ({
|
||||
challengeSeeds,
|
||||
stepNum,
|
||||
challengeType,
|
||||
isFirstChallenge
|
||||
isFirstChallenge,
|
||||
challengeLang
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-base-to-string
|
||||
@@ -79,7 +83,8 @@ const createQuizFile = ({
|
||||
projectPath = getProjectPath(),
|
||||
title,
|
||||
dashedName,
|
||||
questionCount
|
||||
questionCount,
|
||||
challengeLang
|
||||
}: QuizOptions): ObjectID => {
|
||||
const challengeId = new ObjectID();
|
||||
const challengeType = challengeTypes.quiz.toString();
|
||||
@@ -90,7 +95,8 @@ const createQuizFile = ({
|
||||
challengeType,
|
||||
title,
|
||||
dashedName,
|
||||
questionCount
|
||||
questionCount,
|
||||
challengeLang
|
||||
});
|
||||
// eslint-disable-next-line @typescript-eslint/no-base-to-string
|
||||
fs.writeFileSync(`${projectPath}${challengeId.toString()}.md`, quizText);
|
||||
@@ -98,9 +104,11 @@ const createQuizFile = ({
|
||||
};
|
||||
|
||||
const createDialogueFile = ({
|
||||
projectPath
|
||||
projectPath,
|
||||
challengeLang
|
||||
}: {
|
||||
projectPath: string;
|
||||
challengeLang: string;
|
||||
}): ObjectID => {
|
||||
const challengeId = new ObjectID();
|
||||
const challengeType = challengeTypes.dialogue.toString();
|
||||
@@ -110,7 +118,8 @@ const createDialogueFile = ({
|
||||
challengeId,
|
||||
challengeType,
|
||||
title: "Dialogue 1: I'm Tom",
|
||||
dashedName: 'dialogue-1-im-tom'
|
||||
dashedName: 'dialogue-1-im-tom',
|
||||
challengeLang
|
||||
});
|
||||
// eslint-disable-next-line @typescript-eslint/no-base-to-string
|
||||
fs.writeFileSync(`${projectPath}${challengeId.toString()}.md`, dialogueText);
|
||||
@@ -267,6 +276,7 @@ const updateTaskMarkdownFiles = (): void => {
|
||||
type Challenge = {
|
||||
challengeType: number;
|
||||
challengeFiles: ChallengeSeed[];
|
||||
lang?: string;
|
||||
};
|
||||
|
||||
const getChallenge = (challengeId: string): Challenge => {
|
||||
|
||||
Reference in New Issue
Block a user