1
0
mirror of https://github.com/kamranahmedse/developer-roadmap.git synced 2025-08-21 00:21:35 +02:00

chore: update official roadmap endpoint (#8628)

* chore: update official roadmap endpoint

* fix: variable typo

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Arik Chakma
2025-05-19 21:31:03 +06:00
committed by GitHub
parent b8c60093a6
commit 5d9a5bd05c
9 changed files with 87 additions and 60 deletions

8
pnpm-lock.yaml generated
View File

@@ -261,9 +261,6 @@ importers:
tailwind-merge: tailwind-merge:
specifier: ^3.2.0 specifier: ^3.2.0
version: 3.2.0 version: 3.2.0
tinykeys:
specifier: ^3.0.0
version: 3.0.0
unified: unified:
specifier: ^11.0.5 specifier: ^11.0.5
version: 11.0.5 version: 11.0.5
@@ -3485,9 +3482,6 @@ packages:
resolution: {integrity: sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==} resolution: {integrity: sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==}
engines: {node: '>=12.0.0'} engines: {node: '>=12.0.0'}
tinykeys@3.0.0:
resolution: {integrity: sha512-nazawuGv5zx6MuDfDY0rmfXjuOGhD5XU2z0GLURQ1nzl0RUe9OuCJq+0u8xxJZINHe+mr7nw8PWYYZ9WhMFujw==}
tiptap-markdown@0.8.10: tiptap-markdown@0.8.10:
resolution: {integrity: sha512-iDVkR2BjAqkTDtFX0h94yVvE2AihCXlF0Q7RIXSJPRSR5I0PA1TMuAg6FHFpmqTn4tPxJ0by0CK7PUMlnFLGEQ==} resolution: {integrity: sha512-iDVkR2BjAqkTDtFX0h94yVvE2AihCXlF0Q7RIXSJPRSR5I0PA1TMuAg6FHFpmqTn4tPxJ0by0CK7PUMlnFLGEQ==}
peerDependencies: peerDependencies:
@@ -7306,8 +7300,6 @@ snapshots:
fdir: 6.4.4(picomatch@4.0.2) fdir: 6.4.4(picomatch@4.0.2)
picomatch: 4.0.2 picomatch: 4.0.2
tinykeys@3.0.0: {}
tiptap-markdown@0.8.10(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)): tiptap-markdown@0.8.10(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)):
dependencies: dependencies:
'@tiptap/core': 2.12.0(@tiptap/pm@2.12.0) '@tiptap/core': 2.12.0(@tiptap/pm@2.12.0)

View File

