-
Notifications
You must be signed in to change notification settings - Fork 32
feat(cron): trending info calc via github actions cron #86
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
base: master
Are you sure you want to change the base?
Changes from all commits
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,45 @@ | ||
| name: Generate Trending Snapshots | ||
|
|
||
| on: | ||
| schedule: | ||
| - cron: "0 0 * * *" # daily at 00:00 UTC | ||
| workflow_dispatch: # manual trigger | ||
|
|
||
| jobs: | ||
| generate: | ||
| runs-on: ubuntu-latest | ||
| permissions: | ||
| contents: write | ||
|
|
||
| steps: | ||
| - name: Checkout repo | ||
| uses: actions/checkout@v4 | ||
|
|
||
| - name: Setup Node.js | ||
| uses: actions/setup-node@v4 | ||
| with: | ||
| node-version: 20 | ||
| cache: 'npm' | ||
|
|
||
| - name: Install pnpm | ||
| uses: pnpm/action-setup@v4 | ||
| with: | ||
| version: 9 | ||
|
|
||
| - name: Install dependencies | ||
| run: pnpm install | ||
|
|
||
| - name: Generate Prisma Client | ||
| run: pnpm prisma generate | ||
|
|
||
| - name: Generate trending data | ||
| run: pnpm run generate:trending | ||
| env: | ||
| DATABASE_URL: ${{ secrets.DATABASE_URL }} | ||
|
|
||
| - name: Commit updated snapshots | ||
| run: | | ||
| git config user.name "github-actions[bot]" | ||
| git config user.email "github-actions[bot]@users.noreply.github.com" | ||
| git add new-api-details/trending | ||
| git diff --staged --quiet || (git commit -m "chore: update trending snapshots [skip ci]" && git push) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| import { ReactNode } from "react"; | ||
| import { Header } from "@/components/header"; | ||
| import { Footer } from "@/components/Footer"; | ||
|
|
||
| interface TrendingLayoutProps { | ||
| children: ReactNode; | ||
| } | ||
|
|
||
| /** | ||
| * Layout wrapper for all /trending/* routes | ||
| * Includes header and footer with proper spacing | ||
| */ | ||
| export default function TrendingLayout({ | ||
| children, | ||
| }: TrendingLayoutProps) { | ||
| return ( | ||
| <div className="min-h-screen bg-background flex flex-col"> | ||
| <Header /> | ||
| <main className="flex-1 pt-20 lg:pt-24"> | ||
| {children} | ||
| </main> | ||
| <Footer /> | ||
| </div> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,175 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { Metadata } from "next"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { notFound } from "next/navigation"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Heading, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Text, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } from "@/components/ui"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { getFullUrl } from "@/lib/constants"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| loadTrendingSnapshot, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| isValidTrendingEntity, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| isValidTrendingRange, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type TrendingRange, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } from "@/lib/trending-types"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { TrendingPageClient } from "./trending-page-client"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Trending Page | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Route: /trending/:entity | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Shows trending snapshots of entities over time. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Supported entities: organizations | projects | tech-stack | topics | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Time range is selected via ?range=daily|weekly|monthly|yearly | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Uses static JSON snapshots - no API calls, no database queries. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export const revalidate = 3600; // 1 hour | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| interface PageProps { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| params: Promise<{ entity: string }>; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| searchParams: Promise<{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| range?: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| year?: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| month?: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }>; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export async function generateStaticParams() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return [ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { entity: "organizations" }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { entity: "projects" }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { entity: "tech-stack" }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { entity: "topics" }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export async function generateMetadata({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| params, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| searchParams, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }: PageProps): Promise<Metadata> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { entity } = await params; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { range, year, month } = await searchParams; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!isValidTrendingEntity(entity)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| title: "Trending - GSoC Organizations Guide", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const validRange: TrendingRange = isValidTrendingRange(range ?? null) ? (range as TrendingRange) : "monthly"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const entityName = entity === "tech-stack" ? "Tech Stack" : entity.charAt(0).toUpperCase() + entity.slice(1); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const rangeName = validRange.charAt(0).toUpperCase() + validRange.slice(1); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const isArchive = year !== undefined; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let title: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let description: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (isArchive) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const yearNum = parseInt(year || "", 10); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const monthNum = month ? parseInt(month, 10) : undefined; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const monthName = monthNum | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ? new Date(2000, monthNum - 1).toLocaleString("default", { month: "long" }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| : ""; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const archiveLabel = monthNum ? `${monthName} ${yearNum}` : `${yearNum}`; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| title = `Trending ${entityName} - ${archiveLabel} Archive - GSoC Organizations Guide`; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| description = `Historical trending data for ${entityName.toLowerCase()} in Google Summer of Code ${archiveLabel}. Explore archived snapshots and see how trends have evolved.`; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| title = `Trending ${entityName} - ${rangeName} - GSoC Organizations Guide`; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| description = `Discover trending ${entityName.toLowerCase()} in Google Summer of Code. See what's gaining momentum ${validRange === "daily" ? "today" : validRange === "weekly" ? "this week" : validRange === "monthly" ? "this month" : "this year"}.`; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const paramsObj = new URLSearchParams(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (validRange !== "monthly") paramsObj.set("range", validRange); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (year) paramsObj.set("year", year); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (month) paramsObj.set("month", month); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const queryString = paramsObj.toString(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const canonicalUrl = getFullUrl(`/trending/${entity}${queryString ? `?${queryString}` : ""}`); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| title, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| description, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| alternates: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| canonical: canonicalUrl, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| robots: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| index: true, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| follow: true, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| openGraph: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| title: `Trending ${entityName} - ${rangeName}`, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| description, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| url: canonicalUrl, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type: "website", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| siteName: "GSoC Organizations Guide", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| images: [ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| url: getFullUrl("/og/gsoc-organizations-guide.jpg"), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| width: 1200, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| height: 630, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| alt: "GSoC Organizations Guide", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| twitter: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| card: "summary_large_image", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| title: `Trending ${entityName} - ${rangeName}`, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| description, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| images: [getFullUrl("/og/gsoc-organizations-guide.jpg")], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export default async function TrendingPage({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| params, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| searchParams, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }: PageProps) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { entity } = await params; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { range, year, month } = await searchParams; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!isValidTrendingEntity(entity)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| notFound(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const validRange: TrendingRange = isValidTrendingRange(range ?? null) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ? (range as TrendingRange) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| : "monthly"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const yearNum = year ? parseInt(year, 10) : undefined; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const monthNum = month ? parseInt(month, 10) : undefined; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const snapshot = await loadTrendingSnapshot( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| entity, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| validRange, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| yearNum, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| monthNum | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!snapshot) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="container mx-auto px-4 py-16 lg:py-24 max-w-4xl"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="text-center"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Heading variant="section" className="mb-4 text-2xl lg:text-3xl"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Trending data not available | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </Heading> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Text className="mt-4 text-muted-foreground text-base max-w-md mx-auto"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| The trending snapshot for {entity} ({validRange}) has not been generated yet. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <br /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <br /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Please run the trending data generation script to populate this page. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </Text> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+148
to
+163
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. Improve user-facing error message. The fallback UI mentions "run the trending data generation script," which exposes implementation details to end users. Consider a more user-friendly message. Proposed fix <Text className="mt-4 text-muted-foreground text-base max-w-md mx-auto">
- The trending snapshot for {entity} ({validRange}) has not been generated yet.
- <br />
- <br />
- Please run the trending data generation script to populate this page.
+ Trending data for {entity} is not yet available for the selected time range.
+ <br />
+ <br />
+ Please check back later or try a different time range.
</Text>📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <TrendingPageClient | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| entity={entity} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| snapshot={snapshot} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| currentRange={validRange} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| archiveYear={yearNum} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| archiveMonth={monthNum} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
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.
Cache configuration mismatch: using npm cache with pnpm.
The workflow uses
pnpmfor package management but configurescache: 'npm'in setup-node. This means the cache won't be utilized effectively.🔧 Proposed fix
- name: Setup Node.js uses: actions/setup-node@v4 with: node-version: 20 - cache: 'npm' + cache: 'pnpm'📝 Committable suggestion
🤖 Prompt for AI Agents