@ -0,0 +1,2 @@ |
||||
# These are supported funding model platforms |
||||
github: kamranahmedse |
@ -0,0 +1,36 @@ |
||||
name: Deployment to GH Pages |
||||
on: |
||||
push: |
||||
branches: [ master ] |
||||
env: |
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
||||
PAT: ${{ secrets.PAT }} |
||||
CI: true |
||||
jobs: |
||||
build: |
||||
runs-on: ubuntu-latest |
||||
steps: |
||||
- uses: actions/checkout@v2 |
||||
with: |
||||
persist-credentials: false |
||||
- uses: actions/setup-node@v1 |
||||
with: |
||||
node-version: 18 |
||||
- run: git config --global url."https://${{ secrets.PAT }}@github.com/".insteadOf ssh://git@github.com/ |
||||
- uses: pnpm/action-setup@v2.2.2 |
||||
with: |
||||
version: 7.13.4 |
||||
- name: Setup Environment |
||||
run: | |
||||
pnpm install |
||||
- name: Generate meta and build |
||||
run: | |
||||
npm run build |
||||
touch ./dist/.nojekyll |
||||
echo 'roadmap.sh' > ./dist/CNAME |
||||
- name: Deploy to GH Pages |
||||
run: | |
||||
git config user.email "kamranahmed.se@gmail.com" |
||||
git config user.name "Kamran Ahmed" |
||||
git remote set-url origin https://x-access-token:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git |
||||
npm run deploy |
@ -0,0 +1,38 @@ |
||||
name: Update dependencies |
||||
|
||||
on: |
||||
workflow_dispatch: # allow manual run |
||||
schedule: |
||||
- cron: '0 0 * * 0' # every sunday at midnight |
||||
|
||||
jobs: |
||||
upgrade-deps: |
||||
runs-on: ubuntu-latest |
||||
steps: |
||||
- uses: actions/checkout@v2 |
||||
- uses: actions/setup-node@v3 |
||||
with: |
||||
node-version: 18 |
||||
- uses: pnpm/action-setup@v2.2.2 |
||||
with: |
||||
version: 7.13.4 |
||||
- name: Upgrade dependencies |
||||
run: | |
||||
pnpm install |
||||
npm run upgrade |
||||
pnpm install --lockfile-only |
||||
- name: Create PR |
||||
uses: peter-evans/create-pull-request@v4 |
||||
with: |
||||
delete-branch: false |
||||
branch: "update-deps" |
||||
base: "master" |
||||
labels: | |
||||
dependencies |
||||
automated pr |
||||
reviewers: kamranahmedse |
||||
commit-message: "chore: update dependencies to latest" |
||||
title: "Upgrade dependencies to latest" |
||||
body: | |
||||
Updates all dependencies to latest versions. |
||||
Please review the changes and merge if everything looks good. |
@ -0,0 +1,22 @@ |
||||
# build output |
||||
dist/ |
||||
.output/ |
||||
|
||||
# dependencies |
||||
node_modules/ |
||||
|
||||
bin/developer-roadmap |
||||
|
||||
# logs |
||||
npm-debug.log* |
||||
yarn-debug.log* |
||||
yarn-error.log* |
||||
pnpm-debug.log* |
||||
|
||||
|
||||
# environment variables |
||||
.env |
||||
.env.production |
||||
|
||||
# macOS-specific files |
||||
.DS_Store |
@ -0,0 +1,4 @@ |
||||
{ |
||||
"recommendations": ["astro-build.astro-vscode"], |
||||
"unwantedRecommendations": [] |
||||
} |
@ -0,0 +1,11 @@ |
||||
{ |
||||
"version": "0.2.0", |
||||
"configurations": [ |
||||
{ |
||||
"command": "./node_modules/.bin/astro dev", |
||||
"name": "Development server", |
||||
"request": "launch", |
||||
"type": "node-terminal" |
||||
} |
||||
] |
||||
} |
@ -0,0 +1,96 @@ |
||||
<p align="center"> |
||||
<img src="public/brand.png" height="128"> |
||||
<h2 align="center"><a href="https://roadmap.sh">roadmap.sh</a></h2> |
||||
<p align="center">Community driven roadmaps, articles and resources for developers<p> |
||||
<p align="center"> |
||||
<a href="https://roadmap.sh/roadmaps"> |
||||
<img src="https://img.shields.io/badge/-Roadmaps%20-0a0a0a.svg?style=flat&colorA=0a0a0a" alt="roadmaps" /> |
||||
</a> |
||||
<a href="https://youtube.com/theroadmap?sub_confirmation=1"> |
||||
<img src="https://img.shields.io/badge/-Videos-0a0a0a.svg?style=flat&colorA=0a0a0a" alt="videos" /> |
||||
</a> |
||||
<a href="https://github.com/kamranahmedse/developer-roadmap/tree/0471d44c8fae58b6a36a7c57bba12253916d0249/translations"> |
||||
<img src="https://img.shields.io/badge/-Translations-0a0a0a.svg?style=flat&colorA=0a0a0a" alt="videos" /> |
||||
</a> |
||||
<a href="https://www.youtube.com/channel/UCA0H2KIWgWTwpTFjSxp0now?sub_confirmation=1"> |
||||
<img src="https://img.shields.io/badge/%E2%9D%A4-YouTube%20Channel-0a0a0a.svg?style=flat&colorA=0a0a0a" alt="roadmaps" /> |
||||
</a> |
||||
</p> |
||||
</p> |
||||
|
||||
<br> |
||||
|
||||
![](https://i.imgur.com/waxVImv.png) |
||||
|
||||
Roadmaps are now interactive, you can click the nodes to read more about the topics. |
||||
|
||||
### [View all Roadmaps](https://roadmap.sh) |
||||
|
||||
![](https://i.imgur.com/waxVImv.png) |
||||
|
||||
Here is the list of available roadmaps with more being actively worked upon. |
||||
|
||||
- [Frontend Roadmap](https://roadmap.sh/frontend) |
||||
- [Backend Roadmap](https://roadmap.sh/backend) |
||||
- [DevOps Roadmap](https://roadmap.sh/devops) |
||||
- [Computer Science Roadmap](https://roadmap.sh/computer-science) |
||||
- [QA Roadmap](https://roadmap.sh/qa) |
||||
- [Software Architect Roadmap](https://roadmap.sh/software-architect) |
||||
- [Software Design and Architecture Roadmap](https://roadmap.sh/software-design-architecture) |
||||
- [JavaScript Roadmap](https://roadmap.sh/javascript) |
||||
- [React Roadmap](https://roadmap.sh/react) |
||||
- [Vue Roadmap](https://roadmap.sh/vue) |
||||
- [Angular Roadmap](https://roadmap.sh/angular) |
||||
- [Node.js Roadmap](https://roadmap.sh/nodejs) |
||||
- [GraphQL Roadmap](https://roadmap.sh/graphql) |
||||
- [Android Roadmap](https://roadmap.sh/android) |
||||
- [Flutter Roadmap](https://roadmap.sh/flutter) |
||||
- [Python Roadmap](https://roadmap.sh/python) |
||||
- [Go Roadmap](https://roadmap.sh/golang) |
||||
- [Java Roadmap](https://roadmap.sh/java) |
||||
- [Design System Roadmap](https://roadmap.sh/design-system) |
||||
- [DBA Roadmap](https://roadmap.sh/postgresql-dba) |
||||
- [Blockchain Roadmap](https://roadmap.sh/blockchain) |
||||
- [ASP.NET Core Roadmap](https://roadmap.sh/aspnet-core) |
||||
|
||||
![](https://i.imgur.com/waxVImv.png) |
||||
|
||||
## Share with the community |
||||
|
||||
Please consider sharing a post about [roadmap.sh](https://roadmap.sh) and the value it provides. It really does help! |
||||
|
||||
[![GitHub Repo stars](https://img.shields.io/badge/share%20on-reddit-red?logo=reddit)](https://reddit.com/submit?url=https://roadmap.sh&title=Interactive%20roadmaps,%20guides%20and%20other%20educational%20content%20for%20Developers) |
||||
[![GitHub Repo stars](https://img.shields.io/badge/share%20on-hacker%20news-orange?logo=ycombinator)](https://news.ycombinator.com/submitlink?u=https://roadmap.sh) |
||||
[![GitHub Repo stars](https://img.shields.io/badge/share%20on-twitter-03A9F4?logo=twitter)](https://twitter.com/share?url=https://roadmap.sh&text=Interactive%20roadmaps,%20guides%20and%20other%20educational%20content%20for%20Developers) |
||||
[![GitHub Repo stars](https://img.shields.io/badge/share%20on-facebook-1976D2?logo=facebook)](https://www.facebook.com/sharer/sharer.php?u=https://roadmap.sh) |
||||
[![GitHub Repo stars](https://img.shields.io/badge/share%20on-linkedin-3949AB?logo=linkedin)](https://www.linkedin.com/shareArticle?url=https://roadmap.sh&title=Interactive%20roadmaps,%20guides%20and%20other%20educational%20content%20for%20Developers) |
||||
|
||||
## Development |
||||
|
||||
Clone the repository, install the dependencies and start the application |
||||
|
||||
```bash |
||||
git clone git@github.com:kamranahmedse/developer-roadmap.git |
||||
npm install |
||||
npm run dev |
||||
``` |
||||
|
||||
## Contribution |
||||
|
||||
> Have a look at [contribution docs](./contributing.md) for how to update any of the roadmaps |
||||
|
||||
- Add content to roadmaps |
||||
- Add new roadmaps |
||||
- Suggest changes to existing roadmaps |
||||
- Discuss ideas in issues |
||||
- Spread the word |
||||
|
||||
## Thanks to all contributors ❤ |
||||
|
||||
<a href = "https://github.com/kamranahmedse/developer-roadmap/graphs/contributors"> |
||||
<img src = "https://contrib.rocks/image?repo=kamranahmedse/developer-roadmap"/> |
||||
</a> |
||||
|
||||
## License |
||||
|
||||
Have a look at the [license file](./license) for details |
@ -0,0 +1,36 @@ |
||||
// https://astro.build/config
|
||||
import sitemap from '@astrojs/sitemap'; |
||||
import tailwind from '@astrojs/tailwind'; |
||||
import compress from 'astro-compress'; |
||||
import { defineConfig } from 'astro/config'; |
||||
import rehypeExternalLinks from 'rehype-external-links'; |
||||
import { serializeSitemap, shouldIndexPage } from './sitemap.mjs'; |
||||
|
||||
export default defineConfig({ |
||||
site: 'https://roadmap.sh', |
||||
markdown: { |
||||
rehypePlugins: [ |
||||
[ |
||||
rehypeExternalLinks, |
||||
{ |
||||
target: '_blank', |
||||
}, |
||||
], |
||||
], |
||||
}, |
||||
integrations: [ |
||||
tailwind({ |
||||
config: { |
||||
applyBaseStyles: false, |
||||
}, |
||||
}), |
||||
sitemap({ |
||||
filter: shouldIndexPage, |
||||
serialize: serializeSitemap, |
||||
}), |
||||
compress({ |
||||
css: false, |
||||
js: false, |
||||
}), |
||||
], |
||||
}); |
@ -0,0 +1,14 @@ |
||||
const fs = require('node:fs'); |
||||
const path = require('node:path'); |
||||
|
||||
const jsonsDir = path.join(process.cwd(), 'public/jsons'); |
||||
const jsonFiles = fs.readdirSync(jsonsDir); |
||||
|
||||
jsonFiles.forEach((jsonFileName) => { |
||||
console.log(`Compressing ${jsonFileName}...`); |
||||
|
||||
const jsonFilePath = path.join(jsonsDir, jsonFileName); |
||||
const json = require(jsonFilePath); |
||||
|
||||
fs.writeFileSync(jsonFilePath, JSON.stringify(json)); |
||||
}); |
@ -0,0 +1,120 @@ |
||||
const fs = require('fs'); |
||||
const path = require('path'); |
||||
|
||||
// 1 - Renames each readme.md to index.md |
||||
// e.g. |
||||
// before => roadmaps/frontend/content/internet/readme.md |
||||
// after => roadmaps/frontend/content/internet/index.md |
||||
// |
||||
// 2 - Replaces the resource tags with short codes |
||||
// e.g. |
||||
// <ResourceGroupTitle>Free Content</ResourceGroupTitle> |
||||
// <BadgeLink colorScheme='yellow' badgeText='Read' href='https://www.w3schools.com/css/'>W3Schools — Learn CSS</BadgeLink> |
||||
// |
||||
// {% resources %} |
||||
// {% Blog "https://www.w3schools.com/css/", "W3Schools — Learn CSS" %} |
||||
// {% endresources %} |
||||
// |
||||
// 3 - Removes the index.md file from within the content dir i.e. to avoid `/frontend` permalink for `/frontend/index.md` |
||||
// Because we have the `/frontend` permalink serving the actual roadmap and not any content |
||||
const roadmapsDir = path.join(__dirname, '../src/roadmaps'); |
||||
const roadmapDirs = fs.readdirSync(roadmapsDir); |
||||
|
||||
roadmapDirs.forEach((roadmapDirName) => { |
||||
const roadmapDirPath = path.join(roadmapsDir, roadmapDirName); |
||||
const contentDirPath = path.join(roadmapDirPath, 'content'); |
||||
|
||||
console.log(`[Start] == Migrating ${roadmapDirName}`); |
||||
|
||||
if (!fs.existsSync(contentDirPath)) { |
||||
console.log(`Content dir not found ${roadmapDirName}/content`); |
||||
return; |
||||
} |
||||
|
||||
function handleContentDir(parentDirPath) { |
||||
const dirChildrenNames = fs.readdirSync(parentDirPath); |
||||
|
||||
dirChildrenNames.forEach((dirChildName) => { |
||||
let dirChildPath = path.join(parentDirPath, dirChildName); |
||||
|
||||
// If directory, handle the children for it |
||||
if (fs.lstatSync(dirChildPath).isDirectory()) { |
||||
handleContentDir(dirChildPath); |
||||
} |
||||
|
||||
////////////////////////////////////////////////////////// |
||||
// 1 - Rename directories to remove the numbers |
||||
////////////////////////////////////////////////////////// |
||||
// let newDirChildPath = path.join( |
||||
// path.dirname(dirChildPath), |
||||
// path.basename(dirChildPath).replace(/^\d+-/, '') |
||||
// ); |
||||
// fs.renameSync(dirChildPath, dirChildPath); |
||||
|
||||
////////////////////////////////////////////////////////// |
||||
// 1 - Rename readme.md to index.md |
||||
////////////////////////////////////////////////////////// |
||||
if (dirChildPath.endsWith('readme.md')) { |
||||
const newFilePath = path.join(path.dirname(dirChildPath), `index.md`); |
||||
|
||||
fs.renameSync(dirChildPath, newFilePath); |
||||
dirChildPath = newFilePath; |
||||
} |
||||
|
||||
////////////////////////////////////////////////////////// |
||||
// 2 - Replace the resource tags with short codes |
||||
////////////////////////////////////////////////////////// |
||||
if (fs.lstatSync(dirChildPath).isFile()) { |
||||
const fileContent = fs.readFileSync(dirChildPath, 'utf-8'); |
||||
|
||||
let resourceLinks = [...fileContent.matchAll(/<BadgeLink.+<\/BadgeLink>/g)].map(([fullMatch]) => { |
||||
// const resourceType = fullMatch.match(/badgeText=["'](.+?)["']/)[1]; |
||||
const link = fullMatch.match(/href=["'](.+?)["']/)[1]; |
||||
const text = fullMatch.match(/>([^<]+)<\/BadgeLink>$/)[1]; |
||||
|
||||
return `- [${text.replaceAll(/['"]/g, '')}](${link})`; |
||||
}); |
||||
|
||||
////////////////////////////////////////////////////////////////////// |
||||
// Replace the dedicated roadmap tag with the short code |
||||
////////////////////////////////////////////////////////////////////// |
||||
// prettier-ignore |
||||
const dedicatedRegex = /<DedicatedRoadmap\s*href=['"](.+?)['"]\s*title=['"](.+?)['"]\s*description=['"].+?['"]\s*\/>/; |
||||
const dedicatedMatches = fileContent.match(dedicatedRegex); |
||||
|
||||
if (dedicatedMatches) { |
||||
const [, href, title] = dedicatedMatches; |
||||
|
||||
resourceLinks = [`- [Visit Dedicated ${title}](${href})`, ...resourceLinks]; |
||||
} |
||||
|
||||
resourceLinks = ['Visit the following resources to learn more:\n', ...resourceLinks]; |
||||
resourceLinks = resourceLinks.join('\n'); |
||||
|
||||
let newFileContent = fileContent.replace( |
||||
/<ResourceGroupTitle>([^<\/BadgeLink>]|\S|\s)+<\/BadgeLink>/, |
||||
resourceLinks |
||||
); |
||||
|
||||
// In case if the resources were not wrapped in <ResourceGroupTitle> |
||||
newFileContent = newFileContent.replace( |
||||
/<BadgeLink([^<\/BadgeLink>]|\S|\s)+<\/BadgeLink>/, |
||||
resourceLinks |
||||
); |
||||
|
||||
fs.writeFileSync(dirChildPath, newFileContent); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
handleContentDir(contentDirPath); |
||||
|
||||
// 3 - Removes the index.md file from within the content dir i.e. to avoid `/frontend` permalink for `/frontend/index.md` |
||||
// Because we have the `/frontend` permalink serving the actual roadmap and not any content |
||||
const contentRootFile = path.join(contentDirPath, '/index.md'); |
||||
if (fs.existsSync(contentRootFile)) { |
||||
fs.rmSync(contentRootFile); |
||||
} |
||||
|
||||
console.log(` == Migrated ${roadmapDirName}`); |
||||
}); |
@ -0,0 +1,83 @@ |
||||
const fs = require('fs'); |
||||
const path = require('path'); |
||||
const yaml = require('json-to-pretty-yaml'); |
||||
|
||||
const contentDirPath = path.join(__dirname, './developer-roadmap/content'); |
||||
const guides = require('./developer-roadmap/content/guides.json'); |
||||
const authors = require('./developer-roadmap/content/authors.json'); |
||||
|
||||
const guideImagesDirPath = path.join(__dirname, './developer-roadmap/public/guides'); |
||||
const newGuideImagesDirPath = path.join(__dirname, '../public/guides'); |
||||
|
||||
// Remove the guide images directory |
||||
if (fs.existsSync(newGuideImagesDirPath)) { |
||||
fs.rmSync(newGuideImagesDirPath, { recursive: true }); |
||||
} |
||||
|
||||
fs.cpSync(guideImagesDirPath, newGuideImagesDirPath, { recursive: true }); |
||||
|
||||
// Remove the old guides directory |
||||
const newGuidesDirPath = path.join(__dirname, '../src/guides'); |
||||
if (fs.existsSync(newGuidesDirPath)) { |
||||
fs.rmSync(newGuidesDirPath, { recursive: true }); |
||||
} |
||||
|
||||
fs.mkdirSync(newGuidesDirPath); |
||||
|
||||
guides.forEach((guide) => { |
||||
const { id: guideId } = guide; |
||||
|
||||
const originalGuidePath = path.join(contentDirPath, 'guides', `${guideId}.md`); |
||||
const newGuidePath = path.join(__dirname, `../src/guides/${guideId}.md`); |
||||
|
||||
const guideWithoutFrontmatter = fs.readFileSync(originalGuidePath, 'utf8'); |
||||
fs.copyFileSync(originalGuidePath, newGuidePath); |
||||
|
||||
const guideAuthor = authors.find((author) => author.username === guide.authorUsername); |
||||
|
||||
const guideFrontMatter = yaml |
||||
.stringify({ |
||||
title: guide.title, |
||||
description: guide.description, |
||||
author: { |
||||
name: guideAuthor.name, |
||||
url: `https://twitter.com/${guideAuthor.twitter}`, |
||||
imageUrl: `${guideAuthor.picture}`, |
||||
}, |
||||
seo: { |
||||
title: `${guide.title} - roadmap.sh`, |
||||
description: guide.description, |
||||
}, |
||||
isNew: guide.isNew, |
||||
type: guide.type, |
||||
date: guide.createdAt.replace(/T.*/, ''), |
||||
sitemap: { |
||||
priority: 0.7, |
||||
changefreq: 'weekly', |
||||
}, |
||||
tags: ['guide', `${guide.type}-guide`, `guide-sitemap`], |
||||
}) |
||||
.replace(/date: "(.+?)"/, 'date: $1'); |
||||
|
||||
const guideWithUpdatedUrls = guideWithoutFrontmatter |
||||
.replace(/\[\!\[\]\((.+?\.png)\)\]\((.+?\.png)\)/g, '[![]($1)]($2)') |
||||
.replace(/\[\!\[\]\((.+?\.svg)\)\]\((.+?\.svg)\)/g, '[![]($1)]($2)') |
||||
.replace(/\/http/g, 'http') |
||||
.replace(/]\(\/guides\/(.+?)\.png\)/g, '](/guides/$1.png)') |
||||
.replace(/<iframe/g, '<iframe class="w-full aspect-video mb-5"') |
||||
.replace(/<iframe(.+?)\s?\/>/g, '<iframe$1></iframe>'); |
||||
|
||||
const guideWithFrontmatter = `---\n${guideFrontMatter}---\n\n${guideWithUpdatedUrls}`; |
||||
|
||||
console.log(`Writing guide ${guideId} to disk`); |
||||
fs.writeFileSync(newGuidePath, guideWithFrontmatter); |
||||
}); |
||||
|
||||
const oldAuthorAssetsPath = path.join(__dirname, 'developer-roadmap/public/authors'); |
||||
const newAuthorAssetsPath = path.join(__dirname, '../public/authors'); |
||||
|
||||
if (fs.existsSync(newAuthorAssetsPath)) { |
||||
fs.rmSync(newAuthorAssetsPath, { recursive: true }); |
||||
} |
||||
|
||||
fs.cpSync(oldAuthorAssetsPath, newAuthorAssetsPath, { recursive: true }); |
@ -0,0 +1,116 @@ |
||||
module.exports = { |
||||
angular: { |
||||
dimensions: { |
||||
width: 968, |
||||
height: 2277.8, |
||||
}, |
||||
}, |
||||
'aspnet-core': { |
||||
dimensions: { |
||||
width: 968, |
||||
height: 2773.45, |
||||
}, |
||||
}, |
||||
backend: { |
||||
dimensions: { |
||||
width: 968, |
||||
height: 2840.4, |
||||
}, |
||||
}, |
||||
blockchain: { |
||||
dimensions: { |
||||
width: 968, |
||||
height: 2173.87, |
||||
}, |
||||
}, |
||||
'computer-science': { |
||||
dimensions: { |
||||
width: 968, |
||||
height: 3009.05, |
||||
}, |
||||
}, |
||||
'design-system': { |
||||
dimensions: { |
||||
width: 968, |
||||
height: 2309.7, |
||||
}, |
||||
}, |
||||
devops: { |
||||
dimensions: { |
||||
width: 968, |
||||
height: 2527.46, |
||||
}, |
||||
}, |
||||
flutter: { |
||||
dimensions: { |
||||
width: 968, |
||||
height: 2042.2, |
||||
}, |
||||
}, |
||||
frontend: { |
||||
dimensions: { |
||||
width: 968, |
||||
height: 2734.48, |
||||
}, |
||||
}, |
||||
golang: { |
||||
dimensions: { |
||||
width: 968, |
||||
height: 1495.21, |
||||
}, |
||||
}, |
||||
java: { |
||||
dimensions: { |
||||
width: 968, |
||||
height: 1167.29, |
||||
}, |
||||
}, |
||||
javascript: { |
||||
dimensions: { |
||||
width: 968, |
||||
height: 2438.9, |
||||
}, |
||||
}, |
||||
nodejs: { |
||||
dimensions: { |
||||
width: 968, |
||||
height: 2474.06, |
||||
}, |
||||
}, |
||||
python: { |
||||
dimensions: { |
||||
width: 992, |
||||
height: 1259.03, |
||||
}, |
||||
}, |
||||
qa: { |
||||
dimensions: { |
||||
width: 968, |
||||
height: 2107.75, |
||||
}, |
||||
}, |
||||
react: { |
||||
dimensions: { |
||||
width: 968, |
||||
height: 1570.26, |
||||
}, |
||||
}, |
||||
'software-architect': { |
||||
dimensions: { |
||||
width: 968, |
||||
height: 1882.18, |
||||
}, |
||||
}, |
||||
'software-design-architecture': { |
||||
dimensions: { |
||||
width: 968, |
||||
height: 1764.66, |
||||
}, |
||||
}, |
||||
vue: { |
||||
dimensions: { |
||||
width: 968, |
||||
height: 1657.07, |
||||
}, |
||||
}, |
||||
}; |
@ -0,0 +1,132 @@ |
||||
const fs = require('fs'); |
||||
const path = require('path'); |
||||
const yaml = require('json-to-pretty-yaml'); |
||||
const roadmapMetas = require('./roadmap-metas.cjs'); |
||||
|
||||
const oldAssetsPath = path.join(__dirname, 'developer-roadmap/public'); |
||||
const newAssetsPath = path.join(__dirname, '../public/'); |
||||
|
||||
// Create JSONs dir |
||||
const newJsonsPath = path.join(newAssetsPath, 'jsons'); |
||||
if (fs.existsSync(newJsonsPath)) { |
||||
fs.rmSync(newJsonsPath, { recursive: true }); |
||||
} |
||||
|
||||
fs.mkdirSync(newJsonsPath); |
||||
|
||||
// Create PDFs dir |
||||
const newPdfsPath = path.join(newAssetsPath, 'pdfs'); |
||||
if (fs.existsSync(newPdfsPath)) { |
||||
fs.rmSync(newPdfsPath, { recursive: true }); |
||||
} |
||||
|
||||
fs.mkdirSync(newPdfsPath); |
||||
|
||||
const oldRoadmapsDirPath = path.join(__dirname, 'developer-roadmap/content/roadmaps'); |
||||
const newRoadmapsDirPath = path.join(__dirname, '../src/roadmaps'); |
||||
|
||||
if (fs.existsSync(newRoadmapsDirPath)) { |
||||
fs.rmSync(newRoadmapsDirPath, { recursive: true }); |
||||
} |
||||
|
||||
fs.mkdirSync(newRoadmapsDirPath); |
||||
|
||||
const oldRoadmaps = fs |
||||
.readdirSync(oldRoadmapsDirPath) |
||||
.map((roadmapDirName) => path.join(oldRoadmapsDirPath, roadmapDirName)); |
||||
|
||||
const orderInfo = {}; |
||||
const typeCounter = { |
||||
role: 1, |
||||
tool: 1, |
||||
}; |
||||
|
||||
// Calculate the sorting information for the roadmaps |
||||
oldRoadmaps.forEach((oldRoadmapPath) => { |
||||
const roadmapId = path.basename(oldRoadmapPath).replace(/\d+-/g, '').toLowerCase(); |
||||
const oldRoadmapMeta = require(path.join(oldRoadmapPath, 'meta.json')); |
||||
|
||||
orderInfo[roadmapId] = typeCounter[oldRoadmapMeta.type]; |
||||
typeCounter[oldRoadmapMeta.type] += 1; |
||||
}); |
||||
|
||||
// Iterate and create new roadmaps |
||||
oldRoadmaps.forEach((oldRoadmapPath) => { |
||||
const roadmapId = path.basename(oldRoadmapPath).replace(/\d+-/g, '').toLowerCase(); |
||||
|
||||
const metaToMerge = roadmapMetas[roadmapId] ?? {}; |
||||
const oldRoadmapMeta = require(path.join(oldRoadmapPath, 'meta.json')); |
||||
const isTextual = oldRoadmapMeta?.landingPath?.endsWith('.md'); |
||||
|
||||
const hasContentDir = fs.existsSync(path.join(oldRoadmapPath, 'content')); |
||||
|
||||
const roadmapFileContent = isTextual |
||||
? fs.readFileSync(path.join(oldRoadmapPath, oldRoadmapMeta.landingPath), 'utf8') |
||||
: ''; |
||||
|
||||
const roadmapFileContentWithUpdatedUrls = roadmapFileContent |
||||
.replace(/\[\!\[\]\((.+?\.png)\)\]\((.+?\.png)\)/g, '[![](/assets$1)](/assets$2)') |
||||
.replace(/\[\!\[\]\((.+?\.svg)\)\]\((.+?\.svg)\)/g, '[![](/assets$1)](/assets$2)') |
||||
.replace(/\[\!\[\]\((.+?\.svg)\)\]\((.+?\.png)\)/g, '[![](/assets$1)](/assets$2)') |
||||
.replace(/assetshttp\//g, 'http') |
||||
.replace(/assetshttps:\/\//g, 'https://') |
||||
.replace(/\/http/g, 'http') |
||||
.replace(/]\(\/roadmaps\/(.+?)\.png\)/g, '](/assets/roadmaps/$1.png)') |
||||
.replace(/]\(\/roadmaps\/(.+?)\.svg\)/g, '](/assets/roadmaps/$1.svg)') |
||||
.replace(/<iframe/g, '<iframe class="w-full aspect-video mb-5"') |
||||
.replace(/<iframe(.+?)\s?\/>/g, '<iframe$1></iframe>'); |
||||
|
||||
const hasJson = fs.existsSync(path.join(oldAssetsPath, `/project/${roadmapId}.json`)); |
||||
|
||||
const newRoadmapMeta = { |
||||
...( hasJson ? { jsonUrl: `/jsons/${roadmapId}.json`} : {}), |
||||
pdfUrl: `/pdfs/${roadmapId}.pdf`, |
||||
order: orderInfo[roadmapId], |
||||
featuredTitle: |
||||
oldRoadmapMeta.featuredTitle === 'Software Design and Architecture' |
||||
? 'Software Design' |
||||
: oldRoadmapMeta.featuredTitle, |
||||
featuredDescription: oldRoadmapMeta.featuredDescription, |
||||
title: oldRoadmapMeta.title, |
||||
description: oldRoadmapMeta.description, |
||||
isNew: oldRoadmapMeta.isNew, |
||||
hasTopics: hasContentDir, |
||||
...metaToMerge, |
||||
seo: oldRoadmapMeta.seo, |
||||
relatedRoadmaps: oldRoadmapMeta.relatedRoadmaps, |
||||
sitemap: { |
||||
priority: 1, |
||||
changefreq: 'monthly', |
||||
}, |
||||
tags: ['roadmap', 'main-sitemap', `${oldRoadmapMeta.type === 'tool' ? 'skill' : oldRoadmapMeta.type}-roadmap`], |
||||
}; |
||||
|
||||
const frontmatter = yaml.stringify(newRoadmapMeta); |
||||
const newRoadmapDirPath = path.join(newRoadmapsDirPath, roadmapId); |
||||
const newRoadmapFilePath = path.join(newRoadmapDirPath, `/${roadmapId}.md`); |
||||
|
||||
fs.mkdirSync(newRoadmapDirPath); |
||||
fs.writeFileSync(newRoadmapFilePath, `---\n${frontmatter}---\n\n${roadmapFileContentWithUpdatedUrls}`); |
||||
|
||||
const jsonFile = path.join(oldAssetsPath, oldRoadmapMeta.jsonUrl || '/unknown'); |
||||
const pdfFile = path.join(oldAssetsPath, oldRoadmapMeta.pdfUrl || '/unknown'); |
||||
|
||||
if (fs.existsSync(jsonFile)) { |
||||
fs.copyFileSync(jsonFile, path.join(newJsonsPath, `${roadmapId}.json`)); |
||||
} |
||||
|
||||
if (fs.existsSync(pdfFile)) { |
||||
fs.copyFileSync(pdfFile, path.join(newPdfsPath, `${roadmapId}.pdf`)); |
||||
} |
||||
|
||||
// Copy the content directory |
||||
const oldRoadmapContentDir = path.join(oldRoadmapPath, 'content'); |
||||
if (fs.existsSync(oldRoadmapContentDir)) { |
||||
fs.cpSync(oldRoadmapContentDir, path.join(newRoadmapDirPath, 'content'), { recursive: true }); |
||||
} |
||||
}); |
||||
|
||||
const roadmapAssets = path.join(oldAssetsPath, 'roadmaps'); |
||||
if (fs.existsSync(roadmapAssets)) { |
||||
fs.cpSync(roadmapAssets, path.join(newAssetsPath, 'roadmaps'), { recursive: true }); |
||||
} |
@ -0,0 +1,30 @@ |
||||
#!/usr/bin/env bash |
||||
|
||||
set -e |
||||
|
||||
# Change working directory to the directory of this script |
||||
cd "$(dirname "$0")" |
||||
|
||||
if [ ! -d "./developer-roadmap" ]; then |
||||
git clone --depth 1 -b master git@github.com:kamranahmedse/developer-roadmap.git |
||||
fi |
||||
|
||||
echo "Removing old directories" |
||||
rm -rf ../src/videos |
||||
rm -rf ../src/guides |
||||
rm -rf ../src/roadmaps |
||||
|
||||
rm -rf ../public/jsons |
||||
rm -rf ../public/pdfs |
||||
|
||||
echo "=== Migrating Roadmaps ===" |
||||
node roadmap-migrator.cjs |
||||
|
||||
echo "=== Migrating Content ===" |
||||
node content-migrator.cjs |
||||
|
||||
echo "=== Migrating Guides ===" |
||||
node guide-migrator.cjs |
||||
|
||||
echo "=== Migrating Videos ===" |
||||
node video-migrator.cjs |
@ -0,0 +1,58 @@ |
||||
const fs = require('fs'); |
||||
const path = require('path'); |
||||
const yaml = require('json-to-pretty-yaml'); |
||||
|
||||
const contentDirPath = path.join(__dirname, './developer-roadmap/content'); |
||||
const videos = require('./developer-roadmap/content/videos.json'); |
||||
|
||||
// Remove the old videos directory |
||||
const newVideosDirPath = path.join(__dirname, '../src/videos'); |
||||
if (fs.existsSync(newVideosDirPath)) { |
||||
fs.rmSync(newVideosDirPath, { recursive: true }); |
||||
} |
||||
|
||||
fs.mkdirSync(newVideosDirPath); |
||||
|
||||
videos.forEach((video) => { |
||||
const { id: videoId } = video; |
||||
|
||||
const originalVideoPath = path.join( |
||||
contentDirPath, |
||||
'videos', |
||||
`${videoId}.md` |
||||
); |
||||
|
||||
const newVideoPath = path.join(__dirname, `../src/videos/${videoId}.md`); |
||||
|
||||
const videoWithoutFrontmatter = fs.readFileSync(originalVideoPath, 'utf8'); |
||||
fs.copyFileSync(originalVideoPath, newVideoPath); |
||||
|
||||
const videoFrontMatter = yaml |
||||
.stringify({ |
||||
title: video.title, |
||||
description: video.description, |
||||
duration: video.duration, |
||||
isNew: video.isNew, |
||||
date: video.createdAt.replace(/T.*/, ''), |
||||
author: { |
||||
name: 'Kamran Ahmed', |
||||
url: `https://twitter.com/kamranahmedse`, |
||||
imageUrl: `/authors/kamranahmedse.jpeg`, |
||||
}, |
||||
sitemap: { |
||||
priority: 0.7, |
||||
changefreq: 'weekly', |
||||
}, |
||||
tags: ['video', `video-sitemap`], |
||||
}) |
||||
.replace(/date: "(.+?)"/, 'date: $1'); |
||||
|
||||
const videoWithIframeClass = videoWithoutFrontmatter |
||||
.replace(/<iframe/g, '<iframe class="w-full aspect-video mb-5"') |
||||
.replace(/<iframe(.+?)\s?\/>/g, '<iframe$1></iframe>'); |
||||
|
||||
const videoWithFrontmatter = `---\n${videoFrontMatter}---\n\n${videoWithIframeClass}`; |
||||
|
||||
console.log(`Writing video ${videoId} to disk`); |
||||
fs.writeFileSync(newVideoPath, videoWithFrontmatter); |
||||
}); |
@ -0,0 +1,76 @@ |
||||
# Code of Conduct |
||||
|
||||
## Our Pledge |
||||
|
||||
In the interest of fostering an open and welcoming environment, we as |
||||
contributors and maintainers pledge to make participation in our project and |
||||
our community a harassment-free experience for everyone, regardless of age, body |
||||
size, disability, ethnicity, sex characteristics, gender identity and expression, |
||||
level of experience, education, socio-economic status, nationality, personal |
||||
appearance, race, religion, or sexual identity and orientation. |
||||
|
||||
## Our Standards |
||||
|
||||
Examples of behavior that contributes to creating a positive environment |
||||
include: |
||||
|
||||
* Using welcoming and inclusive language |
||||
* Being respectful of differing viewpoints and experiences |
||||
* Gracefully accepting constructive criticism |
||||
* Focusing on what is best for the community |
||||
* Showing empathy towards other community members |
||||
|
||||
Examples of unacceptable behavior by participants include: |
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or |
||||
advances |
||||
* Trolling, insulting/derogatory comments, and personal or political attacks |
||||
* Public or private harassment |
||||
* Publishing others' private information, such as a physical or electronic |
||||
address, without explicit permission |
||||
* Other conduct which could reasonably be considered inappropriate in a |
||||
professional setting |
||||
|
||||
## Our Responsibilities |
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable |
||||
behavior and are expected to take appropriate and fair corrective action in |
||||
response to any instances of unacceptable behavior. |
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or |
||||
reject comments, commits, code, wiki edits, issues, and other contributions |
||||
that are not aligned to this Code of Conduct, or to ban temporarily or |
||||
permanently any contributor for other behaviors that they deem inappropriate, |
||||
threatening, offensive, or harmful. |
||||
|
||||
## Scope |
||||
|
||||
This Code of Conduct applies within all project spaces, and it also applies when |
||||
an individual is representing the project or its community in public spaces. |
||||
Examples of representing a project or community include using an official |
||||
project e-mail address, posting via an official social media account, or acting |
||||
as an appointed representative at an online or offline event. Representation of |
||||
a project may be further defined and clarified by project maintainers. |
||||
|
||||
## Enforcement |
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be |
||||
reported by contacting the project team at <kamranahmed.se@gmail.com>. All |
||||
complaints will be reviewed and investigated and will result in a response that |
||||
is deemed necessary and appropriate to the circumstances. The project team is |
||||
obligated to maintain confidentiality with regard to the reporter of an incident. |
||||
Further details of specific enforcement policies may be posted separately. |
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good |
||||
faith may face temporary or permanent repercussions as determined by other |
||||
members of the project's leadership. |
||||
|
||||
## Attribution |
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, |
||||
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html |
||||
|
||||
[homepage]: https://www.contributor-covenant.org |
||||
|
||||
For answers to common questions about this code of conduct, see |
||||
https://www.contributor-covenant.org/faq |
@ -0,0 +1,40 @@ |
||||
# Contribution |
||||
|
||||
First of all thank you for considering to contribute. Please look at the details below: |
||||
|
||||
- [Contribution](#contribution) |
||||
- [New Roadmaps](#new-roadmaps) |
||||
- [Existing Roadmaps](#existing-roadmaps) |
||||
- [Adding Content](#adding-content) |
||||
- [Guidelines](#guidelines) |
||||
|
||||
## New Roadmaps |
||||
|
||||
For new roadmaps, submit a roadmap by providing [a textual roadmap similar to this roadmap](https://gist.github.com/kamranahmedse/98758d2c73799b3a6ce17385e4c548a5) in an issue. |
||||
|
||||
## Existing Roadmaps |
||||
|
||||
For the existing roadmaps, please follow the details listed for the nature of contribution: |
||||
|
||||
- **Fixing Typos** — Make your changes in the [roadmap JSON file](https://github.com/kamranahmedse/developer-roadmap/tree/master/public/jsons) |
||||
- **Adding or Removing Nodes** — Please open an issue with your suggestion. |
||||
|
||||
**Note:** Please note that our goal is not to have the biggest list of items. Our goal is to list items or skills most relevant today. |
||||
|
||||
## Adding Content |
||||
|
||||
Find [the content directory inside the relevant roadmap](https://github.com/kamranahmedse/roadmap-astro/tree/master/src/roadmaps). Please keep the following guidelines in mind when submitting content: |
||||
|
||||
- Content must be in English. |
||||
- Put a brief description about the topic on top of the file and the a list of links below with each link having title of the URL. |
||||
|
||||
## Guidelines |
||||
|
||||
- <p><strong>Adding everything available out there is not the goal!</strong><br /> |
||||
The roadmaps represent the skillset most valuable today, i.e., if you were to enter any of the listed fields today, what would you learn?! There might be things that are of-course being used today but prioritize the things that are most in demand today, e.g., agreed that lots of people are using angular.js today but you wouldn't want to learn that instead of React, Angular, or Vue. Use your critical thinking to filter out non-essential stuff. Give honest arguments for why the resource should be included.</p> |
||||
- <p><strong>Do not add things you have not evaluated personally!</strong><br /> |
||||
Use your critical thinking to filter out non-essential stuff. Give honest arguments for why the resource should be included. Have you read this book? Can you give a short article?</p> |
||||
- <p><strong>Create a Single PR for Content Additions</strong></p> |
||||
If you are planning to contribute by adding content to the roadmaps, I recommend you to clone the repository, add content to the [content directory of the roadmap](./content/roadmaps/) and create a single PR to make it easier for me to review and merge the PR. |
||||
- Write meaningful commit messages |
||||
- Look at the existing issues/pull requests before opening new ones |
@ -0,0 +1,19 @@ |
||||
Everything including text and images in this project are protected by the copyright laws. |
||||
You are allowed to use this material for personal use but are not allowed to use it for |
||||
any other purpose including publishing the images, the project files or the content in the |
||||
images in any form either digital, non-digital, textual, graphical or written formats. |
||||
You are allowed to share the links to the repository or the website roadmap.sh but not |
||||
the content for any sort of usage that involves the content of this repository taken out |
||||
of the repository and be shared from any other medium including but not limited to blog |
||||
posts, articles, newsletters, you must get prior consent from the understated. These |
||||
conditions do not apply to the readonly GitHub forks created using the Fork button on |
||||
GitHub with the whole purpose of contributing to the project. |
||||
|
||||
Copyright © 2023 Kamran Ahmed <kamranahmed.se@gmail.com> |
||||
|
||||
Please note that I am really flexible with allowing the usage of the content in this |
||||
repository. If you reach out to me with a brief detail of why and how you would like |
||||
to use this content, there is a good chance that I will allow you to use it. The reason |
||||
behind this strictness in the license is to stop the people who have been using these |
||||
roadmaps in ill manners e.g. ripping people off with suggesting random affiliate links, |
||||
redistributing these roadmaps just for the sake of monetizing the traffic. |
@ -0,0 +1,34 @@ |
||||
{ |
||||
"name": "roadmap.sh", |
||||
"type": "module", |
||||
"version": "0.0.1", |
||||
"private": true, |
||||
"scripts": { |
||||
"dev": "astro dev", |
||||
"start": "astro dev", |
||||
"build": "astro build", |
||||
"preview": "astro preview", |
||||
"astro": "astro", |
||||
"deploy": "NODE_DEBUG=gh-pages gh-pages -d dist -t", |
||||
"sync-content": "sh ./bin/sync-content.sh", |
||||
"compress:jsons": "node bin/compress-jsons.cjs" |
||||
}, |
||||
"dependencies": { |
||||
"@astrojs/sitemap": "^1.0.0", |
||||
"@astrojs/tailwind": "^2.1.3", |
||||
"astro": "^1.8.0", |
||||
"astro-compress": "^1.1.24", |
||||
"astro-critters": "^1.1.24", |
||||
"node-html-parser": "^6.1.4", |
||||
"rehype-external-links": "^2.0.1", |
||||
"roadmap-renderer": "^1.0.1", |
||||
"tailwindcss": "^3.2.4" |
||||
}, |
||||
"devDependencies": { |
||||
"@tailwindcss/typography": "^0.5.8", |
||||
"gh-pages": "^4.0.0", |
||||
"json-to-pretty-yaml": "^1.2.2", |
||||
"prettier": "^2.8.1", |
||||
"prettier-plugin-astro": "^0.7.0" |
||||
} |
||||
} |
After Width: | Height: | Size: 25 KiB |
After Width: | Height: | Size: 844 KiB |
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 4.3 KiB |
After Width: | Height: | Size: 31 KiB |
After Width: | Height: | Size: 41 KiB |
After Width: | Height: | Size: 21 KiB |
After Width: | Height: | Size: 7.8 KiB |
After Width: | Height: | Size: 873 B |
After Width: | Height: | Size: 987 KiB |
After Width: | Height: | Size: 875 KiB |
After Width: | Height: | Size: 148 KiB |
After Width: | Height: | Size: 834 KiB |
After Width: | Height: | Size: 404 KiB |
After Width: | Height: | Size: 383 KiB |
After Width: | Height: | Size: 447 KiB |
After Width: | Height: | Size: 1.4 MiB |
After Width: | Height: | Size: 734 KiB |
After Width: | Height: | Size: 1.1 MiB |
After Width: | Height: | Size: 2.1 MiB |
After Width: | Height: | Size: 3.3 MiB |
After Width: | Height: | Size: 2.0 MiB |
After Width: | Height: | Size: 32 KiB |
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 42 KiB |
After Width: | Height: | Size: 248 KiB |
After Width: | Height: | Size: 1.4 MiB |
After Width: | Height: | Size: 1.7 MiB |
After Width: | Height: | Size: 168 KiB |
After Width: | Height: | Size: 297 KiB |
After Width: | Height: | Size: 2.6 MiB |
After Width: | Height: | Size: 1.5 MiB |
After Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 25 KiB |
After Width: | Height: | Size: 24 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 84 KiB |
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 937 KiB |
After Width: | Height: | Size: 1.1 MiB |
After Width: | Height: | Size: 2.4 KiB |
After Width: | Height: | Size: 3.8 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 54 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 7.3 KiB |
After Width: | Height: | Size: 170 KiB |
After Width: | Height: | Size: 4.1 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 2.4 KiB |
After Width: | Height: | Size: 123 B |
After Width: | Height: | Size: 3.6 KiB |