@@ -8,6 +8,7 @@ import { slugify } from '../src/lib/slugger';
import { markdownToHtml } from '../src/lib/markdown'; import { markdownToHtml } from '../src/lib/markdown';
import { HTMLElement, parse } from 'node-html-parser'; import { HTMLElement, parse } from 'node-html-parser';
import { htmlToMarkdown } from '../src/lib/html'; import { htmlToMarkdown } from '../src/lib/html';
import { httpGet } from '../src/lib/http';
const __filename = fileURLToPath(import.meta.url); const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename); const __dirname = path.dirname(__filename);
@@ -53,13 +54,16 @@ if (!stats || !stats.isDirectory()) {
for (const roadmapId of editorRoadmapIds) { for (const roadmapId of editorRoadmapIds) {
console.log(`🚀 Starting ${roadmapId}`); console.log(`🚀 Starting ${roadmapId}`);
const roadmapDir = path.join( const { response: data, error } = await httpGet(
ROADMAP_CONTENT_DIR, `${import.meta.env.PUBLIC_API_URL}/v1-official-roadmap/${roadmapId}`,
roadmapId,
`${roadmapId}.json`,
); );
const roadmapContent = await fs.readFile(roadmapDir, 'utf-8');
let { nodes } = JSON.parse(roadmapContent) as { if (error) {
console.error(error);
continue;
}
let { nodes } = data as {
nodes: Node[]; nodes: Node[];
}; };
nodes = nodes.filter( nodes = nodes.filter(
@@ -97,11 +101,11 @@ for (const roadmapId of editorRoadmapIds) {
> = {}; > = {};
for (const node of nodes) { for (const node of nodes) {
const ndoeDirPatterWithoutExt = `${slugify(node.data.label)}@${node.id}`; const nodeDirPatternWithoutExt = `${slugify(node?.data?.label as string)}@${node.id}`;
const nodeDirPattern = `${ndoeDirPatterWithoutExt}.md`; const nodeDirPattern = `${nodeDirPatternWithoutExt}.md`;
if (!roadmapContentFiles.includes(nodeDirPattern)) { if (!roadmapContentFiles.includes(nodeDirPattern)) {
contentMap[nodeDirPattern] = { contentMap[nodeDirPattern] = {
title: node.data.label, title: node?.data?.label as string,
description: '', description: '',
links: [], links: [],
}; };
@@ -169,7 +173,7 @@ for (const roadmapId of editorRoadmapIds) {
const description = htmlToMarkdown(htmlStringWithoutLinks); const description = htmlToMarkdown(htmlStringWithoutLinks);
contentMap[node.id] = { contentMap[node.id] = {
title: node.data.label, title: node.data.label as string,
description, description,
links: listLinks, links: listLinks,
}; };

View File

@@ -7,6 +7,7 @@ import type { RoadmapFrontmatter } from '../src/lib/roadmap';
import { slugify } from '../src/lib/slugger'; import { slugify } from '../src/lib/slugger';
import OpenAI from 'openai'; import OpenAI from 'openai';
import { runPromisesInBatchSequentially } from '../src/lib/promise'; import { runPromisesInBatchSequentially } from '../src/lib/promise';
import { httpGet } from '../src/lib/http';
// ERROR: `__dirname` is not defined in ES module scope // ERROR: `__dirname` is not defined in ES module scope
// https://iamwebwiz.medium.com/how-to-fix-dirname-is-not-defined-in-es-module-scope-34d94a86694d // https://iamwebwiz.medium.com/how-to-fix-dirname-is-not-defined-in-es-module-scope-34d94a86694d
@@ -50,13 +51,16 @@ if (roadmapFrontmatter.renderer !== 'editor') {
process.exit(1); process.exit(1);
} }
const roadmapDir = path.join( const { response: roadmapContent, error } = await httpGet(
ROADMAP_CONTENT_DIR, `${import.meta.env.PUBLIC_API_URL}/v1-official-roadmap/${roadmapId}`,
roadmapId,
`${roadmapId}.json`,
); );
const roadmapContent = await fs.readFile(roadmapDir, 'utf-8');
let { nodes, edges } = JSON.parse(roadmapContent) as { if (error) {
console.error(error);
process.exit(1);
}
let { nodes, edges } = roadmapContent as {
nodes: Node[]; nodes: Node[];
edges: Edge[]; edges: Edge[];
}; };
@@ -138,7 +142,7 @@ function writeTopicContent(
} }
async function writeNodeContent(node: Node & { parentTitle?: string }) { async function writeNodeContent(node: Node & { parentTitle?: string }) {
const nodeDirPattern = `${slugify(node.data.label)}@${node.id}.md`; const nodeDirPattern = `${slugify(node?.data?.label as string)}@${node.id}.md`;
if (!roadmapContentFiles.includes(nodeDirPattern)) { if (!roadmapContentFiles.includes(nodeDirPattern)) {
console.log(`Missing file for: ${nodeDirPattern}`); console.log(`Missing file for: ${nodeDirPattern}`);
return; return;
@@ -152,7 +156,7 @@ async function writeNodeContent(node: Node & { parentTitle?: string }) {
return; return;
} }
const topic = node.data.label; const topic = node.data.label as string;
const parentTopic = node.parentTitle; const parentTopic = node.parentTitle;
console.log(`⏳ Generating content for ${topic}...`); console.log(`⏳ Generating content for ${topic}...`);

View File

@@ -5,6 +5,7 @@ import type { Node } from '@roadmapsh/editor';
import matter from 'gray-matter'; import matter from 'gray-matter';
import type { RoadmapFrontmatter } from '../src/lib/roadmap'; import type { RoadmapFrontmatter } from '../src/lib/roadmap';
import { slugify } from '../src/lib/slugger'; import { slugify } from '../src/lib/slugger';
import { httpGet } from '../src/lib/http';
// ERROR: `__dirname` is not defined in ES module scope // ERROR: `__dirname` is not defined in ES module scope
// https://iamwebwiz.medium.com/how-to-fix-dirname-is-not-defined-in-es-module-scope-34d94a86694d // https://iamwebwiz.medium.com/how-to-fix-dirname-is-not-defined-in-es-module-scope-34d94a86694d
@@ -48,13 +49,16 @@ if (roadmapFrontmatter.renderer !== 'editor') {
process.exit(1); process.exit(1);
} }
const roadmapDir = path.join( const { response: roadmapContent, error } = await httpGet(
ROADMAP_CONTENT_DIR, `${import.meta.env.PUBLIC_API_URL}/v1-official-roadmap/${roadmapId}`,
roadmapId,
`${roadmapId}.json`,
); );
const roadmapContent = await fs.readFile(roadmapDir, 'utf-8');
let { nodes } = JSON.parse(roadmapContent) as { if (error) {
console.error(error);
process.exit(1);
}
let { nodes } = roadmapContent as {
nodes: Node[]; nodes: Node[];
}; };
nodes = nodes.filter( nodes = nodes.filter(
@@ -73,7 +77,7 @@ const roadmapContentFiles = await fs.readdir(roadmapContentDir, {
}); });
nodes.forEach(async (node, index) => { nodes.forEach(async (node, index) => {
const nodeDirPattern = `${slugify(node.data.label)}@${node.id}.md`; const nodeDirPattern = `${slugify(node.data.label as string)}@${node.id}.md`;
if (roadmapContentFiles.includes(nodeDirPattern)) { if (roadmapContentFiles.includes(nodeDirPattern)) {
console.log(`Skipping ${nodeDirPattern}`); console.log(`Skipping ${nodeDirPattern}`);
return; return;

View File

@@ -36,7 +36,9 @@ export function EditorRoadmap(props: EditorRoadmapProps) {
const { response, error } = await httpGet< const { response, error } = await httpGet<
Omit<RoadmapRendererProps, 'resourceId'> Omit<RoadmapRendererProps, 'resourceId'>
>(`/${switchRoadmapId || resourceId}.json`); >(
`${import.meta.env.PUBLIC_API_URL}/v1-official-roadmap/${switchRoadmapId || resourceId}`,
);
if (error) { if (error) {
console.error(error); console.error(error);

View File

@@ -62,15 +62,6 @@ export function MemberProgressModal(props: ProgressMapProps) {
const toast = useToast(); const toast = useToast();
const [renderer, setRenderer] = useState<PageType['renderer']>('balsamiq'); const [renderer, setRenderer] = useState<PageType['renderer']>('balsamiq');
let resourceJsonUrl = import.meta.env.DEV
? 'http://localhost:3000'
: 'https://roadmap.sh';
if (resourceType === 'roadmap') {
resourceJsonUrl += `/${resourceId}.json`;
} else {
resourceJsonUrl += `/best-practices/${resourceId}.json`;
}
async function getMemberProgress( async function getMemberProgress(
teamId: string, teamId: string,
memberId: string, memberId: string,
@@ -92,7 +83,7 @@ export function MemberProgressModal(props: ProgressMapProps) {
return response; return response;
} }
async function renderResource(jsonUrl: string) { async function renderResource() {
const page = await getResourceMeta(resourceType, resourceId); const page = await getResourceMeta(resourceType, resourceId);
if (!page) { if (!page) {
toast.error('Resource not found'); toast.error('Resource not found');
@@ -102,11 +93,22 @@ export function MemberProgressModal(props: ProgressMapProps) {
const renderer = page.renderer || 'balsamiq'; const renderer = page.renderer || 'balsamiq';
setRenderer(renderer); setRenderer(renderer);
const res = await fetch(jsonUrl, {}); let resourceJsonUrl = import.meta.env.DEV
? 'http://localhost:3000'
: 'https://roadmap.sh';
if (resourceType === 'roadmap' && renderer === 'balsamiq') {
resourceJsonUrl += `/${resourceId}.json`;
} else if (resourceType === 'roadmap' && renderer === 'editor') {
resourceJsonUrl = `${import.meta.env.PUBLIC_API_URL}/v1-official-roadmap/${resourceId}`;
} else {
resourceJsonUrl += `/best-practices/${resourceId}.json`;
}
const res = await fetch(resourceJsonUrl, {});
const json = await res.json(); const json = await res.json();
const svg = const svg =
renderer === 'editor' renderer === 'editor'
? await renderFlowJSON(json as any) ? await renderFlowJSON(json)
: await wireframeJSONToSVG(json, { : await wireframeJSONToSVG(json, {
fontURL: '/fonts/balsamiq.woff2', fontURL: '/fonts/balsamiq.woff2',
}); });
@@ -129,19 +131,13 @@ export function MemberProgressModal(props: ProgressMapProps) {
}); });
useEffect(() => { useEffect(() => {
if ( if (!containerEl.current || !resourceId || !resourceType || !teamId) {
!containerEl.current ||
!resourceJsonUrl ||
!resourceId ||
!resourceType ||
!teamId
) {
return; return;
} }
setIsLoading(true); setIsLoading(true);
Promise.all([ Promise.all([
renderResource(resourceJsonUrl), renderResource(),
getMemberProgress(teamId, member._id, resourceType, resourceId), getMemberProgress(teamId, member._id, resourceType, resourceId),
]) ])
.then(([_, memberProgress = {}]) => { .then(([_, memberProgress = {}]) => {
@@ -276,7 +272,7 @@ export function MemberProgressModal(props: ProgressMapProps) {
}, [member]); }, [member]);
return ( return (
<div className="fixed left-0 right-0 top-0 z-100 h-full items-center justify-center overflow-y-auto overflow-x-hidden overscroll-contain bg-black/50"> <div className="fixed top-0 right-0 left-0 z-100 h-full items-center justify-center overflow-x-hidden overflow-y-auto overscroll-contain bg-black/50">
<div <div
id={renderer === 'editor' ? undefined : 'customized-roadmap'} id={renderer === 'editor' ? undefined : 'customized-roadmap'}
className="relative mx-auto h-full w-full max-w-4xl p-4 md:h-auto" className="relative mx-auto h-full w-full max-w-4xl p-4 md:h-auto"
@@ -304,14 +300,14 @@ export function MemberProgressModal(props: ProgressMapProps) {
<div className="flex w-full justify-center"> <div className="flex w-full justify-center">
<Spinner <Spinner
isDualRing={false} isDualRing={false}
className="mb-4 mt-2 h-4 w-4 animate-spin fill-blue-600 text-gray-200 sm:h-8 sm:w-8" className="mt-2 mb-4 h-4 w-4 animate-spin fill-blue-600 text-gray-200 sm:h-8 sm:w-8"
/> />
</div> </div>
)} )}
<button <button
type="button" type="button"
className={`absolute right-2.5 top-3 ml-auto inline-flex items-center rounded-lg bg-transparent p-1.5 text-sm text-gray-400 hover:text-gray-900 lg:hidden ${ className={`absolute top-3 right-2.5 ml-auto inline-flex items-center rounded-lg bg-transparent p-1.5 text-sm text-gray-400 hover:text-gray-900 lg:hidden ${
isCurrentUser ? 'hover:bg-gray-800' : 'hover:bg-gray-100' isCurrentUser ? 'hover:bg-gray-800' : 'hover:bg-gray-100'
}`} }`}
onClick={onClose} onClick={onClose}

View File

@@ -65,8 +65,10 @@ export function UserProgressModal(props: ProgressMapProps) {
let resourceJsonUrl = import.meta.env.DEV let resourceJsonUrl = import.meta.env.DEV
? 'http://localhost:3000' ? 'http://localhost:3000'
: 'https://roadmap.sh'; : 'https://roadmap.sh';
if (resourceType === 'roadmap') { if (resourceType === 'roadmap' && renderer === 'balsamiq') {
resourceJsonUrl += `/${resourceId}.json`; resourceJsonUrl += `/${resourceId}.json`;
} else if (resourceType === 'roadmap' && renderer === 'editor') {
resourceJsonUrl = `${import.meta.env.PUBLIC_API_URL}/v1-official-roadmap/${resourceId}`;
} else { } else {
resourceJsonUrl += `/best-practices/${resourceId}.json`; resourceJsonUrl += `/best-practices/${resourceId}.json`;
} }

View File

@@ -86,7 +86,7 @@ export function UserProfileRoadmapRenderer(
<div <div
className={cn( className={cn(
'bg-white', 'bg-white',
isCustomResource ? 'w-full' : 'container relative max-w-[1000px]!', isCustomResource ? 'w-full' : 'relative container max-w-[1000px]!',
)} )}
> >
{isCustomResource ? ( {isCustomResource ? (
@@ -136,7 +136,7 @@ export function UserProfileRoadmapRenderer(
<div className="flex w-full justify-center"> <div className="flex w-full justify-center">
<Spinner <Spinner
isDualRing={false} isDualRing={false}
className="mb-4 mt-2 h-4 w-4 animate-spin fill-blue-600 text-gray-200 sm:h-8 sm:w-8" className="mt-2 mb-4 h-4 w-4 animate-spin fill-blue-600 text-gray-200 sm:h-8 sm:w-8"
/> />
</div> </div>
)} )}

View File

@@ -0,0 +1,23 @@
import { queryOptions } from '@tanstack/react-query';
import { httpGet } from '../lib/query-http';
export interface OfficialRoadmapDocument {
_id: string;
title: string;
description?: string;
slug: string;
nodes: any[];
edges: any[];
createdAt: Date;
updatedAt: Date;
}
export function officialRoadmapOptions(slug: string) {
return queryOptions({
queryKey: ['official-roadmap', slug],
queryFn: () => {
return httpGet<OfficialRoadmapDocument>(`/v1-official-roadmap/${slug}`);
},
});
}