mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-05-28 18:26:54 +00:00
fix(tools): prevent create-new-x overwriting old projects (#62621)
This commit is contained in:
committed by
GitHub
parent
f5361f4341
commit
6fc3684049
@@ -19,7 +19,12 @@ import {
|
||||
import { superBlockToFilename } from '../../curriculum/build-curriculum';
|
||||
import { getBaseMeta } from './helpers/get-base-meta';
|
||||
import { createIntroMD } from './helpers/create-intro';
|
||||
import { createDialogueFile, createQuizFile, validateBlockName } from './utils';
|
||||
import {
|
||||
createDialogueFile,
|
||||
createQuizFile,
|
||||
getAllBlocks,
|
||||
validateBlockName
|
||||
} from './utils';
|
||||
import {
|
||||
updateSimpleSuperblockStructure,
|
||||
updateChapterModuleSuperblockStructure
|
||||
@@ -222,118 +227,122 @@ function withTrace<Args extends unknown[], Result>(
|
||||
});
|
||||
}
|
||||
|
||||
void prompt([
|
||||
{
|
||||
name: 'superBlock',
|
||||
message: 'Which certification does this belong to?',
|
||||
default: SuperBlocks.A2English,
|
||||
type: 'list',
|
||||
choices: Object.values(languageSuperBlocks)
|
||||
},
|
||||
{
|
||||
name: 'block',
|
||||
message: 'What is the dashed name (in kebab-case) for this block?',
|
||||
validate: validateBlockName,
|
||||
filter: (block: string) => {
|
||||
return block.toLowerCase().trim();
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'title',
|
||||
default: ({ block }: { block: string }) => block
|
||||
},
|
||||
{
|
||||
name: 'helpCategory',
|
||||
message: 'Choose a help category',
|
||||
default: 'English',
|
||||
type: 'list',
|
||||
choices: helpCategories
|
||||
},
|
||||
{
|
||||
name: 'blockType',
|
||||
message: 'Choose a block type',
|
||||
default: BlockTypes.learn,
|
||||
type: 'list',
|
||||
choices: Object.values(BlockTypes),
|
||||
when: (answers: CreateBlockArgs) =>
|
||||
chapterBasedSuperBlocks.includes(answers.superBlock)
|
||||
},
|
||||
{
|
||||
name: 'blockLayout',
|
||||
message: 'Choose a block layout',
|
||||
default: BlockLayouts.DialogueGrid,
|
||||
type: 'list',
|
||||
choices: Object.values(BlockLayouts),
|
||||
when: (answers: CreateBlockArgs) =>
|
||||
chapterBasedSuperBlocks.includes(answers.superBlock) &&
|
||||
answers.blockType !== BlockTypes.quiz
|
||||
},
|
||||
{
|
||||
name: 'questionCount',
|
||||
message: 'Choose a question count',
|
||||
default: 20,
|
||||
type: 'list',
|
||||
choices: [10, 20],
|
||||
when: (answers: CreateBlockArgs) => answers.blockType === BlockTypes.quiz
|
||||
},
|
||||
{
|
||||
name: 'chapter',
|
||||
message: 'What chapter should this language block go in?',
|
||||
type: 'list',
|
||||
choices: (answers: CreateBlockArgs) => {
|
||||
const superblockFilename = (
|
||||
superBlockToFilename as Record<SuperBlocks, string>
|
||||
)[answers.superBlock];
|
||||
const structure = getSuperblockStructure(superblockFilename) as {
|
||||
chapters: {
|
||||
dashedName: string;
|
||||
modules: { dashedName: string; blocks: string[] }[];
|
||||
}[];
|
||||
};
|
||||
return structure.chapters.map(chapter => chapter.dashedName);
|
||||
},
|
||||
when: (answers: CreateBlockArgs) =>
|
||||
chapterBasedSuperBlocks.includes(answers.superBlock)
|
||||
},
|
||||
{
|
||||
name: 'module',
|
||||
message: 'What module should this language block go in?',
|
||||
type: 'list',
|
||||
choices: (answers: CreateBlockArgs) => {
|
||||
const superblockFilename = (
|
||||
superBlockToFilename as Record<SuperBlocks, string>
|
||||
)[answers.superBlock];
|
||||
const structure = getSuperblockStructure(superblockFilename) as {
|
||||
chapters: {
|
||||
dashedName: string;
|
||||
modules: { dashedName: string; blocks: string[] }[];
|
||||
}[];
|
||||
};
|
||||
return (
|
||||
structure.chapters
|
||||
.find(chapter => chapter.dashedName === answers.chapter)
|
||||
?.modules.map(module => module.dashedName) ?? []
|
||||
);
|
||||
},
|
||||
when: (answers: CreateBlockArgs) =>
|
||||
chapterBasedSuperBlocks.includes(answers.superBlock)
|
||||
},
|
||||
{
|
||||
name: 'position',
|
||||
message: 'At which position does this appear in the module?',
|
||||
default: 1,
|
||||
validate: (position: string) => {
|
||||
return parseInt(position, 10) > 0
|
||||
? true
|
||||
: 'Position must be an number greater than zero.';
|
||||
},
|
||||
when: (answers: CreateBlockArgs) =>
|
||||
chapterBasedSuperBlocks.includes(answers.superBlock),
|
||||
filter: (position: string) => {
|
||||
return parseInt(position, 10);
|
||||
}
|
||||
}
|
||||
])
|
||||
void getAllBlocks()
|
||||
.then(existingBlocks =>
|
||||
prompt([
|
||||
{
|
||||
name: 'superBlock',
|
||||
message: 'Which certification does this belong to?',
|
||||
default: SuperBlocks.A2English,
|
||||
type: 'list',
|
||||
choices: Object.values(languageSuperBlocks)
|
||||
},
|
||||
{
|
||||
name: 'block',
|
||||
message: 'What is the dashed name (in kebab-case) for this block?',
|
||||
validate: (block: string) => validateBlockName(block, existingBlocks),
|
||||
filter: (block: string) => {
|
||||
return block.toLowerCase().trim();
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'title',
|
||||
default: ({ block }: { block: string }) => block
|
||||
},
|
||||
{
|
||||
name: 'helpCategory',
|
||||
message: 'Choose a help category',
|
||||
default: 'English',
|
||||
type: 'list',
|
||||
choices: helpCategories
|
||||
},
|
||||
{
|
||||
name: 'blockType',
|
||||
message: 'Choose a block type',
|
||||
default: BlockTypes.learn,
|
||||
type: 'list',
|
||||
choices: Object.values(BlockTypes),
|
||||
when: (answers: CreateBlockArgs) =>
|
||||
chapterBasedSuperBlocks.includes(answers.superBlock)
|
||||
},
|
||||
{
|
||||
name: 'blockLayout',
|
||||
message: 'Choose a block layout',
|
||||
default: BlockLayouts.DialogueGrid,
|
||||
type: 'list',
|
||||
choices: Object.values(BlockLayouts),
|
||||
when: (answers: CreateBlockArgs) =>
|
||||
chapterBasedSuperBlocks.includes(answers.superBlock) &&
|
||||
answers.blockType !== BlockTypes.quiz
|
||||
},
|
||||
{
|
||||
name: 'questionCount',
|
||||
message: 'Choose a question count',
|
||||
default: 20,
|
||||
type: 'list',
|
||||
choices: [10, 20],
|
||||
when: (answers: CreateBlockArgs) =>
|
||||
answers.blockType === BlockTypes.quiz
|
||||
},
|
||||
{
|
||||
name: 'chapter',
|
||||
message: 'What chapter should this language block go in?',
|
||||
type: 'list',
|
||||
choices: (answers: CreateBlockArgs) => {
|
||||
const superblockFilename = (
|
||||
superBlockToFilename as Record<SuperBlocks, string>
|
||||
)[answers.superBlock];
|
||||
const structure = getSuperblockStructure(superblockFilename) as {
|
||||
chapters: {
|
||||
dashedName: string;
|
||||
modules: { dashedName: string; blocks: string[] }[];
|
||||
}[];
|
||||
};
|
||||
return structure.chapters.map(chapter => chapter.dashedName);
|
||||
},
|
||||
when: (answers: CreateBlockArgs) =>
|
||||
chapterBasedSuperBlocks.includes(answers.superBlock)
|
||||
},
|
||||
{
|
||||
name: 'module',
|
||||
message: 'What module should this language block go in?',
|
||||
type: 'list',
|
||||
choices: (answers: CreateBlockArgs) => {
|
||||
const superblockFilename = (
|
||||
superBlockToFilename as Record<SuperBlocks, string>
|
||||
)[answers.superBlock];
|
||||
const structure = getSuperblockStructure(superblockFilename) as {
|
||||
chapters: {
|
||||
dashedName: string;
|
||||
modules: { dashedName: string; blocks: string[] }[];
|
||||
}[];
|
||||
};
|
||||
return (
|
||||
structure.chapters
|
||||
.find(chapter => chapter.dashedName === answers.chapter)
|
||||
?.modules.map(module => module.dashedName) ?? []
|
||||
);
|
||||
},
|
||||
when: (answers: CreateBlockArgs) =>
|
||||
chapterBasedSuperBlocks.includes(answers.superBlock)
|
||||
},
|
||||
{
|
||||
name: 'position',
|
||||
message: 'At which position does this appear in the module?',
|
||||
default: 1,
|
||||
validate: (position: string) => {
|
||||
return parseInt(position, 10) > 0
|
||||
? true
|
||||
: 'Position must be an number greater than zero.';
|
||||
},
|
||||
when: (answers: CreateBlockArgs) =>
|
||||
chapterBasedSuperBlocks.includes(answers.superBlock),
|
||||
filter: (position: string) => {
|
||||
return parseInt(position, 10);
|
||||
}
|
||||
}
|
||||
])
|
||||
)
|
||||
.then(
|
||||
async ({
|
||||
superBlock,
|
||||
|
||||
@@ -15,7 +15,12 @@ import {
|
||||
writeBlockStructure
|
||||
} from '../../curriculum/file-handler';
|
||||
import { superBlockToFilename } from '../../curriculum/build-curriculum';
|
||||
import { createQuizFile, createStepFile, validateBlockName } from './utils';
|
||||
import {
|
||||
createQuizFile,
|
||||
createStepFile,
|
||||
validateBlockName,
|
||||
getAllBlocks
|
||||
} from './utils';
|
||||
import { getBaseMeta } from './helpers/get-base-meta';
|
||||
import { createIntroMD } from './helpers/create-intro';
|
||||
import {
|
||||
@@ -293,145 +298,151 @@ async function getModules(superBlock: string, chapterName: string) {
|
||||
return modifiedChapter?.modules;
|
||||
}
|
||||
|
||||
void prompt([
|
||||
{
|
||||
name: 'superBlock',
|
||||
message: 'Which certification does this belong to?',
|
||||
default: SuperBlocks.FullStackDeveloper,
|
||||
type: 'list',
|
||||
choices: Object.values(SuperBlocks)
|
||||
},
|
||||
{
|
||||
name: 'block',
|
||||
message: 'What is the dashed name (in kebab-case) for this project?',
|
||||
validate: validateBlockName,
|
||||
filter: (block: string) => {
|
||||
return block.toLowerCase().trim();
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'title',
|
||||
default: ({ block }: { block: string }) => block
|
||||
},
|
||||
{
|
||||
name: 'helpCategory',
|
||||
message: 'Choose a help category',
|
||||
default: 'HTML-CSS',
|
||||
type: 'list',
|
||||
choices: helpCategories
|
||||
},
|
||||
{
|
||||
name: 'blockType',
|
||||
message: 'Choose a block type',
|
||||
default: BlockTypes.lab,
|
||||
type: 'list',
|
||||
choices: Object.values(BlockTypes),
|
||||
when: (answers: CreateProjectArgs) =>
|
||||
chapterBasedSuperBlocks.includes(answers.superBlock)
|
||||
},
|
||||
{
|
||||
name: 'blockLayout',
|
||||
message: 'Choose a block layout',
|
||||
void getAllBlocks()
|
||||
.then(existingBlocks =>
|
||||
prompt([
|
||||
{
|
||||
name: 'superBlock',
|
||||
message: 'Which certification does this belong to?',
|
||||
default: SuperBlocks.FullStackDeveloper,
|
||||
type: 'list',
|
||||
choices: Object.values(SuperBlocks)
|
||||
},
|
||||
{
|
||||
name: 'block',
|
||||
message: 'What is the dashed name (in kebab-case) for this project?',
|
||||
validate: (block: string) => validateBlockName(block, existingBlocks),
|
||||
filter: (block: string) => {
|
||||
return block.toLowerCase().trim();
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'title',
|
||||
default: ({ block }: { block: string }) => block
|
||||
},
|
||||
{
|
||||
name: 'helpCategory',
|
||||
message: 'Choose a help category',
|
||||
default: 'HTML-CSS',
|
||||
type: 'list',
|
||||
choices: helpCategories
|
||||
},
|
||||
{
|
||||
name: 'blockType',
|
||||
message: 'Choose a block type',
|
||||
default: BlockTypes.lab,
|
||||
type: 'list',
|
||||
choices: Object.values(BlockTypes),
|
||||
when: (answers: CreateProjectArgs) =>
|
||||
chapterBasedSuperBlocks.includes(answers.superBlock)
|
||||
},
|
||||
{
|
||||
name: 'blockLayout',
|
||||
message: 'Choose a block layout',
|
||||
|
||||
default: (answers: { blockType: BlockTypes }) =>
|
||||
answers.blockType == BlockTypes.quiz
|
||||
? BlockLayouts.Link
|
||||
: BlockLayouts.ChallengeList,
|
||||
type: 'list',
|
||||
choices: Object.values(BlockLayouts),
|
||||
when: (answers: CreateProjectArgs) =>
|
||||
chapterBasedSuperBlocks.includes(answers.superBlock)
|
||||
},
|
||||
{
|
||||
name: 'questionCount',
|
||||
message: 'Choose a question count',
|
||||
default: 20,
|
||||
type: 'list',
|
||||
choices: [10, 20],
|
||||
when: (answers: CreateProjectArgs) => answers.blockType === BlockTypes.quiz
|
||||
},
|
||||
{
|
||||
name: 'chapter',
|
||||
message: 'What chapter should this project go in?',
|
||||
default: 'html',
|
||||
type: 'list',
|
||||
choices: async (answers: CreateProjectArgs) => {
|
||||
const chapters = await getChapters(answers.superBlock);
|
||||
return chapters.map(x => x.dashedName);
|
||||
},
|
||||
when: (answers: CreateProjectArgs) =>
|
||||
chapterBasedSuperBlocks.includes(answers.superBlock)
|
||||
},
|
||||
{
|
||||
name: 'module',
|
||||
message: 'What module should this project go in?',
|
||||
default: 'html',
|
||||
type: 'list',
|
||||
choices: async (answers: CreateProjectArgs) => {
|
||||
const modules = await getModules(answers.superBlock, answers.chapter!);
|
||||
return modules!.map(x => x.dashedName);
|
||||
},
|
||||
when: (answers: CreateProjectArgs) =>
|
||||
chapterBasedSuperBlocks.includes(answers.superBlock)
|
||||
},
|
||||
{
|
||||
name: 'position',
|
||||
message: 'At which position does this appear in the module?',
|
||||
default: 1,
|
||||
validate: (position: string) => {
|
||||
return parseInt(position, 10) > 0
|
||||
? true
|
||||
: 'Position must be an number greater than zero.';
|
||||
},
|
||||
when: (answers: CreateProjectArgs) =>
|
||||
chapterBasedSuperBlocks.includes(answers.superBlock),
|
||||
filter: (position: string) => {
|
||||
return parseInt(position, 10);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'order',
|
||||
message: 'Which position does this appear in the certificate?',
|
||||
default: 42,
|
||||
validate: (order: string) => {
|
||||
return parseInt(order, 10) > 0
|
||||
? true
|
||||
: 'Order must be an number greater than zero.';
|
||||
},
|
||||
when: (answers: CreateProjectArgs) =>
|
||||
!chapterBasedSuperBlocks.includes(answers.superBlock),
|
||||
filter: (order: string) => {
|
||||
return parseInt(order, 10);
|
||||
}
|
||||
}
|
||||
])
|
||||
.then(
|
||||
async ({
|
||||
superBlock,
|
||||
block,
|
||||
title,
|
||||
helpCategory,
|
||||
blockType,
|
||||
blockLayout,
|
||||
questionCount,
|
||||
chapter,
|
||||
module,
|
||||
position,
|
||||
order
|
||||
}: CreateProjectArgs) =>
|
||||
await createProject({
|
||||
default: (answers: { blockType: BlockTypes }) =>
|
||||
answers.blockType == BlockTypes.quiz
|
||||
? BlockLayouts.Link
|
||||
: BlockLayouts.ChallengeList,
|
||||
type: 'list',
|
||||
choices: Object.values(BlockLayouts),
|
||||
when: (answers: CreateProjectArgs) =>
|
||||
chapterBasedSuperBlocks.includes(answers.superBlock)
|
||||
},
|
||||
{
|
||||
name: 'questionCount',
|
||||
message: 'Choose a question count',
|
||||
default: 20,
|
||||
type: 'list',
|
||||
choices: [10, 20],
|
||||
when: (answers: CreateProjectArgs) =>
|
||||
answers.blockType === BlockTypes.quiz
|
||||
},
|
||||
{
|
||||
name: 'chapter',
|
||||
message: 'What chapter should this project go in?',
|
||||
default: 'html',
|
||||
type: 'list',
|
||||
choices: async (answers: CreateProjectArgs) => {
|
||||
const chapters = await getChapters(answers.superBlock);
|
||||
return chapters.map(x => x.dashedName);
|
||||
},
|
||||
when: (answers: CreateProjectArgs) =>
|
||||
chapterBasedSuperBlocks.includes(answers.superBlock)
|
||||
},
|
||||
{
|
||||
name: 'module',
|
||||
message: 'What module should this project go in?',
|
||||
default: 'html',
|
||||
type: 'list',
|
||||
choices: async (answers: CreateProjectArgs) => {
|
||||
const modules = await getModules(
|
||||
answers.superBlock,
|
||||
answers.chapter!
|
||||
);
|
||||
return modules!.map(x => x.dashedName);
|
||||
},
|
||||
when: (answers: CreateProjectArgs) =>
|
||||
chapterBasedSuperBlocks.includes(answers.superBlock)
|
||||
},
|
||||
{
|
||||
name: 'position',
|
||||
message: 'At which position does this appear in the module?',
|
||||
default: 1,
|
||||
validate: (position: string) => {
|
||||
return parseInt(position, 10) > 0
|
||||
? true
|
||||
: 'Position must be an number greater than zero.';
|
||||
},
|
||||
when: (answers: CreateProjectArgs) =>
|
||||
chapterBasedSuperBlocks.includes(answers.superBlock),
|
||||
filter: (position: string) => {
|
||||
return parseInt(position, 10);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'order',
|
||||
message: 'Which position does this appear in the certificate?',
|
||||
default: 42,
|
||||
validate: (order: string) => {
|
||||
return parseInt(order, 10) > 0
|
||||
? true
|
||||
: 'Order must be an number greater than zero.';
|
||||
},
|
||||
when: (answers: CreateProjectArgs) =>
|
||||
!chapterBasedSuperBlocks.includes(answers.superBlock),
|
||||
filter: (order: string) => {
|
||||
return parseInt(order, 10);
|
||||
}
|
||||
}
|
||||
]).then(
|
||||
async ({
|
||||
superBlock,
|
||||
block,
|
||||
title,
|
||||
helpCategory,
|
||||
blockType,
|
||||
blockLayout,
|
||||
questionCount,
|
||||
title,
|
||||
chapter,
|
||||
module,
|
||||
position,
|
||||
order
|
||||
})
|
||||
}: CreateProjectArgs) =>
|
||||
await createProject({
|
||||
superBlock,
|
||||
block,
|
||||
helpCategory,
|
||||
blockType,
|
||||
blockLayout,
|
||||
questionCount,
|
||||
title,
|
||||
chapter,
|
||||
module,
|
||||
position,
|
||||
order
|
||||
})
|
||||
)
|
||||
)
|
||||
.then(() =>
|
||||
console.log(
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
writeBlockStructure
|
||||
} from '../../curriculum/file-handler';
|
||||
import { superBlockToFilename } from '../../curriculum/build-curriculum';
|
||||
import { createQuizFile, validateBlockName } from './utils';
|
||||
import { createQuizFile, getAllBlocks, validateBlockName } from './utils';
|
||||
import { getBaseMeta } from './helpers/get-base-meta';
|
||||
import { createIntroMD } from './helpers/create-intro';
|
||||
import { updateSimpleSuperblockStructure } from './helpers/create-project';
|
||||
@@ -145,41 +145,44 @@ function withTrace<Args extends unknown[], Result>(
|
||||
});
|
||||
}
|
||||
|
||||
void prompt([
|
||||
{
|
||||
name: 'superBlock',
|
||||
message: 'Which certification does this belong to?',
|
||||
default: SuperBlocks.FullStackDeveloper,
|
||||
type: 'list',
|
||||
choices: Object.values(SuperBlocks)
|
||||
},
|
||||
{
|
||||
name: 'block',
|
||||
message: 'What is the dashed name (in kebab-case) for this quiz?',
|
||||
validate: validateBlockName,
|
||||
filter: (block: string) => {
|
||||
return block.toLowerCase().trim();
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'title',
|
||||
default: ({ block }: { block: string }) => block
|
||||
},
|
||||
{
|
||||
name: 'helpCategory',
|
||||
message: 'Choose a help category',
|
||||
default: 'HTML-CSS',
|
||||
type: 'list',
|
||||
choices: helpCategories
|
||||
},
|
||||
{
|
||||
name: 'questionCount',
|
||||
message: 'Should this quiz have either ten or twenty questions?',
|
||||
default: 20,
|
||||
type: 'list',
|
||||
choices: [20, 10]
|
||||
}
|
||||
])
|
||||
void getAllBlocks()
|
||||
.then(existingBlocks =>
|
||||
prompt([
|
||||
{
|
||||
name: 'superBlock',
|
||||
message: 'Which certification does this belong to?',
|
||||
default: SuperBlocks.FullStackDeveloper,
|
||||
type: 'list',
|
||||
choices: Object.values(SuperBlocks)
|
||||
},
|
||||
{
|
||||
name: 'block',
|
||||
message: 'What is the dashed name (in kebab-case) for this quiz?',
|
||||
validate: (block: string) => validateBlockName(block, existingBlocks),
|
||||
filter: (block: string) => {
|
||||
return block.toLowerCase().trim();
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'title',
|
||||
default: ({ block }: { block: string }) => block
|
||||
},
|
||||
{
|
||||
name: 'helpCategory',
|
||||
message: 'Choose a help category',
|
||||
default: 'HTML-CSS',
|
||||
type: 'list',
|
||||
choices: helpCategories
|
||||
},
|
||||
{
|
||||
name: 'questionCount',
|
||||
message: 'Should this quiz have either ten or twenty questions?',
|
||||
default: 20,
|
||||
type: 'list',
|
||||
choices: [20, 10]
|
||||
}
|
||||
])
|
||||
)
|
||||
.then(
|
||||
async ({
|
||||
superBlock,
|
||||
|
||||
@@ -98,24 +98,29 @@ describe('Challenge utils helper scripts', () => {
|
||||
describe('createProject util', () => {
|
||||
it('should allow alphanumerical names with trailing whitespace', () => {
|
||||
expect(
|
||||
validateBlockName('learn-callbacks-by-creating-a-bookshelf ')
|
||||
validateBlockName('learn-callbacks-by-creating-a-bookshelf ', [])
|
||||
).toBe(true);
|
||||
});
|
||||
it('should allow alphanumerical names with no trailing whitespace', () => {
|
||||
expect(validateBlockName('learn-callbacks-by-creating-a-bookshelf')).toBe(
|
||||
true
|
||||
);
|
||||
expect(
|
||||
validateBlockName('learn-callbacks-by-creating-a-bookshelf', [])
|
||||
).toBe(true);
|
||||
});
|
||||
it('should not allow non-kebab case names', () => {
|
||||
expect(validateBlockName('learnCallbacksBetter')).toBe(
|
||||
expect(validateBlockName('learnCallbacksBetter', [])).toBe(
|
||||
'please use alphanumerical characters and kebab case'
|
||||
);
|
||||
});
|
||||
it('should not allow white space names', () => {
|
||||
expect(validateBlockName(' ')).toBe('please enter a dashed name');
|
||||
expect(validateBlockName(' ', [])).toBe('please enter a dashed name');
|
||||
});
|
||||
it('should not allow empty names', () => {
|
||||
expect(validateBlockName('')).toBe('please enter a dashed name');
|
||||
expect(validateBlockName('', [])).toBe('please enter a dashed name');
|
||||
});
|
||||
it('should not allow names that already exist', () => {
|
||||
expect(validateBlockName('name', ['name'])).toBe(
|
||||
'a block with this name already exists'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -2,7 +2,10 @@ import fs from 'fs';
|
||||
import path from 'path';
|
||||
import ObjectID from 'bson-objectid';
|
||||
import matter from 'gray-matter';
|
||||
import { uniq } from 'lodash';
|
||||
|
||||
import { challengeTypes } from '../../shared/config/challenge-types';
|
||||
import { parseCurriculumStructure } from '../../curriculum/build-curriculum';
|
||||
import { parseMDSync } from '../challenge-parser/parser';
|
||||
import { getMetaData, updateMetaData } from './helpers/project-metadata';
|
||||
import { getProjectPath } from './helpers/get-project-info';
|
||||
@@ -28,6 +31,19 @@ interface QuizOptions {
|
||||
questionCount: number;
|
||||
}
|
||||
|
||||
export async function getAllBlocks() {
|
||||
const { fullSuperblockList } = (await parseCurriculumStructure()) as {
|
||||
fullSuperblockList: {
|
||||
blocks: { dashedName: string }[];
|
||||
}[];
|
||||
};
|
||||
const existingBlocks = fullSuperblockList.flatMap(({ blocks }) =>
|
||||
blocks.map(({ dashedName }) => dashedName)
|
||||
);
|
||||
|
||||
return uniq(existingBlocks);
|
||||
}
|
||||
|
||||
const createStepFile = ({
|
||||
stepNum,
|
||||
challengeType,
|
||||
@@ -259,7 +275,13 @@ const getChallenge = (challengeId: string): Challenge => {
|
||||
return challenge;
|
||||
};
|
||||
|
||||
const validateBlockName = (block: string): boolean | string => {
|
||||
const validateBlockName = (
|
||||
block: string,
|
||||
existingBlocks: string[]
|
||||
): true | string => {
|
||||
if (existingBlocks.includes(block.trim())) {
|
||||
return 'a block with this name already exists';
|
||||
}
|
||||
if (!block.trim().length) {
|
||||
return 'please enter a dashed name';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user