mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-05-28 18:26:54 +00:00
fix(dev): preserve step order during hot reload by reloading all chal… (#62048)
This commit is contained in:
@@ -2,43 +2,69 @@ const path = require('path');
|
|||||||
|
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
|
|
||||||
const envData = require('../config/env.json');
|
|
||||||
const { getChallengesForLang } = require('../../curriculum/get-challenges');
|
const { getChallengesForLang } = require('../../curriculum/get-challenges');
|
||||||
|
|
||||||
const {
|
const {
|
||||||
getContentDir,
|
getContentDir,
|
||||||
getBlockCreator
|
getBlockCreator,
|
||||||
|
getSuperblocks,
|
||||||
|
superBlockToFilename
|
||||||
} = require('../../curriculum/build-curriculum');
|
} = require('../../curriculum/build-curriculum');
|
||||||
const { getBlockStructure } = require('../../curriculum/file-handler');
|
const {
|
||||||
const { getSuperblocks } = require('../../curriculum/build-curriculum');
|
getBlockStructure,
|
||||||
|
getSuperblockStructure
|
||||||
|
} = require('../../curriculum/file-handler');
|
||||||
|
const { transformSuperBlock } = require('../../curriculum/build-superblock');
|
||||||
|
const { getSuperOrder } = require('../../curriculum/utils');
|
||||||
|
|
||||||
const { curriculumLocale } = envData;
|
const curriculumLocale = process.env.CURRICULUM_LOCALE || 'english';
|
||||||
|
|
||||||
exports.localeChallengesRootDir = getContentDir(curriculumLocale);
|
exports.localeChallengesRootDir = getContentDir(curriculumLocale);
|
||||||
|
|
||||||
const blockCreator = getBlockCreator(curriculumLocale);
|
const blockCreator = getBlockCreator(curriculumLocale);
|
||||||
|
|
||||||
|
function getBlockMetadata(block, superBlock) {
|
||||||
|
// Compute metadata for the given block in the specified superblock
|
||||||
|
const sbFilename = superBlockToFilename[superBlock];
|
||||||
|
const sbData = getSuperblockStructure(sbFilename);
|
||||||
|
const blocks = transformSuperBlock(sbData, {
|
||||||
|
showComingSoon: process.env.SHOW_UPCOMING_CHANGES === 'true'
|
||||||
|
});
|
||||||
|
|
||||||
|
const order = blocks.findIndex(b => b.dashedName === block);
|
||||||
|
const superOrder = getSuperOrder(superBlock);
|
||||||
|
|
||||||
|
if (order === -1) {
|
||||||
|
throw new Error(`Block ${block} not found in superblock ${superBlock}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { order, superOrder };
|
||||||
|
}
|
||||||
|
|
||||||
exports.replaceChallengeNodes = () => {
|
exports.replaceChallengeNodes = () => {
|
||||||
return async function replaceChallengeNodes(filePath) {
|
return async function replaceChallengeNodes(filePath) {
|
||||||
const parentDir = path.dirname(filePath);
|
const parentDir = path.dirname(filePath);
|
||||||
const block = path.basename(parentDir);
|
const block = path.basename(parentDir);
|
||||||
const filename = path.basename(filePath);
|
const filename = path.basename(filePath);
|
||||||
|
|
||||||
console.log(`Replacing challenge node for ${filePath}`);
|
console.log(`Replacing challenge nodes for ${filePath}`);
|
||||||
const meta = getBlockStructure(block);
|
const meta = getBlockStructure(block);
|
||||||
const superblocks = getSuperblocks(block);
|
const superblocks = getSuperblocks(block);
|
||||||
|
|
||||||
const challenge = await blockCreator.createChallenge({
|
// Create a challenge for each superblock containing this block
|
||||||
filename,
|
const challenges = await Promise.all(
|
||||||
block,
|
superblocks.map(async superBlock => {
|
||||||
meta,
|
const { order, superOrder } = getBlockMetadata(block, superBlock);
|
||||||
isAudited: true
|
return blockCreator.createChallenge({
|
||||||
});
|
filename,
|
||||||
|
block,
|
||||||
|
meta: { ...meta, superBlock, order, superOrder },
|
||||||
|
isAudited: true
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
return superblocks.map(superBlock => ({
|
return challenges;
|
||||||
...challenge,
|
|
||||||
superBlock
|
|
||||||
}));
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
const path = require('path');
|
|
||||||
const chokidar = require('chokidar');
|
const chokidar = require('chokidar');
|
||||||
const readdirp = require('readdirp');
|
|
||||||
|
|
||||||
const { createChallengeNode } = require('./create-challenge-nodes');
|
const { createChallengeNode } = require('./create-challenge-nodes');
|
||||||
|
|
||||||
@@ -36,68 +34,34 @@ exports.sourceNodes = function sourceChallengesSourceNodes(
|
|||||||
cwd: curriculumPath
|
cwd: curriculumPath
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function handleChallengeUpdate(filePath, action = 'changed') {
|
||||||
|
return onSourceChange(filePath)
|
||||||
|
.then(challenges => {
|
||||||
|
const actionText = action === 'added' ? 'creating' : 'replacing';
|
||||||
|
reporter.info(
|
||||||
|
`Challenge file ${action}: ${filePath}, ${actionText} challengeNodes with ids ${challenges.map(({ id }) => id).join(', ')}`
|
||||||
|
);
|
||||||
|
challenges.forEach(challenge =>
|
||||||
|
createVisibleChallenge(challenge, { isReloading: true })
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.catch(e =>
|
||||||
|
reporter.error(
|
||||||
|
`fcc-replace-challenge\nattempting to replace ${filePath}\n\n${e.message}\n${e.stack ? ` ${e.stack}` : ''}`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// On file change, replace only the changed challenge. The key is ensuring
|
||||||
|
// onSourceChange returns a challenge with complete metadata.
|
||||||
watcher.on('change', filePath =>
|
watcher.on('change', filePath =>
|
||||||
/\.md?$/.test(filePath)
|
/\.md?$/.test(filePath) ? handleChallengeUpdate(filePath, 'changed') : null
|
||||||
? onSourceChange(filePath)
|
|
||||||
.then(challenges => {
|
|
||||||
reporter.info(
|
|
||||||
`
|
|
||||||
File changed at ${filePath}, replacing challengeNodes with ids ${challenges.map(({ id }) => id).join(', ')}
|
|
||||||
`
|
|
||||||
);
|
|
||||||
challenges.forEach(challenge =>
|
|
||||||
createVisibleChallenge(challenge, { isReloading: true })
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.catch(e =>
|
|
||||||
reporter.error(`fcc-replace-challenge
|
|
||||||
attempting to replace ${filePath}
|
|
||||||
|
|
||||||
${e.message}
|
|
||||||
${e.stack}
|
|
||||||
|
|
||||||
`)
|
|
||||||
)
|
|
||||||
: null
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// if a file is added, that might change the order of the challenges in the
|
// On file add, replace just the new challenge.
|
||||||
// containing block, so we recreate them all
|
|
||||||
watcher.on('add', filePath => {
|
watcher.on('add', filePath => {
|
||||||
if (/\.md?$/.test(filePath)) {
|
if (!/\.md?$/.test(filePath)) return;
|
||||||
const blockPath = path.dirname(filePath);
|
handleChallengeUpdate(filePath, 'added');
|
||||||
const fullBlockPath = path.join(
|
|
||||||
__dirname,
|
|
||||||
'../../../curriculum/challenges/english/',
|
|
||||||
blockPath
|
|
||||||
);
|
|
||||||
readdirp(fullBlockPath, { fileFilter: '*.md' })
|
|
||||||
.on('data', entry => {
|
|
||||||
const { path: siblingPath } = entry;
|
|
||||||
const relativePath = path.join(blockPath, siblingPath);
|
|
||||||
onSourceChange(relativePath)
|
|
||||||
.then(challenges => {
|
|
||||||
reporter.info(
|
|
||||||
`
|
|
||||||
File changed at ${relativePath}, replacing challengeNodes with ids ${challenges.map(({ id }) => id).join(', ')}
|
|
||||||
`
|
|
||||||
);
|
|
||||||
challenges.forEach(challenge =>
|
|
||||||
createVisibleChallenge(challenge)
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.catch(e =>
|
|
||||||
reporter.error(`fcc-replace-challenge
|
|
||||||
attempting to replace ${relativePath}
|
|
||||||
|
|
||||||
${e.message}
|
|
||||||
|
|
||||||
`)
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.on('warn', error => console.error('non-fatal error', error))
|
|
||||||
.on('error', error => console.error('fatal error', error));
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
function sourceAndCreateNodes() {
|
function sourceAndCreateNodes() {
|
||||||
|
|||||||
Reference in New Issue
Block a user