From 87280b4c9e41eb648900913e64d86dd781ca6c87 Mon Sep 17 00:00:00 2001 From: Arik Chakma Date: Wed, 20 Aug 2025 12:19:06 +0600 Subject: [PATCH] chore: sync content to repo --- .github/workflows/sync-content-to-repo.yml | 55 ++++++++++ package.json | 1 + scripts/sync-content-to-repo.ts | 111 +++++++++++++++++++++ 3 files changed, 167 insertions(+) create mode 100644 .github/workflows/sync-content-to-repo.yml create mode 100644 scripts/sync-content-to-repo.ts diff --git a/.github/workflows/sync-content-to-repo.yml b/.github/workflows/sync-content-to-repo.yml new file mode 100644 index 000000000..4313d0f1f --- /dev/null +++ b/.github/workflows/sync-content-to-repo.yml @@ -0,0 +1,55 @@ +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: | + pnpm install + npm run sync:content-to-repo --roadmap-slug=${{ inputs.roadmap_slug }} --secret=${{ secrets.SYNC_CONTENT_TO_REPO_SECRET }} + + - name: Create PR + uses: peter-evans/create-pull-request@v7 + with: + delete-branch: false + branch: "chore/sync-content-to-repo" + base: "master" + labels: | + dependencies + automated pr + reviewers: arikchakma + commit-message: "chore: sync content to repo" + title: "Sync Content to Repo - Automated" + 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.** \ No newline at end of file diff --git a/package.json b/package.json index 41a8b7922..044c518d2 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "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", "test:e2e": "playwright test" }, "dependencies": { diff --git a/scripts/sync-content-to-repo.ts b/scripts/sync-content-to-repo.ts new file mode 100644 index 000000000..1903372ee --- /dev/null +++ b/scripts/sync-content-to-repo.ts @@ -0,0 +1,111 @@ +import fs from 'node:fs/promises'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { slugify } from '../src/lib/slugger'; + +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'); + process.exit(1); +} + +if (!roadmapSlug || roadmapSlug === '__default__') { + throw new Error('Roadmap slug is required'); + process.exit(1); +} + +console.log(`🚀 Starting ${roadmapSlug}`); +export const allowedOfficialRoadmapTopicResourceType = [ + 'official', + 'opensource', + 'article', + 'course', + 'podcast', + 'video', + 'book', +] as const; +export type AllowedOfficialRoadmapTopicResourceType = + (typeof allowedOfficialRoadmapTopicResourceType)[number]; + +type OfficialRoadmapTopicResource = { + _id?: string; + type: AllowedOfficialRoadmapTopicResourceType; + title: string; + url: string; +}; + +export interface OfficialRoadmapTopicContentDocument { + _id?: string; + roadmapSlug: string; + nodeId: string; + title: string; + description: string; + resources: OfficialRoadmapTopicResource[]; + createdAt: Date; + updatedAt: Date; +} + +export async function roadmapTopics( + roadmapId: string, + secret: string, +): Promise { + const path = `https://api.chit.fun/v1-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; +} + +// Directory containing the roadmaps +const ROADMAP_CONTENT_DIR = path.join( + __dirname, + '../src/data/roadmaps', + roadmapSlug, +); + +const allTopics = await roadmapTopics(roadmapSlug, secret); +for (const topic of allTopics) { + const { title, nodeId } = topic; + + const topicSlug = `${slugify(title)}@${nodeId}.md`; + + const topicPath = path.join(ROADMAP_CONTENT_DIR, 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; + + const content = ` +${description} + +Visit the following resources to learn more: + +${resources.map((resource) => `- [@${resource.type}@${resource.title}](${resource.url})`).join('\n')} + `.trim(); + + return content; +}