mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-05-28 18:26:54 +00:00
feat(i18n): check no slug duplicates (#47127)
* refactor: move challenge auditing to function * feat: confirm each slug only appears once * refactor: rename explanatory variables
This commit is contained in:
committed by
GitHub
parent
08ce28fba3
commit
97a36fff9a
@@ -1,7 +1,16 @@
|
||||
import { access, readdir } from 'fs/promises';
|
||||
import { join } from 'path';
|
||||
import { join, resolve } from 'path';
|
||||
|
||||
import { flatten } from 'lodash/fp';
|
||||
import { config } from 'dotenv';
|
||||
|
||||
const envPath = resolve(__dirname, '../../.env');
|
||||
config({ path: envPath });
|
||||
|
||||
import { availableLangs, auditedCerts } from '../../config/i18n/all-langs';
|
||||
import { getChallengesForLang } from '../../curriculum/getChallenges';
|
||||
import { SuperBlocks } from '../../config/certification-settings';
|
||||
import { ChallengeNode } from '../../client/src/redux/prop-types';
|
||||
|
||||
const superBlockFolderMap = {
|
||||
'responsive-web-design': '01-responsive-web-design',
|
||||
@@ -26,9 +35,32 @@ const superBlockFolderMap = {
|
||||
// the audit, we just ignore them.
|
||||
const blocksThatNeedToMove = ['d3-dashboard'];
|
||||
|
||||
// Adding types for getChallengesForLang is possible, but not worth the effort
|
||||
// at this time.
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
|
||||
const getChallenges = async (lang: string) => {
|
||||
const curriculum = await getChallengesForLang(lang);
|
||||
return Object.keys(curriculum)
|
||||
.map(key => curriculum[key].blocks)
|
||||
.reduce((challengeArray, superBlock) => {
|
||||
const challengesForBlock = Object.keys(superBlock).map(
|
||||
key => superBlock[key].challenges
|
||||
);
|
||||
return [...challengeArray, ...flatten(challengesForBlock)];
|
||||
}, []) as unknown as ChallengeNode['challenge'][];
|
||||
};
|
||||
|
||||
/* eslint-enable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-enable @typescript-eslint/no-unsafe-argument */
|
||||
/* eslint-enable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-enable @typescript-eslint/no-unsafe-member-access */
|
||||
|
||||
void (async () => {
|
||||
let actionShouldFail = false;
|
||||
const languagesFailing: string[] = [];
|
||||
const englishCurriculumDirectory = join(
|
||||
process.cwd(),
|
||||
'curriculum',
|
||||
@@ -56,7 +88,6 @@ void (async () => {
|
||||
lang => lang !== 'english'
|
||||
);
|
||||
for (const lang of langsToCheck) {
|
||||
let languageIsFailing = false;
|
||||
console.log(`\n=== ${lang} ===`);
|
||||
const certs = auditedCerts[lang as keyof typeof auditedCerts];
|
||||
const langCurriculumDirectory = join(
|
||||
@@ -66,27 +97,68 @@ void (async () => {
|
||||
lang
|
||||
);
|
||||
const auditedFiles = englishFilePaths.filter(file =>
|
||||
certs.some(cert => file.startsWith(superBlockFolderMap[cert]))
|
||||
certs.some(
|
||||
cert =>
|
||||
// we're not ready to audit the new curriculum yet
|
||||
(cert !== SuperBlocks.JsAlgoDataStructNew ||
|
||||
process.env.SHOW_UPCOMING_CHANGES === 'true') &&
|
||||
file.startsWith(superBlockFolderMap[cert])
|
||||
)
|
||||
);
|
||||
for (const file of auditedFiles) {
|
||||
if (blocksThatNeedToMove.some(block => file.includes(`/${block}/`))) {
|
||||
continue;
|
||||
}
|
||||
const filePath = join(langCurriculumDirectory, file);
|
||||
const fileExists = await access(filePath)
|
||||
.then(() => true)
|
||||
.catch(() => false);
|
||||
if (!fileExists) {
|
||||
console.log(`${filePath} does not exist.`);
|
||||
languageIsFailing = true;
|
||||
}
|
||||
}
|
||||
if (languageIsFailing) {
|
||||
languagesFailing.push(lang);
|
||||
actionShouldFail = true;
|
||||
const noMissingFiles = await auditChallengeFiles(auditedFiles, {
|
||||
langCurriculumDirectory
|
||||
});
|
||||
const noDuplicateSlugs = await auditSlugs(lang, certs);
|
||||
if (noMissingFiles && noDuplicateSlugs) {
|
||||
console.log(`All challenges pass.`);
|
||||
} else {
|
||||
console.log(`All expected files found.`);
|
||||
actionShouldFail = true;
|
||||
}
|
||||
}
|
||||
actionShouldFail ? process.exit(1) : process.exit(0);
|
||||
})();
|
||||
|
||||
async function auditChallengeFiles(
|
||||
auditedFiles: string[],
|
||||
{ langCurriculumDirectory }: { langCurriculumDirectory: string }
|
||||
) {
|
||||
let auditPassed = true;
|
||||
for (const file of auditedFiles) {
|
||||
if (blocksThatNeedToMove.some(block => file.includes(`/${block}/`))) {
|
||||
continue;
|
||||
}
|
||||
const filePath = join(langCurriculumDirectory, file);
|
||||
const fileExists = await access(filePath)
|
||||
.then(() => true)
|
||||
.catch(() => false);
|
||||
if (!fileExists) {
|
||||
console.log(`${filePath} does not exist.`);
|
||||
auditPassed = false;
|
||||
}
|
||||
}
|
||||
return auditPassed;
|
||||
}
|
||||
|
||||
async function auditSlugs(lang: string, certs: SuperBlocks[]) {
|
||||
let auditPassed = true;
|
||||
const slugs = new Map<string, string>();
|
||||
const challenges = await getChallenges(lang);
|
||||
|
||||
for (const challenge of challenges) {
|
||||
const { block, dashedName, superBlock } = challenge;
|
||||
const slug = `/learn/${superBlock}/${block}/${dashedName}`;
|
||||
// Skipping certifications
|
||||
const isCertification = challenge.challengeType === 7;
|
||||
if (certs.includes(superBlock) && !isCertification && slugs.has(slug)) {
|
||||
console.log(
|
||||
`${slug} appears more than once: ${slugs.get(slug) ?? ''} and ${
|
||||
challenge.id
|
||||
}`
|
||||
);
|
||||
auditPassed = false;
|
||||
}
|
||||
slugs.set(slug, challenge.id);
|
||||
}
|
||||
|
||||
return auditPassed;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user