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