Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 150 additions & 0 deletions .github/scripts/update-example-dates.js
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;
Copy link

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 values

The regex /<SampleAppCard\s+([^>]+)>/g uses [^>]+ 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 the href prop that comes after the description. This would cause the script to silently skip that card without updating its date.

Fix in Cursor Fix in Web


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"
);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regex can match dates across different card components

The regex pattern (title="..."[\s\S]*?)date="[^"]*" uses non-greedy matching to find a date after a title, but if a SampleAppCard component lacks a date attribute, the regex will continue matching across component boundaries until it finds a date in a subsequent card. This could cause the script to incorrectly update the wrong card's date attribute, corrupting the file contents.

Fix in Cursor Fix in Web

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);
});
59 changes: 59 additions & 0 deletions .github/workflows/update-example-dates.yml
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
109 changes: 79 additions & 30 deletions app/_components/sample-app-card.tsx
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 = {
Expand All @@ -10,6 +8,8 @@ type SampleAppCardProps = {
image: string;
href: string;
blank?: boolean;
tags?: string[];
date?: string;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Required image prop is accepted but never rendered

The image prop is defined as required in SampleAppCardProps (line 8) and destructured from props (line 18), but it's never used in the component's JSX. The previous implementation rendered an Image component with the image value, but this was removed during refactoring while keeping the prop in the type definition. All usages in page.mdx still pass image="/images/logo/arcade.png" which gets silently ignored. Either the image rendering was accidentally removed, or the prop definition and MDX usages need to be cleaned up.

Fix in Cursor Fix in Web

};

export function SampleAppCard({
Expand All @@ -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
Copy link
Collaborator

Choose a reason for hiding this comment

The 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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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>
);
}
3 changes: 3 additions & 0 deletions app/en/home/_meta.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ export const meta: MetaRecord = {
"api-keys": {
title: "Get an API key",
},
examples: {
title: "Example agents",
},
"-- Authoring Tools": {
type: "separator",
title: "Authoring Tools",
Expand Down
Loading