mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-04-27 19:00:42 -04:00
feat(tools): script to create daily challenge files (#61921)
This commit is contained in:
138
tools/challenge-helper-scripts/create-daily-challenges.ts
Normal file
138
tools/challenge-helper-scripts/create-daily-challenges.ts
Normal file
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
Script to create daily coding challenge files in the dev-playground superblock.
|
||||
Accepts a number arg for how many challenges to create.
|
||||
*/
|
||||
|
||||
import { readFileSync, writeFileSync } from 'fs';
|
||||
import path, { join } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import ObjectID from 'bson-objectid';
|
||||
import { Meta } from './helpers/project-metadata';
|
||||
import { getArgValue } from './helpers/get-arg-value';
|
||||
import {
|
||||
getDailyJavascriptChallengeTemplate,
|
||||
getDailyPythonChallengeTemplate
|
||||
} from './helpers/get-challenge-template';
|
||||
|
||||
const numberOfChallengesToCreate = getArgValue(process.argv);
|
||||
|
||||
if (numberOfChallengesToCreate > 10) {
|
||||
throw new Error('Are you sure you want to create that many challenges?');
|
||||
}
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
const curriculumPath = join(__dirname, '../../curriculum');
|
||||
|
||||
const structureBlocksPath = join(curriculumPath, '/structure/blocks');
|
||||
const jsStructurePath = join(
|
||||
structureBlocksPath,
|
||||
'/daily-coding-challenges-javascript.json'
|
||||
);
|
||||
const pyStructurePath = join(
|
||||
structureBlocksPath,
|
||||
'/daily-coding-challenges-python.json'
|
||||
);
|
||||
|
||||
const challengeBlocksPath = join(curriculumPath, '/challenges/english/blocks');
|
||||
const jsChallengesPath = join(
|
||||
challengeBlocksPath,
|
||||
'/daily-coding-challenges-javascript'
|
||||
);
|
||||
const pyChallengesPath = join(
|
||||
challengeBlocksPath,
|
||||
'/daily-coding-challenges-python'
|
||||
);
|
||||
|
||||
for (let i = 0; i < numberOfChallengesToCreate; i++) {
|
||||
const jsMeta = JSON.parse(readFileSync(jsStructurePath, 'utf-8')) as Meta;
|
||||
const pyMeta = JSON.parse(readFileSync(pyStructurePath, 'utf-8')) as Meta;
|
||||
|
||||
const numberOfJsChallenges = jsMeta.challengeOrder.length;
|
||||
const numberOfPyChallenges = pyMeta.challengeOrder.length;
|
||||
|
||||
if (numberOfJsChallenges !== numberOfPyChallenges) {
|
||||
throw new Error(
|
||||
'Inconsistent number of challenges in each daily challenge language, cannot create new challenge.'
|
||||
);
|
||||
}
|
||||
|
||||
const newChallengeNumber = numberOfJsChallenges + 1;
|
||||
|
||||
createDailyJsChallenge({ challengeNumber: newChallengeNumber, meta: jsMeta });
|
||||
createDailyPyChallenge({ challengeNumber: newChallengeNumber, meta: pyMeta });
|
||||
}
|
||||
|
||||
interface CreateDailyChallengeOptions {
|
||||
challengeNumber: number;
|
||||
meta: Meta;
|
||||
}
|
||||
|
||||
function createDailyJsChallenge({
|
||||
challengeNumber,
|
||||
meta
|
||||
}: CreateDailyChallengeOptions) {
|
||||
const challengeId = new ObjectID();
|
||||
|
||||
const newMeta = {
|
||||
...meta,
|
||||
challengeOrder: [
|
||||
...meta.challengeOrder,
|
||||
{
|
||||
// eslint-disable-next-line @typescript-eslint/no-base-to-string
|
||||
id: challengeId.toString(),
|
||||
title: `JavaScript Challenge ${challengeNumber}`
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
writeFileSync(jsStructurePath, JSON.stringify(newMeta, null, 2));
|
||||
|
||||
const jsTemplate = getDailyJavascriptChallengeTemplate({
|
||||
challengeId,
|
||||
challengeNumber
|
||||
});
|
||||
|
||||
const jsChallengePath = join(
|
||||
jsChallengesPath,
|
||||
// eslint-disable-next-line @typescript-eslint/no-base-to-string
|
||||
`${challengeId.toString()}.md`
|
||||
);
|
||||
|
||||
writeFileSync(jsChallengePath, jsTemplate);
|
||||
}
|
||||
|
||||
function createDailyPyChallenge({
|
||||
challengeNumber,
|
||||
meta
|
||||
}: CreateDailyChallengeOptions) {
|
||||
const challengeId = new ObjectID();
|
||||
|
||||
const newMeta = {
|
||||
...meta,
|
||||
challengeOrder: [
|
||||
...meta.challengeOrder,
|
||||
{
|
||||
// eslint-disable-next-line @typescript-eslint/no-base-to-string
|
||||
id: challengeId.toString(),
|
||||
title: `Python Challenge ${challengeNumber}`
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
writeFileSync(pyStructurePath, JSON.stringify(newMeta, null, 2));
|
||||
|
||||
const pyTemplate = getDailyPythonChallengeTemplate({
|
||||
challengeId,
|
||||
challengeNumber
|
||||
});
|
||||
|
||||
const pyChallengePath = join(
|
||||
pyChallengesPath,
|
||||
// eslint-disable-next-line @typescript-eslint/no-base-to-string
|
||||
`${challengeId.toString()}.md`
|
||||
);
|
||||
|
||||
writeFileSync(pyChallengePath, pyTemplate);
|
||||
}
|
||||
@@ -343,6 +343,98 @@ Generic challenge description.
|
||||
Do the assignment.
|
||||
`;
|
||||
|
||||
interface DailyCodingChallengeOptions {
|
||||
challengeId: ObjectID;
|
||||
challengeNumber: number;
|
||||
}
|
||||
|
||||
export const getDailyJavascriptChallengeTemplate = ({
|
||||
challengeId,
|
||||
challengeNumber
|
||||
}: DailyCodingChallengeOptions) => `---
|
||||
id: ${challengeId.toString()}
|
||||
title: "JavaScript Challenge ${challengeNumber}: Placeholder"
|
||||
challengeType: 28
|
||||
dashedName: javascript-challenge-${challengeNumber}
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Placeholder description
|
||||
|
||||
# --hints--
|
||||
|
||||
Placeholder test
|
||||
|
||||
\`\`\`js
|
||||
assert.isTrue(true);
|
||||
\`\`\`
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
\`\`\`js
|
||||
function placeholder(arg) {
|
||||
|
||||
return arg;
|
||||
}
|
||||
\`\`\`
|
||||
|
||||
# --solutions--
|
||||
|
||||
\`\`\`js
|
||||
function placeholder(arg) {
|
||||
|
||||
return arg;
|
||||
}
|
||||
\`\`\`
|
||||
`;
|
||||
|
||||
export const getDailyPythonChallengeTemplate = ({
|
||||
challengeId,
|
||||
challengeNumber
|
||||
}: DailyCodingChallengeOptions) => `---
|
||||
id: ${challengeId.toString()}
|
||||
title: "Python Challenge ${challengeNumber}: Placeholder"
|
||||
challengeType: 29
|
||||
dashedName: python-challenge-${challengeNumber}
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Placeholder description
|
||||
|
||||
# --hints--
|
||||
|
||||
Placeholder test
|
||||
|
||||
\`\`\`js
|
||||
({test: () => { runPython(\`
|
||||
from unittest import TestCase
|
||||
TestCase().assertTrue(True)\`)
|
||||
}})
|
||||
\`\`\`
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
\`\`\`py
|
||||
def placeholder(arg):
|
||||
|
||||
return arg
|
||||
\`\`\`
|
||||
|
||||
# --solutions--
|
||||
|
||||
\`\`\`py
|
||||
def placeholder(arg):
|
||||
|
||||
return arg
|
||||
\`\`\`
|
||||
`;
|
||||
|
||||
type Template = (opts: ChallengeOptions) => string;
|
||||
|
||||
export const getTemplate = (challengeType: string): Template => {
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
"main": "utils.js",
|
||||
"scripts": {
|
||||
"test": "mocha --delay --reporter progress --bail",
|
||||
"create-daily-challenges": "tsx create-daily-challenges",
|
||||
"create-project": "tsx create-project",
|
||||
"create-language-block": "tsx create-language-block",
|
||||
"create-quiz": "tsx create-quiz"
|
||||
|
||||
Reference in New Issue
Block a user