mirror of
https://github.com/kamranahmedse/developer-roadmap.git
synced 2026-03-13 02:01:57 +08:00
Compare commits
24 Commits
fix/pages-
...
fix/sync-c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0560cc930a | ||
|
|
c903b76934 | ||
|
|
4f586fd122 | ||
|
|
cb7c13fd1b | ||
|
|
704657cb36 | ||
|
|
eba3a78c70 | ||
|
|
d6cf9eb66d | ||
|
|
885e95399e | ||
|
|
d70582411e | ||
|
|
07277708eb | ||
|
|
87280b4c9e | ||
|
|
91b0a232ab | ||
|
|
bbedfec17d | ||
|
|
96b2eb2797 | ||
|
|
fc1f666daf | ||
|
|
8fb38ae944 | ||
|
|
bfe340508c | ||
|
|
fc260ec3f0 | ||
|
|
cd18dbad95 | ||
|
|
949ada2fda | ||
|
|
2823038d79 | ||
|
|
dbb25ca129 | ||
|
|
467581bbf4 | ||
|
|
bd7cf6e4d7 |
67
.github/workflows/sync-content-to-repo.yml
vendored
Normal file
67
.github/workflows/sync-content-to-repo.yml
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
name: Sync Content to Repo
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
roadmap_slug:
|
||||
description: "The ID of the roadmap to sync"
|
||||
required: true
|
||||
default: "__default__"
|
||||
|
||||
jobs:
|
||||
sync-content:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup pnpm@v9
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 9
|
||||
run_install: false
|
||||
|
||||
- name: Setup Node.js Version 20 (LTS)
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install Dependencies and Sync Content
|
||||
run: |
|
||||
echo "Installing Dependencies"
|
||||
pnpm install
|
||||
echo "Syncing Content to Repo"
|
||||
npm run sync:content-to-repo -- --roadmap-slug=${{ inputs.roadmap_slug }} --secret=${{ secrets.GH_SYNC_SECRET }}
|
||||
|
||||
- name: Check for changes
|
||||
id: verify-changed-files
|
||||
run: |
|
||||
if [ -n "$(git status --porcelain)" ]; then
|
||||
echo "changed=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "changed=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Create PR
|
||||
if: steps.verify-changed-files.outputs.changed == 'true'
|
||||
uses: peter-evans/create-pull-request@v7
|
||||
with:
|
||||
delete-branch: false
|
||||
branch: "chore/sync-content-to-repo-${{ inputs.roadmap_slug }}"
|
||||
base: "master"
|
||||
labels: |
|
||||
dependencies
|
||||
automated pr
|
||||
reviewers: arikchakma
|
||||
commit-message: "chore: sync content to repo"
|
||||
title: "chore: sync content to repository"
|
||||
body: |
|
||||
## Sync Content to Repo
|
||||
|
||||
> [!IMPORTANT]
|
||||
> This PR Syncs the Content to the Repo for the Roadmap: ${{ inputs.roadmap_slug }}
|
||||
>
|
||||
> Commit: ${{ github.sha }}
|
||||
> Workflow Path: ${{ github.workflow_ref }}
|
||||
|
||||
**Please Review the Changes and Merge the PR if everything is fine.**
|
||||
67
.github/workflows/sync-repo-to-database.yml
vendored
Normal file
67
.github/workflows/sync-repo-to-database.yml
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
name: Sync on Roadmap Changes
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- 'src/data/roadmaps/**'
|
||||
|
||||
jobs:
|
||||
sync-on-changes:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.actor != 'github-actions[bot]' && github.actor != 'dependabot[bot]'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 2 # Fetch previous commit to compare changes
|
||||
|
||||
- name: Setup pnpm@v9
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 9
|
||||
run_install: false
|
||||
|
||||
- name: Setup Node.js Version 20 (LTS)
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
run: |
|
||||
echo "Getting changed files in /src/data/roadmaps/"
|
||||
|
||||
# Get changed files between HEAD and previous commit
|
||||
CHANGED_FILES=$(git diff --name-only HEAD~1 HEAD -- src/data/roadmaps/)
|
||||
|
||||
if [ -z "$CHANGED_FILES" ]; then
|
||||
echo "No changes found in roadmaps directory"
|
||||
echo "has_changes=false" >> $GITHUB_OUTPUT
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Changed files:"
|
||||
echo "$CHANGED_FILES"
|
||||
|
||||
# Convert to space-separated list for the script
|
||||
CHANGED_FILES_LIST=$(echo "$CHANGED_FILES" | tr '\n' ' ')
|
||||
|
||||
echo "has_changes=true" >> $GITHUB_OUTPUT
|
||||
echo "changed_files=$CHANGED_FILES_LIST" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Install Dependencies
|
||||
if: steps.changed-files.outputs.has_changes == 'true'
|
||||
run: |
|
||||
echo "Installing Dependencies"
|
||||
pnpm install
|
||||
|
||||
- name: Run sync script with changed files
|
||||
if: steps.changed-files.outputs.has_changes == 'true'
|
||||
run: |
|
||||
echo "Running sync script for changed roadmap files"
|
||||
echo "Changed files: ${{ steps.changed-files.outputs.changed_files }}"
|
||||
|
||||
# Run your script with the changed file paths
|
||||
npm run sync:repo-to-database -- --files="${{ steps.changed-files.outputs.changed_files }}" --secret=${{ secrets.GH_SYNC_SECRET }}
|
||||
@@ -29,6 +29,8 @@
|
||||
"compress:images": "tsx ./scripts/compress-images.ts",
|
||||
"generate:roadmap-content-json": "tsx ./scripts/editor-roadmap-content-json.ts",
|
||||
"migrate:editor-roadmaps": "tsx ./scripts/migrate-editor-roadmap.ts",
|
||||
"sync:content-to-repo": "tsx ./scripts/sync-content-to-repo.ts",
|
||||
"sync:repo-to-database": "tsx ./scripts/sync-repo-to-database.ts",
|
||||
"test:e2e": "playwright test"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
BIN
public/pdfs/roadmaps/machine-learning.pdf
Normal file
BIN
public/pdfs/roadmaps/machine-learning.pdf
Normal file
Binary file not shown.
BIN
public/roadmaps/machine-learning.png
Normal file
BIN
public/roadmaps/machine-learning.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 523 KiB |
@@ -48,6 +48,7 @@ Here is the list of available roadmaps with more being actively worked upon.
|
||||
- [Terraform Roadmap](https://roadmap.sh/terraform)
|
||||
- [Data Analyst Roadmap](https://roadmap.sh/data-analyst)
|
||||
- [Data Engineer Roadmap](https://roadmap.sh/data-engineer)
|
||||
- [Machine Learning Roadmap](https://roadmap.sh/machine-learning)
|
||||
- [MLOps Roadmap](https://roadmap.sh/mlops)
|
||||
- [Product Manager Roadmap](https://roadmap.sh/product-manager)
|
||||
- [Engineering Manager Roadmap](https://roadmap.sh/engineering-manager)
|
||||
|
||||
142
scripts/sync-content-to-repo.ts
Normal file
142
scripts/sync-content-to-repo.ts
Normal file
@@ -0,0 +1,142 @@
|
||||
import fs from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { slugify } from '../src/lib/slugger';
|
||||
import type { OfficialRoadmapDocument } from '../src/queries/official-roadmap';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
const roadmapSlug = args?.[0]?.replace('--roadmap-slug=', '');
|
||||
const secret = args?.[1]?.replace('--secret=', '');
|
||||
if (!secret) {
|
||||
throw new Error('Secret is required');
|
||||
}
|
||||
|
||||
if (!roadmapSlug || roadmapSlug === '__default__') {
|
||||
throw new Error('Roadmap slug is required');
|
||||
}
|
||||
|
||||
console.log(`🚀 Starting ${roadmapSlug}`);
|
||||
export const allowedOfficialRoadmapTopicResourceType = [
|
||||
'roadmap',
|
||||
'official',
|
||||
'opensource',
|
||||
'article',
|
||||
'course',
|
||||
'podcast',
|
||||
'video',
|
||||
'book',
|
||||
'feed',
|
||||
] as const;
|
||||
export type AllowedOfficialRoadmapTopicResourceType =
|
||||
(typeof allowedOfficialRoadmapTopicResourceType)[number];
|
||||
|
||||
export type OfficialRoadmapTopicResource = {
|
||||
_id?: string;
|
||||
type: AllowedOfficialRoadmapTopicResourceType;
|
||||
title: string;
|
||||
url: string;
|
||||
};
|
||||
|
||||
export interface OfficialRoadmapTopicContentDocument {
|
||||
_id?: string;
|
||||
roadmapSlug: string;
|
||||
nodeId: string;
|
||||
description: string;
|
||||
resources: OfficialRoadmapTopicResource[];
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
export async function roadmapTopics(
|
||||
roadmapId: string,
|
||||
secret: string,
|
||||
): Promise<OfficialRoadmapTopicContentDocument[]> {
|
||||
const path = `https://roadmap.sh/api/v1-list-official-roadmap-topics/${roadmapId}?secret=${secret}`;
|
||||
const response = await fetch(path);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch roadmap topics: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
if (data.error) {
|
||||
throw new Error(`Failed to fetch roadmap topics: ${data.error}`);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function fetchRoadmapJson(
|
||||
roadmapId: string,
|
||||
): Promise<OfficialRoadmapDocument> {
|
||||
const response = await fetch(
|
||||
`https://roadmap.sh/api/v1-official-roadmap/${roadmapId}`,
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch roadmap json: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
if (data.error) {
|
||||
throw new Error(`Failed to fetch roadmap json: ${data.error}`);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
// Directory containing the roadmaps
|
||||
const ROADMAP_CONTENT_DIR = path.join(
|
||||
__dirname,
|
||||
'../src/data/roadmaps',
|
||||
roadmapSlug,
|
||||
);
|
||||
|
||||
const allTopics = await roadmapTopics(roadmapSlug, secret);
|
||||
const roadmap = await fetchRoadmapJson(roadmapSlug);
|
||||
const { nodes } = roadmap;
|
||||
|
||||
for (const topic of allTopics) {
|
||||
const { nodeId } = topic;
|
||||
|
||||
const node = nodes.find((node) => node.id === nodeId);
|
||||
if (!node) {
|
||||
console.error(`Node not found: ${nodeId}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const label = node?.data?.label as string;
|
||||
if (!label) {
|
||||
console.error(`Label not found: ${nodeId}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const topicSlug = `${slugify(label)}@${nodeId}.md`;
|
||||
|
||||
const topicPath = path.join(ROADMAP_CONTENT_DIR, 'content', topicSlug);
|
||||
const topicDir = path.dirname(topicPath);
|
||||
const topicDirExists = await fs
|
||||
.stat(topicDir)
|
||||
.then(() => true)
|
||||
.catch(() => false);
|
||||
if (!topicDirExists) {
|
||||
await fs.mkdir(topicDir, { recursive: true });
|
||||
}
|
||||
|
||||
const topicContent = prepareTopicContent(topic);
|
||||
await fs.writeFile(topicPath, topicContent);
|
||||
console.log(`✅ Synced ${topicSlug}`);
|
||||
}
|
||||
|
||||
function prepareTopicContent(topic: OfficialRoadmapTopicContentDocument) {
|
||||
const { description, resources = [] } = topic;
|
||||
|
||||
let content = description;
|
||||
if (resources.length > 0) {
|
||||
content += `\n\nVisit the following resources to learn more:\n\n${resources.map((resource) => `- [@${resource.type}@${resource.title}](${resource.url})`).join('\n')}`;
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
215
scripts/sync-repo-to-database.ts
Normal file
215
scripts/sync-repo-to-database.ts
Normal file
@@ -0,0 +1,215 @@
|
||||
import fs from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import type { OfficialRoadmapDocument } from '../src/queries/official-roadmap';
|
||||
import { parse } from 'node-html-parser';
|
||||
import { markdownToHtml } from '../src/lib/markdown';
|
||||
import { htmlToMarkdown } from '../src/lib/html';
|
||||
import {
|
||||
allowedOfficialRoadmapTopicResourceType,
|
||||
type AllowedOfficialRoadmapTopicResourceType,
|
||||
type OfficialRoadmapTopicContentDocument,
|
||||
type OfficialRoadmapTopicResource,
|
||||
} from './sync-content-to-repo';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
const allFiles = args?.[0]?.replace('--files=', '');
|
||||
const secret = args?.[1]?.replace('--secret=', '');
|
||||
if (!secret) {
|
||||
throw new Error('Secret is required');
|
||||
}
|
||||
|
||||
let roadmapJsonCache: Map<string, OfficialRoadmapDocument> = new Map();
|
||||
export async function fetchRoadmapJson(
|
||||
roadmapId: string,
|
||||
): Promise<OfficialRoadmapDocument> {
|
||||
if (roadmapJsonCache.has(roadmapId)) {
|
||||
return roadmapJsonCache.get(roadmapId)!;
|
||||
}
|
||||
|
||||
const response = await fetch(
|
||||
`https://roadmap.sh/api/v1-official-roadmap/${roadmapId}`,
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch roadmap json: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
if (data.error) {
|
||||
throw new Error(`Failed to fetch roadmap json: ${data.error}`);
|
||||
}
|
||||
|
||||
roadmapJsonCache.set(roadmapId, data);
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function syncContentToDatabase(
|
||||
topics: Omit<
|
||||
OfficialRoadmapTopicContentDocument,
|
||||
'createdAt' | 'updatedAt' | '_id'
|
||||
>[],
|
||||
) {
|
||||
const response = await fetch(
|
||||
`https://roadmap.sh/api/v1-sync-official-roadmap-topics`,
|
||||
{
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
topics,
|
||||
secret,
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
`Failed to sync content to database: ${response.statusText}`,
|
||||
);
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
||||
const files = allFiles.split(' ');
|
||||
console.log(`🚀 Starting ${files.length} files`);
|
||||
|
||||
const ROADMAP_CONTENT_DIR = path.join(__dirname, '../src/data/roadmaps');
|
||||
|
||||
try {
|
||||
const topics: Omit<
|
||||
OfficialRoadmapTopicContentDocument,
|
||||
'createdAt' | 'updatedAt' | '_id'
|
||||
>[] = [];
|
||||
|
||||
for (const file of files) {
|
||||
const isContentFile = file.endsWith('.md') && file.includes('content/');
|
||||
if (!isContentFile) {
|
||||
console.log(`🚨 Skipping ${file} because it is not a content file`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const pathParts = file.replace('src/data/roadmaps/', '').split('/');
|
||||
const roadmapSlug = pathParts?.[0];
|
||||
if (!roadmapSlug) {
|
||||
console.error(`🚨 Roadmap slug is required: ${file}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const nodeSlug = pathParts?.[2]?.replace('.md', '');
|
||||
if (!nodeSlug) {
|
||||
console.error(`🚨 Node id is required: ${file}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const nodeId = nodeSlug.split('@')?.[1];
|
||||
if (!nodeId) {
|
||||
console.error(`🚨 Node id is required: ${file}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const roadmap = await fetchRoadmapJson(roadmapSlug);
|
||||
const node = roadmap.nodes.find((node) => node.id === nodeId);
|
||||
if (!node) {
|
||||
console.error(`🚨 Node not found: ${file}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const filePath = path.join(
|
||||
ROADMAP_CONTENT_DIR,
|
||||
roadmapSlug,
|
||||
'content',
|
||||
`${nodeSlug}.md`,
|
||||
);
|
||||
|
||||
const content = await fs.readFile(filePath, 'utf8');
|
||||
const html = markdownToHtml(content, false);
|
||||
const rootHtml = parse(html);
|
||||
|
||||
let ulWithLinks: HTMLElement | undefined;
|
||||
rootHtml.querySelectorAll('ul').forEach((ul) => {
|
||||
const listWithJustLinks = Array.from(ul.querySelectorAll('li')).filter(
|
||||
(li) => {
|
||||
const link = li.querySelector('a');
|
||||
return link && link.textContent?.trim() === li.textContent?.trim();
|
||||
},
|
||||
);
|
||||
|
||||
if (listWithJustLinks.length > 0) {
|
||||
// @ts-expect-error - TODO: fix this
|
||||
ulWithLinks = ul;
|
||||
}
|
||||
});
|
||||
|
||||
const listLinks: Omit<OfficialRoadmapTopicResource, '_id'>[] =
|
||||
ulWithLinks !== undefined
|
||||
? Array.from(ulWithLinks.querySelectorAll('li > a'))
|
||||
.map((link) => {
|
||||
const typePattern = /@([a-z.]+)@/;
|
||||
let linkText = link.textContent || '';
|
||||
const linkHref = link.getAttribute('href') || '';
|
||||
let linkType = linkText.match(typePattern)?.[1] || 'article';
|
||||
linkType = allowedOfficialRoadmapTopicResourceType.includes(
|
||||
linkType as any,
|
||||
)
|
||||
? linkType
|
||||
: 'article';
|
||||
|
||||
linkText = linkText.replace(typePattern, '');
|
||||
|
||||
return {
|
||||
title: linkText,
|
||||
url: linkHref,
|
||||
type: linkType as AllowedOfficialRoadmapTopicResourceType,
|
||||
};
|
||||
})
|
||||
.sort((a, b) => {
|
||||
const order = [
|
||||
'official',
|
||||
'opensource',
|
||||
'article',
|
||||
'video',
|
||||
'feed',
|
||||
];
|
||||
return order.indexOf(a.type) - order.indexOf(b.type);
|
||||
})
|
||||
: [];
|
||||
|
||||
const title = rootHtml.querySelector('h1');
|
||||
ulWithLinks?.remove();
|
||||
title?.remove();
|
||||
|
||||
const allParagraphs = rootHtml.querySelectorAll('p');
|
||||
if (listLinks.length > 0 && allParagraphs.length > 0) {
|
||||
// to remove the view more see more from the description
|
||||
const lastParagraph = allParagraphs[allParagraphs.length - 1];
|
||||
lastParagraph?.remove();
|
||||
}
|
||||
|
||||
const htmlStringWithoutLinks = rootHtml.toString();
|
||||
const description = htmlToMarkdown(htmlStringWithoutLinks);
|
||||
|
||||
const updatedDescription =
|
||||
`# ${title?.textContent}\n\n${description}`.trim();
|
||||
|
||||
const label = node?.data?.label as string;
|
||||
if (!label) {
|
||||
console.error(`🚨 Label is required: ${file}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
topics.push({
|
||||
roadmapSlug,
|
||||
nodeId,
|
||||
description: updatedDescription,
|
||||
resources: listLinks,
|
||||
});
|
||||
}
|
||||
|
||||
await syncContentToDatabase(topics);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -470,10 +470,10 @@ export function PersonalDashboard(props: PersonalDashboardProps) {
|
||||
<div className="grid grid-cols-1 gap-5 bg-gray-50 px-4 py-5 sm:gap-16 sm:px-0 sm:py-16">
|
||||
<FeaturedGuideList
|
||||
heading="Guides"
|
||||
guides={guides.slice(0, 7)}
|
||||
guides={guides.slice(0, 15)}
|
||||
questions={guides
|
||||
.filter((guide) => guide.roadmapId === 'questions')
|
||||
.slice(0, 7)}
|
||||
.slice(0, 15)}
|
||||
/>
|
||||
<FeaturedVideoList heading="Videos" videos={videos} />
|
||||
</div>
|
||||
|
||||
@@ -25,7 +25,7 @@ export function ForkCourseAlert(props: ForkCourseAlertProps) {
|
||||
)}
|
||||
>
|
||||
<p className="text-sm text-balance">
|
||||
Fork the course to track progress and make changes to the course.
|
||||
Fork the course to track you progress and make changes to the course.
|
||||
</p>
|
||||
|
||||
<button
|
||||
|
||||
@@ -1,182 +0,0 @@
|
||||
---
|
||||
import { getGuideTableOfContent, type HeadingGroupType } from '../../lib/guide';
|
||||
import { markdownToHtml } from '../../lib/markdown';
|
||||
import {
|
||||
type QuestionGroupType,
|
||||
type QuestionType,
|
||||
} from '../../lib/question-group';
|
||||
import { slugify } from '../../lib/slugger';
|
||||
import { RelatedGuides } from '../Guide/RelatedGuides';
|
||||
import MarkdownFile from '../MarkdownFile.astro';
|
||||
import { TableOfContent } from '../TableOfContent/TableOfContent';
|
||||
import { QuestionsList } from './QuestionsList';
|
||||
|
||||
interface Props {
|
||||
questionGroup: QuestionGroupType;
|
||||
}
|
||||
|
||||
const { questionGroup } = Astro.props;
|
||||
|
||||
const { frontmatter: guideFrontmatter, author } = questionGroup;
|
||||
|
||||
// Group questions by topics
|
||||
const questionsGroupedByTopics = questionGroup.questions.reduce(
|
||||
(acc, question) => {
|
||||
question.topics?.forEach((topic) => {
|
||||
acc[topic] = [...(acc[topic] || []), question];
|
||||
});
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, QuestionType[]>,
|
||||
);
|
||||
|
||||
// Get all unique topics in the order they appear in the questions array
|
||||
const topicsInOrder: string[] = [];
|
||||
questionGroup.questions.forEach((question) => {
|
||||
question.topics?.forEach((topic) => {
|
||||
if (!topicsInOrder.includes(topic)) {
|
||||
topicsInOrder.push(topic);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const allHeadings = questionGroup.getHeadings();
|
||||
let tableOfContent: HeadingGroupType[] = [
|
||||
...getGuideTableOfContent(allHeadings),
|
||||
{
|
||||
depth: 2,
|
||||
children: [],
|
||||
slug: 'test-with-flashcards',
|
||||
text: 'Test yourself with Flashcards',
|
||||
},
|
||||
{
|
||||
depth: 2,
|
||||
children: topicsInOrder.map((topic) => {
|
||||
let topicText = topic;
|
||||
let topicSlug = slugify(topic);
|
||||
if (topic.toLowerCase() === 'beginners') {
|
||||
topicText = 'Beginner Level';
|
||||
topicSlug = 'beginner-level';
|
||||
} else if (topic.toLowerCase() === 'intermediate') {
|
||||
topicText = 'Intermediate Level';
|
||||
topicSlug = 'intermediate-level';
|
||||
} else if (topic.toLowerCase() === 'advanced') {
|
||||
topicText = 'Advanced Level';
|
||||
topicSlug = 'advanced-level';
|
||||
}
|
||||
|
||||
return {
|
||||
depth: 2,
|
||||
children: [],
|
||||
slug: topicSlug,
|
||||
text: topicText,
|
||||
};
|
||||
}),
|
||||
slug: 'questions-list',
|
||||
text: 'Questions List',
|
||||
},
|
||||
];
|
||||
|
||||
const showTableOfContent = tableOfContent.length > 0;
|
||||
---
|
||||
|
||||
<article class='lg:grid lg:max-w-full lg:grid-cols-[1fr_minmax(0,700px)_1fr]'>
|
||||
<!-- {
|
||||
showTableOfContent && (
|
||||
<div class='bg-linear-to-r from-gray-50 py-0 lg:col-start-3 lg:col-end-4 lg:row-start-1'>
|
||||
<RelatedGuides
|
||||
relatedTitle={guideFrontmatter?.relatedTitle}
|
||||
relatedGuides={questionGroup?.relatedGuides || {}}
|
||||
client:load
|
||||
/>
|
||||
<TableOfContent toc={tableOfContent} client:load />
|
||||
</div>
|
||||
)
|
||||
} -->
|
||||
|
||||
<div
|
||||
class:list={[
|
||||
'col-start-2 col-end-3 row-start-1 mx-auto max-w-[700px] py-5 sm:py-10',
|
||||
{
|
||||
'lg:border-r': showTableOfContent,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<MarkdownFile>
|
||||
<h1 class='mb-3 text-4xl font-bold text-balance'>
|
||||
{guideFrontmatter.title}
|
||||
</h1>
|
||||
{
|
||||
author && (
|
||||
<p class='my-0 flex items-center justify-start text-sm text-gray-400'>
|
||||
<a
|
||||
href={`/authors/${author?.id}`}
|
||||
class='inline-flex items-center font-medium underline-offset-2 hover:text-gray-600 hover:underline'
|
||||
>
|
||||
<img
|
||||
alt={author.frontmatter.name}
|
||||
src={author.frontmatter.imageUrl}
|
||||
class='mr-2 mb-0 inline h-5 w-5 rounded-full'
|
||||
/>
|
||||
{author.frontmatter.name}
|
||||
</a>
|
||||
<span class='mx-2 hidden sm:inline'>·</span>
|
||||
<a
|
||||
class='hidden underline-offset-2 hover:text-gray-600 sm:inline'
|
||||
href={`https://github.com/kamranahmedse/developer-roadmap/tree/master/src/data/question-groups/${questionGroup.id}`}
|
||||
target='_blank'
|
||||
>
|
||||
Improve this Guide
|
||||
</a>
|
||||
</p>
|
||||
)
|
||||
}
|
||||
<questionGroup.Content />
|
||||
|
||||
<h2 id='test-with-flashcards'>Test yourself with Flashcards</h2>
|
||||
<p>
|
||||
You can either use these flashcards or jump to the questions list
|
||||
section below to see them in a list format.
|
||||
</p>
|
||||
<!-- <div class='mx-0 sm:-mb-32'>
|
||||
<QuestionsList
|
||||
groupId={questionGroup.id}
|
||||
questions={questionGroup.questions}
|
||||
client:load
|
||||
/>
|
||||
</div> -->
|
||||
|
||||
<h2 id='questions-list'>Questions List</h2>
|
||||
<p>
|
||||
If you prefer to see the questions in a list format, you can find them
|
||||
below.
|
||||
</p>
|
||||
|
||||
{
|
||||
topicsInOrder.map((questionLevel) => (
|
||||
<div class='mb-5'>
|
||||
<h3 id={slugify(questionLevel)} class='mb-0 capitalize'>
|
||||
{questionLevel.toLowerCase() === 'beginners' ? 'Beginner Level' :
|
||||
questionLevel.toLowerCase() === 'intermediate' ? 'Intermediate Level' :
|
||||
questionLevel.toLowerCase() === 'advanced' ? 'Advanced Level' :
|
||||
questionLevel}
|
||||
</h3>
|
||||
{questionsGroupedByTopics[questionLevel].map((q) => (
|
||||
<div class='mb-5'>
|
||||
<h4>{q.question}</h4>
|
||||
<div set:html={markdownToHtml(q.answer, false)} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))
|
||||
}
|
||||
{
|
||||
questionGroup.ending && (
|
||||
<div class='mb-5'>
|
||||
<div set:html={markdownToHtml(questionGroup.ending, false)} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</MarkdownFile>
|
||||
</div>
|
||||
</article>
|
||||
@@ -378,6 +378,11 @@ const groups: GroupType[] = [
|
||||
{
|
||||
group: 'Machine Learning',
|
||||
roadmaps: [
|
||||
{
|
||||
title: 'Machine Learning',
|
||||
link: '/machine-learning',
|
||||
type: 'role',
|
||||
},
|
||||
{
|
||||
title: 'AI and Data Scientist',
|
||||
link: '/ai-data-scientist',
|
||||
@@ -403,6 +408,11 @@ const groups: GroupType[] = [
|
||||
link: '/data-analyst',
|
||||
type: 'role',
|
||||
},
|
||||
{
|
||||
title: 'Data Engineer',
|
||||
link: '/data-engineer',
|
||||
type: 'role',
|
||||
},
|
||||
{
|
||||
title: 'MLOps',
|
||||
link: '/mlops',
|
||||
@@ -597,7 +607,7 @@ export function RoadmapsPage() {
|
||||
{isFilterOpen && <X size={13} className="mr-1" />}
|
||||
Categories
|
||||
</button>
|
||||
<div className="container relative flex flex-col gap-4 sm:flex-row">
|
||||
<div className="relative container flex flex-col gap-4 sm:flex-row">
|
||||
<div
|
||||
className={cn(
|
||||
'hidden w-full flex-col from-gray-100 sm:w-[180px] sm:border-r sm:bg-linear-to-l sm:pt-6',
|
||||
@@ -635,10 +645,10 @@ export function RoadmapsPage() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex grow flex-col gap-6 pb-20 pt-2 sm:pt-8">
|
||||
<div className="flex grow flex-col gap-6 pt-2 pb-20 sm:pt-8">
|
||||
{visibleGroups.map((group) => (
|
||||
<div key={`${group.group}-${group.roadmaps.length}`}>
|
||||
<h2 className="mb-2 text-xs uppercase tracking-wide text-gray-400">
|
||||
<h2 className="mb-2 text-xs tracking-wide text-gray-400 uppercase">
|
||||
{group.group}
|
||||
</h2>
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ const links = [
|
||||
isHighlighted: true,
|
||||
},
|
||||
{
|
||||
link: '/ai?format=roadmap',
|
||||
link: '/ai/roadmap',
|
||||
label: 'AI Roadmaps',
|
||||
description: 'Generate roadmaps with AI',
|
||||
Icon: Sparkles,
|
||||
|
||||
@@ -208,6 +208,19 @@ export function TopicDetailAI(props: TopicDetailAIProps) {
|
||||
{roadmapTreeMapping?.subjects?.length === 0 && (
|
||||
<a
|
||||
target="_blank"
|
||||
onClick={(e) => {
|
||||
if (!isLoggedIn()) {
|
||||
e.preventDefault();
|
||||
onLogin();
|
||||
return;
|
||||
}
|
||||
|
||||
if (isLimitExceeded) {
|
||||
e.preventDefault();
|
||||
onUpgrade();
|
||||
return;
|
||||
}
|
||||
}}
|
||||
href={`/ai/course/search?term=${roadmapTreeMapping?.text}&difficulty=beginner&src=topic`}
|
||||
className="flex items-center gap-1 rounded-md border border-gray-300 bg-gray-100 px-2 py-1 hover:bg-gray-200 hover:text-black [&>svg:last-child]:hidden"
|
||||
>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
jsonUrl: '/jsons/roadmaps/ai-data-scientist.json'
|
||||
pdfUrl: '/pdfs/roadmaps/ai-data-scientist.pdf'
|
||||
order: 5
|
||||
order: 4.5
|
||||
renderer: 'editor'
|
||||
briefTitle: 'AI and Data Scientist'
|
||||
briefDescription: 'Step by step guide to becoming an AI and Data Scientist in 2025'
|
||||
|
||||
@@ -8,7 +8,7 @@ briefDescription: 'Step by step guide to becoming an AI Engineer in 2025'
|
||||
title: 'AI Engineer'
|
||||
description: 'Step by step guide to becoming an AI Engineer in 2025'
|
||||
hasTopics: true
|
||||
isNew: true
|
||||
isNew: false
|
||||
dimensions:
|
||||
width: 968
|
||||
height: 3200
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
pdfUrl: '/pdfs/roadmaps/android.pdf'
|
||||
renderer: 'editor'
|
||||
order: 5
|
||||
order: 4.7
|
||||
briefTitle: 'Android'
|
||||
briefDescription: 'Step by step guide to becoming an Android Developer in 2025'
|
||||
title: 'Android Developer'
|
||||
|
||||
@@ -6,7 +6,7 @@ briefTitle: 'Cloudflare'
|
||||
briefDescription: 'Learn to deploy your applications on Cloudflare'
|
||||
title: 'Cloudflare'
|
||||
description: 'Learn to deploy your applications on Cloudflare'
|
||||
isNew: true
|
||||
isNew: false
|
||||
hasTopics: true
|
||||
renderer: editor
|
||||
dimensions:
|
||||
|
||||
@@ -1 +1,9 @@
|
||||
# Distributed Systems Basics
|
||||
# Distributed Systems
|
||||
|
||||
A distributed system is a collection of independent computers that communicate and coordinate to appear as a single unified system. They are widely used for scalability, fault tolerance, and high availability in modern applications. However, they bring challenges such as synchronization, consistency trade-offs (CAP theorem), concurrency, and network latency.
|
||||
|
||||
Visit the following resources to learn more:
|
||||
|
||||
- [@video@Quick overview](https://www.youtube.com/watch?v=IJWwfMyPu1c)
|
||||
- [@article@Introduction to Distributed Systems](https://www.freecodecamp.org/news/a-thorough-introduction-to-distributed-systems-3b91562c9b3c/)
|
||||
- [@article@Distributed Systems Guide](https://www.baeldung.com/cs/distributed-systems-guide)
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
---
|
||||
jsonUrl: '/jsons/roadmaps/data-engineer.json'
|
||||
pdfUrl: '/pdfs/roadmaps/data-engineer.pdf'
|
||||
order: 4
|
||||
order: 4.6
|
||||
renderer: "editor"
|
||||
briefTitle: 'Data Engineer'
|
||||
briefDescription: 'Step by step guide to becoming an Data Engineer in 2025'
|
||||
briefDescription: 'Step by step guide to becoming a Data Engineer in 2025'
|
||||
title: 'Data Engineer Roadmap'
|
||||
description: 'Step by step guide to becoming an Data Engineer in 2025'
|
||||
description: 'Step by step guide to becoming a Data Engineer in 2025'
|
||||
hasTopics: true
|
||||
isNew: true
|
||||
dimensions:
|
||||
@@ -28,17 +28,17 @@ courses:
|
||||
title: 'Founder - roadmap.sh'
|
||||
schema:
|
||||
headline: 'Data Engineer Roadmap'
|
||||
description: 'Learn how to become an Data Engineer with this interactive step by step guide in 2025. We also have resources and short descriptions attached to the roadmap items so you can get everything you want to learn in one place.'
|
||||
description: 'Learn how to become a Data Engineer with this interactive step by step guide in 2025. We also have resources and short descriptions attached to the roadmap items so you can get everything you want to learn in one place.'
|
||||
imageUrl: 'https://roadmap.sh/roadmaps/data-engineer.png'
|
||||
datePublished: '2025-08-13'
|
||||
dateModified: '2025-08-13'
|
||||
seo:
|
||||
title: 'Data Engineer Roadmap'
|
||||
description: 'Learn to become an Data Engineer using this roadmap. Community driven, articles, resources, guides, interview questions, quizzes for modern data engineers.'
|
||||
description: 'Learn to become a Data Engineer using this roadmap. Community driven, articles, resources, guides, interview questions, quizzes for modern data engineers.'
|
||||
keywords:
|
||||
- 'data engineer roadmap 2025'
|
||||
- 'data engineering roadmap 2025'
|
||||
- 'guide to becoming an data engineer'
|
||||
- 'guide to becoming a data engineer'
|
||||
- 'easy data engineer roadmap'
|
||||
- 'data engineer'
|
||||
- 'data engineer roadmap'
|
||||
|
||||
@@ -5,4 +5,4 @@ In CI/CD pattern, the build, test, and deployment of applications to Kubernetes
|
||||
Learn more from the following resources:
|
||||
|
||||
- [@article@Kubernetes CI/CD Pipelines – 8 Best Practices and Tools](https://spacelift.io/blog/kubernetes-ci-cd)
|
||||
- [@article@Octopus - Deploying to Kubernetes](https://octopus.com/use-case/kubernetes)
|
||||
- [@article@8 Kubernetes CI/CD tools every developer should know](https://octopus.com/devops/kubernetes-deployments/kubernetes-ci-cd-tools-for-developers/)
|
||||
@@ -0,0 +1 @@
|
||||
# Accuracy
|
||||
@@ -0,0 +1 @@
|
||||
# Activation Functions
|
||||
@@ -0,0 +1 @@
|
||||
# Actor-Critic Methods
|
||||
@@ -0,0 +1 @@
|
||||
# APIs
|
||||
@@ -0,0 +1 @@
|
||||
# Applications of CNNs
|
||||
@@ -0,0 +1 @@
|
||||
# Attention Mechanisms
|
||||
@@ -0,0 +1 @@
|
||||
# Attention Models
|
||||
@@ -0,0 +1 @@
|
||||
# Autoencoders
|
||||
@@ -0,0 +1 @@
|
||||
# Autoencoders
|
||||
@@ -0,0 +1 @@
|
||||
# Back Propagation
|
||||
@@ -0,0 +1 @@
|
||||
# Basic concepts
|
||||
@@ -0,0 +1 @@
|
||||
# Basic Syntax
|
||||
@@ -0,0 +1 @@
|
||||
# Basics of Probability
|
||||
@@ -0,0 +1 @@
|
||||
# Bayes Theorem
|
||||
@@ -0,0 +1 @@
|
||||
# Calculus
|
||||
@@ -0,0 +1 @@
|
||||
# Chain rule of derivation
|
||||
@@ -0,0 +1 @@
|
||||
# Classification
|
||||
@@ -0,0 +1 @@
|
||||
# Clustering
|
||||
@@ -0,0 +1 @@
|
||||
# Conditionals
|
||||
@@ -0,0 +1 @@
|
||||
# Confusion Matrix
|
||||
@@ -0,0 +1 @@
|
||||
# Convolution
|
||||
@@ -0,0 +1 @@
|
||||
# Convolutional Neural Network
|
||||
@@ -0,0 +1 @@
|
||||
# CSV
|
||||
@@ -0,0 +1 @@
|
||||
# Data Cleaning
|
||||
@@ -0,0 +1 @@
|
||||
# Data Formats
|
||||
@@ -0,0 +1 @@
|
||||
# Data Loading
|
||||
@@ -0,0 +1 @@
|
||||
# Data Preparation
|
||||
@@ -0,0 +1 @@
|
||||
# Data Sources
|
||||
@@ -0,0 +1 @@
|
||||
# Data Structures
|
||||
@@ -0,0 +1 @@
|
||||
# Databases (SQL, No-SQL)
|
||||
@@ -0,0 +1 @@
|
||||
# Decision Trees, Random Forest
|
||||
@@ -0,0 +1 @@
|
||||
# Deep Learning Architectures
|
||||
@@ -0,0 +1 @@
|
||||
# Deep Learning Libraries
|
||||
@@ -0,0 +1 @@
|
||||
# Deep-Q Networks
|
||||
@@ -0,0 +1 @@
|
||||
# Derivatives, Partial Derivatives
|
||||
@@ -0,0 +1 @@
|
||||
# Descriptive Statistics
|
||||
@@ -0,0 +1 @@
|
||||
# Determinants, inverse of Matrix
|
||||
@@ -0,0 +1 @@
|
||||
# Dimensionality Reduction
|
||||
@@ -0,0 +1 @@
|
||||
# Dimensionality Reduction
|
||||
@@ -0,0 +1 @@
|
||||
# Discrete Mathematics
|
||||
@@ -0,0 +1 @@
|
||||
# Eigenvalues, Diagonalization
|
||||
@@ -0,0 +1 @@
|
||||
# ElasticNet Regularization
|
||||
@@ -0,0 +1 @@
|
||||
# Embeddings
|
||||
@@ -0,0 +1 @@
|
||||
# Essential libraries
|
||||
@@ -0,0 +1 @@
|
||||
# Excel
|
||||
@@ -0,0 +1 @@
|
||||
# Exceptions
|
||||
@@ -0,0 +1 @@
|
||||
# Exclusive
|
||||
@@ -0,0 +1 @@
|
||||
# Explainable AI
|
||||
@@ -0,0 +1 @@
|
||||
# F1-Score
|
||||
@@ -0,0 +1 @@
|
||||
# Feature Engineering
|
||||
@@ -0,0 +1 @@
|
||||
# Feature Scaling & Normalization
|
||||
@@ -0,0 +1 @@
|
||||
# Feature Selection
|
||||
@@ -0,0 +1 @@
|
||||
# Forward propagation
|
||||
@@ -0,0 +1 @@
|
||||
# Functions, Builtin Functions
|
||||
@@ -0,0 +1 @@
|
||||
# Generative Adversarial Networks
|
||||
@@ -0,0 +1 @@
|
||||
# Gradient Boosting Machines
|
||||
@@ -0,0 +1 @@
|
||||
# Gradient, Jacobian, Hessian
|
||||
@@ -0,0 +1 @@
|
||||
# Graphs & Charts
|
||||
@@ -0,0 +1 @@
|
||||
# GRU
|
||||
@@ -0,0 +1 @@
|
||||
# Hierarchical
|
||||
@@ -0,0 +1 @@
|
||||
# Image & Video Recognition
|
||||
@@ -0,0 +1 @@
|
||||
# Image Classification
|
||||
@@ -0,0 +1 @@
|
||||
# Image Segmentation
|
||||
@@ -0,0 +1 @@
|
||||
# Inferential Statistics
|
||||
@@ -0,0 +1 @@
|
||||
# Internet
|
||||
@@ -0,0 +1 @@
|
||||
# Introduction
|
||||
@@ -0,0 +1 @@
|
||||
# IoT
|
||||
@@ -0,0 +1 @@
|
||||
# JSON
|
||||
@@ -0,0 +1 @@
|
||||
# K-Fold Cross Validation
|
||||
@@ -0,0 +1 @@
|
||||
# K-Nearest Neighbors (KNN)
|
||||
@@ -0,0 +1 @@
|
||||
# Keras
|
||||
@@ -0,0 +1 @@
|
||||
# Lasso
|
||||
@@ -0,0 +1 @@
|
||||
# Lemmatization
|
||||
@@ -0,0 +1 @@
|
||||
# Linear Algebra
|
||||
@@ -0,0 +1 @@
|
||||
# Linear Algebra
|
||||
@@ -0,0 +1 @@
|
||||
# Linear Regression
|
||||
@@ -0,0 +1 @@
|
||||
# Log Loss
|
||||
@@ -0,0 +1 @@
|
||||
# Logistic Regression
|
||||
@@ -0,0 +1 @@
|
||||
# LOOCV
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user