computer-scienceangular-roadmapbackend-roadmapblockchain-roadmapdba-roadmapdeveloper-roadmapdevops-roadmapfrontend-roadmapgo-roadmaphactoberfestjava-roadmapjavascript-roadmapnodejs-roadmappython-roadmapqa-roadmapreact-roadmaproadmapstudy-planvue-roadmapweb3-roadmap
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.
255 lines
7.5 KiB
255 lines
7.5 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'), |
|
'graphql': require('../public/project/graphql.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));
|
|
|