1
mirror of https://github.com/jakejarvis/jarv.is.git synced 2025-06-27 17:05:42 -04:00

bump some deps

This commit is contained in:
2025-06-09 22:02:27 -04:00
parent 030c79fb0e
commit 06ac28df1f
11 changed files with 769 additions and 971 deletions

View File

@ -1,5 +1,3 @@
# Copilot Instructions
This file provides guidance to GitHub Copilot when working with code in this repository.
## Project Overview
@ -9,9 +7,6 @@ This is a personal website (jarv.is) built with Next.js, TypeScript, and various
## Development Commands
```bash
# Install dependencies
pnpm install
# Start development server
pnpm dev
@ -27,13 +22,16 @@ pnpm lint
# Type check
pnpm typecheck
# Database migrations (using Drizzle)
npx drizzle-kit generate
# Generate database migrations (using Drizzle)
pnpm db:generate
# Apply database migrations (using Drizzle)
pnpm db:migrate
```
## Environment Setup
The project requires several environment variables to function properly. Copy `.env.example` to a new `.env` file and populate the required values. The environment variables are documented and type-checked in `lib/env.ts`.
The project requires several environment variables to function properly. The environment variables are documented and type-checked in `lib/env.ts`. Use `.env.example` as a template if a `.env` or `.env.local` file does not already exist.
Required server environment variables:
@ -66,11 +64,7 @@ Required client environment variables:
### Database Schema
The database schema is defined in `lib/db/schema.ts` and includes tables for:
- User accounts and sessions (auth system)
- Pages (for hit counter)
- Comments system
The Drizzle ORM database schema is defined in `lib/db/schema.ts`.
### Content Structure
@ -80,24 +74,26 @@ The database schema is defined in `lib/db/schema.ts` and includes tables for:
- `/notes`: MDX content for blog posts
- `/public`: Static assets
### Important Features
1. **Authentication**: GitHub OAuth integration via Better Auth
2. **MDX Processing**: Custom rehype/remark plugins for enhanced content
3. **Comments System**: GitHub-authenticated commenting system
4. **Hit Counter**: Simple analytics for page views
5. **Contact Form**: With Cloudflare Turnstile protection
## Development Considerations
1. The project assumes deployment to Vercel and makes use of Vercel-specific features
1. When using ANY library, always use `use context7` to lookup documentation from the context7 MCP server, which provides access to all project-specific configuration files and standards
2. When working with MDX content, note the custom plugins and transformations in `next.config.ts`
2. Always prefer React Server Components (RSC) over client components
3. Database operations use Drizzle ORM with Neon's serverless PostgreSQL client
3. React components follow patterns from Tailwind v4 with shadcn/ui components
4. The repository uses strict linting and type checking through ESLint and TypeScript
4. The project assumes deployment to Vercel and makes use of Vercel-specific features
5. React components follow patterns from shadcn/ui style system
5. When working with MDX content, note the custom plugins and transformations in `next.config.ts`
6. Always prefer React Server Components (RSC) over client components, and server actions ("use server") over API routes
6. Database operations use Drizzle ORM with Neon's serverless PostgreSQL client
7. The repository uses strict linting and type checking through ESLint and TypeScript
## External Documentation Lookup
1. The Context7 MCP server is available to reference documentation for any library or dependency
2. Before installing any package, running commands, or creating/updating dependency files, you MUST use `use context7` to retrieve the most up-to-date and authoritative documentation for the relevant stack, library, or technology
3. Do NOT rely solely on model training data or general knowledge; Context7 must be consulted for all dependency and setup actions

View File

