refactor: use pre-built curriculum when starting client (#65878)

This commit is contained in:
Oliver Eyton-Williams
2026-02-19 08:55:33 +01:00
committed by GitHub
parent 052416e5bf
commit 990b64d229
10 changed files with 41 additions and 32 deletions
+9 -6
View File
@@ -2,10 +2,6 @@ const path = require('path');
const _ = require('lodash'); const _ = require('lodash');
const {
getChallengesForLang
} = require('@freecodecamp/curriculum/get-challenges');
const { const {
getBlockCreator, getBlockCreator,
getSuperblocks, getSuperblocks,
@@ -14,12 +10,14 @@ const {
const { const {
getContentDir, getContentDir,
getBlockStructure, getBlockStructure,
getSuperblockStructure getSuperblockStructure,
CURRICULUM_DIR
} = require('@freecodecamp/curriculum/file-handler'); } = require('@freecodecamp/curriculum/file-handler');
const { const {
transformSuperBlock transformSuperBlock
} = require('@freecodecamp/curriculum/build-superblock'); } = require('@freecodecamp/curriculum/build-superblock');
const { getSuperOrder } = require('@freecodecamp/curriculum/super-order'); const { getSuperOrder } = require('@freecodecamp/curriculum/super-order');
const { readFile } = require('fs/promises');
const curriculumLocale = process.env.CURRICULUM_LOCALE || 'english'; const curriculumLocale = process.env.CURRICULUM_LOCALE || 'english';
@@ -72,7 +70,12 @@ exports.replaceChallengeNodes = () => {
}; };
exports.buildChallenges = async function buildChallenges() { exports.buildChallenges = async function buildChallenges() {
const curriculum = await getChallengesForLang(curriculumLocale); const curriculum = JSON.parse(
await readFile(
path.resolve(CURRICULUM_DIR, 'generated', 'curriculum.json'),
'utf-8'
)
);
const superBlocks = Object.keys(curriculum); const superBlocks = Object.keys(curriculum);
const blocks = superBlocks const blocks = superBlocks
.map(superBlock => curriculum[superBlock].blocks) .map(superBlock => curriculum[superBlock].blocks)
+9 -13
View File
@@ -1,4 +1,5 @@
import { resolve } from 'path'; import { resolve } from 'node:path';
import assert from 'node:assert';
import { config } from 'dotenv'; import { config } from 'dotenv';
import { availableLangs } from '@freecodecamp/shared/config/i18n'; import { availableLangs } from '@freecodecamp/shared/config/i18n';
@@ -7,20 +8,15 @@ config({ path: resolve(__dirname, '../../.env') });
const curriculumLangs = availableLangs.curriculum; const curriculumLangs = availableLangs.curriculum;
// checks that the CURRICULUM_LOCALE exists and is an available language function isAllowedLang(lang: string): lang is (typeof curriculumLangs)[number] {
export function testedLang() { return curriculumLangs.includes(lang as (typeof curriculumLangs)[number]);
if (process.env.CURRICULUM_LOCALE) {
if (curriculumLangs.includes(process.env.CURRICULUM_LOCALE)) {
return process.env.CURRICULUM_LOCALE;
} else {
throw Error(`${process.env.CURRICULUM_LOCALE} is not a supported language.
Before the site can be built, this language needs to be manually approved`);
}
} else {
throw Error('CURRICULUM_LOCALE must be set for testing');
}
} }
assert.ok(process.env.CURRICULUM_LOCALE);
assert.ok(isAllowedLang(process.env.CURRICULUM_LOCALE));
export const CURRICULUM_LOCALE = process.env.CURRICULUM_LOCALE;
export const SHOW_UPCOMING_CHANGES = export const SHOW_UPCOMING_CHANGES =
process.env.SHOW_UPCOMING_CHANGES === 'true'; process.env.SHOW_UPCOMING_CHANGES === 'true';
+1 -1
View File
@@ -26,7 +26,7 @@ if (typeof __dirname !== 'undefined') {
__dirnameCompat = dirname(fileURLToPath(metaUrl)); __dirnameCompat = dirname(fileURLToPath(metaUrl));
} }
const CURRICULUM_DIR = resolve(__dirnameCompat, '..'); export const CURRICULUM_DIR = resolve(__dirnameCompat, '..');
const I18N_CURRICULUM_DIR = resolve( const I18N_CURRICULUM_DIR = resolve(
CURRICULUM_DIR, CURRICULUM_DIR,
'i18n-curriculum', 'i18n-curriculum',
+2 -1
View File
@@ -2,10 +2,11 @@ import fs from 'fs';
import path from 'path'; import path from 'path';
import { getChallengesForLang } from '../get-challenges'; import { getChallengesForLang } from '../get-challenges';
import { CURRICULUM_LOCALE } from '../config';
const globalConfigPath = path.resolve(__dirname, '../../generated/'); const globalConfigPath = path.resolve(__dirname, '../../generated/');
void getChallengesForLang('english') void getChallengesForLang(CURRICULUM_LOCALE)
.then(JSON.stringify) .then(JSON.stringify)
.then(json => { .then(json => {
fs.mkdirSync(globalConfigPath, { recursive: true }); fs.mkdirSync(globalConfigPath, { recursive: true });
+2 -2
View File
@@ -1,8 +1,8 @@
import path from 'node:path'; import path from 'node:path';
import { configure } from '@freecodecamp/challenge-linter'; import { configure } from '@freecodecamp/challenge-linter';
import { testedLang } from './config'; import { CURRICULUM_LOCALE } from './config';
const CONFIG_PATH = path.resolve(__dirname, '../challenges/.markdownlint.yaml'); const CONFIG_PATH = path.resolve(__dirname, '../challenges/.markdownlint.yaml');
const { lintAll } = configure(CONFIG_PATH); const { lintAll } = configure(CONFIG_PATH);
lintAll(`challenges/${testedLang()}/**/*.md`); lintAll(`challenges/${CURRICULUM_LOCALE}/**/*.md`);
+3 -4
View File
@@ -4,12 +4,11 @@ vi.stubEnv('SHOW_UPCOMING_CHANGES', 'true');
// We need to use dynamic imports here to ensure the environment variable is set // We need to use dynamic imports here to ensure the environment variable is set
// before the module is loaded. // before the module is loaded.
const { testedLang } = await import('../config.js'); const { CURRICULUM_LOCALE } = await import('../config.js');
const { getChallenges } = await import('./test-challenges.js'); const { getChallenges } = await import('./test-challenges.js');
describe('Daily Coding Challenges', async () => { describe('Daily Coding Challenges', async () => {
const lang = testedLang(); const challenges = await getChallenges(CURRICULUM_LOCALE, {
const challenges = await getChallenges(lang, {
superBlock: 'dev-playground' superBlock: 'dev-playground'
}); });
@@ -57,7 +56,7 @@ describe('Daily Coding Challenges', async () => {
} }
// skip these for non-English for now. // skip these for non-English for now.
if (lang !== 'english') continue; if (CURRICULUM_LOCALE !== 'english') continue;
if (jsChallenge.title !== pyChallenge.title) { if (jsChallenge.title !== pyChallenge.title) {
errors.push( errors.push(
+3 -4
View File
@@ -16,7 +16,7 @@ import { challengeSchemaValidator } from '../../schema/challenge-schema.js';
import { curriculumSchemaValidator } from '../../schema/curriculum-schema.js'; import { curriculumSchemaValidator } from '../../schema/curriculum-schema.js';
import { validateMetaSchema } from '../../schema/meta-schema.js'; import { validateMetaSchema } from '../../schema/meta-schema.js';
import { getBlockStructure } from '../file-handler.js'; import { getBlockStructure } from '../file-handler.js';
import { FCC_CHALLENGE_ID, testedLang } from '../config.js'; import { FCC_CHALLENGE_ID, CURRICULUM_LOCALE } from '../config.js';
import ChallengeTitles from './utils/challenge-titles.js'; import ChallengeTitles from './utils/challenge-titles.js';
import MongoIds from './utils/mongo-ids.js'; import MongoIds from './utils/mongo-ids.js';
import createPseudoWorker from './utils/pseudo-worker.js'; import createPseudoWorker from './utils/pseudo-worker.js';
@@ -79,8 +79,7 @@ async function newPageContext() {
} }
export async function defineTestsForBlock(testFilter) { export async function defineTestsForBlock(testFilter) {
const lang = testedLang(); const challenges = await getChallenges(CURRICULUM_LOCALE, testFilter);
const challenges = await getChallenges(lang, testFilter);
const nonCertificationChallenges = challenges.filter( const nonCertificationChallenges = challenges.filter(
({ challengeType }) => challengeType !== 7 ({ challengeType }) => challengeType !== 7
); );
@@ -107,7 +106,7 @@ export async function defineTestsForBlock(testFilter) {
} }
} }
const challengeData = { meta, challenges, lang }; const challengeData = { meta, challenges, lang: CURRICULUM_LOCALE };
describe('Check challenges', async () => { describe('Check challenges', async () => {
beforeAll(async () => { beforeAll(async () => {
+1 -1
View File
@@ -4,7 +4,7 @@
"tasks": { "tasks": {
"build": { "build": {
"outputs": ["dist/**", "generated/**"], "outputs": ["dist/**", "generated/**"],
"env": ["FCC_*", "SHOW_UPCOMING_CHANGES"] "env": ["FCC_*", "SHOW_UPCOMING_CHANGES", "CURRICULUM_LOCALE"]
}, },
"test": { "test": {
"passThroughEnv": ["VITEST_POOL_ID", "PUPPETEER_WS_ENDPOINT"], "passThroughEnv": ["VITEST_POOL_ID", "PUPPETEER_WS_ENDPOINT"],
+2
View File
@@ -26,6 +26,8 @@ RUN cd api && pnpm prisma generate
# following env vars. # following env vars.
ARG SHOW_UPCOMING_CHANGES=false ARG SHOW_UPCOMING_CHANGES=false
ENV SHOW_UPCOMING_CHANGES=$SHOW_UPCOMING_CHANGES ENV SHOW_UPCOMING_CHANGES=$SHOW_UPCOMING_CHANGES
ARG CURRICULUM_LOCALE=english
ENV CURRICULUM_LOCALE=$CURRICULUM_LOCALE
RUN pnpm turbo -F=@freecodecamp/api build RUN pnpm turbo -F=@freecodecamp/api build
@@ -0,0 +1,9 @@
{
"$schema": "https://v2-8-7.turborepo.dev/schema.json",
"extends": ["//"],
"tasks": {
"test": {
"env": ["CURRICULUM_LOCALE", "SHOW_UPCOMING_CHANGES"]
}
}
}