refactor: organise TypeScript config and migrate helpers (#44747)
* feat: allow more 1000 steps to be created at once * refactor: start migrating to typescript * refactor: delete-step to ts * refactor: migrated some helpers * refactor: migrate create-empty-steps * refactor: migrate create-step-between * refactor: finish migrating to TS * refactor: migrate tests * fix: ensure mock.restore is done after each test * fix: prevent double-tscing * fix: repair the tests * chore: use ts-node for scripts We don't need the performance boost of incremental compilation and ts-node is easier to work with * refactor: consolidate tsconfigs * refactor: replace gulp * fix: use ts-node for build-curriculum * fix: allow ts compilation of config * feat: create and use create:config script * fix: add /config to eslint projects * fix: remove gulp script
This commit is contained in:
committed by
GitHub
parent
82ca6d8441
commit
7216ca55cc
@ -3,8 +3,5 @@ client/static/**
|
||||
client/public/**
|
||||
api-server/src/public/**
|
||||
api-server/lib/**
|
||||
tools/scripts/build/ensure-env.js
|
||||
tools/scripts/lint/validate-keys.js
|
||||
tools/scripts/build/tsconfig.tsbuildinfo
|
||||
config/i18n/all-langs.js
|
||||
config/certification-settings.js
|
||||
|
@ -43,8 +43,8 @@
|
||||
"parserOptions": {
|
||||
"project": [
|
||||
"./tsconfig.json",
|
||||
"./tools/ui-components/tsconfig.json",
|
||||
"./tools/tsconfig.json"
|
||||
"./config/tsconfig.json",
|
||||
"./tools/ui-components/tsconfig.json"
|
||||
]
|
||||
},
|
||||
"extends": [
|
||||
|
2
.github/workflows/cypress.yml
vendored
2
.github/workflows/cypress.yml
vendored
@ -113,7 +113,7 @@ jobs:
|
||||
- name: Install and Build
|
||||
run: |
|
||||
npm ci
|
||||
npm run ensure-env
|
||||
npm run create:config
|
||||
npm run build:curriculum
|
||||
npm run build:server
|
||||
|
||||
|
4
.github/workflows/node.js-tests-upcoming.yml
vendored
4
.github/workflows/node.js-tests-upcoming.yml
vendored
@ -39,7 +39,7 @@ jobs:
|
||||
- name: Lint Source Files
|
||||
run: |
|
||||
npm ci
|
||||
npm run ensure-env
|
||||
npm run create:config
|
||||
npm run build:curriculum
|
||||
npm run lint
|
||||
|
||||
@ -73,7 +73,7 @@ jobs:
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
npm ci
|
||||
npm run ensure-env
|
||||
npm run create:config
|
||||
npm run build:curriculum
|
||||
|
||||
- name: Run Tests
|
||||
|
8
.github/workflows/node.js-tests.yml
vendored
8
.github/workflows/node.js-tests.yml
vendored
@ -33,7 +33,7 @@ jobs:
|
||||
- name: Lint Source Files
|
||||
run: |
|
||||
npm ci
|
||||
npm run ensure-env
|
||||
npm run create:config
|
||||
npm run build:curriculum
|
||||
npm run lint
|
||||
|
||||
@ -66,7 +66,7 @@ jobs:
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
npm ci
|
||||
npm run ensure-env
|
||||
npm run create:config
|
||||
npm run build:curriculum
|
||||
|
||||
- name: Run Tests
|
||||
@ -102,7 +102,7 @@ jobs:
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
npm ci
|
||||
npm run ensure-env
|
||||
npm run create:config
|
||||
npm run build:curriculum
|
||||
|
||||
- name: Run Tests
|
||||
@ -142,7 +142,7 @@ jobs:
|
||||
CLIENT_LOCALE: ${{ matrix.locale }}
|
||||
run: |
|
||||
npm ci
|
||||
npm run ensure-env
|
||||
npm run create:config
|
||||
npm run build:curriculum
|
||||
|
||||
- name: Run Tests
|
||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -165,9 +165,6 @@ config/curriculum.json
|
||||
config/i18n/all-langs.js
|
||||
config/certification-settings.js
|
||||
|
||||
tools/scripts/build/ensure-env.js
|
||||
tools/tsconfig.tsbuildinfo
|
||||
tools/scripts/lint/validate-keys.js
|
||||
|
||||
### vim ###
|
||||
# Swap
|
||||
|
@ -43,7 +43,7 @@ tasks:
|
||||
npm run seed &&
|
||||
mongo --eval "db.fsyncLock(); db.fsyncUnlock()"
|
||||
command: >
|
||||
npm run ensure-env &&
|
||||
npm run create:config &&
|
||||
npm run build:curriculum &&
|
||||
gp await-port 27017 &&
|
||||
npm run develop:server
|
||||
|
@ -10,5 +10,3 @@ config/certification-settings.js
|
||||
client/i18n/**/*.json
|
||||
docs/i18n
|
||||
**/package-lock.json
|
||||
tools/scripts/build/ensure-env.js
|
||||
tools/scripts/lint/validate-keys.js
|
@ -21,7 +21,7 @@ USER node
|
||||
WORKDIR /home/node/build
|
||||
COPY --chown=node:node . .
|
||||
RUN npm ci
|
||||
# we don't need to separately run ensure-env, since it gets called as part of
|
||||
# we don't need to separately run create:config, since it gets called as part of
|
||||
# build:client
|
||||
RUN npm run build:client
|
||||
|
||||
|
@ -19,18 +19,18 @@
|
||||
"author": "freeCodeCamp <team@freecodecamp.org>",
|
||||
"main": "none",
|
||||
"scripts": {
|
||||
"prebuild": "tsc -p ../tools/ && node ../tools/scripts/build/ensure-env.js && npm run build:workers -- --env production",
|
||||
"prebuild": "npm --prefix ../ run create:config && npm run build:workers -- --env production",
|
||||
"build": "cross-env NODE_OPTIONS=\"--max-old-space-size=7168\" gatsby build --prefix-paths",
|
||||
"build:workers": "cross-env NODE_OPTIONS=\"--max-old-space-size=7168\" webpack --config ./webpack-workers.js",
|
||||
"clean": "gatsby clean",
|
||||
"predevelop": "tsc -p ../tools/ && node ../tools/scripts/build/ensure-env.js && npm run build:workers -- --env development",
|
||||
"predevelop": "npm --prefix ../ run create:config && npm run build:workers -- --env development",
|
||||
"develop": "cross-env NODE_OPTIONS=\"--max-old-space-size=5000\" gatsby develop --inspect=9230",
|
||||
"lint": "node ./i18n/schema-validation.js",
|
||||
"serve": "gatsby serve -p 8000",
|
||||
"serve-ci": "serve -l 8000 -c ../serve.json public",
|
||||
"prestand-alone": "npm run prebuild",
|
||||
"stand-alone": "gatsby develop",
|
||||
"validate-keys": "tsc -p ../tools/ && node ../tools/scripts/lint/validate-keys.js"
|
||||
"validate-keys": "ts-node --project ../tsconfig.json ../tools/scripts/lint/validate-keys"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/plugin-proposal-export-default-from": "7.16.7",
|
||||
@ -145,6 +145,7 @@
|
||||
"redux-mock-store": "1.5.4",
|
||||
"redux-saga-test-plan": "4.0.4",
|
||||
"serve": "13.0.2",
|
||||
"ts-node": "^10.4.0",
|
||||
"webpack": "5.67.0",
|
||||
"webpack-cli": "4.9.2"
|
||||
}
|
||||
|
8
config/tsconfig.json
Normal file
8
config/tsconfig.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"include": ["**/*.ts"],
|
||||
"extends": "../tsconfig-base.json",
|
||||
"compilerOptions": {
|
||||
"noEmit": false,
|
||||
"module": "CommonJS"
|
||||
}
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
const gulp = require('gulp');
|
||||
const through2 = require('through2');
|
||||
|
||||
const lintMarkdown = require('../tools/scripts/lint');
|
||||
const { testedLang } = require('./utils');
|
||||
|
||||
/**
|
||||
* Tasks
|
||||
**/
|
||||
|
||||
function lint() {
|
||||
return gulp.src(globLang(testedLang()), { read: false }).pipe(
|
||||
through2.obj(function obj(file, enc, next) {
|
||||
lintMarkdown(file, next);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper functions
|
||||
**/
|
||||
|
||||
function globLang(lang) {
|
||||
return `./challenges/${lang}/**/*.md`;
|
||||
}
|
||||
|
||||
gulp.task('lint', lint);
|
8
curriculum/lint-localized.js
Normal file
8
curriculum/lint-localized.js
Normal file
@ -0,0 +1,8 @@
|
||||
var glob = require('glob');
|
||||
const lint = require('../tools/scripts/lint');
|
||||
const { testedLang } = require('./utils');
|
||||
|
||||
glob(`challenges/${testedLang()}/**/*.md`, (err, files) => {
|
||||
if (!files.length) throw Error('No files found');
|
||||
files.forEach(file => lint({ path: file }));
|
||||
});
|
@ -18,13 +18,13 @@
|
||||
"homepage": "https://github.com/freeCodeCamp/freeCodeCamp#readme",
|
||||
"author": "freeCodeCamp <team@freecodecamp.org>",
|
||||
"scripts": {
|
||||
"build": "node ../tools/scripts/build/build-curriculum.js",
|
||||
"create-empty-steps": "cross-env CALLING_DIR=$INIT_CWD node ../tools/challenge-helper-scripts/create-empty-steps",
|
||||
"create-next-step": "cross-env CALLING_DIR=$INIT_CWD node ../tools/challenge-helper-scripts/create-next-step",
|
||||
"create-step-between": "cross-env CALLING_DIR=$INIT_CWD node ../tools/challenge-helper-scripts/create-step-between",
|
||||
"delete-step": "cross-env CALLING_DIR=$INIT_CWD node ../tools/challenge-helper-scripts/delete-step",
|
||||
"lint": "gulp lint",
|
||||
"reorder-steps": "cross-env CALLING_DIR=$INIT_CWD node ../tools/challenge-helper-scripts/reorder-steps",
|
||||
"build": "ts-node --project ../tsconfig.json ../tools/scripts/build/build-curriculum",
|
||||
"create-empty-steps": "cross-env CALLING_DIR=$INIT_CWD ts-node --project ../tsconfig.json ../tools/challenge-helper-scripts/create-empty-steps",
|
||||
"create-next-step": "cross-env CALLING_DIR=$INIT_CWD ts-node --project ../tsconfig.json ../tools/challenge-helper-scripts/create-next-step",
|
||||
"create-step-between": "cross-env CALLING_DIR=$INIT_CWD ts-node --project ../tsconfig.json ../tools/challenge-helper-scripts/create-step-between",
|
||||
"delete-step": "cross-env CALLING_DIR=$INIT_CWD ts-node --project ../tsconfig.json ../tools/challenge-helper-scripts/delete-step",
|
||||
"lint": "ts-node --project ../tsconfig.json lint-localized",
|
||||
"reorder-steps": "cross-env CALLING_DIR=$INIT_CWD ts-node --project ../tsconfig.json ../tools/challenge-helper-scripts/reorder-steps",
|
||||
"test": "mocha --delay --exit --reporter progress --bail",
|
||||
"test:full-output": "cross-env FULL_OUTPUT=true mocha --delay --reporter progress"
|
||||
},
|
||||
@ -45,7 +45,6 @@
|
||||
"chai": "4.3.4",
|
||||
"cross-env": "7.0.3",
|
||||
"css": "3.0.0",
|
||||
"gulp": "4.0.2",
|
||||
"invariant": "2.2.4",
|
||||
"joi": "17.5.0",
|
||||
"joi-objectid": "3.0.1",
|
||||
|
@ -482,7 +482,7 @@ Provisioning VMs with the Code
|
||||
7. Build the server
|
||||
|
||||
```console
|
||||
npm run ensure-env && npm run build:curriculum && npm run build:server
|
||||
npm run create:config && npm run build:curriculum && npm run build:server
|
||||
```
|
||||
|
||||
8. Start Instances
|
||||
@ -528,7 +528,7 @@ npm ci
|
||||
3. Build the server
|
||||
|
||||
```console
|
||||
npm run ensure-env && npm run build:curriculum && npm run build:server
|
||||
npm run create:config && npm run build:curriculum && npm run build:server
|
||||
```
|
||||
|
||||
4. Start Instances
|
||||
|
@ -19,7 +19,7 @@ Most of files for translating the platform are located in the [`client/i18n`](ht
|
||||
|
||||
```console
|
||||
config/i18n
|
||||
└── all-langs.js
|
||||
└── all-langs.ts
|
||||
...
|
||||
client/i18n
|
||||
├── configForTests.js
|
||||
@ -57,7 +57,7 @@ Most of files for translating the platform are located in the [`client/i18n`](ht
|
||||
│ └── trending.json
|
||||
├── locales.test.js
|
||||
├── schema-validation.js
|
||||
└── validate-keys.js
|
||||
└── validate-keys.ts
|
||||
```
|
||||
|
||||
Some of these files are translated on our translation platform (Crowdin), some are not.
|
||||
|
4071
package-lock.json
generated
4071
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
21
package.json
21
package.json
@ -33,20 +33,20 @@
|
||||
],
|
||||
"scripts": {
|
||||
"analyze-bundle": "webpack-bundle-analyzer",
|
||||
"prebuild": "npm run ensure-env",
|
||||
"prebuild": "npm run create:config",
|
||||
"build": "npm-run-all -p build:*",
|
||||
"build-workers": "cd ./client && npm run prebuild",
|
||||
"build:client": "cd ./client && npm run build",
|
||||
"build:curriculum": "cd ./curriculum && npm run build",
|
||||
"build:server": "cd ./api-server && npm run build",
|
||||
"clean": "npm-run-all clean:build-files clean:client clean:server clean:packages",
|
||||
"clean": "npm-run-all clean:client clean:server clean:packages",
|
||||
"clean-and-develop": "npm run clean && npm ci && npm run develop",
|
||||
"clean:build-files": "shx rm -f ./tools/tsconfig.tsbuildinfo",
|
||||
"clean:client": "cd ./client && npm run clean",
|
||||
"clean:curriculum": "shx rm ./config/curriculum.json",
|
||||
"clean:gatsby-site": "npm run clean:client",
|
||||
"clean:packages": "shx rm -rf **/node_modules",
|
||||
"clean:server": "shx rm -rf ./api-server/lib",
|
||||
"create:config": "tsc -p config && npm run ensure-env",
|
||||
"precypress": "node ./cypress-install.js",
|
||||
"cypress": "cypress",
|
||||
"cypress:dev:run": "npm run cypress -- run",
|
||||
@ -55,7 +55,7 @@
|
||||
"cypress:install-build-tools": "sh ./cypress-install.sh",
|
||||
"cypress:prd:run": "npm run cypress -- run",
|
||||
"cypress:prd:watch": "npm run cypress -- open",
|
||||
"predevelop": "npm run ensure-env",
|
||||
"predevelop": "npm run create:config",
|
||||
"develop": "npm-run-all build:curriculum -p develop:*",
|
||||
"develop:client": "cd ./client && npm run develop",
|
||||
"develop:server": "npm run predevelop && cd ./api-server && npm run develop",
|
||||
@ -65,26 +65,26 @@
|
||||
"e2e:dev:watch": "start-test develop ':3000/status/ping|8000' cypress:dev:watch",
|
||||
"e2e:prd:run": "npm run build && start-test ':3000/status/ping|8000' cypress:dev:run",
|
||||
"e2e:prd:watch": "npm run build && start-test ':3000/status/ping|8000' cypress:dev:watch",
|
||||
"ensure-env": "cd ./tools/ && tsc --incremental && cross-env DEBUG=fcc:* node ./scripts/build/ensure-env.js",
|
||||
"ensure-env": "cross-env DEBUG=fcc:* ts-node ./tools/scripts/build/ensure-env.ts",
|
||||
"format": "run-s format:eslint format:prettier",
|
||||
"format:eslint": "eslint . --fix",
|
||||
"format:prettier": "prettier --write .",
|
||||
"hooks:install": "node node_modules/husky/husky.js install",
|
||||
"hooks:uninstall": "node node_modules/husky/husky.js uninstall",
|
||||
"lint": "npm-run-all ensure-env -p lint:*",
|
||||
"lint": "npm-run-all create:config -p lint:*",
|
||||
"lint:challenges": "cd ./curriculum && npm run lint",
|
||||
"lint:js": "eslint --max-warnings 0 .",
|
||||
"lint:ts": "tsc && tsc -p tools/ui-components && tsc -p tools",
|
||||
"lint:ts": "tsc & tsc -p config & tsc -p tools/ui-components",
|
||||
"lint:prettier": "prettier --list-different .",
|
||||
"seed": "cross-env DEBUG=fcc:* node ./tools/scripts/seed/seedAuthUser",
|
||||
"seed:certified-user": "cross-env DEBUG=fcc:* node ./tools/scripts/seed/seedAuthUser certUser",
|
||||
"serve:client": "cd ./client && npm run serve",
|
||||
"serve:client-ci": "cd ./client && npm run serve-ci",
|
||||
"start": "npm-run-all ensure-env -p develop:server serve:client",
|
||||
"start-ci": "npm-run-all ensure-env -p start:server serve:client-ci",
|
||||
"start": "npm-run-all create:config -p develop:server serve:client",
|
||||
"start-ci": "npm-run-all create:config -p start:server serve:client-ci",
|
||||
"start:server": "cd ./api-server && npm start",
|
||||
"storybook": "cd ./tools/ui-components && npm run storybook",
|
||||
"test": "run-s ensure-env build:curriculum build-workers test:*",
|
||||
"test": "run-s create:config build:curriculum build-workers test:*",
|
||||
"test:source": "jest",
|
||||
"test:curriculum": "cd ./curriculum && npm test",
|
||||
"test-curriculum-full-output": "cd ./curriculum && npm run test:full-output",
|
||||
@ -171,6 +171,7 @@
|
||||
"process": "0.11.10",
|
||||
"shx": "0.3.4",
|
||||
"start-server-and-test": "1.14.0",
|
||||
"ts-node": "^10.4.0",
|
||||
"typescript": "4.5.5",
|
||||
"webpack-bundle-analyzer": "4.5.0"
|
||||
}
|
||||
|
@ -1,38 +0,0 @@
|
||||
const { getArgValues } = require('./helpers/get-arg-values');
|
||||
const { getExistingStepNums } = require('./helpers/get-existing-step-nums');
|
||||
const { getProjectPath } = require('./helpers/get-project-path');
|
||||
const { createStepFile, reorderSteps } = require('./utils');
|
||||
|
||||
const anyStepExists = (steps, stepsToFind) =>
|
||||
stepsToFind.some(num => steps.includes(num));
|
||||
|
||||
const projectPath = getProjectPath();
|
||||
const args = getArgValues(process.argv);
|
||||
|
||||
let { num, start } = args;
|
||||
if (!start) {
|
||||
throw `No steps created. start arg val must be specified.`;
|
||||
}
|
||||
if (!num) {
|
||||
throw `No steps created. num arg val must be specified.`;
|
||||
}
|
||||
num = parseInt(num, 10);
|
||||
const stepStart = parseInt(start, 10);
|
||||
|
||||
if (num < 1 || num > 100) {
|
||||
throw `No steps created. arg 'num' must be between 1 and 100 inclusive`;
|
||||
}
|
||||
|
||||
const maxStepNum = stepStart + num - 1;
|
||||
|
||||
const existingSteps = getExistingStepNums(projectPath);
|
||||
|
||||
if (anyStepExists(existingSteps, [start, maxStepNum])) {
|
||||
throw `Step not created. At least one of the steps specified between ${start} and ${maxStepNum} already exists.`;
|
||||
}
|
||||
|
||||
for (let stepNum = stepStart; stepNum <= maxStepNum; stepNum++) {
|
||||
createStepFile({ stepNum, projectPath });
|
||||
}
|
||||
console.log(`Sucessfully added ${num} steps`);
|
||||
reorderSteps();
|
38
tools/challenge-helper-scripts/create-empty-steps.ts
Normal file
38
tools/challenge-helper-scripts/create-empty-steps.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import { getArgValues } from './helpers/get-arg-values';
|
||||
import { getExistingStepNums } from './helpers/get-existing-step-nums';
|
||||
import { getProjectPath } from './helpers/get-project-path';
|
||||
import { createStepFile, reorderSteps } from './utils';
|
||||
|
||||
const anyStepExists = (steps: number[], stepsToFind: number[]) =>
|
||||
stepsToFind.some(num => steps.includes(num));
|
||||
|
||||
const projectPath = getProjectPath();
|
||||
const args = getArgValues(process.argv);
|
||||
|
||||
const { num: numString, start: startString } = args;
|
||||
if (!startString) {
|
||||
throw `No steps created. start arg val must be specified.`;
|
||||
}
|
||||
if (!numString) {
|
||||
throw `No steps created. num arg val must be specified.`;
|
||||
}
|
||||
const num = parseInt(numString, 10);
|
||||
const stepStart = parseInt(startString, 10);
|
||||
|
||||
if (num < 1 || num > 1000) {
|
||||
throw `No steps created. arg 'num' must be between 1 and 1000 inclusive`;
|
||||
}
|
||||
|
||||
const maxStepNum = stepStart + num - 1;
|
||||
|
||||
const existingSteps = getExistingStepNums(projectPath);
|
||||
|
||||
if (anyStepExists(existingSteps, [stepStart, maxStepNum])) {
|
||||
throw `Step not created. At least one of the steps specified between ${startString} and ${maxStepNum} already exists.`;
|
||||
}
|
||||
|
||||
for (let stepNum = stepStart; stepNum <= maxStepNum; stepNum++) {
|
||||
createStepFile({ stepNum, projectPath });
|
||||
}
|
||||
console.log(`Sucessfully added ${numString} steps`);
|
||||
reorderSteps();
|
@ -1,8 +1,6 @@
|
||||
const {
|
||||
getLastStepFileContent
|
||||
} = require('./helpers/get-last-step-file-content');
|
||||
const { getProjectPath } = require('./helpers/get-project-path');
|
||||
const { reorderSteps, createStepFile } = require('./utils');
|
||||
import { getLastStepFileContent } from './helpers/get-last-step-file-content';
|
||||
import { getProjectPath } from './helpers/get-project-path';
|
||||
import { reorderSteps, createStepFile } from './utils';
|
||||
|
||||
const projectPath = getProjectPath();
|
||||
const { nextStepNum, challengeSeeds } = getLastStepFileContent();
|
@ -4,9 +4,10 @@ import path from 'path';
|
||||
import { prompt } from 'inquirer';
|
||||
import { format } from 'prettier';
|
||||
|
||||
import ObjectID from 'bson-objectid';
|
||||
import { SuperBlocks } from '../../config/certification-settings';
|
||||
import { blockNameify } from '../../utils/block-nameify';
|
||||
import { createStepFile } from './utils.js';
|
||||
import { createStepFile } from './utils';
|
||||
|
||||
const helpCategories = ['HTML-CSS', 'JavaScript', 'Python'] as const;
|
||||
|
||||
@ -135,7 +136,7 @@ async function createMetaJson(
|
||||
block: string,
|
||||
title: string,
|
||||
order: number,
|
||||
challengeId: string
|
||||
challengeId: ObjectID
|
||||
) {
|
||||
const metaDir = path.resolve(__dirname, '../../curriculum/challenges/_meta');
|
||||
const newMeta = await parseJson<Meta>('./base-meta.json');
|
||||
@ -144,7 +145,7 @@ async function createMetaJson(
|
||||
newMeta.order = order;
|
||||
newMeta.superOrder = Object.values(SuperBlocks).indexOf(superBlock) + 1;
|
||||
newMeta.superBlock = superBlock;
|
||||
newMeta.challengeOrder = [[challengeId, 'Step 1']];
|
||||
newMeta.challengeOrder = [[challengeId.toString(), 'Step 1']];
|
||||
const newMetaDir = path.resolve(metaDir, block);
|
||||
if (!existsSync(newMetaDir)) {
|
||||
await fs.mkdir(newMetaDir);
|
||||
@ -185,7 +186,7 @@ This is a test for the new project-based curriculum.
|
||||
async function createFirstChallenge(
|
||||
superBlock: SuperBlocks,
|
||||
block: string
|
||||
): Promise<string> {
|
||||
): Promise<ObjectID> {
|
||||
const superBlockId = (Object.values(SuperBlocks).indexOf(superBlock) + 1)
|
||||
.toString()
|
||||
.padStart(2, '0');
|
||||
|
@ -1,10 +1,10 @@
|
||||
const { getArgValues } = require('./helpers/get-arg-values');
|
||||
const { getExistingStepNums } = require('./helpers/get-existing-step-nums');
|
||||
const { getProjectPath } = require('./helpers/get-project-path');
|
||||
const { padWithLeadingZeros } = require('./helpers/pad-with-leading-zeros');
|
||||
const { createStepFile, getChallengeSeeds, reorderSteps } = require('./utils');
|
||||
import { getArgValues } from './helpers/get-arg-values';
|
||||
import { getExistingStepNums } from './helpers/get-existing-step-nums';
|
||||
import { getProjectPath } from './helpers/get-project-path';
|
||||
import { padWithLeadingZeros } from './helpers/pad-with-leading-zeros';
|
||||
import { createStepFile, getChallengeSeeds, reorderSteps } from './utils';
|
||||
|
||||
const allStepsExist = (steps, stepsToFind) =>
|
||||
const allStepsExist = (steps: number[], stepsToFind: number[]) =>
|
||||
stepsToFind.every(num => steps.includes(num));
|
||||
|
||||
const projectPath = getProjectPath();
|
@ -1,17 +1,17 @@
|
||||
const fs = require('fs');
|
||||
const { getArgValues } = require('./helpers/get-arg-values');
|
||||
const { getExistingStepNums } = require('./helpers/get-existing-step-nums');
|
||||
const { getProjectPath } = require('./helpers/get-project-path');
|
||||
const { padWithLeadingZeros } = require('./helpers/pad-with-leading-zeros');
|
||||
const { reorderSteps } = require('./utils');
|
||||
import fs from 'fs';
|
||||
import { getArgValues } from './helpers/get-arg-values';
|
||||
import { getExistingStepNums } from './helpers/get-existing-step-nums';
|
||||
import { getProjectPath } from './helpers/get-project-path';
|
||||
import { padWithLeadingZeros } from './helpers/pad-with-leading-zeros';
|
||||
import { reorderSteps } from './utils';
|
||||
|
||||
const stepExists = (steps, stepToFind) => steps.includes(stepToFind);
|
||||
const stepExists = (steps: number[], stepToFind: number) =>
|
||||
steps.includes(stepToFind);
|
||||
|
||||
const projectPath = getProjectPath();
|
||||
const args = getArgValues(process.argv);
|
||||
|
||||
let { num } = args;
|
||||
num = parseInt(num, 10);
|
||||
const num = parseInt(args.num, 10);
|
||||
|
||||
if (!Number.isInteger(num) || num < 1) {
|
||||
throw 'Step not deleted. Step num must be a number greater than 0.';
|
@ -1 +0,0 @@
|
||||
export declare function getArgValues(argv: string[]): Record<string, string>;
|
@ -1,4 +1,4 @@
|
||||
const { getArgValues } = require('./get-arg-values');
|
||||
import { getArgValues } from './get-arg-values';
|
||||
|
||||
describe('getArgValues helper', () => {
|
||||
it('should be able to run if there are no values to process', () => {
|
@ -1,6 +1,6 @@
|
||||
// Creates an object with the values starting at the third position of argv,
|
||||
// ['lorem', 'ipsum', 'one=1', 'two=2', ...] => { one: 1, two: 2, ...}
|
||||
function getArgValues(argv = []) {
|
||||
function getArgValues(argv: string[] = []): Record<string, string> {
|
||||
return argv.slice(2).reduce((argsObj, arg) => {
|
||||
const [argument, value] = arg.replace(/\s/g, '').split('=');
|
||||
|
||||
@ -12,4 +12,4 @@ function getArgValues(argv = []) {
|
||||
}, {});
|
||||
}
|
||||
|
||||
exports.getArgValues = getArgValues;
|
||||
export { getArgValues };
|
@ -1 +0,0 @@
|
||||
export declare function getExistingStepNums(options: string[]): number[];
|
@ -1,5 +1,5 @@
|
||||
const mock = require('mock-fs');
|
||||
const { getExistingStepNums } = require('./get-existing-step-nums');
|
||||
import mock from 'mock-fs';
|
||||
import { getExistingStepNums } from './get-existing-step-nums';
|
||||
|
||||
// NOTE:
|
||||
// Use `console.log()` before mocking the filesystem or use
|
||||
@ -63,12 +63,12 @@ describe('getExistingStepNums helper', () => {
|
||||
|
||||
const folder = `${process.cwd()}/mock-project/`;
|
||||
const steps = getExistingStepNums(folder);
|
||||
const expected = [];
|
||||
const expected: number[] = [];
|
||||
|
||||
expect(steps).toEqual(expected);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
afterEach(() => {
|
||||
mock.restore();
|
||||
});
|
||||
});
|
@ -1,30 +1,30 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
// Generates an array with the output of processing filenames with an expected
|
||||
// format (`step-###.md`).
|
||||
// ['step-001.md', 'step-002.md'] => [1, 2]
|
||||
function getExistingStepNums(projectPath) {
|
||||
function getExistingStepNums(projectPath: string): number[] {
|
||||
return fs.readdirSync(projectPath).reduce((stepNums, fileName) => {
|
||||
if (
|
||||
path.extname(fileName).toLowerCase() === '.md' &&
|
||||
!fileName.endsWith('final.md')
|
||||
) {
|
||||
let stepNum = fileName.split('.')[0].split('-')[1];
|
||||
const stepNumString = fileName.split('.')[0].split('-')[1];
|
||||
|
||||
if (!/^\d{3}$/.test(stepNum)) {
|
||||
if (!/^\d{3}$/.test(stepNumString)) {
|
||||
throw (
|
||||
`Step not created. File ${fileName} has a step number containing non-digits.` +
|
||||
' Please run reorder-steps script first.'
|
||||
);
|
||||
}
|
||||
|
||||
stepNum = parseInt(stepNum, 10);
|
||||
const stepNum = parseInt(stepNumString, 10);
|
||||
stepNums.push(stepNum);
|
||||
}
|
||||
|
||||
return stepNums;
|
||||
}, []);
|
||||
}, [] as number[]);
|
||||
}
|
||||
|
||||
exports.getExistingStepNums = getExistingStepNums;
|
||||
export { getExistingStepNums };
|
@ -1 +0,0 @@
|
||||
export declare function getLastStepFileContent(): Record<string, unknown>;
|
@ -1,5 +1,5 @@
|
||||
const mock = require('mock-fs');
|
||||
const { getLastStepFileContent } = require('./get-last-step-file-content');
|
||||
import mock from 'mock-fs';
|
||||
import { getLastStepFileContent } from './get-last-step-file-content';
|
||||
|
||||
jest.mock('./get-project-path', () => {
|
||||
return {
|
||||
@ -30,8 +30,6 @@ describe('getLastStepFileContent helper', () => {
|
||||
expect(() => {
|
||||
getLastStepFileContent();
|
||||
}).toThrow();
|
||||
|
||||
mock.restore();
|
||||
});
|
||||
|
||||
it('should return information if steps count is correct', () => {
|
||||
@ -51,7 +49,9 @@ describe('getLastStepFileContent helper', () => {
|
||||
};
|
||||
|
||||
expect(getLastStepFileContent()).toEqual(expected);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
mock.restore();
|
||||
});
|
||||
});
|
@ -1,13 +1,17 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { getChallengeSeeds } = require('../utils');
|
||||
const { getProjectPath } = require('./get-project-path');
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { getChallengeSeeds } from '../utils';
|
||||
import { getProjectPath } from './get-project-path';
|
||||
import { ChallengeSeed } from './get-step-template';
|
||||
|
||||
// Looks up the last file found with format `step-###.md` in a directory and
|
||||
// returns associated information to it. At the same time validates that the
|
||||
// number of files match the names used to name these.
|
||||
function getLastStepFileContent() {
|
||||
const filesArr = [];
|
||||
function getLastStepFileContent(): {
|
||||
challengeSeeds: Record<string, ChallengeSeed>;
|
||||
nextStepNum: number;
|
||||
} {
|
||||
const filesArr: string[] = [];
|
||||
const projectPath = getProjectPath();
|
||||
|
||||
fs.readdirSync(projectPath).forEach(fileName => {
|
||||
@ -20,16 +24,18 @@ function getLastStepFileContent() {
|
||||
});
|
||||
|
||||
const fileName = filesArr[filesArr.length - 1];
|
||||
let lastStepFileNum = fileName.split('.')[0].split('-')[1];
|
||||
lastStepFileNum = parseInt(lastStepFileNum, 10);
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
|
||||
const lastStepFileString: string = fileName.split('.')[0].split('-')[1];
|
||||
const lastStepFileNum = parseInt(lastStepFileString, 10);
|
||||
if (filesArr.length !== lastStepFileNum) {
|
||||
throw `Error: The last file step is ${lastStepFileNum} and there are ${filesArr.length} files.`;
|
||||
}
|
||||
|
||||
return {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
challengeSeeds: getChallengeSeeds(projectPath + fileName),
|
||||
nextStepNum: lastStepFileNum + 1
|
||||
};
|
||||
}
|
||||
|
||||
exports.getLastStepFileContent = getLastStepFileContent;
|
||||
export { getLastStepFileContent };
|
@ -1,4 +0,0 @@
|
||||
export declare function getProjectMetaPath(
|
||||
curriculumPath: string,
|
||||
projectName: string
|
||||
): string;
|
@ -1,19 +1,7 @@
|
||||
const { getProjectMetaPath } = require('./get-project-meta-path');
|
||||
import { getProjectMetaPath } from './get-project-meta-path';
|
||||
|
||||
describe('getProjectMetaPath helper', () => {
|
||||
it('should throw if args are invalid', () => {
|
||||
expect(() => {
|
||||
getProjectMetaPath();
|
||||
}).toThrow();
|
||||
|
||||
expect(() => {
|
||||
getProjectMetaPath('test-curriculum', {});
|
||||
}).toThrow();
|
||||
|
||||
expect(() => {
|
||||
getProjectMetaPath([], []);
|
||||
}).toThrow();
|
||||
|
||||
expect(() => {
|
||||
getProjectMetaPath('', '');
|
||||
}).toThrow();
|
@ -1,7 +1,10 @@
|
||||
const path = require('path');
|
||||
import path from 'path';
|
||||
|
||||
// Returns the path of the meta file associated to arguments provided.
|
||||
const getProjectMetaPath = (curriculumPath, projectName) => {
|
||||
const getProjectMetaPath = (
|
||||
curriculumPath: string,
|
||||
projectName: string
|
||||
): string => {
|
||||
if (typeof curriculumPath !== 'string' || typeof projectName !== 'string') {
|
||||
throw `${curriculumPath} and ${projectName} should be of type string`;
|
||||
}
|
||||
@ -19,4 +22,4 @@ const getProjectMetaPath = (curriculumPath, projectName) => {
|
||||
);
|
||||
};
|
||||
|
||||
exports.getProjectMetaPath = getProjectMetaPath;
|
||||
export { getProjectMetaPath };
|
@ -1 +0,0 @@
|
||||
export declare function getMetaData(string): Record<string, unknown>;
|
@ -1,16 +0,0 @@
|
||||
const fs = require('fs');
|
||||
|
||||
// Process the contents of a argument (json) to an Object
|
||||
function getMetaData(file) {
|
||||
let metaData;
|
||||
|
||||
try {
|
||||
metaData = fs.readFileSync(file);
|
||||
} catch (err) {
|
||||
throw `No _meta.json file exists at ${file}`;
|
||||
}
|
||||
|
||||
return JSON.parse(metaData);
|
||||
}
|
||||
|
||||
exports.getMetaData = getMetaData;
|
@ -1,3 +1,6 @@
|
||||
import { readFileSync } from 'fs';
|
||||
import { getMetaData } from './get-project-path-metadata';
|
||||
|
||||
jest.mock('fs', () => {
|
||||
return {
|
||||
readFileSync: jest.fn()
|
||||
@ -5,11 +8,11 @@ jest.mock('fs', () => {
|
||||
});
|
||||
|
||||
const mockPath = '/mock/path';
|
||||
const { readFileSync } = require('fs');
|
||||
const { getMetaData } = require('./get-project-path-metadata');
|
||||
|
||||
describe('getMetaData helper', () => {
|
||||
it('should process requested file', () => {
|
||||
// @ts-expect-error - readFileSync is mocked
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
||||
readFileSync.mockImplementation(() => '{"name": "Test Project"}');
|
||||
|
||||
const expected = {
|
||||
@ -20,6 +23,8 @@ describe('getMetaData helper', () => {
|
||||
});
|
||||
|
||||
it('should throw if file is not found', () => {
|
||||
// @ts-expect-error - readFileSync is mocked
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
||||
readFileSync.mockImplementation(() => {
|
||||
throw new Error();
|
||||
});
|
@ -0,0 +1,16 @@
|
||||
import fs from 'fs';
|
||||
|
||||
// Process the contents of a argument (json) to an Object
|
||||
function getMetaData(file: string): Record<string, unknown> {
|
||||
let metaData;
|
||||
|
||||
try {
|
||||
metaData = fs.readFileSync(file, 'utf8');
|
||||
} catch (err) {
|
||||
throw `No _meta.json file exists at ${file}`;
|
||||
}
|
||||
|
||||
return JSON.parse(metaData) as Record<string, unknown>;
|
||||
}
|
||||
|
||||
export { getMetaData };
|
@ -1 +0,0 @@
|
||||
export declare function getProjectPath(): string;
|
@ -1,4 +1,4 @@
|
||||
const { getProjectPath } = require('./get-project-path');
|
||||
import { getProjectPath } from './get-project-path';
|
||||
|
||||
describe('getProjectPath helper', () => {
|
||||
it('should return the calling dir path', () => {
|
@ -1,8 +1,8 @@
|
||||
const path = require('path');
|
||||
import path from 'path';
|
||||
|
||||
// Returns the path of a project
|
||||
function getProjectPath() {
|
||||
function getProjectPath(): string {
|
||||
return (process.env.CALLING_DIR || process.cwd()) + path.sep;
|
||||
}
|
||||
|
||||
exports.getProjectPath = getProjectPath;
|
||||
export { getProjectPath };
|
@ -1,8 +0,0 @@
|
||||
type StepOptions = {
|
||||
challengeId: string;
|
||||
challengeSeeds: unknown;
|
||||
stepBetween: boolean;
|
||||
stepNum: number;
|
||||
};
|
||||
|
||||
export declare function getStepTemplate(options: StepOptions): string;
|
@ -1,4 +1,5 @@
|
||||
const { getStepTemplate } = require('./get-step-template');
|
||||
import ObjectID from 'bson-objectid';
|
||||
import { getStepTemplate } from './get-step-template';
|
||||
|
||||
// Note: evaluates at highlevel the process, but seedHeads and seedTails could
|
||||
// be tested if more specifics are needed.
|
||||
@ -34,7 +35,7 @@ Test 1
|
||||
\`\`\`\n`;
|
||||
|
||||
const props = {
|
||||
challengeId: '60d4ebe4801158d1abe1b18f',
|
||||
challengeId: new ObjectID('60d4ebe4801158d1abe1b18f'),
|
||||
challengeSeeds: {
|
||||
indexhtml: {
|
||||
contents: '',
|
@ -1,14 +1,15 @@
|
||||
const { insertErms } = require('./insert-erms');
|
||||
import ObjectID from 'bson-objectid';
|
||||
import { insertErms } from './insert-erms';
|
||||
|
||||
// Builds a block
|
||||
function getCodeBlock(label, content) {
|
||||
function getCodeBlock(label: string, content?: string) {
|
||||
return `\`\`\`${label}
|
||||
${typeof content !== 'undefined' ? content : ''}
|
||||
\`\`\`\n`;
|
||||
}
|
||||
|
||||
// Builds a section
|
||||
function getSeedSection(content, label) {
|
||||
function getSeedSection(content: string, label: string) {
|
||||
return content
|
||||
? `
|
||||
|
||||
@ -18,15 +19,30 @@ ${content}`
|
||||
: '';
|
||||
}
|
||||
|
||||
type StepOptions = {
|
||||
challengeId: ObjectID;
|
||||
challengeSeeds: Record<string, ChallengeSeed>;
|
||||
stepBetween: boolean;
|
||||
stepNum: number;
|
||||
};
|
||||
|
||||
export interface ChallengeSeed {
|
||||
contents: string;
|
||||
ext: string;
|
||||
editableRegionBoundaries: number[];
|
||||
head?: string;
|
||||
tail?: string;
|
||||
}
|
||||
|
||||
// Build the base markdown for a step
|
||||
function getStepTemplate({
|
||||
challengeId,
|
||||
challengeSeeds,
|
||||
stepBetween,
|
||||
stepNum
|
||||
}) {
|
||||
}: StepOptions): string {
|
||||
const seedTexts = Object.values(challengeSeeds)
|
||||
.map(({ contents, ext, editableRegionBoundaries }) => {
|
||||
.map(({ contents, ext, editableRegionBoundaries }: ChallengeSeed) => {
|
||||
let fullContents = contents;
|
||||
if (editableRegionBoundaries.length >= 2) {
|
||||
fullContents = insertErms(contents, editableRegionBoundaries);
|
||||
@ -36,13 +52,13 @@ function getStepTemplate({
|
||||
.join('\n');
|
||||
|
||||
const seedHeads = Object.values(challengeSeeds)
|
||||
.filter(({ head }) => head)
|
||||
.map(({ ext, head }) => getCodeBlock(ext, head))
|
||||
.filter(({ head }: ChallengeSeed) => head)
|
||||
.map(({ ext, head }: ChallengeSeed) => getCodeBlock(ext, head))
|
||||
.join('\n');
|
||||
|
||||
const seedTails = Object.values(challengeSeeds)
|
||||
.filter(({ tail }) => tail)
|
||||
.map(({ ext, tail }) => getCodeBlock(ext, tail))
|
||||
.filter(({ tail }: ChallengeSeed) => tail)
|
||||
.map(({ ext, tail }: ChallengeSeed) => getCodeBlock(ext, tail))
|
||||
.join('\n');
|
||||
|
||||
const descStepNum = stepBetween ? stepNum + 1 : stepNum;
|
||||
@ -57,7 +73,7 @@ function getStepTemplate({
|
||||
|
||||
return (
|
||||
`---
|
||||
id: ${challengeId}
|
||||
id: ${challengeId.toString()}
|
||||
title: Step ${stepNum}
|
||||
challengeType: 0
|
||||
dashedName: step-${stepNum}
|
||||
@ -79,4 +95,4 @@ ${getCodeBlock('js')}
|
||||
);
|
||||
}
|
||||
|
||||
exports.getStepTemplate = getStepTemplate;
|
||||
export { getStepTemplate };
|
@ -1 +0,0 @@
|
||||
export declare function insertErms(seedCode: string, erms: number[]): string;
|
@ -1,4 +1,4 @@
|
||||
const { insertErms } = require('./insert-erms');
|
||||
import { insertErms } from './insert-erms';
|
||||
|
||||
describe('insertErms helper', () => {
|
||||
const code = `<h1>Hello World</h1>
|
||||
@ -9,12 +9,6 @@ describe('insertErms helper', () => {
|
||||
<p>Purr jump eat the grass rip the couch scratched sunbathe, shed everywhere rip the couch sleep in the sink fluffy fur catnip scratched.</p>
|
||||
</main>`;
|
||||
|
||||
it('should throw error if erm is undefined', () => {
|
||||
expect(() => {
|
||||
insertErms(code);
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
it('should throw error if erm length is less than 2', () => {
|
||||
const items = [[], [1]];
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Update given value with markers (labels)
|
||||
const insertErms = (seedCode, erms) => {
|
||||
const insertErms = (seedCode: string, erms: number[]): string => {
|
||||
if (!erms || erms.length <= 1) {
|
||||
throw `erms should be provided`;
|
||||
}
|
||||
@ -27,4 +27,4 @@ const insertErms = (seedCode, erms) => {
|
||||
return newSeedCode;
|
||||
};
|
||||
|
||||
exports.insertErms = insertErms;
|
||||
export { insertErms };
|
@ -1 +0,0 @@
|
||||
export declare function padWithLeadingZeros(value: number | string): string;
|
@ -1,18 +0,0 @@
|
||||
// Prepends zero's to the given value until length is equal to 3:
|
||||
// '1' -> '001', '12' -> '012', ...
|
||||
// Note: always want file step numbers 3 digits
|
||||
function padWithLeadingZeros(value) {
|
||||
if (!(typeof value === 'number' || typeof value === 'string')) {
|
||||
throw `${value} should be of type number or string`;
|
||||
}
|
||||
|
||||
const newValue = '' + value;
|
||||
|
||||
if (newValue.length > 3) {
|
||||
throw `${newValue} should be less than 4 characters`;
|
||||
}
|
||||
|
||||
return newValue.padStart(3, '0');
|
||||
}
|
||||
|
||||
exports.padWithLeadingZeros = padWithLeadingZeros;
|
@ -13,20 +13,6 @@ describe('padWithLeadingZeros helper', () => {
|
||||
expect(padWithLeadingZeros('111')).toEqual('111');
|
||||
});
|
||||
|
||||
it('should throw on invalid values', () => {
|
||||
const items = ['undefined', null, []];
|
||||
|
||||
items.forEach(item =>
|
||||
expect(() => {
|
||||
if (item !== 'undefined') {
|
||||
padWithLeadingZeros(item);
|
||||
} else {
|
||||
padWithLeadingZeros();
|
||||
}
|
||||
}).toThrow()
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw on valid values that are longer that 3 characters', () => {
|
||||
expect(() => {
|
||||
padWithLeadingZeros('19850809');
|
||||
|
@ -0,0 +1,14 @@
|
||||
// Prepends zero's to the given value until length is equal to 3:
|
||||
// '1' -> '001', '12' -> '012', ...
|
||||
// Note: always want file step numbers 3 digits
|
||||
function padWithLeadingZeros(value: number): string {
|
||||
const valueString = value.toString();
|
||||
|
||||
if (valueString.length > 3) {
|
||||
throw `${valueString} should be less than 4 characters`;
|
||||
}
|
||||
|
||||
return valueString.padStart(3, '0');
|
||||
}
|
||||
|
||||
export { padWithLeadingZeros };
|
@ -23,6 +23,7 @@
|
||||
"create-project": "ts-node create-project"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/mock-fs": "^4.13.1",
|
||||
"bson-objectid": "2.0.2",
|
||||
"cross-env": "7.0.3",
|
||||
"gray-matter": "4.0.3",
|
||||
|
10
tools/challenge-helper-scripts/utils.d.ts
vendored
10
tools/challenge-helper-scripts/utils.d.ts
vendored
@ -1,10 +0,0 @@
|
||||
type CreateStepOptions = {
|
||||
challengeSeeds: Record<string, unknown>;
|
||||
projectPath: string;
|
||||
stepBetween: boolean;
|
||||
stepNum: number;
|
||||
};
|
||||
|
||||
export declare function createStepFile(options: CreateStepOptions): string;
|
||||
export declare function reorderSteps(): void;
|
||||
export declare function getChallengeSeeds(string): Record<string, unknown>;
|
@ -1,7 +1,7 @@
|
||||
const fs = require('fs');
|
||||
const ObjectID = require('bson-objectid');
|
||||
const glob = require('glob');
|
||||
const mock = require('mock-fs');
|
||||
import fs from 'fs';
|
||||
import ObjectID from 'bson-objectid';
|
||||
import glob from 'glob';
|
||||
import mock from 'mock-fs';
|
||||
|
||||
// NOTE:
|
||||
// Use `console.log()` before mocking the filesystem or use
|
||||
@ -9,7 +9,7 @@ const mock = require('mock-fs');
|
||||
// `require`.
|
||||
|
||||
jest.mock('bson-objectid', () => {
|
||||
return jest.fn(() => mockChallengeId);
|
||||
return jest.fn(() => ({ toString: () => mockChallengeId }));
|
||||
});
|
||||
|
||||
jest.mock('./helpers/get-step-template', () => {
|
||||
@ -33,9 +33,9 @@ jest.mock('./helpers/get-project-path', () => {
|
||||
jest.mock('gray-matter', () => {
|
||||
return {
|
||||
read: jest.fn(() => ({
|
||||
data: { id: mockChallengeId },
|
||||
data: { id: mockChallengeId }
|
||||
})),
|
||||
stringify: jest.fn(() => 'Lorem ipsum...')
|
||||
}))
|
||||
};
|
||||
});
|
||||
|
||||
@ -49,8 +49,8 @@ jest.mock(
|
||||
);
|
||||
|
||||
const mockChallengeId = '60d35cf3fe32df2ce8e31b03';
|
||||
const { getStepTemplate } = require('./helpers/get-step-template');
|
||||
const { createStepFile, reorderSteps } = require('./utils');
|
||||
import { getStepTemplate } from './helpers/get-step-template';
|
||||
import { createStepFile, reorderSteps } from './utils';
|
||||
|
||||
describe('Challenge utils helper scripts', () => {
|
||||
describe('createStepFile util', () => {
|
||||
@ -67,7 +67,7 @@ describe('Challenge utils helper scripts', () => {
|
||||
stepNum: 3
|
||||
});
|
||||
|
||||
expect(step).toEqual(mockChallengeId);
|
||||
expect(step.toString()).toEqual(mockChallengeId);
|
||||
expect(ObjectID).toHaveBeenCalledTimes(1);
|
||||
|
||||
// Internal tasks
|
||||
@ -82,8 +82,6 @@ describe('Challenge utils helper scripts', () => {
|
||||
`project/step-002.md`,
|
||||
`project/step-003.md`
|
||||
]);
|
||||
|
||||
mock.restore();
|
||||
});
|
||||
});
|
||||
|
||||
@ -132,8 +130,9 @@ describe('Challenge utils helper scripts', () => {
|
||||
}`;
|
||||
|
||||
expect(result).toEqual(expectedResult);
|
||||
|
||||
});
|
||||
});
|
||||
afterEach(() => {
|
||||
mock.restore();
|
||||
});
|
||||
});
|
||||
});
|
@ -1,23 +1,28 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const ObjectID = require('bson-objectid');
|
||||
const matter = require('gray-matter');
|
||||
const {
|
||||
getMetaData
|
||||
} = require('../challenge-helper-scripts/helpers/get-project-path-metadata');
|
||||
const { parseMDSync } = require('../challenge-parser/parser');
|
||||
const { getProjectMetaPath } = require('./helpers/get-project-meta-path');
|
||||
const { getProjectPath } = require('./helpers/get-project-path');
|
||||
const { getStepTemplate } = require('./helpers/get-step-template');
|
||||
const { padWithLeadingZeros } = require('./helpers/pad-with-leading-zeros');
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import ObjectID from 'bson-objectid';
|
||||
import * as matter from 'gray-matter';
|
||||
import { getMetaData } from '../challenge-helper-scripts/helpers/get-project-path-metadata';
|
||||
import { parseMDSync } from '../challenge-parser/parser';
|
||||
import { getProjectMetaPath } from './helpers/get-project-meta-path';
|
||||
import { getProjectPath } from './helpers/get-project-path';
|
||||
import { ChallengeSeed, getStepTemplate } from './helpers/get-step-template';
|
||||
import { padWithLeadingZeros } from './helpers/pad-with-leading-zeros';
|
||||
|
||||
interface Options {
|
||||
projectPath: string;
|
||||
stepNum: number;
|
||||
challengeSeeds?: Record<string, ChallengeSeed>;
|
||||
stepBetween?: boolean;
|
||||
}
|
||||
|
||||
const createStepFile = ({
|
||||
projectPath,
|
||||
stepNum,
|
||||
challengeSeeds = {},
|
||||
stepBetween = false
|
||||
}) => {
|
||||
const challengeId = ObjectID();
|
||||
}: Options) => {
|
||||
const challengeId = new ObjectID();
|
||||
|
||||
let finalStepNum = padWithLeadingZeros(stepNum);
|
||||
finalStepNum += stepBetween ? 'a' : '';
|
||||
@ -78,7 +83,7 @@ const reorderSteps = () => {
|
||||
};
|
||||
});
|
||||
|
||||
const challengeOrder = [];
|
||||
const challengeOrder: string[][] = [];
|
||||
|
||||
filesToReorder.forEach(({ oldFileName, newFileName, newStepNum }) => {
|
||||
fs.renameSync(
|
||||
@ -87,7 +92,8 @@ const reorderSteps = () => {
|
||||
);
|
||||
const filePath = `${projectPath}${newFileName}.tmp`;
|
||||
const frontMatter = matter.read(filePath);
|
||||
const challengeID = frontMatter.data.id || ObjectID();
|
||||
const challengeID =
|
||||
(frontMatter.data.id as string) || new ObjectID().toString();
|
||||
const title =
|
||||
newFileName === 'final.md' ? 'Final Prototype' : `Step ${newStepNum}`;
|
||||
const dashedName = `step-${newStepNum}`;
|
||||
@ -98,7 +104,7 @@ const reorderSteps = () => {
|
||||
title,
|
||||
dashedName
|
||||
};
|
||||
fs.writeFileSync(filePath, frontMatter.stringify(newData));
|
||||
fs.writeFileSync(filePath, matter.stringify(frontMatter.content, newData));
|
||||
});
|
||||
|
||||
filesToReorder.forEach(({ newFileName }) => {
|
||||
@ -112,12 +118,11 @@ const reorderSteps = () => {
|
||||
fs.writeFileSync(projectMetaPath, JSON.stringify(newMeta, null, 2));
|
||||
};
|
||||
|
||||
const getChallengeSeeds = challengeFilePath => {
|
||||
const getChallengeSeeds = (
|
||||
challengeFilePath: string
|
||||
): Record<string, ChallengeSeed> => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access
|
||||
return parseMDSync(challengeFilePath).challengeFiles;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
createStepFile,
|
||||
getChallengeSeeds,
|
||||
reorderSteps
|
||||
};
|
||||
export { createStepFile, reorderSteps, getChallengeSeeds };
|
@ -12,26 +12,25 @@ const globalConfigPath = path.resolve(__dirname, '../../../config');
|
||||
const { FREECODECAMP_NODE_ENV } = process.env;
|
||||
|
||||
function checkClientLocale() {
|
||||
if (!process.env.CLIENT_LOCALE) throw Error('CLIENT_LOCALE is not set');
|
||||
if (!availableLangs.client.includes(process.env.CLIENT_LOCALE)) {
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
throw Error(`
|
||||
|
||||
CLIENT_LOCALE, ${process.env.CLIENT_LOCALE}, is not an available language in config/i18n/all-langs.ts
|
||||
|
||||
`);
|
||||
/* eslint-enable @typescript-eslint/restrict-template-expressions */
|
||||
}
|
||||
}
|
||||
|
||||
function checkCurriculumLocale() {
|
||||
if (!process.env.CURRICULUM_LOCALE)
|
||||
throw Error('CURRICULUM_LOCALE is not set');
|
||||
if (!availableLangs.curriculum.includes(process.env.CURRICULUM_LOCALE)) {
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
throw Error(`
|
||||
|
||||
CURRICULUM_LOCALE, ${process.env.CURRICULUM_LOCALE}, is not an available language in config/i18n/all-langs.ts
|
||||
|
||||
`);
|
||||
/* eslint-enable @typescript-eslint/restrict-template-expressions */
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,13 +0,0 @@
|
||||
{
|
||||
"include": [
|
||||
"./scripts/lint/validate-keys.ts",
|
||||
"./scripts/build/ensure-env.ts"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"esModuleInterop": true,
|
||||
"resolveJsonModule": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"typeRoots": ["../node_modules/@types"],
|
||||
"types": ["node"]
|
||||
}
|
||||
}
|
26
tsconfig-base.json
Normal file
26
tsconfig-base.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": ["WebWorker", "DOM", "DOM.Iterable"],
|
||||
"target": "es2020",
|
||||
"module": "es2020",
|
||||
"moduleResolution": "node",
|
||||
"allowJs": true,
|
||||
"jsx": "react",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"noEmit": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"types": ["node", "jest", "@testing-library/jest-dom"]
|
||||
},
|
||||
// since ts-node compiles ts on the fly and then uses node, it needs to
|
||||
// compile the scripts to commonjs (or node will complain about the requires)
|
||||
"ts-node": {
|
||||
"compilerOptions": {
|
||||
"module": "commonjs"
|
||||
},
|
||||
"transpileOnly": true
|
||||
}
|
||||
}
|
@ -4,24 +4,8 @@
|
||||
"client/plugins/**/*",
|
||||
"client/src/**/*",
|
||||
"client/utils/**/*",
|
||||
"tools/challenge-helper-scripts/**/*",
|
||||
"config/certification-settings.ts",
|
||||
"config/i18n/**/*"
|
||||
"tools/challenge-helper-scripts/**/*.ts",
|
||||
"tools/scripts/**/*.ts"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"lib": ["WebWorker", "DOM", "DOM.Iterable"],
|
||||
"target": "es2020",
|
||||
"module": "es2020",
|
||||
"moduleResolution": "node",
|
||||
"allowJs": true,
|
||||
"jsx": "react",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"noEmit": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"types": ["node", "jest", "@testing-library/jest-dom"]
|
||||
}
|
||||
"extends": "./tsconfig-base.json"
|
||||
}
|
||||
|
Reference in New Issue
Block a user