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:
Oliver Eyton-Williams
2022-01-25 11:34:16 +01:00
committed by GitHub
parent 82ca6d8441
commit 7216ca55cc
61 changed files with 444 additions and 4287 deletions

View File

@ -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();

View 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();

View File

@ -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();

View File

@ -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');

View File

@ -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();

View File

@ -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.';

View File

@ -1 +0,0 @@
export declare function getArgValues(argv: string[]): Record<string, string>;

View File

@ -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', () => {

View File

@ -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 };

View File

@ -1 +0,0 @@
export declare function getExistingStepNums(options: string[]): number[];

View File

@ -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();
});
});

View File

@ -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 };

View File

@ -1 +0,0 @@
export declare function getLastStepFileContent(): Record<string, unknown>;

View File

@ -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();
});
});

View File

@ -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 };

View File

@ -1,4 +0,0 @@
export declare function getProjectMetaPath(
curriculumPath: string,
projectName: string
): string;

View File

@ -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();

View File

@ -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 };

View File

@ -1 +0,0 @@
export declare function getMetaData(string): Record<string, unknown>;

View File

@ -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;

View File

@ -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();
});

View File

@ -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 };

View File

@ -1 +0,0 @@
export declare function getProjectPath(): string;

View File

@ -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', () => {

View File

@ -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 };

View File

@ -1,8 +0,0 @@
type StepOptions = {
challengeId: string;
challengeSeeds: unknown;
stepBetween: boolean;
stepNum: number;
};
export declare function getStepTemplate(options: StepOptions): string;

View File

@ -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: '',

View File

@ -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 };

View File

@ -1 +0,0 @@
export declare function insertErms(seedCode: string, erms: number[]): string;

View File

@ -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]];

View File

@ -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 };

View File

@ -1 +0,0 @@
export declare function padWithLeadingZeros(value: number | string): string;

View File

@ -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;

View File

@ -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');

View File

@ -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 };

View File

@ -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",

View File

@ -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>;

View File

@ -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 },
stringify: jest.fn(() => 'Lorem ipsum...')
}))
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);
mock.restore();
});
});
afterEach(() => {
mock.restore();
});
});

View File

@ -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 };