Roadmap to becoming a developer in 2022
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

254 lines
7.4 KiB

import fs from 'fs';
import path from 'path';
const CONTENT_DIR = path.join(__dirname, '../content');
// Directory containing the roadmaps
const ROADMAP_CONTENT_DIR = path.join(__dirname, '../content/roadmaps');
const roadmapKey = process.argv[2];
type ControlType = {
ID: string;
typeID: string;
zOrder: string;
w: string;
h: string;
measuredW: string;
measuredH: string;
x: string;
y: string;
properties: {
controlName: string;
};
children: {
controls: {
control: ControlType[];
};
};
};
type RoadmapType = {
mockup: {
controls: {
control: ControlType[];
};
};
};
const roadmaps: Record<string, RoadmapType> = {
frontend: require('../public/project/frontend.json'),
backend: require('../public/project/backend.json'),
devops: require('../public/project/devops.json'),
android: require('../public/project/android.json'),
golang: require('../public/project/golang.json'),
java: require('../public/project/java.json'),
python: require('../public/project/python.json'),
react: require('../public/project/react.json'),
vue: require('../public/project/vue.json'),
angular: require('../public/project/angular.json'),
blockchain: require('../public/project/blockchain.json'),
javascript: require('../public/project/javascript.json'),
nodejs: require('../public/project/nodejs.json'),
qa: require('../public/project/qa.json'),
'design-system': require('../public/project/design-system.json'),
'software-architect': require('../public/project/software-architect.json'),
'aspnet-core': require('../public/project/aspnet-core.json'),
'flutter': require('../public/project/flutter.json'),
'computer-science': require('../public/project/computer-science.json'),
};
if (!roadmapKey || !roadmaps[roadmapKey]) {
console.error(`Invalid roadmap key ${roadmapKey}`);
console.error(`Allowed keys are ${Object.keys(roadmaps).join(', ')}`);
process.exit(1);
}
// Directory holding the roadmap content files
const roadmapDirName = fs
.readdirSync(ROADMAP_CONTENT_DIR)
.find((dirName: string) => dirName.replace(/\d+-/, '') === roadmapKey);
if (!roadmapDirName) {
console.error('Roadmap directory not found');
process.exit(1);
}
const roadmapDirPath = path.join(ROADMAP_CONTENT_DIR, roadmapDirName);
const roadmapContentDirPath = path.join(
ROADMAP_CONTENT_DIR,
roadmapDirName,
'content'
);
// If roadmap content already exists do not proceed as it would override the files
if (fs.existsSync(roadmapContentDirPath)) {
console.error(`Roadmap content already exists @ ${roadmapContentDirPath}`);
process.exit(1);
}
// Type representing the nested dir tree
// {
// frontend: {
// internet: {
// dns-how-does-it-work: {}
// what-is-domain-name: {}
// },
// html: {},
// css: {
// making-layouts: {}
// },
// }
// }
type DirTreeType = {
[dirName: string]: DirTreeType;
};
// Hashmap containing the dir key to sort order mapping
// {
// "frontend": 100,
// "frontend:internet": 200,
// "frontend:internet:dns-how-does-it-work": 100
// }
type DirSortOrdersType = Record<string, number>;
function prepareDirTree(
control: ControlType,
dirTree: DirTreeType,
dirSortOrders: DirSortOrdersType
) {
// Directories are only created for groups
if (control.typeID !== '__group__') {
return;
}
// e.g. 104-testing-your-apps:other-options
const controlName = control?.properties?.controlName || '';
// e.g. 104
const sortOrder = controlName.match(/^\d+/)?.[0];
// No directory for a group without control name
if (!controlName || !sortOrder) {
return;
}
// e.g. testing-your-apps:other-options
const controlNameWithoutSortOrder = controlName.replace(/^\d+-/, '');
// e.g. ['testing-your-apps', 'other-options']
const dirParts = controlNameWithoutSortOrder.split(':');
// Nest the dir path in the dirTree
let currDirTree = dirTree;
dirParts.forEach((dirPart) => {
currDirTree[dirPart] = currDirTree[dirPart] || {};
currDirTree = currDirTree[dirPart];
});
dirSortOrders[controlNameWithoutSortOrder] = Number(sortOrder);
const childrenControls = control.children.controls.control;
// No more children
if (childrenControls.length) {
childrenControls.forEach((childControl) => {
prepareDirTree(childControl, dirTree, dirSortOrders);
});
}
return { dirTree, dirSortOrders };
}
const roadmap = roadmaps[roadmapKey];
const controls = roadmap.mockup.controls.control;
// Prepare the dir tree that we will be creating and also calculate the sort orders
const dirTree: DirTreeType = {};
const dirSortOrders: DirSortOrdersType = {};
controls.forEach((control) => {
prepareDirTree(control, dirTree, dirSortOrders);
});
/**
* @param parentDir Parent directory in which directory is to be created
* @param dirTree Nested dir tree to be created
* @param sortOrders Mapping from groupName to sort order
* @param filePaths The mapping from groupName to file path
*/
function createDirTree(
parentDir: string,
dirTree: DirTreeType,
sortOrders: DirSortOrdersType,
filePaths: Record<string, string> = {}
) {
const childrenDirNames = Object.keys(dirTree);
const hasChildren = childrenDirNames.length !== 0;
// @todo write test for this, yolo for now
const groupName = parentDir
.replace(roadmapContentDirPath, '') // Remove base dir path
.replace(/(^\/)|(\/$)/g, '') // Remove trailing slashes
.replace(/(^\d+?-)/g, '') // Remove sorting information
.replaceAll('/', ':') // Replace slashes with `:`
.replace(/:\d+-/, ':');
const humanizedGroupName = groupName
.split(':')
.pop()
?.replaceAll('-', ' ')
.replace(/^\w/, ($0) => $0.toUpperCase());
const sortOrder = sortOrders[groupName] || '';
// Attach sorting information to dirname
// e.g. /roadmaps/100-frontend/content/internet
// ———> /roadmaps/100-frontend/content/103-internet
if (sortOrder) {
parentDir = parentDir.replace(/(.+?)([^\/]+)?$/, `$1${sortOrder}-$2`);
}
// If no children, create a file for this under the parent directory
if (!hasChildren) {
let fileName = `${parentDir}.md`;
fs.writeFileSync(fileName, `# ${humanizedGroupName}`);
filePaths[groupName || 'home'] = fileName.replace(CONTENT_DIR, '');
return filePaths;
}
// There *are* children, so create the parent as a directory
// and create `readme.md` as the content file for this
fs.mkdirSync(parentDir);
let readmeFilePath = path.join(parentDir, 'readme.md');
fs.writeFileSync(readmeFilePath, `# ${humanizedGroupName}`);
filePaths[groupName || 'home'] = readmeFilePath.replace(CONTENT_DIR, '');
// For each of the directory names, create a
// directory inside the given directory
childrenDirNames.forEach((dirName) => {
createDirTree(
path.join(parentDir, dirName),
dirTree[dirName],
dirSortOrders,
filePaths
);
});
return filePaths;
}
// Create directories and get back the paths for created directories
const filePaths = createDirTree(roadmapContentDirPath, dirTree, dirSortOrders);
const contentPathsFilePath = path.join(roadmapDirPath, 'content-paths.json');
fs.writeFileSync(contentPathsFilePath, JSON.stringify(filePaths, null, 2));
const roadmapMetaFilePath = path.join(roadmapDirPath, 'meta.json');
const roadmapMeta = require(roadmapMetaFilePath);
// Put the content paths file path in the roadmap meta
roadmapMeta.contentPathsFilePath = contentPathsFilePath.replace(
roadmapDirPath,
'.'
);
fs.writeFileSync(roadmapMetaFilePath, JSON.stringify(roadmapMeta, null, 2));