mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-05-28 18:26:54 +00:00
refactor(tools) : migrate inquirer prompts (#66139)
Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com> Co-authored-by: Sem Bauke <sem@freecodecamp.org>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
import { prompt } from 'inquirer';
|
||||
import { select, input, number } from '@inquirer/prompts';
|
||||
import { format } from 'prettier';
|
||||
import { ObjectId } from 'bson';
|
||||
|
||||
@@ -335,231 +335,276 @@ function getBlockPrefix(
|
||||
}
|
||||
|
||||
void getAllBlocks()
|
||||
.then(existingBlocks =>
|
||||
prompt([
|
||||
{
|
||||
name: 'superBlock',
|
||||
message: 'Which certification does this belong to?',
|
||||
default: SuperBlocks.A2English,
|
||||
type: 'list',
|
||||
choices: Object.values(languageSuperBlocks)
|
||||
},
|
||||
{
|
||||
name: 'blockLabel',
|
||||
message: 'Choose a block label',
|
||||
default: BlockLabel.learn,
|
||||
type: 'list',
|
||||
choices: Object.values(BlockLabel)
|
||||
},
|
||||
{
|
||||
name: 'block',
|
||||
message: (answers: CreateBlockArgs) => {
|
||||
const prefix = getBlockPrefix(answers.superBlock, answers.blockLabel);
|
||||
return prefix
|
||||
? `Complete the dashed name after the prefix below.\nPrefix: ${prefix}`
|
||||
: 'What is the dashed name (in kebab-case) for this block?';
|
||||
},
|
||||
validate: (block: string, answers: CreateBlockArgs) => {
|
||||
const prefix = getBlockPrefix(answers.superBlock, answers.blockLabel);
|
||||
.then(async existingBlocks => {
|
||||
const superBlock = await select<SuperBlocks>({
|
||||
message: 'Which certification does it this belong to?',
|
||||
default: SuperBlocks.A2English,
|
||||
choices: Object.values(languageSuperBlocks).map(value => ({
|
||||
name: value,
|
||||
value
|
||||
}))
|
||||
});
|
||||
|
||||
if (prefix) {
|
||||
const uniquePart = block.slice(prefix.length);
|
||||
const blockLabel = await select<BlockLabel>({
|
||||
message: 'Choose a block label',
|
||||
default: BlockLabel.learn,
|
||||
choices: Object.values(BlockLabel).map(value => ({
|
||||
name: value,
|
||||
value
|
||||
}))
|
||||
});
|
||||
|
||||
// Check if user accidentally included block label at the end
|
||||
if (answers.blockLabel) {
|
||||
// Exclude exam as it is an exception
|
||||
const blockLabelValues = Object.values(BlockLabel).filter(
|
||||
label => label !== BlockLabel.exam
|
||||
);
|
||||
const prefix = getBlockPrefix(superBlock, blockLabel);
|
||||
|
||||
const endsWithLabel = blockLabelValues.some(label =>
|
||||
uniquePart.endsWith(`-${label}`)
|
||||
);
|
||||
const rawBlock = await input({
|
||||
message: prefix
|
||||
? `Complete the dashed name after the prefix below.\nPrefix: ${prefix}`
|
||||
: 'What is the dashed name (in kebab-case) for this block?',
|
||||
|
||||
if (endsWithLabel) {
|
||||
return `Block name should not end with a block label (e.g., '-${answers.blockLabel}'). The label is already in the prefix.`;
|
||||
}
|
||||
}
|
||||
validate: (value: string) => {
|
||||
if (prefix) {
|
||||
const uniquePart = value.slice(prefix.length);
|
||||
|
||||
const blockLabelValues = Object.values(BlockLabel).filter(
|
||||
label => label !== BlockLabel.exam
|
||||
);
|
||||
|
||||
const endsWithLabel = blockLabelValues.some(label =>
|
||||
uniquePart.endsWith(`-${label}`)
|
||||
);
|
||||
|
||||
if (endsWithLabel) {
|
||||
return `Block name should not end with a block label (e.g., '-${blockLabel}'). The label is already in the prefix.`;
|
||||
}
|
||||
|
||||
return validateBlockName(block, existingBlocks);
|
||||
},
|
||||
filter: (block: string, answers: CreateBlockArgs) => {
|
||||
const prefix = getBlockPrefix(answers.superBlock, answers.blockLabel);
|
||||
const normalized = block.toLowerCase().trim();
|
||||
|
||||
if (prefix) {
|
||||
// Strip prefix if already present (happens on re-validation), then re-add it
|
||||
const withoutPrefix = normalized.startsWith(prefix)
|
||||
? normalized.slice(prefix.length)
|
||||
: normalized;
|
||||
return prefix + withoutPrefix;
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'title',
|
||||
default: ({ block }: { block: string }) => block
|
||||
},
|
||||
{
|
||||
name: 'helpCategory',
|
||||
message: 'Choose a help category',
|
||||
default: 'English',
|
||||
type: 'list',
|
||||
choices: helpCategories
|
||||
},
|
||||
{
|
||||
name: 'blockLayout',
|
||||
|
||||
return validateBlockName(value, existingBlocks);
|
||||
}
|
||||
});
|
||||
|
||||
const block = prefix
|
||||
? `${prefix}${rawBlock.toLowerCase().trim()}`
|
||||
: rawBlock.toLowerCase().trim();
|
||||
|
||||
const title = await input({
|
||||
message: 'Enter a title for this block:',
|
||||
default: block
|
||||
});
|
||||
|
||||
const helpCategory = await select<string>({
|
||||
message: 'Choose a help category',
|
||||
default: 'English',
|
||||
choices: helpCategories.map(value => ({
|
||||
name: value,
|
||||
value
|
||||
}))
|
||||
});
|
||||
|
||||
let blockLayout: string | undefined;
|
||||
|
||||
if (
|
||||
chapterBasedSuperBlocks.includes(superBlock) &&
|
||||
blockLabel !== BlockLabel.quiz
|
||||
) {
|
||||
blockLayout = await select<BlockLayouts>({
|
||||
message: 'Choose a block layout',
|
||||
default: BlockLayouts.DialogueGrid,
|
||||
type: 'list',
|
||||
choices: Object.values(BlockLayouts),
|
||||
when: (answers: CreateBlockArgs) =>
|
||||
chapterBasedSuperBlocks.includes(answers.superBlock) &&
|
||||
answers.blockLabel !== BlockLabel.quiz
|
||||
},
|
||||
{
|
||||
name: 'questionCount',
|
||||
choices: Object.values(BlockLayouts).map(value => ({
|
||||
name: value,
|
||||
value
|
||||
}))
|
||||
});
|
||||
}
|
||||
|
||||
let questionCount: number | undefined;
|
||||
|
||||
if (blockLabel === BlockLabel.quiz) {
|
||||
questionCount = await select<number>({
|
||||
message: 'Choose a question count',
|
||||
default: 20,
|
||||
type: 'list',
|
||||
choices: [10, 20],
|
||||
when: (answers: CreateBlockArgs) =>
|
||||
answers.blockLabel === BlockLabel.quiz
|
||||
},
|
||||
{
|
||||
name: 'chapter',
|
||||
choices: [
|
||||
{ value: 10, name: '10' },
|
||||
{ value: 20, name: '20' }
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
let chapter: string | undefined;
|
||||
|
||||
if (chapterBasedSuperBlocks.includes(superBlock)) {
|
||||
const superblockFilename = (
|
||||
superBlockToFilename as Record<SuperBlocks, string>
|
||||
)[superBlock];
|
||||
|
||||
const structure = getSuperblockStructure(superblockFilename) as {
|
||||
chapters: {
|
||||
dashedName: string;
|
||||
modules: { dashedName: string; blocks: string[] }[];
|
||||
}[];
|
||||
};
|
||||
|
||||
chapter = await select({
|
||||
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),
|
||||
'-- Create new chapter --'
|
||||
];
|
||||
},
|
||||
when: (answers: CreateBlockArgs) =>
|
||||
chapterBasedSuperBlocks.includes(answers.superBlock)
|
||||
},
|
||||
{
|
||||
name: 'newChapterName',
|
||||
choices: [
|
||||
...structure.chapters.map(ch => ({
|
||||
value: ch.dashedName,
|
||||
name: ch.dashedName
|
||||
})),
|
||||
{
|
||||
value: '-- Create new chapter --',
|
||||
name: '-- Create new chapter --'
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
let newChapterName: string | undefined;
|
||||
|
||||
if (
|
||||
chapterBasedSuperBlocks.includes(superBlock) &&
|
||||
chapter === '-- Create new chapter --'
|
||||
) {
|
||||
const rawName = await input({
|
||||
message: 'Enter the dashed name for the new chapter (in kebab-case):',
|
||||
validate: (name: string) => {
|
||||
if (!name || name.trim() === '') {
|
||||
return 'Chapter name cannot be empty.';
|
||||
}
|
||||
|
||||
if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(name.trim())) {
|
||||
return 'Chapter name must be in kebab-case (e.g., "chapter-one").';
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
filter: (name: string) => name.toLowerCase().trim(),
|
||||
when: (answers: CreateBlockArgs) =>
|
||||
chapterBasedSuperBlocks.includes(answers.superBlock) &&
|
||||
answers.chapter === '-- Create new chapter --'
|
||||
},
|
||||
{
|
||||
name: 'newChapterTitle',
|
||||
}
|
||||
});
|
||||
|
||||
newChapterName = rawName.toLowerCase().trim();
|
||||
}
|
||||
|
||||
let newChapterTitle: string | undefined;
|
||||
|
||||
if (
|
||||
chapterBasedSuperBlocks.includes(superBlock) &&
|
||||
chapter === '-- Create new chapter --'
|
||||
) {
|
||||
newChapterTitle = await input({
|
||||
message: 'Enter the title for the new chapter:',
|
||||
default: ({ newChapterName }: { newChapterName: string }) =>
|
||||
newChapterName,
|
||||
default: newChapterName,
|
||||
validate: (title: string) => {
|
||||
if (!title || title.trim() === '') {
|
||||
return 'Chapter title cannot be empty.';
|
||||
}
|
||||
return true;
|
||||
},
|
||||
when: (answers: CreateBlockArgs) =>
|
||||
chapterBasedSuperBlocks.includes(answers.superBlock) &&
|
||||
answers.chapter === '-- Create new chapter --'
|
||||
},
|
||||
{
|
||||
name: 'module',
|
||||
message: 'What module should this language block go in?',
|
||||
type: 'list',
|
||||
choices: (answers: CreateBlockArgs) => {
|
||||
if (answers.chapter === '-- Create new chapter --') {
|
||||
return ['-- Create new module --'];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let module: string | undefined;
|
||||
|
||||
if (chapterBasedSuperBlocks.includes(superBlock)) {
|
||||
const superblockFilename = (
|
||||
superBlockToFilename as Record<SuperBlocks, string>
|
||||
)[superBlock];
|
||||
|
||||
const structure = getSuperblockStructure(superblockFilename) as {
|
||||
chapters: {
|
||||
dashedName: string;
|
||||
modules: { dashedName: string; blocks: string[] }[];
|
||||
}[];
|
||||
};
|
||||
|
||||
let moduleChoices: { value: string; name: string }[];
|
||||
|
||||
if (chapter === '-- Create new chapter --') {
|
||||
moduleChoices = [
|
||||
{
|
||||
value: '-- Create new module --',
|
||||
name: '-- Create new module --'
|
||||
}
|
||||
const superblockFilename = (
|
||||
superBlockToFilename as Record<SuperBlocks, string>
|
||||
)[answers.superBlock];
|
||||
const structure = getSuperblockStructure(superblockFilename) as {
|
||||
chapters: {
|
||||
dashedName: string;
|
||||
modules: { dashedName: string; blocks: string[] }[];
|
||||
}[];
|
||||
};
|
||||
const existingModules =
|
||||
structure.chapters
|
||||
.find(chapter => chapter.dashedName === answers.chapter)
|
||||
?.modules.map(module => module.dashedName) ?? [];
|
||||
return [...existingModules, '-- Create new module --'];
|
||||
},
|
||||
when: (answers: CreateBlockArgs) =>
|
||||
chapterBasedSuperBlocks.includes(answers.superBlock)
|
||||
},
|
||||
{
|
||||
name: 'newModuleName',
|
||||
];
|
||||
} else {
|
||||
const existingModules =
|
||||
structure.chapters
|
||||
.find(ch => ch.dashedName === chapter)
|
||||
?.modules.map(m => m.dashedName) ?? [];
|
||||
|
||||
moduleChoices = [
|
||||
...existingModules.map(m => ({
|
||||
value: m,
|
||||
name: m
|
||||
})),
|
||||
{
|
||||
value: '-- Create new module --',
|
||||
name: '-- Create new module --'
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
module = await select({
|
||||
message: 'What module should this language block go in?',
|
||||
choices: moduleChoices
|
||||
});
|
||||
}
|
||||
|
||||
let newModuleName: string | undefined;
|
||||
|
||||
if (
|
||||
chapterBasedSuperBlocks.includes(superBlock) &&
|
||||
module === '-- Create new module --'
|
||||
) {
|
||||
const rawName = await input({
|
||||
message: 'Enter the dashed name for the new module (in kebab-case):',
|
||||
validate: (name: string) => {
|
||||
if (!name || name.trim() === '') {
|
||||
return 'Module name cannot be empty.';
|
||||
}
|
||||
|
||||
if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(name.trim())) {
|
||||
return 'Module name must be in kebab-case (e.g., "module-one").';
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
filter: (name: string) => name.toLowerCase().trim(),
|
||||
when: (answers: CreateBlockArgs) =>
|
||||
chapterBasedSuperBlocks.includes(answers.superBlock) &&
|
||||
answers.module === '-- Create new module --'
|
||||
},
|
||||
{
|
||||
name: 'newModuleTitle',
|
||||
}
|
||||
});
|
||||
|
||||
newModuleName = rawName.toLowerCase().trim();
|
||||
}
|
||||
|
||||
let newModuleTitle: string | undefined;
|
||||
|
||||
if (
|
||||
chapterBasedSuperBlocks.includes(superBlock) &&
|
||||
module === '-- Create new module --'
|
||||
) {
|
||||
newModuleTitle = await input({
|
||||
message: 'Enter the title for the new module:',
|
||||
default: ({ newModuleName }: { newModuleName: string }) =>
|
||||
newModuleName,
|
||||
default: newModuleName,
|
||||
validate: (title: string) => {
|
||||
if (!title || title.trim() === '') {
|
||||
return 'Module title cannot be empty.';
|
||||
}
|
||||
return true;
|
||||
},
|
||||
when: (answers: CreateBlockArgs) =>
|
||||
chapterBasedSuperBlocks.includes(answers.superBlock) &&
|
||||
answers.module === '-- Create new module --'
|
||||
},
|
||||
{
|
||||
name: 'position',
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let position: number | undefined;
|
||||
|
||||
if (chapterBasedSuperBlocks.includes(superBlock)) {
|
||||
position = await number({
|
||||
message: 'At which position does this new block 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);
|
||||
validate: (value: number | undefined) => {
|
||||
if (!value || value <= 0) {
|
||||
return 'Position must be a number greater than zero.';
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
])
|
||||
)
|
||||
.then(
|
||||
async ({
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
superBlock,
|
||||
block,
|
||||
helpCategory,
|
||||
@@ -574,33 +619,57 @@ void getAllBlocks()
|
||||
blockLabel,
|
||||
blockLayout,
|
||||
questionCount
|
||||
}: CreateBlockArgs) => {
|
||||
const resolvedChapter =
|
||||
chapter === '-- Create new chapter --' ? newChapterName : chapter;
|
||||
const resolvedModule =
|
||||
module === '-- Create new module --' ? newModuleName : module;
|
||||
};
|
||||
})
|
||||
.then(async (answers: CreateBlockArgs) => {
|
||||
const {
|
||||
superBlock,
|
||||
block,
|
||||
helpCategory,
|
||||
title,
|
||||
chapter,
|
||||
module,
|
||||
newChapterName,
|
||||
newModuleTitle,
|
||||
newChapterTitle,
|
||||
newModuleName,
|
||||
position,
|
||||
blockLabel,
|
||||
blockLayout,
|
||||
questionCount
|
||||
} = answers;
|
||||
|
||||
// Only pass chapter title if we're creating a new chapter
|
||||
const chapterTitle =
|
||||
chapter === '-- Create new chapter --' ? newChapterTitle : undefined;
|
||||
// Only pass module title if we're creating a new module
|
||||
const moduleTitle =
|
||||
module === '-- Create new module --' ? newModuleTitle : undefined;
|
||||
const resolvedChapter =
|
||||
chapter === '-- Create new chapter --' ? newChapterName : chapter;
|
||||
const resolvedModule =
|
||||
module === '-- Create new module --' ? newModuleName : module;
|
||||
|
||||
await createLanguageBlock(
|
||||
superBlock,
|
||||
block,
|
||||
helpCategory,
|
||||
title,
|
||||
resolvedChapter,
|
||||
resolvedModule,
|
||||
chapterTitle,
|
||||
moduleTitle,
|
||||
position,
|
||||
blockLabel,
|
||||
blockLayout,
|
||||
questionCount
|
||||
);
|
||||
}
|
||||
)
|
||||
.then(() => console.log('All set. Refresh the page to see the changes.'));
|
||||
// Only pass chapter title if we're creating a new chapter
|
||||
const chapterTitle =
|
||||
chapter === '-- Create new chapter --' ? newChapterTitle : undefined;
|
||||
// Only pass module title if we're creating a new module
|
||||
const moduleTitle =
|
||||
module === '-- Create new module --' ? newModuleTitle : undefined;
|
||||
|
||||
await createLanguageBlock(
|
||||
superBlock,
|
||||
block,
|
||||
helpCategory,
|
||||
title,
|
||||
resolvedChapter,
|
||||
resolvedModule,
|
||||
chapterTitle,
|
||||
moduleTitle,
|
||||
position,
|
||||
blockLabel,
|
||||
blockLayout,
|
||||
questionCount
|
||||
);
|
||||
})
|
||||
.then(() => console.log('All set. Refresh the page to see the changes.'))
|
||||
.catch((err: unknown) =>
|
||||
console.error(
|
||||
'Error creating language block:',
|
||||
err instanceof Error ? err.message : String(err)
|
||||
)
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
import { prompt } from 'inquirer';
|
||||
import { select, input, number } from '@inquirer/prompts';
|
||||
import { format } from 'prettier';
|
||||
import { ObjectId } from 'bson';
|
||||
|
||||
@@ -267,149 +267,136 @@ async function getModules(superBlock: string, chapterName: string) {
|
||||
}
|
||||
|
||||
void getAllBlocks()
|
||||
.then(existingBlocks =>
|
||||
prompt([
|
||||
{
|
||||
name: 'superBlock',
|
||||
message: 'Which certification does this belong to?',
|
||||
default: SuperBlocks.RespWebDesignV9,
|
||||
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: 'blockLabel',
|
||||
.then(async existingBlocks => {
|
||||
const superBlock = await select<SuperBlocks>({
|
||||
message: 'Which certification does this belong to?',
|
||||
default: SuperBlocks.RespWebDesignV9,
|
||||
choices: Object.values(SuperBlocks).map(value => ({
|
||||
name: value,
|
||||
value
|
||||
}))
|
||||
});
|
||||
|
||||
const rawBlock = await input({
|
||||
message: 'What is the dashed name (in kebab-case) for this project?',
|
||||
validate: (value: string) => validateBlockName(value, existingBlocks)
|
||||
});
|
||||
|
||||
const block = rawBlock.toLowerCase().trim();
|
||||
|
||||
const title = await input({
|
||||
message: 'Enter a title for this project:',
|
||||
default: block
|
||||
});
|
||||
|
||||
const helpCategory = await select<string>({
|
||||
message: 'Choose a help category',
|
||||
default: 'HTML-CSS',
|
||||
choices: helpCategories.map(value => ({
|
||||
name: value,
|
||||
value
|
||||
}))
|
||||
});
|
||||
|
||||
let blockLabel: BlockLabel | undefined;
|
||||
let blockLayout: BlockLayouts | undefined;
|
||||
let questionCount: number | undefined;
|
||||
let chapter: string | undefined;
|
||||
let module: string | undefined;
|
||||
let position: number | undefined;
|
||||
let order: number | undefined;
|
||||
|
||||
if (chapterBasedSuperBlocks.includes(superBlock)) {
|
||||
blockLabel = await select<BlockLabel>({
|
||||
message: 'Choose a block label',
|
||||
default: BlockLabel.lab,
|
||||
type: 'list',
|
||||
choices: Object.values(BlockLabel),
|
||||
when: (answers: CreateProjectArgs) =>
|
||||
chapterBasedSuperBlocks.includes(answers.superBlock)
|
||||
},
|
||||
{
|
||||
name: 'blockLayout',
|
||||
message: 'Choose a block layout',
|
||||
choices: Object.values(BlockLabel).map(value => ({
|
||||
name: value,
|
||||
value
|
||||
}))
|
||||
});
|
||||
|
||||
default: (answers: { blockLabel: BlockLabel }) =>
|
||||
answers.blockLabel == BlockLabel.quiz
|
||||
blockLayout = await select<BlockLayouts>({
|
||||
message: 'Choose a block layout',
|
||||
default:
|
||||
blockLabel === BlockLabel.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.blockLabel === BlockLabel.quiz
|
||||
},
|
||||
{
|
||||
name: 'chapter',
|
||||
choices: Object.values(BlockLayouts).map(value => ({
|
||||
name: value,
|
||||
value
|
||||
}))
|
||||
});
|
||||
|
||||
if (blockLabel === BlockLabel.quiz) {
|
||||
questionCount = await select<number>({
|
||||
message: 'Choose a question count',
|
||||
default: 20,
|
||||
choices: [
|
||||
{ name: '10', value: 10 },
|
||||
{ name: '20', value: 20 }
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
const chapters = await getChapters(superBlock);
|
||||
chapter = await select({
|
||||
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',
|
||||
choices: chapters.map(x => ({
|
||||
name: x.dashedName,
|
||||
value: x.dashedName
|
||||
}))
|
||||
});
|
||||
|
||||
const modules = await getModules(superBlock, chapter);
|
||||
module = await select({
|
||||
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',
|
||||
choices: modules!.map(x => ({
|
||||
name: x.dashedName,
|
||||
value: x.dashedName
|
||||
}))
|
||||
});
|
||||
|
||||
position = await number({
|
||||
message: 'At which position does this appear in the module?',
|
||||
default: 1,
|
||||
validate: (position: string) => {
|
||||
return parseInt(position, 10) > 0
|
||||
validate: (value: number | undefined) =>
|
||||
value && value > 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',
|
||||
: 'Position must be a number greater than zero.'
|
||||
});
|
||||
} else {
|
||||
order = await number({
|
||||
message: 'Which position does this appear in the certificate?',
|
||||
default: 42,
|
||||
validate: (order: string) => {
|
||||
return parseInt(order, 10) > 0
|
||||
validate: (value: number | undefined) =>
|
||||
value && value > 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,
|
||||
blockLabel,
|
||||
blockLayout,
|
||||
questionCount,
|
||||
chapter,
|
||||
module,
|
||||
position,
|
||||
order
|
||||
}: CreateProjectArgs) =>
|
||||
await createProject({
|
||||
superBlock,
|
||||
block,
|
||||
helpCategory,
|
||||
blockLabel,
|
||||
blockLayout,
|
||||
questionCount,
|
||||
title,
|
||||
chapter,
|
||||
module,
|
||||
position,
|
||||
order
|
||||
})
|
||||
: 'Order must be a number greater than zero.'
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
superBlock,
|
||||
block,
|
||||
title,
|
||||
helpCategory,
|
||||
blockLabel,
|
||||
blockLayout,
|
||||
questionCount,
|
||||
chapter,
|
||||
module,
|
||||
position,
|
||||
order
|
||||
};
|
||||
})
|
||||
.then(async (answers: CreateProjectArgs) => {
|
||||
await createProject(answers);
|
||||
})
|
||||
.then(() => console.log('All set. Refresh the page to see the changes.'))
|
||||
.catch((err: unknown) =>
|
||||
console.error(
|
||||
'Error creating project:',
|
||||
err instanceof Error ? err.message : String(err)
|
||||
)
|
||||
)
|
||||
.then(() => console.log('All set. Refresh the page to see the changes.'));
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
import { prompt } from 'inquirer';
|
||||
import { select, input } from '@inquirer/prompts';
|
||||
import { format } from 'prettier';
|
||||
import { ObjectId } from 'bson';
|
||||
|
||||
@@ -32,21 +32,13 @@ type SuperBlockInfo = {
|
||||
|
||||
type IntroJson = Record<SuperBlocks, SuperBlockInfo>;
|
||||
|
||||
interface CreateQuizArgs {
|
||||
superBlock: SuperBlocks;
|
||||
block: string;
|
||||
helpCategory: string;
|
||||
title?: string;
|
||||
questionCount: number;
|
||||
}
|
||||
|
||||
async function createQuiz({
|
||||
superBlock,
|
||||
block,
|
||||
helpCategory,
|
||||
questionCount,
|
||||
title
|
||||
}: CreateQuizArgs) {
|
||||
async function createQuiz(
|
||||
superBlock: SuperBlocks,
|
||||
block: string,
|
||||
helpCategory: string,
|
||||
questionCount: number,
|
||||
title?: string
|
||||
) {
|
||||
if (!title) {
|
||||
title = block;
|
||||
}
|
||||
@@ -137,43 +129,53 @@ function withTrace<Args extends unknown[], Result>(
|
||||
});
|
||||
}
|
||||
|
||||
void getAllBlocks()
|
||||
.then(existingBlocks =>
|
||||
prompt([
|
||||
{
|
||||
name: 'superBlock',
|
||||
message: 'Which certification does this belong to?',
|
||||
default: SuperBlocks.RespWebDesignV9,
|
||||
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 (args: CreateQuizArgs) => await createQuiz(args))
|
||||
.then(() => console.log('All set. Restart the client to see the changes.'));
|
||||
void getAllBlocks().then(async existingBlocks => {
|
||||
const superBlock = await select<SuperBlocks>({
|
||||
message: 'Which certification does this belong to?',
|
||||
default: SuperBlocks.RespWebDesignV9,
|
||||
choices: Object.values(SuperBlocks).map(value => ({
|
||||
name: value,
|
||||
value
|
||||
}))
|
||||
});
|
||||
|
||||
const block = await input({
|
||||
message: 'What is the dashed name (in kebab-case) for this quiz?',
|
||||
validate: (block: string) => validateBlockName(block, existingBlocks)
|
||||
});
|
||||
|
||||
const transformedBlock = block.toLowerCase().trim();
|
||||
|
||||
const title = await input({
|
||||
message: 'What is the new name?',
|
||||
default: transformedBlock
|
||||
});
|
||||
|
||||
const helpCategory = await select<string>({
|
||||
message: 'Choose a help category',
|
||||
default: 'HTML-CSS',
|
||||
choices: helpCategories.map(value => ({
|
||||
name: value,
|
||||
value
|
||||
}))
|
||||
});
|
||||
|
||||
const questionCount = await select<number>({
|
||||
message: 'Should this quiz have either ten or twenty questions?',
|
||||
default: 20,
|
||||
choices: [
|
||||
{ name: '20 questions', value: 20 },
|
||||
{ name: '10 questions', value: 10 }
|
||||
]
|
||||
});
|
||||
|
||||
await createQuiz(
|
||||
superBlock,
|
||||
transformedBlock,
|
||||
helpCategory,
|
||||
questionCount,
|
||||
title
|
||||
);
|
||||
|
||||
console.log('All set. Refresh the page to see the changes.');
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { unlink } from 'fs/promises';
|
||||
import { prompt } from 'inquirer';
|
||||
import { select } from '@inquirer/prompts';
|
||||
import { getProjectPath } from './helpers/get-project-info.js';
|
||||
import { getMetaData, updateMetaData } from './helpers/project-metadata.js';
|
||||
import { getFileName } from './helpers/get-file-name.js';
|
||||
@@ -9,22 +9,22 @@ const deleteChallenge = async () => {
|
||||
|
||||
const challenges = getMetaData().challengeOrder;
|
||||
|
||||
const challengeToDelete = (await prompt({
|
||||
name: 'id',
|
||||
const challengeToDeleteId = await select<string>({
|
||||
message: 'Which challenge should be deleted?',
|
||||
type: 'list',
|
||||
choices: challenges.map(({ id, title }) => ({
|
||||
name: title,
|
||||
value: id
|
||||
}))
|
||||
})) as { id: string };
|
||||
});
|
||||
|
||||
const indexToDelete = challenges.findIndex(
|
||||
({ id }) => id === challengeToDelete.id
|
||||
({ id }) => id === challengeToDeleteId
|
||||
);
|
||||
const fileToDelete = await getFileName(challengeToDelete.id);
|
||||
|
||||
const fileToDelete = await getFileName(challengeToDeleteId);
|
||||
|
||||
if (!fileToDelete) {
|
||||
throw new Error(`File not found for challenge ${challengeToDelete.id}`);
|
||||
throw new Error(`File not found for challenge ${challengeToDeleteId}`);
|
||||
}
|
||||
|
||||
await unlink(`${path}${fileToDelete}`);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { unlink } from 'fs/promises';
|
||||
import { prompt } from 'inquirer';
|
||||
import { select } from '@inquirer/prompts';
|
||||
import { getProjectPath } from './helpers/get-project-info.js';
|
||||
import { getFileName } from './helpers/get-file-name.js';
|
||||
import {
|
||||
@@ -14,23 +14,21 @@ const deleteTask = async () => {
|
||||
const path = getProjectPath();
|
||||
const challenges = getMetaData().challengeOrder;
|
||||
|
||||
const challengeToDelete = (await prompt({
|
||||
name: 'id',
|
||||
const challengeToDeleteId = await select<string>({
|
||||
message: 'Which challenge should be deleted?',
|
||||
type: 'list',
|
||||
choices: challenges.map(({ id, title }) => ({
|
||||
name: title,
|
||||
value: id
|
||||
}))
|
||||
})) as { id: string };
|
||||
});
|
||||
|
||||
const indexToDelete = challenges.findIndex(
|
||||
({ id }) => id === challengeToDelete.id
|
||||
({ id }) => id === challengeToDeleteId
|
||||
);
|
||||
|
||||
const fileToDelete = await getFileName(challengeToDelete.id);
|
||||
const fileToDelete = await getFileName(challengeToDeleteId);
|
||||
if (!fileToDelete) {
|
||||
throw new Error(`File not found for challenge ${challengeToDelete.id}`);
|
||||
throw new Error(`File not found for challenge ${challengeToDeleteId}`);
|
||||
}
|
||||
|
||||
await unlink(`${path}${fileToDelete}`);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { prompt } from 'inquirer';
|
||||
import { select } from '@inquirer/prompts';
|
||||
import { ChallengeLang } from '@freecodecamp/shared/config/curriculum';
|
||||
import { challengeTypes } from '@freecodecamp/shared/config/challenge-types';
|
||||
|
||||
@@ -14,12 +14,13 @@ export const getInputType = async (
|
||||
return;
|
||||
}
|
||||
|
||||
const inputType = await prompt<{ value: string }>({
|
||||
name: 'value',
|
||||
const inputType = await select<string>({
|
||||
message: 'What input type is challenge using?',
|
||||
type: 'list',
|
||||
choices: ['pinyin-tone', 'pinyin-to-hanzi']
|
||||
choices: [
|
||||
{ name: 'pinyin-tone', value: 'pinyin-tone' },
|
||||
{ name: 'pinyin-to-hanzi', value: 'pinyin-to-hanzi' }
|
||||
]
|
||||
});
|
||||
|
||||
return inputType.value;
|
||||
return inputType;
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { prompt } from 'inquirer';
|
||||
import { input, select } from '@inquirer/prompts';
|
||||
import { challengeTypes } from '@freecodecamp/shared/config/challenge-types';
|
||||
import { getLastStep } from './get-last-step-file-number.js';
|
||||
|
||||
@@ -7,13 +7,11 @@ export const newChallengePrompts = async (): Promise<{
|
||||
dashedName: string;
|
||||
challengeType: string;
|
||||
}> => {
|
||||
const challengeType = await prompt<{ value: string }>({
|
||||
name: 'value',
|
||||
const challengeType = await select<string>({
|
||||
message: 'What type of challenge is this?',
|
||||
type: 'list',
|
||||
choices: Object.entries(challengeTypes).map(([key, value]) => ({
|
||||
name: key,
|
||||
value
|
||||
value: value.toString()
|
||||
}))
|
||||
});
|
||||
|
||||
@@ -21,9 +19,9 @@ export const newChallengePrompts = async (): Promise<{
|
||||
const defaultTitle = `Step ${lastStep + 1}`;
|
||||
const defaultDashedName = `step-${lastStep + 1}`;
|
||||
|
||||
const dashedName = await prompt<{ value: string }>({
|
||||
name: 'value',
|
||||
const dashedName = await input({
|
||||
message: 'What is the dashed name (in kebab-case) for this challenge?',
|
||||
default: defaultDashedName,
|
||||
validate: (block: string) => {
|
||||
if (!block.length) {
|
||||
return 'please enter a dashed name';
|
||||
@@ -33,20 +31,16 @@ export const newChallengePrompts = async (): Promise<{
|
||||
}
|
||||
return true;
|
||||
},
|
||||
filter: (block: string) => {
|
||||
return block.toLowerCase();
|
||||
},
|
||||
default: defaultDashedName
|
||||
transformer: (block: string) => block.toLowerCase()
|
||||
});
|
||||
const title = await prompt<{ value: string }>({
|
||||
name: 'value',
|
||||
const title = await input({
|
||||
message: 'What is the title of this challenge?',
|
||||
default: defaultTitle
|
||||
});
|
||||
|
||||
return {
|
||||
title: title.value,
|
||||
dashedName: dashedName.value,
|
||||
challengeType: challengeType.value
|
||||
title,
|
||||
dashedName,
|
||||
challengeType
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { prompt } from 'inquirer';
|
||||
import { select } from '@inquirer/prompts';
|
||||
import { challengeTypes } from '@freecodecamp/shared/config/challenge-types';
|
||||
|
||||
const taskChallenges = [
|
||||
@@ -10,19 +10,17 @@ const taskChallenges = [
|
||||
export const newTaskPrompts = async (): Promise<{
|
||||
challengeType: string;
|
||||
}> => {
|
||||
const challengeType = await prompt<{ value: string }>({
|
||||
name: 'value',
|
||||
const challengeType = await select<string>({
|
||||
message: 'What type of task challenge is this?',
|
||||
type: 'list',
|
||||
choices: Object.entries(challengeTypes)
|
||||
.filter(entry => taskChallenges.includes(entry[1]))
|
||||
.map(([key, value]) => ({
|
||||
name: key,
|
||||
value
|
||||
value: value.toString()
|
||||
}))
|
||||
});
|
||||
|
||||
return {
|
||||
challengeType: challengeType.value
|
||||
challengeType
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ObjectId } from 'bson';
|
||||
import { prompt } from 'inquirer';
|
||||
import { select } from '@inquirer/prompts';
|
||||
import { getTemplate } from './helpers/get-challenge-template.js';
|
||||
import { newChallengePrompts } from './helpers/new-challenge-prompts.js';
|
||||
import { getProjectPath } from './helpers/get-project-info.js';
|
||||
@@ -13,17 +13,15 @@ const insertChallenge = async () => {
|
||||
|
||||
const challenges = getMetaData().challengeOrder;
|
||||
|
||||
const challengeAfter = await prompt<{ id: string }>({
|
||||
name: 'id',
|
||||
const challengeAfterId = await select<string>({
|
||||
message: 'Which challenge should come AFTER this new one?',
|
||||
type: 'list',
|
||||
choices: challenges.map(({ id, title }) => ({
|
||||
name: title,
|
||||
value: id
|
||||
}))
|
||||
});
|
||||
const indexToInsert = challenges.findIndex(
|
||||
({ id }) => id === challengeAfter.id
|
||||
({ id }) => id === challengeAfterId
|
||||
);
|
||||
|
||||
const template = getTemplate(options.challengeType);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ObjectId } from 'bson';
|
||||
import { prompt } from 'inquirer';
|
||||
import { select } from '@inquirer/prompts';
|
||||
import { getTemplate } from './helpers/get-challenge-template.js';
|
||||
import { newTaskPrompts } from './helpers/new-task-prompts.js';
|
||||
import { getProjectPath } from './helpers/get-project-info.js';
|
||||
@@ -15,19 +15,17 @@ import { getInputType } from './helpers/get-input-type.js';
|
||||
|
||||
const insertChallenge = async () => {
|
||||
const challenges = getMetaData().challengeOrder;
|
||||
const challengeAfter = await prompt<{ id: string }>({
|
||||
name: 'id',
|
||||
const challengeAfterId = await select<string>({
|
||||
message: 'Which challenge should come AFTER this new one?',
|
||||
type: 'list',
|
||||
choices: challenges.map(({ id, title }) => ({
|
||||
name: title,
|
||||
value: id
|
||||
}))
|
||||
});
|
||||
const challengeLang = getChallenge(challengeAfter.id)?.lang;
|
||||
const challengeLang = getChallenge(challengeAfterId)?.lang;
|
||||
|
||||
const indexToInsert = challenges.findIndex(
|
||||
({ id }) => id === challengeAfter.id
|
||||
({ id }) => id === challengeAfterId
|
||||
);
|
||||
|
||||
const newTaskTitle = 'Task 0';
|
||||
|
||||
@@ -34,15 +34,14 @@
|
||||
"@freecodecamp/curriculum": "workspace:*",
|
||||
"@freecodecamp/eslint-config": "workspace:*",
|
||||
"@freecodecamp/shared": "workspace:*",
|
||||
"@inquirer/prompts": "^7.8.3",
|
||||
"@total-typescript/ts-reset": "^0.6.1",
|
||||
"@types/glob": "^8.0.1",
|
||||
"@types/inquirer": "^8.2.5",
|
||||
"@vitest/ui": "^4.0.15",
|
||||
"bson": "^7.0.0",
|
||||
"eslint": "^9.39.1",
|
||||
"glob": "^8.1.0",
|
||||
"gray-matter": "4.0.3",
|
||||
"inquirer": "8.2.6",
|
||||
"prettier": "3.2.5",
|
||||
"typescript": "5.9.3",
|
||||
"vitest": "^4.0.15"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import fs from 'fs/promises';
|
||||
import path, { join } from 'path';
|
||||
import { prompt } from 'inquirer';
|
||||
import { input } from '@inquirer/prompts';
|
||||
import { format } from 'prettier';
|
||||
|
||||
import { IntroJson, parseJson } from './helpers/parse-json';
|
||||
@@ -80,32 +80,24 @@ async function renameBlock({ newBlock, newName, oldBlock }: RenameBlockArgs) {
|
||||
}
|
||||
}
|
||||
|
||||
void getAllBlocks()
|
||||
.then(existingBlocks =>
|
||||
prompt([
|
||||
{
|
||||
name: 'oldBlock',
|
||||
message: 'What is the dashed name of block to rename?',
|
||||
type: 'input',
|
||||
validate: (block: string) => existingBlocks.includes(block)
|
||||
},
|
||||
{
|
||||
name: 'newName',
|
||||
message: 'What is the new name?',
|
||||
type: 'input',
|
||||
default: ({ oldBlock }: RenameBlockArgs) =>
|
||||
getBlockStructure(oldBlock).name
|
||||
},
|
||||
{
|
||||
name: 'newBlock',
|
||||
message: 'What is the new dashed name (in kebab-case)?',
|
||||
validate: (newBlock: string) =>
|
||||
validateBlockName(newBlock, existingBlocks)
|
||||
}
|
||||
])
|
||||
)
|
||||
.then(
|
||||
async ({ newBlock, newName, oldBlock }: RenameBlockArgs) =>
|
||||
await renameBlock({ newBlock, newName, oldBlock })
|
||||
)
|
||||
.then(() => console.log('All set. Refresh the page to see the changes.'));
|
||||
void getAllBlocks().then(async existingBlocks => {
|
||||
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: getBlockStructure(oldBlock).name
|
||||
});
|
||||
|
||||
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.');
|
||||
});
|
||||
|
||||
@@ -4,7 +4,7 @@ import { join } from 'path';
|
||||
import { promisify } from 'util';
|
||||
|
||||
import gray from 'gray-matter';
|
||||
import { prompt } from 'inquirer';
|
||||
import { select } from '@inquirer/prompts';
|
||||
|
||||
const asyncExec = promisify(exec);
|
||||
|
||||
@@ -13,23 +13,19 @@ void (async () => {
|
||||
join(process.cwd(), 'curriculum', 'challenges', 'english')
|
||||
);
|
||||
|
||||
const { superblock } = (await prompt({
|
||||
name: 'superblock',
|
||||
const superblock = await select<string>({
|
||||
message: 'Select target superblock:',
|
||||
type: 'list',
|
||||
choices: superblocks.map(e => ({ name: e, value: e }))
|
||||
})) as { superblock: string };
|
||||
choices: superblocks.map(value => ({ name: value, value }))
|
||||
});
|
||||
|
||||
const blocks = await readdir(
|
||||
join(process.cwd(), 'curriculum', 'challenges', 'english', superblock)
|
||||
);
|
||||
|
||||
const { block } = (await prompt({
|
||||
name: 'block',
|
||||
const block = await select<string>({
|
||||
message: 'Select target block:',
|
||||
type: 'list',
|
||||
choices: blocks.map(e => ({ name: e, value: e }))
|
||||
})) as { block: string };
|
||||
choices: blocks.map(value => ({ name: value, value }))
|
||||
});
|
||||
|
||||
const files = await readdir(
|
||||
join(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { prompt } from 'inquirer';
|
||||
import { select, confirm } from '@inquirer/prompts';
|
||||
|
||||
import { getMetaData, updateMetaData } from './helpers/project-metadata.js';
|
||||
|
||||
@@ -10,21 +10,19 @@ const updateChallengeOrder = async () => {
|
||||
const newChallengeOrder: { id: string; title: string }[] = [];
|
||||
|
||||
while (oldChallengeOrder.length) {
|
||||
const nextChallenge = (await prompt({
|
||||
name: 'id',
|
||||
const nextChallengeId = await select<string>({
|
||||
message: newChallengeOrder.length
|
||||
? `What challenge comes after ${
|
||||
newChallengeOrder[newChallengeOrder.length - 1].title
|
||||
}?`
|
||||
: 'What is the first challenge?',
|
||||
type: 'list',
|
||||
choices: oldChallengeOrder.map(({ id, title }) => ({
|
||||
name: title,
|
||||
value: id
|
||||
}))
|
||||
})) as { id: string };
|
||||
});
|
||||
const nextChallengeIndex = oldChallengeOrder.findIndex(
|
||||
({ id }) => id === nextChallenge.id
|
||||
({ id }) => id === nextChallengeId
|
||||
);
|
||||
const targetChallenge = oldChallengeOrder[nextChallengeIndex];
|
||||
oldChallengeOrder.splice(nextChallengeIndex, 1);
|
||||
@@ -34,14 +32,12 @@ const updateChallengeOrder = async () => {
|
||||
console.log('New challenge order is: ');
|
||||
console.table(newChallengeOrder.map(({ title }) => ({ title })));
|
||||
|
||||
const confirm = await prompt({
|
||||
name: 'correct',
|
||||
const isCorrect = await confirm({
|
||||
message: 'Is this correct?',
|
||||
type: 'confirm',
|
||||
default: false
|
||||
});
|
||||
|
||||
if (!confirm.correct) {
|
||||
if (!isCorrect) {
|
||||
console.error('Aborting.');
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user