-
Notifications
You must be signed in to change notification settings - Fork 7
Add example apps page to docs #615
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
ba2db18
833b1d8
7c98fe9
dbeee7c
e783177
08022df
2da4993
b7347a0
181c2af
6130080
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,150 @@ | ||
| #!/usr/bin/env node | ||
|
|
||
| const fs = require("fs"); | ||
| const path = require("path"); | ||
|
|
||
| function parseRepositoriesFromMDX(content) { | ||
| const repositories = []; | ||
|
|
||
| // Regex to find all SampleAppCard components with title and href | ||
| const sampleAppCardRegex = /<SampleAppCard\s+([^>]+)>/g; | ||
|
|
||
| let match; | ||
| while ((match = sampleAppCardRegex.exec(content)) !== null) { | ||
| const propsString = match[1]; | ||
|
|
||
| // Extract title and href from props | ||
| const titleMatch = propsString.match(/title="([^"]+)"/); | ||
| const hrefMatch = propsString.match(/href="([^"]+)"/); | ||
|
|
||
| if (titleMatch && hrefMatch) { | ||
| const title = titleMatch[1]; | ||
| const href = hrefMatch[1]; | ||
|
|
||
| // Check if it's a GitHub URL and extract repo name | ||
| const githubMatch = href.match(/https:\/\/github\.com\/([^/]+\/[^/]+)/); | ||
|
|
||
| if (githubMatch) { | ||
| const repoName = githubMatch[1]; | ||
| repositories.push({ | ||
| title, | ||
| repo: repoName, | ||
| href, | ||
| }); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return repositories; | ||
| } | ||
|
|
||
| async function fetchRepoData(repoInfo) { | ||
| const url = `https://api.github.com/repos/${repoInfo.repo}`; | ||
|
|
||
| try { | ||
| const response = await fetch(url, { | ||
| headers: { | ||
| Authorization: `token ${process.env.GITHUB_TOKEN}`, | ||
| "User-Agent": "arcade-docs-updater", | ||
| }, | ||
| }); | ||
|
|
||
| if (!response.ok) { | ||
| throw new Error(`HTTP error! status: ${response.status}`); | ||
| } | ||
|
|
||
| const data = await response.json(); | ||
| return { | ||
| repo: repoInfo.repo, | ||
| title: repoInfo.title, | ||
| href: repoInfo.href, | ||
| createdAt: new Date(data.created_at), | ||
| updatedAt: new Date(data.updated_at), | ||
| }; | ||
| } catch (error) { | ||
| console.error(`Error fetching data for ${repoInfo.repo}:`, error); | ||
| return null; | ||
| } | ||
| } | ||
|
|
||
| function formatDate(date) { | ||
| const months = [ | ||
| "Jan", | ||
| "Feb", | ||
| "Mar", | ||
| "Apr", | ||
| "May", | ||
| "Jun", | ||
| "Jul", | ||
| "Aug", | ||
| "Sep", | ||
| "Oct", | ||
| "Nov", | ||
| "Dec", | ||
| ]; | ||
|
|
||
| return `${months[date.getMonth()]} ${date.getFullYear()}`; | ||
| } | ||
|
|
||
| async function updateExampleDates() { | ||
| console.log("Parsing repositories from MDX file..."); | ||
|
|
||
| // Read the current MDX file | ||
| const mdxPath = path.join(__dirname, "../../app/en/home/examples/page.mdx"); | ||
| let content = fs.readFileSync(mdxPath, "utf8"); | ||
|
|
||
| // Parse repositories from the MDX file | ||
| const repositories = parseRepositoriesFromMDX(content); | ||
| console.log( | ||
| "Found repositories:", | ||
| repositories.map((r) => `${r.title} (${r.repo})`) | ||
| ); | ||
|
|
||
| if (repositories.length === 0) { | ||
| console.log("No GitHub repositories found in MDX file."); | ||
| return; | ||
| } | ||
|
|
||
| console.log("Fetching repository data from GitHub API..."); | ||
|
|
||
| // Fetch data for all repositories | ||
| const repoDataPromises = repositories.map(fetchRepoData); | ||
| const repoData = (await Promise.all(repoDataPromises)).filter(Boolean); | ||
|
|
||
| // Sort by creation date (newest first) | ||
| repoData.sort((a, b) => b.createdAt - a.createdAt); | ||
|
|
||
| console.log( | ||
| "Repository dates:", | ||
| repoData.map((r) => `${r.title}: ${formatDate(r.createdAt)}`) | ||
| ); | ||
|
|
||
| // Update dates for each repository | ||
| repoData.forEach((repo) => { | ||
| // Find the SampleAppCard with this title and update its date | ||
| const titleRegex = new RegExp( | ||
| `(title="${repo.title.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}"[\\s\\S]*?)date="[^"]*"`, | ||
| "g" | ||
| ); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Regex can match dates across different card componentsThe regex pattern |
||
| const newDate = formatDate(repo.createdAt); | ||
|
|
||
| const before = content; | ||
| content = content.replace(titleRegex, `$1date="${newDate}"`); | ||
|
|
||
| if (content !== before) { | ||
| console.log(`Updated date for "${repo.title}" to ${newDate}`); | ||
| } else { | ||
| console.warn(`Could not find or update date for "${repo.title}"`); | ||
| } | ||
| }); | ||
|
|
||
| // Write the updated content back | ||
| fs.writeFileSync(mdxPath, content, "utf8"); | ||
| console.log("Successfully updated example dates!"); | ||
| } | ||
|
|
||
| // Run the update | ||
| updateExampleDates().catch((error) => { | ||
| console.error("Error updating example dates:", error); | ||
| process.exit(1); | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| name: Update Example App Dates | ||
|
|
||
| on: | ||
| schedule: | ||
| # Run every Monday at 00:00 UTC | ||
| - cron: '0 0 * * 1' | ||
| workflow_dispatch: # Allow manual triggering | ||
|
|
||
| jobs: | ||
| update-dates: | ||
| runs-on: ubuntu-latest | ||
|
|
||
| steps: | ||
| - name: Checkout repository | ||
| uses: actions/checkout@v4 | ||
| with: | ||
| token: ${{ secrets.GITHUB_TOKEN }} | ||
| fetch-depth: 1 | ||
|
|
||
| - name: Setup Node.js | ||
| uses: actions/setup-node@v4 | ||
| with: | ||
| node-version: '22.x' | ||
|
|
||
| - name: Update repository dates | ||
| env: | ||
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
| run: | | ||
| node .github/scripts/update-example-dates.js | ||
|
|
||
| - name: Check for changes | ||
| id: check-changes | ||
| run: | | ||
| if [ -n "$(git status --porcelain)" ]; then | ||
| echo "changes=true" >> $GITHUB_OUTPUT | ||
| else | ||
| echo "changes=false" >> $GITHUB_OUTPUT | ||
| fi | ||
|
|
||
| - name: Create Pull Request | ||
| if: steps.check-changes.outputs.changes == 'true' | ||
| uses: peter-evans/create-pull-request@v6 | ||
| with: | ||
| token: ${{ secrets.GITHUB_TOKEN }} | ||
| commit-message: "chore: update example app dates [skip ci]" | ||
| title: "🤖 Update example app repository dates" | ||
| body: | | ||
| This PR contains automated updates to the example app creation/update dates. | ||
|
|
||
| **Generated on:** ${{ github.event_name == 'workflow_dispatch' && 'Manual trigger' || 'Scheduled run' }} | ||
| **Triggered by:** ${{ github.actor }} | ||
|
|
||
| The script has fetched the latest repository information from GitHub API and updated the dates accordingly. | ||
| branch: chore/update-example-dates-${{ github.run_id }} | ||
| delete-branch: true | ||
| labels: | | ||
| automated | ||
| examples | ||
| assignees: rachelnabors |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,5 @@ | ||
| "use client"; | ||
| import { Card, CardContent } from "@arcadeai/design-system"; | ||
| import { motion } from "motion/react"; | ||
| import Image from "next/image"; | ||
| import Link from "next/link"; | ||
|
|
||
| type SampleAppCardProps = { | ||
|
|
@@ -10,6 +8,8 @@ type SampleAppCardProps = { | |
| image: string; | ||
| href: string; | ||
| blank?: boolean; | ||
| tags?: string[]; | ||
| date?: string; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: Required
|
||
| }; | ||
|
|
||
| export function SampleAppCard({ | ||
|
|
@@ -18,38 +18,87 @@ export function SampleAppCard({ | |
| image, | ||
| href, | ||
| blank = false, | ||
| tags = [], | ||
| date, | ||
| }: SampleAppCardProps) { | ||
| return ( | ||
| <motion.div | ||
| whileHover={{ | ||
| scale: 1.02, | ||
| boxShadow: "0 0 20px 0 rgba(238, 23, 94, 0.1)", | ||
| }} | ||
| whileTap={{ scale: 0.98 }} | ||
| > | ||
| <Link href={href} target={blank ? "_blank" : undefined}> | ||
| <Card className="group h-full overflow-hidden border-gray-200 bg-white/90 backdrop-blur-xs transition-all hover:border-[#ee175e]/30 dark:border-gray-800 dark:bg-[rgba(17,17,17,0.8)]"> | ||
| <CardContent className="p-0"> | ||
| <div className="relative aspect-video overflow-hidden bg-gray-100 dark:bg-zinc-900"> | ||
| <Image | ||
| alt={title} | ||
| className="scale-110 object-cover transition-transform duration-300" | ||
| fill | ||
| sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw" | ||
| src={image} | ||
| /> | ||
| </div> | ||
| <div className="space-y-2 p-6"> | ||
| <h3 className="font-semibold text-gray-900 text-xl tracking-tight transition-colors group-hover:text-[#ee175e] dark:text-white"> | ||
| <Link href={href} target={blank ? "_blank" : undefined}> | ||
| <Card className="flex h-full flex-col gap-1.5 border border-gray-600/20 bg-white/90 py-3 backdrop-blur-sm transition-all duration-300 hover:border-primary hover:bg-gray-600/[0.03] hover:shadow-lg dark:bg-gray-900/80"> | ||
| <CardContent className="p-0"> | ||
| <div className="space-y-2 p-6"> | ||
| <div className="flex items-start justify-between gap-2"> | ||
| <h3 className="font-semibold text-gray-900 text-xl tracking-tight dark:text-white"> | ||
| {title} | ||
| </h3> | ||
| <p className="text-gray-600 text-sm leading-relaxed dark:text-gray-300"> | ||
| {description} | ||
| </p> | ||
| {date && ( | ||
| <span className="whitespace-nowrap font-medium text-gray-500 text-xs dark:text-gray-400"> | ||
| {date} | ||
| </span> | ||
| )} | ||
| </div> | ||
| </CardContent> | ||
| </Card> | ||
| </Link> | ||
| </motion.div> | ||
| <p className="text-gray-600 text-sm leading-relaxed dark:text-gray-300"> | ||
| {description} | ||
| </p> | ||
| {tags.length > 0 && ( | ||
| <div className="flex flex-wrap gap-2 pt-2"> | ||
| {tags.map((tag, index) => { | ||
| const getTagColor = (tag: string) => { | ||
| const languages = [ | ||
| "JavaScript", | ||
| "Python", | ||
| "TypeScript", | ||
| "Java", | ||
| "Go", | ||
| "Rust", | ||
| ]; | ||
| const frameworks = [ | ||
| "Langchain", | ||
| "mastra", | ||
| "CrewAI", | ||
| "LangGraph", | ||
| "OpenAI", | ||
| "Anthropic", | ||
| "Next.js", | ||
| ]; | ||
| const integrations = [ | ||
| "Slack", | ||
| "GitHub", | ||
| "Gmail", | ||
| "Discord", | ||
| "Notion", | ||
| "Linear", | ||
| "Jira", | ||
| "Weaviate", | ||
| "Email", | ||
| "Stytch", | ||
| ]; | ||
|
Comment on lines
+46
to
+74
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we maintain this component in the design system repo instead? @sdserranog what do you think?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I based this and these on the MCP Servers page's components. They're all a liiiiittle bit different and in flux. Would love to commit to them! |
||
|
|
||
| if (languages.includes(tag)) { | ||
| return "bg-gradient-to-br from-emerald-600 to-emerald-800"; | ||
| } | ||
| if (frameworks.includes(tag)) { | ||
| return "bg-gradient-to-br from-blue-600 to-blue-800"; | ||
| } | ||
| if (integrations.includes(tag)) { | ||
| return "bg-gradient-to-br from-yellow-600 to-yellow-800"; | ||
| } | ||
| return "bg-gradient-to-br from-gray-600 to-gray-800"; | ||
| }; | ||
|
|
||
| return ( | ||
| <span | ||
| className={`inline-flex w-fit shrink-0 items-center justify-center overflow-hidden whitespace-nowrap rounded-md border-0 border-transparent px-2 py-1 font-semibold text-[0.725rem] text-white uppercase leading-4 tracking-wide shadow-md ${getTagColor(tag)}`} | ||
| key={index} | ||
| > | ||
| {tag} | ||
| </span> | ||
| ); | ||
| })} | ||
| </div> | ||
| )} | ||
| </div> | ||
| </CardContent> | ||
| </Card> | ||
| </Link> | ||
| ); | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Parsing regex fails on
>in prop valuesThe regex
/<SampleAppCard\s+([^>]+)>/guses[^>]+to capture all props, which stops matching at the first>character. If a description contains>(e.g., "For values x > y"), the regex would prematurely stop capturing, missing thehrefprop that comes after the description. This would cause the script to silently skip that card without updating its date.