@ -18,6 +18,8 @@
--muted-foreground: oklch(0.556 0 0);
--accent: oklch(0.98 0 0);
--accent-foreground: oklch(0.33 0 0);
--highlight: oklch(0.50 0.13 245.46);
--highlight-foreground: oklch(0.99 0 0);
--destructive: oklch(0.62 0.21 25.77);
--warning: oklch(0.67 0.179 58.318);
--success: oklch(0.63 0.194 149.214);
@ -26,15 +28,6 @@
--ring: oklch(0.708 0 0);
--radius: 0.625rem;
--shadow-2xs: 0 1px 3px 0px hsl(0 0% 0% / 0.05);
--shadow-xs: 0 1px 3px 0px hsl(0 0% 0% / 0.05);
--shadow-sm: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10);
--shadow: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10);
--shadow-md: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 2px 4px -1px hsl(0 0% 0% / 0.10);
--shadow-lg: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 4px 6px -1px hsl(0 0% 0% / 0.10);
--shadow-xl: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 8px 10px -1px hsl(0 0% 0% / 0.10);
--shadow-2xl: 0 1px 3px 0px hsl(0 0% 0% / 0.25);
}
[data-theme="dark"] {
@ -52,6 +45,8 @@
--muted-foreground: oklch(0.708 0 0);
--accent: oklch(0.27 0.01 286.03);
--accent-foreground: oklch(0.98 0 0);
--highlight: oklch(0.81 0.10 251.81);
--highlight-foreground: oklch(0.21 0.01 285.88);
--destructive: oklch(0.70 0.19 22.23);
--warning: oklch(0.8 0.184 86.047);
--success: oklch(0.79 0.209 151.711);
@ -80,6 +75,8 @@
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-highlight: var(--highlight);
--color-highlight-foreground: var(--highlight-foreground);
--color-destructive: var(--destructive);
--color-warning: var(--warning);
--color-success: var(--success);
@ -91,15 +88,6 @@
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
--shadow-2xs: var(--shadow-2xs);
--shadow-xs: var(--shadow-xs);
--shadow-sm: var(--shadow-sm);
--shadow: var(--shadow);
--shadow-md: var(--shadow-md);
--shadow-lg: var(--shadow-lg);
--shadow-xl: var(--shadow-xl);
--shadow-2xl: var(--shadow-2xl);
}
@theme {
@ -161,7 +149,7 @@
}
body {
@apply bg-background text-foreground selection:bg-primary selection:text-primary-foreground;
@apply bg-background text-foreground selection:bg-highlight selection:text-highlight-foreground;
}
::-webkit-scrollbar {

View File

@ -1,13 +1,13 @@
import { env } from "@/lib/env";
import { JsonLd } from "react-schemaorg";
import { ThemeProvider } from "@/components/layout/theme-context";
import { ThemeScript } from "@/components/layout/theme-script";
import { ThemeProvider } from "@/components/theme/theme-context";
import { ThemeScript } from "@/components/theme/theme-script";
import Header from "@/components/layout/header";
import Footer from "@/components/layout/footer";
import Toaster from "@/components/ui/sonner";
import Analytics from "@/app/analytics";
import { defaultMetadata } from "@/lib/metadata";
import { GeistMono, GeistSans } from "@/lib/fonts";
import { GeistSans, GeistMono } from "@/lib/fonts";
import siteConfig from "@/lib/config/site";
import authorConfig from "@/lib/config/author";
import type { Person, WebSite } from "schema-dts";

View File

@ -125,7 +125,7 @@ const Page = async ({ params }: { params: Promise<{ slug: string }> }) => {
<Suspense
// when this loads, the component will count up from zero to the actual number of hits, so we can simply
// show a zero here as a "loading indicator"
fallback={<span>0</span>}
fallback={<span className="motion-safe:animate-pulse">0</span>}
>
<ViewCounter slug={`${POSTS_DIR}/${frontmatter!.slug}`} />
</Suspense>

View File

@ -2,7 +2,7 @@
import { useSelectedLayoutSegment } from "next/navigation";
import MenuItem from "@/components/layout/menu-item";
import ThemeToggle from "@/components/layout/theme-toggle";
import ThemeToggle from "@/components/theme/theme-toggle";
import { cn } from "@/lib/utils";
import { HomeIcon, PencilLineIcon, CodeXmlIcon, MailIcon } from "lucide-react";

View File

@ -2,7 +2,7 @@
import { useContext } from "react";
import { MoonIcon, SunIcon } from "lucide-react";
import { ThemeContext } from "@/components/layout/theme-context";
import { ThemeContext } from "@/components/theme/theme-context";
import { cn } from "@/lib/utils";
const ThemeToggle = ({ className, ...rest }: React.ComponentProps<"button">) => {

View File

@ -1,229 +0,0 @@
import "dotenv/config";
import { eq } from "drizzle-orm";
import { graphql } from "@octokit/graphql";
import { db } from "@/lib/db";
import * as schema from "@/lib/db/schema";
// GitHub GraphQL API authentication
const graphqlWithAuth = graphql.defaults({
headers: {
authorization: `token ${process.env.GITHUB_TOKEN}`,
},
});
// Map of page slugs to GitHub discussion numbers
// You'll need to manually map these based on your discussions
const discussionMapping: Record<string, number> = {
"notes/dropping-dropbox": 780,
"notes/cloudflare-dns-archive-is-blocked": 1693,
"notes/finding-candidates-subdomain-takeovers": 1628,
"notes/how-to-pull-request-fork-github": 779,
"notes/how-to-backup-linux-server": 1506,
"notes/hugo-to-nextjs": 1017,
"notes/dark-mode": 780,
};
async function main() {
console.log("🌱 Starting database seed...");
// Loop through each discussion mapping
for (const [pageSlug, discussionNumber] of Object.entries(discussionMapping)) {
console.log(`Processing discussion #${discussionNumber} for page ${pageSlug}...`);
// Make sure the page exists in the database
const existingPage = await db
.select()
.from(schema.page)
.where(eq(schema.page.slug, pageSlug))
.then((results) => results[0]);
if (!existingPage) {
console.log(`Creating page entry for ${pageSlug}...`);
await db.insert(schema.page).values({
slug: pageSlug,
views: 1, // Default value
});
}
try {
// Fetch the discussion and its comments from GitHub GraphQL API
const { repository } = await graphqlWithAuth<{
repository: {
discussion: {
comments: {
nodes: Array<{
id: string;
author: {
login: string;
avatarUrl: string;
} | null;
bodyText: string;
createdAt: string;
replies: {
nodes: Array<{
id: string;
author: {
login: string;
avatarUrl: string;
} | null;
bodyText: string;
createdAt: string;
}>;
};
}>;
};
};
};
}>(
`
query GetDiscussionComments($owner: String!, $repo: String!, $number: Int!) {
repository(owner: $owner, name: $repo) {
discussion(number: $number) {
comments(first: 100) {
nodes {
id
author {
login
avatarUrl
}
bodyText
createdAt
replies(first: 100) {
nodes {
id
author {
login
avatarUrl
}
bodyText
createdAt
}
}
}
}
}
}
}
`,
{
owner: "jakejarvis", // Replace with your GitHub username
repo: "jarv.is", // Replace with your repository name
number: discussionNumber,
}
);
const comments = repository.discussion.comments.nodes;
for (const comment of comments) {
if (!comment.author) continue; // Skip comments from deleted users
// Find or create the user
const existingUser = await db
.select()
.from(schema.user)
.where(eq(schema.user.name, comment.author.login))
.then((results) => results[0]);
let userId: string;
if (!existingUser) {
console.log(`Creating user ${comment.author.login}...`);
// Create a new user
const insertedUser = await db
.insert(schema.user)
.values({
id: `github-${comment.author.login}`,
name: comment.author.login,
email: `${comment.author.login}@users.noreply.github.com`, // GitHub users get noreply email addresses
emailVerified: true,
image: comment.author.avatarUrl,
createdAt: new Date(comment.createdAt),
updatedAt: new Date(),
})
.returning({ id: schema.user.id });
userId = insertedUser[0].id;
} else {
userId = existingUser.id;
}
// Insert the parent comment
console.log(`Adding comment from ${comment.author.login}...`);
const [insertedComment] = await db
.insert(schema.comment)
.values({
content: comment.bodyText,
pageSlug: pageSlug,
userId: userId,
createdAt: new Date(comment.createdAt),
updatedAt: new Date(),
})
.returning({ id: schema.comment.id });
// Process replies
for (const reply of comment.replies.nodes) {
if (!reply.author) continue; // Skip replies from deleted users
// Find or create the user for the reply
const existingReplyUser = await db
.select()
.from(schema.user)
.where(eq(schema.user.name, reply.author.login))
.then((results) => results[0]);
let replyUserId: string;
if (!existingReplyUser) {
console.log(`Creating user ${reply.author.login}...`);
// Create a new user
const insertedReplyUser = await db
.insert(schema.user)
.values({
id: `github-${reply.author.login}`,
name: reply.author.login,
email: `${reply.author.login}@users.noreply.github.com`,
emailVerified: true,
image: reply.author.avatarUrl,
createdAt: new Date(reply.createdAt),
updatedAt: new Date(),
})
.returning({ id: schema.user.id });
replyUserId = insertedReplyUser[0].id;
} else {
replyUserId = existingReplyUser.id;
}
// Insert the reply
console.log(`Adding reply from ${reply.author.login}...`);
await db.insert(schema.comment).values({
content: reply.bodyText,
pageSlug: pageSlug,
parentId: insertedComment.id,
userId: replyUserId,
createdAt: new Date(reply.createdAt),
updatedAt: new Date(),
});
}
}
console.log(`Finished processing discussion #${discussionNumber} for ${pageSlug}`);
} catch (error) {
console.error(`Error processing discussion #${discussionNumber}:`, error);
}
}
console.log("🌱 Seed completed successfully!");
}
main()
.catch((e) => {
console.error("Error during seeding:", e);
process.exit(1);
})
.finally(async () => {
console.log("Disconnecting from database...");
process.exit(0);
});

View File

@ -25,9 +25,9 @@
"@marsidev/react-turnstile": "^1.1.0",
"@mdx-js/loader": "^3.1.0",
"@mdx-js/react": "^3.1.0",
"@neondatabase/serverless": "^1.0.0",
"@next/bundle-analyzer": "15.4.0-canary.50",
"@next/mdx": "15.4.0-canary.50",
"@neondatabase/serverless": "^1.0.1",
"@next/bundle-analyzer": "15.4.0-canary.74",
"@next/mdx": "15.4.0-canary.74",
"@octokit/graphql": "^9.0.1",
"@octokit/graphql-schema": "^15.26.0",
"@radix-ui/react-alert-dialog": "^1.1.14",
@ -42,22 +42,22 @@
"@radix-ui/react-slot": "^1.2.3",
"@radix-ui/react-toast": "^1.2.14",
"@radix-ui/react-tooltip": "^1.2.7",
"@t3-oss/env-nextjs": "^0.13.4",
"@t3-oss/env-nextjs": "^0.13.8",
"@vercel/analytics": "^1.5.0",
"@vercel/speed-insights": "^1.2.0",
"better-auth": "1.2.9-beta.1",
"cheerio": "^1.0.0",
"cheerio": "^1.1.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"copy-to-clipboard": "^3.3.3",
"date-fns": "^4.1.0",
"drizzle-orm": "^0.43.1",
"drizzle-orm": "^0.44.2",
"fast-glob": "^3.3.3",
"feed": "^5.0.1",
"feed": "^5.1.0",
"geist": "^1.4.2",
"html-entities": "^2.6.0",
"lucide-react": "0.511.0",
"next": "15.4.0-canary.50",
"lucide-react": "0.513.0",
"next": "15.4.0-canary.74",
"react": "19.1.0",
"react-activity-calendar": "^2.7.12",
"react-countup": "^6.5.3",
@ -80,51 +80,51 @@
"remark-frontmatter": "^5.0.0",
"remark-gfm": "^4.0.1",
"remark-mdx": "^3.1.0",
"remark-mdx-frontmatter": "^5.1.0",
"remark-mdx-frontmatter": "^5.2.0",
"remark-parse": "^11.0.0",
"remark-rehype": "^11.1.2",
"remark-smartypants": "^3.0.2",
"remark-strip-mdx-imports-exports": "^1.0.1",
"resend": "^4.5.1",
"resend": "^4.5.2",
"server-only": "0.0.1",
"shiki": "^3.4.2",
"sonner": "^2.0.3",
"shiki": "^3.6.0",
"sonner": "^2.0.5",
"tailwind-merge": "^3.3.0",
"tailwindcss": "^4.1.7",
"tw-animate-css": "^1.3.0",
"tailwindcss": "^4.1.8",
"tw-animate-css": "^1.3.4",
"unified": "^11.0.5",
"zod": "3.25.20"
"zod": "3.25.56"
},
"devDependencies": {
"@eslint/eslintrc": "^3.3.1",
"@eslint/js": "^9.27.0",
"@eslint/js": "^9.28.0",
"@jakejarvis/eslint-config": "^4.0.7",
"@tailwindcss/postcss": "^4.1.7",
"@tailwindcss/postcss": "^4.1.8",
"@types/mdx": "^2.0.13",
"@types/node": "^22.15.21",
"@types/react": "19.1.5",
"@types/react-dom": "19.1.5",
"@types/node": "^22.15.30",
"@types/react": "19.1.7",
"@types/react-dom": "19.1.6",
"babel-plugin-react-compiler": "19.1.0-rc.2",
"cross-env": "^7.0.3",
"dotenv": "^16.5.0",
"drizzle-kit": "^0.31.1",
"eslint": "^9.27.0",
"eslint-config-next": "15.4.0-canary.50",
"eslint": "^9.28.0",
"eslint-config-next": "15.4.0-canary.74",
"eslint-config-prettier": "^10.1.5",
"eslint-plugin-css-modules": "^2.12.0",
"eslint-plugin-drizzle": "^0.2.3",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-jsx-a11y": "^6.10.2",
"eslint-plugin-mdx": "^3.4.2",
"eslint-plugin-prettier": "^5.4.0",
"eslint-plugin-prettier": "^5.4.1",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-compiler": "19.1.0-rc.2",
"eslint-plugin-react-hooks": "^5.2.0",
"husky": "^9.1.7",
"lint-staged": "^16.0.0",
"postcss": "^8.5.3",
"lint-staged": "^16.1.0",
"postcss": "^8.5.4",
"prettier": "^3.5.3",
"prettier-plugin-tailwindcss": "^0.6.11",
"prettier-plugin-tailwindcss": "^0.6.12",
"schema-dts": "^1.1.5",
"typescript": "5.8.3"
},
@ -134,7 +134,7 @@
"engines": {
"node": ">=22.x"
},
"packageManager": "pnpm@10.11.0+sha512.6540583f41cc5f628eb3d9773ecee802f4f9ef9923cc45b69890fb47991d4b092964694ec3a4f738a420c918a333062c8b925d312f42e4f0c263eb603551f977",
"packageManager": "pnpm@10.12.1+sha512.f0dda8580f0ee9481c5c79a1d927b9164f2c478e90992ad268bbb2465a736984391d6333d2c327913578b2804af33474ca554ba29c04a8b13060a717675ae3ac",
"cacheDirectories": [
"node_modules",
".next/cache"

1367
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff