refactor: (slightly) decentralize type checking (#64163)

This commit is contained in:
Oliver Eyton-Williams
2025-12-01 12:43:53 +01:00
committed by GitHub
parent 176bba6c15
commit 37ce134123
46 changed files with 214 additions and 226 deletions
+1 -1
View File
@@ -52,7 +52,7 @@
"knip:all": "npx -y knip@5 ",
"lint": "pnpm npm-run-all lint:*",
"lint:challenges": "cd ./curriculum && pnpm run lint-challenges",
"lint:ts": "tsc && tsc -p shared && tsc -p api && tsc -p client && tsc -p curriculum",
"lint:ts": "tsc && tsc -p shared && tsc -p api && tsc -p client && tsc -p curriculum && tsc -p e2e && tsc -p tools/challenge-helper-scripts",
"lint:prettier": "prettier --list-different .",
"lint:css": "stylelint '**/*.css'",
"preseed": "npm-run-all compile:ts",
+34 -38
View File
@@ -705,7 +705,7 @@ importers:
version: 4.0.2
'@typescript/vfs-1.6.1':
specifier: npm:@typescript/vfs@1.6.1
version: '@typescript/vfs@1.6.1(typescript@5.9.3)'
version: '@typescript/vfs@1.6.1(typescript@5.7.3)'
'@vitest/ui':
specifier: ^3.2.4
version: 3.2.4(vitest@3.2.4)
@@ -744,7 +744,7 @@ importers:
version: 0.5.2
puppeteer:
specifier: 22.12.1
version: 22.12.1(typescript@5.9.3)
version: 22.12.1(typescript@5.7.3)
sirv:
specifier: ^3.0.2
version: 3.0.2
@@ -756,7 +756,7 @@ importers:
version: typescript@5.9.2
vitest:
specifier: ^3.2.4
version: 3.2.4(@types/debug@4.1.12)(@types/node@20.12.8)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@16.7.0)(msw@2.8.7(@types/node@20.12.8)(typescript@5.9.3))(terser@5.28.1)(tsx@4.19.1)(yaml@2.8.1)
version: 3.2.4(@types/debug@4.1.12)(@types/node@20.12.8)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@16.7.0)(msw@2.8.7(@types/node@20.12.8)(typescript@5.7.3))(terser@5.28.1)(tsx@4.19.1)(yaml@2.8.1)
e2e:
devDependencies:
@@ -930,6 +930,9 @@ importers:
'@freecodecamp/eslint-config':
specifier: workspace:*
version: link:../../packages/eslint-config
'@total-typescript/ts-reset':
specifier: ^0.6.1
version: 0.6.1
'@types/glob':
specifier: ^8.0.1
version: 8.1.0
@@ -939,9 +942,9 @@ importers:
'@vitest/ui':
specifier: ^3.2.4
version: 3.2.4(vitest@3.2.4)
bson-objectid:
specifier: 2.0.4
version: 2.0.4
bson:
specifier: ^7.0.0
version: 7.0.0
eslint:
specifier: ^9.39.1
version: 9.39.1(jiti@2.6.1)
@@ -5990,14 +5993,15 @@ packages:
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
hasBin: true
bson-objectid@2.0.4:
resolution: {integrity: sha512-vgnKAUzcDoa+AeyYwXCoHyF2q6u/8H46dxu5JN+4/TZeq/Dlinn0K6GvxsCLb3LHUJl0m/TLiEK31kUwtgocMQ==}
bson@6.9.0:
resolution: {integrity: sha512-X9hJeyeM0//Fus+0pc5dSUMhhrrmWwQUtdavaQeF3Ta6m69matZkGWV/MrBcnwUeLC8W9kwwc2hfkZgUuCX3Ig==}
engines: {node: '>=16.20.1'}
deprecated: a critical bug affecting only useBigInt64=true deserialization usage is fixed in bson@6.10.3
bson@7.0.0:
resolution: {integrity: sha512-Kwc6Wh4lQ5OmkqqKhYGKIuELXl+EPYSCObVE6bWsp1T/cGkOCBN0I8wF/T44BiuhHyNi1mmKVPXk60d41xZ7kw==}
engines: {node: '>=20.19.0'}
buffer-crc32@0.2.13:
resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==}
@@ -19628,6 +19632,13 @@ snapshots:
'@typescript-eslint/types': 8.47.0
eslint-visitor-keys: 4.2.1
'@typescript/vfs@1.6.1(typescript@5.7.3)':
dependencies:
debug: 4.3.4(supports-color@8.1.1)
typescript: 5.7.3
transitivePeerDependencies:
- supports-color
'@typescript/vfs@1.6.1(typescript@5.9.2)':
dependencies:
debug: 4.3.4(supports-color@8.1.1)
@@ -19635,13 +19646,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@typescript/vfs@1.6.1(typescript@5.9.3)':
dependencies:
debug: 4.3.4(supports-color@8.1.1)
typescript: 5.9.3
transitivePeerDependencies:
- supports-color
'@uiw/react-codemirror@3.2.10(@babel/runtime@7.27.3)(codemirror@5.65.16)(react-dom@17.0.2(react@17.0.2))(react@17.0.2)':
dependencies:
'@babel/runtime': 7.27.3
@@ -19792,7 +19796,7 @@ snapshots:
sirv: 3.0.2
tinyglobby: 0.2.14
tinyrainbow: 2.0.0
vitest: 3.2.4(@types/debug@4.1.12)(@types/node@20.12.8)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@26.1.0)(msw@2.8.7(@types/node@20.12.8)(typescript@5.9.3))(terser@5.28.1)(tsx@4.19.1)(yaml@2.8.1)
vitest: 3.2.4(@types/debug@4.1.12)(@types/node@20.12.8)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@26.1.0)(msw@2.8.7(@types/node@20.12.8)(typescript@5.2.2))(terser@5.28.1)(tsx@4.19.1)(yaml@2.8.1)
'@vitest/utils@3.2.4':
dependencies:
@@ -20805,10 +20809,10 @@ snapshots:
node-releases: 2.0.27
update-browserslist-db: 1.1.4(browserslist@4.28.0)
bson-objectid@2.0.4: {}
bson@6.9.0: {}
bson@7.0.0: {}
buffer-crc32@0.2.13: {}
buffer-equal-constant-time@1.0.1: {}
@@ -21415,15 +21419,6 @@ snapshots:
optionalDependencies:
typescript: 5.9.2
cosmiconfig@9.0.0(typescript@5.9.3):
dependencies:
env-paths: 2.2.1
import-fresh: 3.3.0
js-yaml: 4.1.0
parse-json: 5.2.0
optionalDependencies:
typescript: 5.9.3
create-ecdh@4.0.4:
dependencies:
bn.js: 4.12.0
@@ -22400,7 +22395,7 @@ snapshots:
confusing-browser-globals: 1.0.11
eslint: 7.32.0
eslint-plugin-flowtype: 5.10.0(eslint@7.32.0)
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@4.33.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.2.2))(eslint@9.39.1(jiti@2.6.1))
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@4.33.0(eslint@7.32.0)(typescript@5.2.2))(eslint@9.39.1(jiti@2.6.1))
eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.1(jiti@2.6.1))
eslint-plugin-react: 7.37.4(eslint@9.39.1(jiti@2.6.1))
eslint-plugin-react-hooks: 4.6.0(eslint@9.39.1(jiti@2.6.1))
@@ -22430,7 +22425,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
eslint-module-utils@2.12.0(@typescript-eslint/parser@4.33.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.2.2))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1(jiti@2.6.1)):
eslint-module-utils@2.12.0(@typescript-eslint/parser@4.33.0(eslint@7.32.0)(typescript@5.2.2))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1(jiti@2.6.1)):
dependencies:
debug: 3.2.7
optionalDependencies:
@@ -22484,7 +22479,7 @@ snapshots:
- typescript
- utf-8-validate
eslint-plugin-import@2.31.0(@typescript-eslint/parser@4.33.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.2.2))(eslint@9.39.1(jiti@2.6.1)):
eslint-plugin-import@2.31.0(@typescript-eslint/parser@4.33.0(eslint@7.32.0)(typescript@5.2.2))(eslint@9.39.1(jiti@2.6.1)):
dependencies:
'@rtsao/scc': 1.1.0
array-includes: 3.1.8
@@ -22495,7 +22490,7 @@ snapshots:
doctrine: 2.1.0
eslint: 9.39.1(jiti@2.6.1)
eslint-import-resolver-node: 0.3.9
eslint-module-utils: 2.12.0(@typescript-eslint/parser@4.33.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.2.2))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1(jiti@2.6.1))
eslint-module-utils: 2.12.0(@typescript-eslint/parser@4.33.0(eslint@7.32.0)(typescript@5.2.2))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1(jiti@2.6.1))
hasown: 2.0.2
is-core-module: 2.16.1
is-glob: 4.0.3
@@ -23718,7 +23713,7 @@ snapshots:
eslint-config-react-app: 6.0.0(@typescript-eslint/eslint-plugin@4.33.0(@typescript-eslint/parser@4.33.0(eslint@7.32.0)(typescript@5.2.2))(eslint@7.32.0)(typescript@5.2.2))(@typescript-eslint/parser@4.33.0(eslint@7.32.0)(typescript@5.2.2))(babel-eslint@10.1.0(eslint@9.39.1(jiti@2.6.1)))(eslint-plugin-flowtype@5.10.0(eslint@7.32.0))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@4.33.0(eslint@7.32.0)(typescript@5.2.2))(eslint@7.32.0))(eslint-plugin-jsx-a11y@6.10.2(eslint@7.32.0))(eslint-plugin-react-hooks@4.6.0(eslint@7.32.0))(eslint-plugin-react@7.37.4(eslint@7.32.0))(eslint@7.32.0)(typescript@5.2.2)
eslint-plugin-flowtype: 5.10.0(eslint@7.32.0)
eslint-plugin-graphql: 4.0.0(@types/node@20.12.8)(graphql@15.8.0)(typescript@5.2.2)
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@4.33.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.2.2))(eslint@9.39.1(jiti@2.6.1))
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@4.33.0(eslint@7.32.0)(typescript@5.2.2))(eslint@9.39.1(jiti@2.6.1))
eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.1(jiti@2.6.1))
eslint-plugin-react: 7.37.4(eslint@9.39.1(jiti@2.6.1))
eslint-plugin-react-hooks: 4.6.0(eslint@9.39.1(jiti@2.6.1))
@@ -27627,10 +27622,10 @@ snapshots:
- supports-color
- utf-8-validate
puppeteer@22.12.1(typescript@5.9.3):
puppeteer@22.12.1(typescript@5.7.3):
dependencies:
'@puppeteer/browsers': 2.2.3
cosmiconfig: 9.0.0(typescript@5.9.3)
cosmiconfig: 9.0.0(typescript@5.7.3)
devtools-protocol: 0.0.1299070
puppeteer-core: 22.12.1
transitivePeerDependencies:
@@ -29843,7 +29838,8 @@ snapshots:
typescript@5.9.2: {}
typescript@5.9.3: {}
typescript@5.9.3:
optional: true
uc.micro@2.0.0: {}
@@ -30332,11 +30328,11 @@ snapshots:
- debug
- typescript
vitest@3.2.4(@types/debug@4.1.12)(@types/node@20.12.8)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@16.7.0)(msw@2.8.7(@types/node@20.12.8)(typescript@5.9.3))(terser@5.28.1)(tsx@4.19.1)(yaml@2.8.1):
vitest@3.2.4(@types/debug@4.1.12)(@types/node@20.12.8)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@16.7.0)(msw@2.8.7(@types/node@20.12.8)(typescript@5.7.3))(terser@5.28.1)(tsx@4.19.1)(yaml@2.8.1):
dependencies:
'@types/chai': 5.2.2
'@vitest/expect': 3.2.4
'@vitest/mocker': 3.2.4(msw@2.8.7(@types/node@20.12.8)(typescript@5.9.3))(vite@7.1.3(@types/node@20.12.8)(jiti@2.6.1)(terser@5.28.1)(tsx@4.19.1)(yaml@2.8.1))
'@vitest/mocker': 3.2.4(msw@2.8.7(@types/node@20.12.8)(typescript@5.7.3))(vite@7.1.3(@types/node@20.12.8)(jiti@2.6.1)(terser@5.28.1)(tsx@4.19.1)(yaml@2.8.1))
'@vitest/pretty-format': 3.2.4
'@vitest/runner': 3.2.4
'@vitest/snapshot': 3.2.4
+3 -3
View File
@@ -1,13 +1,13 @@
import fs from 'fs';
import { getProjectPath } from './helpers/get-project-info';
import { getMetaData } from './helpers/project-metadata';
import { getProjectPath } from './helpers/get-project-info.js';
import { getMetaData } from './helpers/project-metadata.js';
import {
createStepFile,
deleteStepFromMeta,
getChallenge,
insertStepIntoMeta,
updateStepTitles
} from './utils';
} from './utils.js';
async function deleteStep(stepNum: number): Promise<void> {
if (stepNum < 1) {
@@ -4,15 +4,14 @@
*/
import { readFileSync, writeFileSync } from 'fs';
import path, { join } from 'path';
import { fileURLToPath } from 'url';
import ObjectID from 'bson-objectid';
import { Meta } from './helpers/project-metadata';
import { getArgValue } from './helpers/get-arg-value';
import { join } from 'path';
import { ObjectId } from 'bson';
import { Meta } from './helpers/project-metadata.js';
import { getArgValue } from './helpers/get-arg-value.js';
import {
getDailyJavascriptChallengeTemplate,
getDailyPythonChallengeTemplate
} from './helpers/get-challenge-template';
} from './helpers/get-challenge-template.js';
const numberOfChallengesToCreate = getArgValue(process.argv);
@@ -20,9 +19,6 @@ if (numberOfChallengesToCreate > 10) {
throw new Error('Are you sure you want to create that many challenges?');
}
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const curriculumPath = join(__dirname, '../../curriculum');
const structureBlocksPath = join(curriculumPath, '/structure/blocks');
@@ -58,7 +54,7 @@ for (let i = 0; i < numberOfChallengesToCreate; i++) {
);
}
const challengeId = new ObjectID();
const challengeId = new ObjectId();
const newChallengeNumber = numberOfJsChallenges + 1;
createDailyJsChallenge({
@@ -74,7 +70,7 @@ for (let i = 0; i < numberOfChallengesToCreate; i++) {
}
interface CreateDailyChallengeOptions {
challengeId: ObjectID;
challengeId: ObjectId;
challengeNumber: number;
meta: Meta;
}
@@ -89,7 +85,6 @@ function createDailyJsChallenge({
challengeOrder: [
...meta.challengeOrder,
{
// eslint-disable-next-line @typescript-eslint/no-base-to-string
id: challengeId.toString(),
title: `Challenge ${challengeNumber}: Placeholder`
}
@@ -105,7 +100,7 @@ function createDailyJsChallenge({
const jsChallengePath = join(
jsChallengesPath,
// eslint-disable-next-line @typescript-eslint/no-base-to-string
`${challengeId.toString()}.md`
);
@@ -122,7 +117,6 @@ function createDailyPyChallenge({
challengeOrder: [
...meta.challengeOrder,
{
// eslint-disable-next-line @typescript-eslint/no-base-to-string
id: challengeId.toString(),
title: `Challenge ${challengeNumber}: Placeholder`
}
@@ -138,7 +132,7 @@ function createDailyPyChallenge({
const pyChallengePath = join(
pyChallengesPath,
// eslint-disable-next-line @typescript-eslint/no-base-to-string
`${challengeId.toString()}.md`
);
@@ -1,4 +1,4 @@
import { getArgValue } from './helpers/get-arg-value';
import { createEmptySteps } from './commands';
import { getArgValue } from './helpers/get-arg-value.js';
import { createEmptySteps } from './commands.js';
void createEmptySteps(getArgValue(process.argv));
@@ -2,35 +2,35 @@ import fs from 'fs/promises';
import path from 'path';
import { prompt } from 'inquirer';
import { format } from 'prettier';
import ObjectID from 'bson-objectid';
import { ObjectId } from 'bson';
import {
SuperBlocks,
languageSuperBlocks,
chapterBasedSuperBlocks
} from '../../shared/config/curriculum';
} from '../../shared-dist/config/curriculum.js';
import { BlockLayouts, BlockLabel } from '../../shared/config/blocks';
import { BlockLayouts, BlockLabel } from '../../shared-dist/config/blocks.js';
import {
getContentConfig,
writeBlockStructure,
getSuperblockStructure,
createBlockFolder
} from '../../curriculum/src/file-handler';
import { superBlockToFilename } from '../../curriculum/src/build-curriculum';
import { getBaseMeta } from './helpers/get-base-meta';
import { createIntroMD } from './helpers/create-intro';
createBlockFolder,
getSuperblockStructure
} from '../../curriculum/src/file-handler.js';
import { superBlockToFilename } from '../../curriculum/src/build-curriculum.js';
import { getBaseMeta } from './helpers/get-base-meta.js';
import { createIntroMD } from './helpers/create-intro.js';
import {
createDialogueFile,
createQuizFile,
getAllBlocks,
validateBlockName
} from './utils';
} from './utils.js';
import {
updateSimpleSuperblockStructure,
updateChapterModuleSuperblockStructure
} from './helpers/create-project';
import { getLangFromSuperBlock } from './helpers/get-lang-from-superblock';
} from './helpers/create-project.js';
import { getLangFromSuperBlock } from './helpers/get-lang-from-superblock.js';
const helpCategories = [
'English',
@@ -80,7 +80,7 @@ async function createLanguageBlock(
await updateIntroJson(superBlock, block, title);
const challengeLang = getLangFromSuperBlock(superBlock);
let challengeId: ObjectID;
let challengeId: ObjectId;
if (blockLabel === BlockLabel.quiz) {
challengeId = await createQuizChallenge(
@@ -157,7 +157,7 @@ async function createMetaJson(
block: string,
title: string,
helpCategory: string,
challengeId: ObjectID,
challengeId: ObjectId,
blockLabel?: BlockLabel,
blockLayout?: string
) {
@@ -178,7 +178,6 @@ async function createMetaJson(
newMeta.challengeOrder = [
{
// eslint-disable-next-line @typescript-eslint/no-base-to-string
id: challengeId.toString(),
title: challengeTitle
}
@@ -191,7 +190,7 @@ async function createDialogueChallenge(
superBlock: SuperBlocks,
block: string,
challengeLang: string
): Promise<ObjectID> {
): Promise<ObjectId> {
const { blockContentDir } = getContentConfig('english') as {
blockContentDir: string;
};
@@ -210,7 +209,7 @@ async function createQuizChallenge(
title: string,
questionCount: number,
challengeLang: string
): Promise<ObjectID> {
): Promise<ObjectId> {
return createQuizFile({
projectPath: await createBlockFolder(block),
title: title,
@@ -1,9 +1,9 @@
import ObjectID from 'bson-objectid';
import { getTemplate } from './helpers/get-challenge-template';
import { newChallengePrompts } from './helpers/new-challenge-prompts';
import { getProjectPath } from './helpers/get-project-info';
import { getMetaData, updateMetaData } from './helpers/project-metadata';
import { createChallengeFile } from './utils';
import { ObjectId } from 'bson';
import { getTemplate } from './helpers/get-challenge-template.js';
import { newChallengePrompts } from './helpers/new-challenge-prompts.js';
import { getProjectPath } from './helpers/get-project-info.js';
import { getMetaData, updateMetaData } from './helpers/project-metadata.js';
import { createChallengeFile } from './utils.js';
const createNextChallenge = async () => {
const path = getProjectPath();
@@ -11,14 +11,13 @@ const createNextChallenge = async () => {
const options = await newChallengePrompts();
const template = getTemplate(options.challengeType);
const challengeId = new ObjectID();
const challengeId = new ObjectId();
const challengeText = template({ ...options, challengeId });
createChallengeFile(options.dashedName, challengeText, path);
const meta = getMetaData();
meta.challengeOrder.push({
// eslint-disable-next-line @typescript-eslint/no-base-to-string
id: challengeId.toString(),
title: options.title
});
@@ -1,4 +1,4 @@
import { getLastStep } from './helpers/get-last-step-file-number';
import { insertStep } from './commands';
import { getLastStep } from './helpers/get-last-step-file-number.js';
import { insertStep } from './commands.js';
void insertStep(getLastStep().stepNum + 1);
@@ -1,15 +1,15 @@
import ObjectID from 'bson-objectid';
import { getTemplate } from './helpers/get-challenge-template';
import { newTaskPrompts } from './helpers/new-task-prompts';
import { getProjectPath } from './helpers/get-project-info';
import { getMetaData, updateMetaData } from './helpers/project-metadata';
import { ObjectId } from 'bson';
import { getTemplate } from './helpers/get-challenge-template.js';
import { newTaskPrompts } from './helpers/new-task-prompts.js';
import { getProjectPath } from './helpers/get-project-info.js';
import { getMetaData, updateMetaData } from './helpers/project-metadata.js';
import {
createChallengeFile,
getChallenge,
updateTaskMeta,
updateTaskMarkdownFiles
} from './utils';
import { getInputType } from './helpers/get-input-type';
} from './utils.js';
import { getInputType } from './helpers/get-input-type.js';
const createNextTask = async () => {
const { challengeType } = await newTaskPrompts();
@@ -32,9 +32,9 @@ const createNextTask = async () => {
const path = getProjectPath();
const template = getTemplate(options.challengeType);
const challengeId = new ObjectID();
const challengeId = new ObjectId();
const challengeText = template({ ...options, challengeId });
// eslint-disable-next-line @typescript-eslint/no-base-to-string
const challengeIdString = challengeId.toString();
createChallengeFile(challengeIdString, challengeText, path);
@@ -2,31 +2,31 @@ import fs from 'fs/promises';
import path from 'path';
import { prompt } from 'inquirer';
import { format } from 'prettier';
import ObjectID from 'bson-objectid';
import { ObjectId } from 'bson';
import {
SuperBlocks,
chapterBasedSuperBlocks
} from '../../shared/config/curriculum';
import { BlockLayouts, BlockLabel } from '../../shared/config/blocks';
} from '../../shared-dist/config/curriculum.js';
import { BlockLayouts, BlockLabel } from '../../shared-dist/config/blocks.js';
import {
createBlockFolder,
writeBlockStructure
} from '../../curriculum/src/file-handler';
import { superBlockToFilename } from '../../curriculum/src/build-curriculum';
} from '../../curriculum/src/file-handler.js';
import { superBlockToFilename } from '../../curriculum/src/build-curriculum.js';
import {
createQuizFile,
createStepFile,
validateBlockName,
getAllBlocks
} from './utils';
import { getBaseMeta } from './helpers/get-base-meta';
import { createIntroMD } from './helpers/create-intro';
} from './utils.js';
import { getBaseMeta } from './helpers/get-base-meta.js';
import { createIntroMD } from './helpers/create-intro.js';
import {
ChapterModuleSuperblockStructure,
updateChapterModuleSuperblockStructure,
updateSimpleSuperblockStructure
} from './helpers/create-project';
} from './helpers/create-project.js';
const helpCategories = [
'HTML-CSS',
@@ -181,7 +181,7 @@ async function createMetaJson(
block: string,
title: string,
helpCategory: string,
challengeId: ObjectID,
challengeId: ObjectId,
order?: number,
blockLabel?: string,
blockLayout?: string
@@ -201,13 +201,13 @@ async function createMetaJson(
newMeta.name = title;
newMeta.dashedName = block;
newMeta.helpCategory = helpCategory;
// eslint-disable-next-line @typescript-eslint/no-base-to-string
newMeta.challengeOrder = [{ id: challengeId.toString(), title: 'Step 1' }];
await writeBlockStructure(block, newMeta);
}
async function createFirstChallenge(block: string): Promise<ObjectID> {
async function createFirstChallenge(block: string): Promise<ObjectId> {
// TODO: would be nice if the extension made sense for the challenge, but, at
// least until react I think they're all going to be html anyway.
const challengeSeeds = [
@@ -231,7 +231,7 @@ async function createQuizChallenge(
block: string,
title: string,
questionCount: number
): Promise<ObjectID> {
): Promise<ObjectId> {
return createQuizFile({
projectPath: await createBlockFolder(block),
title: title,
+11 -11
View File
@@ -2,18 +2,18 @@ import fs from 'fs/promises';
import path from 'path';
import { prompt } from 'inquirer';
import { format } from 'prettier';
import ObjectID from 'bson-objectid';
import { ObjectId } from 'bson';
import { SuperBlocks } from '../../shared/config/curriculum';
import { SuperBlocks } from '../../shared-dist/config/curriculum.js';
import {
createBlockFolder,
writeBlockStructure
} from '../../curriculum/src/file-handler';
import { superBlockToFilename } from '../../curriculum/src/build-curriculum';
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';
} from '../../curriculum/src/file-handler.js';
import { superBlockToFilename } from '../../curriculum/src/build-curriculum.js';
import { createQuizFile, getAllBlocks, validateBlockName } from './utils.js';
import { getBaseMeta } from './helpers/get-base-meta.js';
import { createIntroMD } from './helpers/create-intro.js';
import { updateSimpleSuperblockStructure } from './helpers/create-project.js';
const helpCategories = [
'HTML-CSS',
@@ -93,13 +93,13 @@ async function createMetaJson(
block: string,
title: string,
helpCategory: string,
challengeId: ObjectID
challengeId: ObjectId
) {
const newMeta = getBaseMeta('Quiz');
newMeta.name = title;
newMeta.dashedName = block;
newMeta.helpCategory = helpCategory;
// eslint-disable-next-line @typescript-eslint/no-base-to-string
newMeta.challengeOrder = [{ id: challengeId.toString(), title: title }];
await writeBlockStructure(block, newMeta);
@@ -110,7 +110,7 @@ async function createQuizChallenge(
block: string,
title: string,
questionCount: number
): Promise<ObjectID> {
): Promise<ObjectId> {
return createQuizFile({
projectPath: await createBlockFolder(block),
title: title,
@@ -6,18 +6,17 @@
* filename. Change the `challengeId` at the bottom to use the dashed name if
* you want that.
*/
import ObjectID from 'bson-objectid';
import { ObjectId } from 'bson';
import {
getBlockStructure,
writeBlockStructure
} from '../../curriculum/src/file-handler';
import { createChallengeFile } from './utils';
import { getProjectPath } from './helpers/get-project-info';
import { getBlock, type Meta } from './helpers/project-metadata';
} from '../../curriculum/src/file-handler.js';
import { createChallengeFile } from './utils.js';
import { getProjectPath } from './helpers/get-project-info.js';
import { getBlock, type Meta } from './helpers/project-metadata.js';
// eslint-disable-next-line @typescript-eslint/no-base-to-string
const challengeId = new ObjectID().toString();
const challengeId = new ObjectId().toString();
/***** Only change code below this line *****/
@@ -1,8 +1,8 @@
import { unlink } from 'fs/promises';
import { prompt } from 'inquirer';
import { getProjectPath } from './helpers/get-project-info';
import { getMetaData, updateMetaData } from './helpers/project-metadata';
import { getFileName } from './helpers/get-file-name';
import { getProjectPath } from './helpers/get-project-info.js';
import { getMetaData, updateMetaData } from './helpers/project-metadata.js';
import { getFileName } from './helpers/get-file-name.js';
const deleteChallenge = async () => {
const path = getProjectPath();
@@ -1,4 +1,4 @@
import { deleteStep } from './commands';
import { getArgValue } from './helpers/get-arg-value';
import { deleteStep } from './commands.js';
import { getArgValue } from './helpers/get-arg-value.js';
void deleteStep(getArgValue(process.argv));
@@ -1,14 +1,14 @@
import { unlink } from 'fs/promises';
import { prompt } from 'inquirer';
import { getProjectPath } from './helpers/get-project-info';
import { getFileName } from './helpers/get-file-name';
import { getProjectPath } from './helpers/get-project-info.js';
import { getFileName } from './helpers/get-file-name.js';
import {
deleteChallengeFromMeta,
updateTaskMarkdownFiles,
updateTaskMeta
} from './utils';
import { isTaskChallenge } from './helpers/task-helpers';
import { getMetaData } from './helpers/project-metadata';
} from './utils.js';
import { isTaskChallenge } from './helpers/task-helpers.js';
import { getMetaData } from './helpers/project-metadata.js';
const deleteTask = async () => {
const path = getProjectPath();
@@ -2,11 +2,11 @@ import { describe, it, expect, beforeEach, vi } from 'vitest';
import {
getSuperblockStructure,
writeSuperblockStructure
} from '../../../curriculum/src/file-handler';
} from '../../../curriculum/src/file-handler.js';
import {
updateChapterModuleSuperblockStructure,
updateSimpleSuperblockStructure
} from './create-project';
} from './create-project.js';
vi.mock('../../../curriculum/src/file-handler');
@@ -3,8 +3,8 @@
import {
getSuperblockStructure,
writeSuperblockStructure
} from '../../../curriculum/src/file-handler';
import { insertInto } from './utils';
} from '../../../curriculum/src/file-handler.js';
import { insertInto } from './utils.js';
export async function updateSimpleSuperblockStructure(
block: string,
@@ -1,5 +1,5 @@
import { describe, it, expect } from 'vitest';
import { getArgValue } from './get-arg-value';
import { getArgValue } from './get-arg-value.js';
describe('getArgValue helper', () => {
it('should throw if there no arguments', () => {
@@ -1,12 +1,11 @@
/* eslint-disable @typescript-eslint/no-base-to-string */
import ObjectID from 'bson-objectid';
import { ObjectId } from 'bson';
const sanitizeTitle = (title: string) => {
return title.includes(':') || title.includes("'") ? `"${title}"` : title;
};
interface ChallengeOptions {
challengeId: ObjectID;
challengeId: ObjectId;
title: string;
dashedName: string;
challengeType: string;
@@ -359,7 +358,7 @@ Do the assignment.
`;
interface DailyCodingChallengeOptions {
challengeId: ObjectID;
challengeId: ObjectId;
challengeNumber: number;
}
@@ -2,7 +2,7 @@ import fs from 'fs';
import { join } from 'path';
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { getFileName } from './get-file-name';
import { getFileName } from './get-file-name.js';
const basePath = join(process.cwd(), '__fixtures__');
const commonPath = join(basePath, 'curriculum', 'challenges');
@@ -1,6 +1,6 @@
import { readdir } from 'fs/promises';
import matter from 'gray-matter';
import { getProjectPath } from './get-project-info';
import { getProjectPath } from './get-project-info.js';
export const getFileName = async (id: string): Promise<string | null> => {
const path = getProjectPath();
@@ -1,6 +1,6 @@
import { prompt } from 'inquirer';
import { ChallengeLang } from '../../../shared/config/curriculum';
import { challengeTypes } from '../../../shared/config/challenge-types';
import { ChallengeLang } from '../../../shared-dist/config/curriculum.js';
import { challengeTypes } from '../../../shared-dist/config/challenge-types.js';
export const getInputType = async (
challengeType: string,
@@ -2,7 +2,7 @@ import {
ChallengeLang,
SuperBlocks,
superBlockToSpeechLang
} from '../../../shared/config/curriculum';
} from '../../../shared-dist/config/curriculum.js';
export const getLangFromSuperBlock = (
superBlock: SuperBlocks
@@ -1,5 +1,5 @@
import { last } from 'lodash';
import { getMetaData } from './project-metadata';
import { getMetaData } from './project-metadata.js';
function getLastStep(): { stepNum: number } {
const meta = getMetaData();
@@ -1,5 +1,5 @@
import { describe, it, expect } from 'vitest';
import { getProjectName, getProjectPath } from './get-project-info';
import { getProjectName, getProjectPath } from './get-project-info.js';
describe('getProjectPath helper', () => {
it('should return the calling dir path', () => {
@@ -1,9 +1,9 @@
import { describe, it, expect } from 'vitest';
import ObjectID from 'bson-objectid';
import { getStepTemplate } from './get-step-template';
import { ObjectId } from 'bson';
import { getStepTemplate } from './get-step-template.js';
const props = {
challengeId: new ObjectID('60d4ebe4801158d1abe1b18f'),
challengeId: new ObjectId('60d4ebe4801158d1abe1b18f'),
challengeSeeds: [
{
contents: '',
@@ -1,6 +1,5 @@
/* eslint-disable @typescript-eslint/no-base-to-string */
import ObjectID from 'bson-objectid';
import { insertErms } from './insert-erms';
import { ObjectId } from 'bson';
import { insertErms } from './insert-erms.js';
// Builds a block
function getCodeBlock(label: string, content?: string) {
@@ -21,7 +20,7 @@ ${content}`
}
type StepOptions = {
challengeId: ObjectID;
challengeId: ObjectId;
challengeSeeds: ChallengeSeed[];
stepNum: number;
challengeType?: number;
@@ -1,5 +1,5 @@
import { describe, it, expect } from 'vitest';
import { insertErms } from './insert-erms';
import { insertErms } from './insert-erms.js';
describe('insertErms helper', () => {
const code = `<h1>Hello World</h1>
@@ -1,6 +1,6 @@
import { prompt } from 'inquirer';
import { challengeTypes } from '../../../shared/config/challenge-types';
import { getLastStep } from './get-last-step-file-number';
import { challengeTypes } from '../../../shared-dist/config/challenge-types.js';
import { getLastStep } from './get-last-step-file-number.js';
export const newChallengePrompts = async (): Promise<{
title: string;
@@ -1,5 +1,5 @@
import { prompt } from 'inquirer';
import { challengeTypes } from '../../../shared/config/challenge-types';
import { challengeTypes } from '../../../shared-dist/config/challenge-types.js';
const taskChallenges = [
challengeTypes.multipleChoice,
@@ -1,7 +1,7 @@
import { join } from 'path';
import { describe, it, expect, vi } from 'vitest';
import { getBlockStructure } from '../../../curriculum/src/file-handler';
import { getMetaData } from './project-metadata';
import { getBlockStructure } from '../../../curriculum/src/file-handler.js';
import { getMetaData } from './project-metadata.js';
vi.mock('../../../curriculum/src/file-handler');
@@ -2,8 +2,8 @@ import path from 'path';
import {
getBlockStructure,
writeBlockStructure
} from '../../../curriculum/src/file-handler';
import { getProjectPath } from './get-project-info';
} from '../../../curriculum/src/file-handler.js';
import { getProjectPath } from './get-project-info.js';
export type Meta = {
name: string;
@@ -1,5 +1,5 @@
import { describe, it, expect } from 'vitest';
import { insertInto } from './utils';
import { insertInto } from './utils.js';
describe('insertInto', () => {
it('should not modify the original array', () => {
@@ -1,10 +1,10 @@
import ObjectID from 'bson-objectid';
import { ObjectId } from 'bson';
import { prompt } from 'inquirer';
import { getTemplate } from './helpers/get-challenge-template';
import { newChallengePrompts } from './helpers/new-challenge-prompts';
import { getProjectPath } from './helpers/get-project-info';
import { getMetaData, updateMetaData } from './helpers/project-metadata';
import { createChallengeFile } from './utils';
import { getTemplate } from './helpers/get-challenge-template.js';
import { newChallengePrompts } from './helpers/new-challenge-prompts.js';
import { getProjectPath } from './helpers/get-project-info.js';
import { getMetaData, updateMetaData } from './helpers/project-metadata.js';
import { createChallengeFile } from './utils.js';
const insertChallenge = async () => {
const path = getProjectPath();
@@ -27,13 +27,12 @@ const insertChallenge = async () => {
);
const template = getTemplate(options.challengeType);
const challengeId = new ObjectID();
const challengeId = new ObjectId();
const challengeText = template({ ...options, challengeId });
createChallengeFile(options.dashedName, challengeText, path);
const meta = getMetaData();
meta.challengeOrder.splice(indexToInsert, 0, {
// eslint-disable-next-line @typescript-eslint/no-base-to-string
id: challengeId.toString(),
title: options.title
});
@@ -1,4 +1,4 @@
import { getArgValue } from './helpers/get-arg-value';
import { insertStep } from './commands';
import { getArgValue } from './helpers/get-arg-value.js';
import { insertStep } from './commands.js';
void insertStep(getArgValue(process.argv));
@@ -1,17 +1,17 @@
import ObjectID from 'bson-objectid';
import { ObjectId } from 'bson';
import { prompt } from 'inquirer';
import { getTemplate } from './helpers/get-challenge-template';
import { newTaskPrompts } from './helpers/new-task-prompts';
import { getProjectPath } from './helpers/get-project-info';
import { getTemplate } from './helpers/get-challenge-template.js';
import { newTaskPrompts } from './helpers/new-task-prompts.js';
import { getProjectPath } from './helpers/get-project-info.js';
import {
createChallengeFile,
getChallenge,
insertChallengeIntoMeta,
updateTaskMeta,
updateTaskMarkdownFiles
} from './utils';
import { getMetaData } from './helpers/project-metadata';
import { getInputType } from './helpers/get-input-type';
} from './utils.js';
import { getMetaData } from './helpers/project-metadata.js';
import { getInputType } from './helpers/get-input-type.js';
const insertChallenge = async () => {
const challenges = getMetaData().challengeOrder;
@@ -45,9 +45,9 @@ const insertChallenge = async () => {
const path = getProjectPath();
const template = getTemplate(challengeType);
const challengeId = new ObjectID();
const challengeId = new ObjectId();
const challengeText = template({ ...options, challengeId });
// eslint-disable-next-line @typescript-eslint/no-base-to-string
const challengeIdString = challengeId.toString();
createChallengeFile(challengeIdString, challengeText, path);
+2 -1
View File
@@ -28,10 +28,11 @@
},
"devDependencies": {
"@freecodecamp/eslint-config": "workspace:*",
"@total-typescript/ts-reset": "^0.6.1",
"@types/glob": "^8.0.1",
"@types/inquirer": "^8.2.5",
"@vitest/ui": "^3.2.4",
"bson-objectid": "2.0.4",
"bson": "^7.0.0",
"eslint": "^9.39.1",
"glob": "^8.1.0",
"gray-matter": "4.0.3",
@@ -1,4 +1,4 @@
import { updateTaskMeta, updateTaskMarkdownFiles } from './utils';
import { updateTaskMeta, updateTaskMarkdownFiles } from './utils.js';
const reorderTasks = async () => {
await updateTaskMeta();
+1
View File
@@ -0,0 +1 @@
import '@total-typescript/ts-reset';
+6 -1
View File
@@ -1,3 +1,8 @@
{
"extends": "../../tsconfig-base.json"
"extends": "../../tsconfig-base.json",
"include": ["**/*.ts"],
"compilerOptions": {
"module": "node16",
"moduleResolution": "node16"
}
}
@@ -1,6 +1,6 @@
import { prompt } from 'inquirer';
import { getMetaData, updateMetaData } from './helpers/project-metadata';
import { getMetaData, updateMetaData } from './helpers/project-metadata.js';
const updateChallengeOrder = async () => {
const oldChallengeOrder = getMetaData().challengeOrder;
@@ -1,3 +1,3 @@
import { updateStepTitles } from './utils';
import { updateStepTitles } from './utils.js';
updateStepTitles();
+8 -9
View File
@@ -1,7 +1,7 @@
import fs from 'fs';
import path, { join } from 'path';
import matter from 'gray-matter';
import ObjectID from 'bson-objectid';
import { ObjectId } from 'bson';
import { vi, describe, it, expect, afterEach } from 'vitest';
vi.mock('fs', () => {
@@ -22,9 +22,9 @@ vi.mock('gray-matter', () => {
};
});
vi.mock('bson-objectid', () => {
vi.mock('bson', () => {
return {
default: vi.fn(() => ({ toString: () => mockChallengeId }))
ObjectId: vi.fn(() => ({ toString: () => mockChallengeId }))
};
});
@@ -46,15 +46,15 @@ vi.mock('./helpers/project-metadata', () => {
});
const mockChallengeId = '60d35cf3fe32df2ce8e31b03';
import { getStepTemplate } from './helpers/get-step-template';
import { getStepTemplate } from './helpers/get-step-template.js';
import {
createChallengeFile,
createStepFile,
insertStepIntoMeta,
updateStepTitles,
validateBlockName
} from './utils';
import { updateMetaData } from './helpers/project-metadata';
} from './utils.js';
import { updateMetaData } from './helpers/project-metadata.js';
const block = 'utils-project';
const projectPath = join(
@@ -81,9 +81,8 @@ describe('Challenge utils helper scripts', () => {
challengeType: 0
});
// eslint-disable-next-line @typescript-eslint/no-base-to-string
expect(step.toString()).toEqual(mockChallengeId);
expect(ObjectID).toHaveBeenCalledTimes(1);
expect(ObjectId).toHaveBeenCalledTimes(1);
// Internal tasks
// - Should generate a template for the step that is being created
@@ -144,7 +143,7 @@ describe('Challenge utils helper scripts', () => {
await insertStepIntoMeta({
stepNum: 3,
stepId: new ObjectID(mockChallengeId)
stepId: new ObjectId(mockChallengeId)
});
expect(updateMetaData).toHaveBeenCalledWith({
+20 -22
View File
@@ -1,20 +1,20 @@
import fs from 'fs';
import path from 'path';
import ObjectID from 'bson-objectid';
import { ObjectId } from 'bson';
import matter from 'gray-matter';
import { uniq } from 'lodash';
import { challengeTypes } from '../../shared/config/challenge-types';
import { parseCurriculumStructure } from '../../curriculum/src/build-curriculum';
import { parseMDSync } from '../challenge-parser/parser';
import { getMetaData, updateMetaData } from './helpers/project-metadata';
import { getProjectPath } from './helpers/get-project-info';
import { ChallengeSeed, getStepTemplate } from './helpers/get-step-template';
import { challengeTypes } from '../../shared-dist/config/challenge-types.js';
import { parseCurriculumStructure } from '../../curriculum/src/build-curriculum.js';
import { parseMDSync } from '../challenge-parser/parser/index.js';
import { getMetaData, updateMetaData } from './helpers/project-metadata.js';
import { getProjectPath } from './helpers/get-project-info.js';
import { ChallengeSeed, getStepTemplate } from './helpers/get-step-template.js';
import {
isTaskChallenge,
getTaskNumberFromTitle
} from './helpers/task-helpers';
import { getTemplate } from './helpers/get-challenge-template';
} from './helpers/task-helpers.js';
import { getTemplate } from './helpers/get-challenge-template.js';
interface Options {
stepNum: number;
@@ -53,8 +53,8 @@ const createStepFile = ({
challengeSeeds = [],
isFirstChallenge = false,
challengeLang
}: Options): ObjectID => {
const challengeId = new ObjectID();
}: Options): ObjectId => {
const challengeId = new ObjectId();
const template = getStepTemplate({
challengeId,
@@ -65,7 +65,6 @@ const createStepFile = ({
challengeLang
});
// eslint-disable-next-line @typescript-eslint/no-base-to-string
fs.writeFileSync(`${projectPath}${challengeId.toString()}.md`, template);
return challengeId;
@@ -85,8 +84,8 @@ const createQuizFile = ({
dashedName,
questionCount,
challengeLang
}: QuizOptions): ObjectID => {
const challengeId = new ObjectID();
}: QuizOptions): ObjectId => {
const challengeId = new ObjectId();
const challengeType = challengeTypes.quiz.toString();
const template = getTemplate(challengeType);
@@ -98,7 +97,7 @@ const createQuizFile = ({
questionCount,
challengeLang
});
// eslint-disable-next-line @typescript-eslint/no-base-to-string
fs.writeFileSync(`${projectPath}${challengeId.toString()}.md`, quizText);
return challengeId;
};
@@ -109,8 +108,8 @@ const createDialogueFile = ({
}: {
projectPath: string;
challengeLang: string;
}): ObjectID => {
const challengeId = new ObjectID();
}): ObjectId => {
const challengeId = new ObjectId();
const challengeType = challengeTypes.dialogue.toString();
const template = getTemplate(challengeType);
@@ -121,19 +120,19 @@ const createDialogueFile = ({
dashedName: 'dialogue-1-im-tom',
challengeLang
});
// eslint-disable-next-line @typescript-eslint/no-base-to-string
fs.writeFileSync(`${projectPath}${challengeId.toString()}.md`, dialogueText);
return challengeId;
};
interface InsertOptions {
stepNum: number;
stepId: ObjectID;
stepId: ObjectId;
}
interface InsertChallengeOptions {
index: number;
id: ObjectID;
id: ObjectId;
title: string;
}
@@ -145,7 +144,6 @@ async function insertChallengeIntoMeta({
const existingMeta = getMetaData();
const challengeOrder = [...existingMeta.challengeOrder];
// eslint-disable-next-line @typescript-eslint/no-base-to-string
challengeOrder.splice(index, 0, { id: id.toString(), title });
await updateMetaData({ ...existingMeta, challengeOrder });
}
@@ -153,7 +151,7 @@ async function insertChallengeIntoMeta({
async function insertStepIntoMeta({ stepNum, stepId }: InsertOptions) {
const existingMeta = getMetaData();
const oldOrder = [...existingMeta.challengeOrder];
// eslint-disable-next-line @typescript-eslint/no-base-to-string
oldOrder.splice(stepNum - 1, 0, { id: stepId.toString(), title: '' });
// rename all the files in challengeOrder
const challengeOrder = oldOrder.map(({ id }, index) => ({
-2
View File
@@ -2,10 +2,8 @@
"include": [
"*.ts",
"curriculum/*.test.ts",
"e2e/**/*.ts",
"tools/challenge-auditor/index.ts",
"tools/challenge-editor/**/*",
"tools/challenge-helper-scripts/**/*.ts",
"tools/scripts/**/*.ts",
"tools/daily-challenges/**/*.ts"
],
+3 -1
View File
@@ -1,7 +1,9 @@
{
"$schema": "https://turborepo.com/schema.json",
"tasks": {
"lint": {},
"lint": {
"dependsOn": ["@freecodecamp/shared#compile"]
},
"@freecodecamp/client#lint": {
"dependsOn": [
"@freecodecamp/curriculum#compile",