Files
freeCodeCamp/tools/challenge-helper-scripts/rename-block.ts
2026-03-17 15:43:14 +00:00

178 lines
4.9 KiB
TypeScript

import fs from 'fs/promises';
import path, { join } from 'path';
import { input } from '@inquirer/prompts';
import { format } from 'prettier';
import { IntroJson, parseIntroJson } from './helpers/parse-json';
import { withTrace } from './helpers/utils';
import { getAllBlocks, validateBlockName } from './utils';
import {
getBlockStructure,
getBlockStructurePath,
getSuperblockStructure,
writeBlockStructure,
writeSuperblockStructure,
getContentConfig,
getCurriculumStructure
} from '@freecodecamp/curriculum/file-handler';
interface RenameBlockArgs {
newBlock: string;
oldBlock: string;
newName: string;
}
const introJsonPath = path.resolve(
__dirname,
'../../client/i18n/locales/english/intro.json'
);
function getBlockTitleFromIntro(intro: IntroJson, block: string) {
for (const superBlockInfo of Object.values(intro)) {
const blockInfo = superBlockInfo.blocks[block];
if (blockInfo?.title) return blockInfo.title;
}
}
function renameBlockInSimpleStructure(
blocks: string[] | undefined,
oldBlock: string,
newBlock: string
) {
if (!blocks) return false;
const blockIndex = blocks.findIndex(block => block === oldBlock);
if (blockIndex === -1) return false;
blocks[blockIndex] = newBlock;
return true;
}
function renameBlockInChapterStructure(
chapters:
| {
modules: {
blocks: string[];
}[];
}[]
| undefined,
oldBlock: string,
newBlock: string
) {
if (!chapters) return false;
let updated = false;
for (const chapter of chapters) {
for (const module of chapter.modules) {
const blockIndex = module.blocks.findIndex(block => block === oldBlock);
if (blockIndex !== -1) {
module.blocks[blockIndex] = newBlock;
updated = true;
}
}
}
return updated;
}
function renameBlockInIntro(
intro: IntroJson,
superblock: string,
oldBlock: string,
newBlock: string,
newName: string
) {
const superBlockIntro = intro[superblock];
if (!superBlockIntro) return false;
const introBlocks = Object.entries(superBlockIntro.blocks);
const blockIntroIndex = introBlocks.findIndex(
([block]) => block === oldBlock
);
if (blockIntroIndex === -1) return false;
const currentBlockInfo = introBlocks[blockIntroIndex]?.[1];
if (!currentBlockInfo) return false;
introBlocks[blockIntroIndex] = [
newBlock,
{ ...currentBlockInfo, title: newName }
];
superBlockIntro.blocks = Object.fromEntries(introBlocks);
return true;
}
async function renameBlock({ newBlock, newName, oldBlock }: RenameBlockArgs) {
const blockStructure = getBlockStructure(oldBlock);
const blockStructurePath = getBlockStructurePath(oldBlock);
blockStructure.dashedName = newBlock;
await writeBlockStructure(newBlock, blockStructure);
await fs.rm(blockStructurePath);
console.log('New block structure .json written.');
const { blockContentDir } = getContentConfig('english');
const oldBlockContentDir = join(blockContentDir, oldBlock);
const newBlockContentDir = join(blockContentDir, newBlock);
await fs.rename(oldBlockContentDir, newBlockContentDir);
console.log('Block challenges moved to new directory.');
const newIntro = await parseIntroJson(introJsonPath);
let didUpdateIntro = false;
const { superblocks } = getCurriculumStructure();
console.log('Updating superblocks containing renamed block.');
for (const superblock of superblocks) {
const superblockStructure = getSuperblockStructure(superblock);
const didUpdateSuperblock =
renameBlockInSimpleStructure(
superblockStructure.blocks,
oldBlock,
newBlock
) ||
renameBlockInChapterStructure(
superblockStructure.chapters,
oldBlock,
newBlock
);
if (didUpdateSuperblock) {
await writeSuperblockStructure(superblock, superblockStructure);
console.log(`Updated superblock .json file written for ${superblock}.`);
didUpdateIntro =
renameBlockInIntro(newIntro, superblock, oldBlock, newBlock, newName) ||
didUpdateIntro;
}
}
if (didUpdateIntro) {
await withTrace(
fs.writeFile,
introJsonPath,
await format(JSON.stringify(newIntro), { parser: 'json' })
);
console.log('Updated locale intro.json file written.');
}
}
void getAllBlocks().then(async existingBlocks => {
const intro = await parseIntroJson(introJsonPath);
const oldBlock = await input({
message: 'What is the dashed name of block to rename?',
validate: (block: string) =>
existingBlocks.includes(block) || 'Block not found in existing blocks.'
});
const newName = await input({
message: 'What is the new name?',
default: getBlockTitleFromIntro(intro, oldBlock) ?? oldBlock
});
const newBlock = await input({
message: 'What is the new dashed name (in kebab-case)?',
validate: (block: string) => validateBlockName(block, existingBlocks)
});
await renameBlock({ newBlock, newName, oldBlock });
console.log('All set. Refresh the page to see the changes.');
});