mirror of
https://github.com/jakejarvis/jarv.is.git
synced 2026-04-17 10:28:46 -04:00
refactor: remove @t3-oss/env-nextjs, use process.env directly
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2,7 +2,7 @@ import { JsonLd } from "react-schemaorg";
|
||||
import type { VideoObject } from "schema-dts";
|
||||
import { PageTitle } from "@/components/layout/page-title";
|
||||
import { Video } from "@/components/video";
|
||||
import { env } from "@/lib/env";
|
||||
|
||||
import { createMetadata } from "@/lib/metadata";
|
||||
|
||||
import thumbnail from "./thumbnail.png";
|
||||
@@ -31,8 +31,8 @@ const Page = () => (
|
||||
description: metadata.description as string,
|
||||
contentUrl:
|
||||
"https://ijyxfbpcm3itvdly.public.blob.vercel-storage.com/birthday-pavk1LBK4H6xF8ZWeR0oTcaabGuQ8T.webm",
|
||||
thumbnailUrl: `${env.NEXT_PUBLIC_BASE_URL}${thumbnail.src}`,
|
||||
embedUrl: `${env.NEXT_PUBLIC_BASE_URL}/birthday`,
|
||||
thumbnailUrl: `${process.env.NEXT_PUBLIC_BASE_URL}${thumbnail.src}`,
|
||||
embedUrl: `${process.env.NEXT_PUBLIC_BASE_URL}/birthday`,
|
||||
uploadDate: "1996-02-06T00:00:00Z",
|
||||
duration: "PT6M10S",
|
||||
}}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { JsonLd } from "react-schemaorg";
|
||||
import type { VideoObject } from "schema-dts";
|
||||
import { PageTitle } from "@/components/layout/page-title";
|
||||
import { Video } from "@/components/video";
|
||||
import { env } from "@/lib/env";
|
||||
|
||||
import { createMetadata } from "@/lib/metadata";
|
||||
|
||||
import thumbnail from "./thumbnail.png";
|
||||
@@ -32,8 +32,8 @@ const Page = () => (
|
||||
description: metadata.description as string,
|
||||
contentUrl:
|
||||
"https://ijyxfbpcm3itvdly.public.blob.vercel-storage.com/convention-ZTUBLwMcmOE8EJ4tNAhpCli4NAHKcG.webm",
|
||||
thumbnailUrl: `${env.NEXT_PUBLIC_BASE_URL}${thumbnail.src}`,
|
||||
embedUrl: `${env.NEXT_PUBLIC_BASE_URL}/hillary`,
|
||||
thumbnailUrl: `${process.env.NEXT_PUBLIC_BASE_URL}${thumbnail.src}`,
|
||||
embedUrl: `${process.env.NEXT_PUBLIC_BASE_URL}/hillary`,
|
||||
uploadDate: "2016-07-25T00:00:00Z",
|
||||
duration: "PT1M51S",
|
||||
}}
|
||||
|
||||
@@ -8,7 +8,7 @@ import { Providers } from "@/components/providers";
|
||||
import { Toaster } from "@/components/ui/sonner";
|
||||
import authorConfig from "@/lib/config/author";
|
||||
import siteConfig from "@/lib/config/site";
|
||||
import { env } from "@/lib/env";
|
||||
|
||||
import { Inter, JetBrainsMono } from "@/lib/fonts";
|
||||
import { defaultMetadata } from "@/lib/metadata";
|
||||
|
||||
@@ -18,7 +18,7 @@ export const metadata = defaultMetadata;
|
||||
|
||||
const RootLayout = ({ children }: Readonly<{ children: React.ReactNode }>) => (
|
||||
<html
|
||||
lang={env.NEXT_PUBLIC_SITE_LOCALE}
|
||||
lang={process.env.NEXT_PUBLIC_SITE_LOCALE}
|
||||
className={`${Inter.variable} ${JetBrainsMono.variable}`}
|
||||
suppressHydrationWarning
|
||||
>
|
||||
@@ -27,12 +27,14 @@ const RootLayout = ({ children }: Readonly<{ children: React.ReactNode }>) => (
|
||||
item={{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "Person",
|
||||
"@id": `${env.NEXT_PUBLIC_BASE_URL}/#person`,
|
||||
"@id": `${process.env.NEXT_PUBLIC_BASE_URL}/#person`,
|
||||
name: authorConfig.name,
|
||||
url: env.NEXT_PUBLIC_BASE_URL,
|
||||
image: [`${env.NEXT_PUBLIC_BASE_URL}/opengraph-image.jpg`],
|
||||
// biome-ignore lint/style/noNonNullAssertion: expected to be set in env
|
||||
url: process.env.NEXT_PUBLIC_BASE_URL!,
|
||||
image: [`${process.env.NEXT_PUBLIC_BASE_URL}/opengraph-image.jpg`],
|
||||
sameAs: [
|
||||
env.NEXT_PUBLIC_BASE_URL,
|
||||
// biome-ignore lint/style/noNonNullAssertion: expected to be set in env
|
||||
process.env.NEXT_PUBLIC_BASE_URL!,
|
||||
`https://${authorConfig.social?.mastodon}`,
|
||||
`https://github.com/${authorConfig.social?.github}`,
|
||||
`https://bsky.app/profile/${authorConfig.social?.bluesky}`,
|
||||
@@ -49,12 +51,12 @@ const RootLayout = ({ children }: Readonly<{ children: React.ReactNode }>) => (
|
||||
item={{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "WebSite",
|
||||
"@id": `${env.NEXT_PUBLIC_BASE_URL}/#website`,
|
||||
"@id": `${process.env.NEXT_PUBLIC_BASE_URL}/#website`,
|
||||
name: siteConfig.name,
|
||||
url: env.NEXT_PUBLIC_BASE_URL,
|
||||
url: process.env.NEXT_PUBLIC_BASE_URL,
|
||||
author: authorConfig.name,
|
||||
description: siteConfig.description,
|
||||
inLanguage: env.NEXT_PUBLIC_SITE_LOCALE,
|
||||
inLanguage: process.env.NEXT_PUBLIC_SITE_LOCALE,
|
||||
license: `https://spdx.org/licenses/${siteConfig.license}.html`,
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -2,7 +2,7 @@ import { JsonLd } from "react-schemaorg";
|
||||
import type { VideoObject } from "schema-dts";
|
||||
import { PageTitle } from "@/components/layout/page-title";
|
||||
import { Video } from "@/components/video";
|
||||
import { env } from "@/lib/env";
|
||||
|
||||
import { createMetadata } from "@/lib/metadata";
|
||||
|
||||
import thumbnail from "./thumbnail.png";
|
||||
@@ -31,8 +31,8 @@ const Page = () => (
|
||||
description: metadata.description as string,
|
||||
contentUrl:
|
||||
"https://ijyxfbpcm3itvdly.public.blob.vercel-storage.com/leo-uoCXHS9gViyRnQhr8CEGXFvj4VGh5Y.webm",
|
||||
thumbnailUrl: `${env.NEXT_PUBLIC_BASE_URL}${thumbnail.src}`,
|
||||
embedUrl: `${env.NEXT_PUBLIC_BASE_URL}/leo`,
|
||||
thumbnailUrl: `${process.env.NEXT_PUBLIC_BASE_URL}${thumbnail.src}`,
|
||||
embedUrl: `${process.env.NEXT_PUBLIC_BASE_URL}/leo`,
|
||||
uploadDate: "2007-05-10T00:00:00Z",
|
||||
duration: "PT1M48S",
|
||||
}}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { MetadataRoute } from "next";
|
||||
import siteConfig from "@/lib/config/site";
|
||||
import { env } from "@/lib/env";
|
||||
|
||||
const manifest = (): MetadataRoute.Manifest => {
|
||||
return {
|
||||
@@ -8,7 +7,7 @@ const manifest = (): MetadataRoute.Manifest => {
|
||||
// eslint-disable-next-line camelcase
|
||||
short_name: siteConfig.name,
|
||||
description: siteConfig.description,
|
||||
lang: env.NEXT_PUBLIC_SITE_LOCALE,
|
||||
lang: process.env.NEXT_PUBLIC_SITE_LOCALE,
|
||||
icons: [
|
||||
{
|
||||
src: "/icon.png",
|
||||
|
||||
@@ -3,7 +3,7 @@ import path from "node:path";
|
||||
import { notFound } from "next/navigation";
|
||||
import { ImageResponse } from "next/og";
|
||||
import siteConfig from "@/lib/config/site";
|
||||
import { env } from "@/lib/env";
|
||||
|
||||
import { loadGoogleFont } from "@/lib/og-utils";
|
||||
import { getFrontMatter, getSlugs, POSTS_DIR } from "@/lib/posts";
|
||||
|
||||
@@ -214,7 +214,7 @@ const OpenGraphImage = async ({
|
||||
}}
|
||||
>
|
||||
{new Date(frontmatter.date).toLocaleDateString(
|
||||
env.NEXT_PUBLIC_SITE_LOCALE,
|
||||
process.env.NEXT_PUBLIC_SITE_LOCALE,
|
||||
{
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
|
||||
@@ -17,7 +17,7 @@ import { CommentsSkeleton } from "@/components/comments/comments-skeleton";
|
||||
import { ViewCounter } from "@/components/view-counter";
|
||||
import authorConfig from "@/lib/config/author";
|
||||
import siteConfig from "@/lib/config/site";
|
||||
import { env } from "@/lib/env";
|
||||
|
||||
import { createMetadata } from "@/lib/metadata";
|
||||
import { getFrontMatter, getSlugs, POSTS_DIR } from "@/lib/posts";
|
||||
import { size as ogImageSize } from "./opengraph-image";
|
||||
@@ -94,18 +94,18 @@ const Page = async ({ params }: { params: Promise<{ slug: string }> }) => {
|
||||
url: frontmatter?.permalink,
|
||||
image: {
|
||||
"@type": "ImageObject",
|
||||
contentUrl: `${env.NEXT_PUBLIC_BASE_URL}/${POSTS_DIR}/${frontmatter?.slug}/opengraph-image`,
|
||||
contentUrl: `${process.env.NEXT_PUBLIC_BASE_URL}/${POSTS_DIR}/${frontmatter?.slug}/opengraph-image`,
|
||||
width: `${ogImageSize.width}`,
|
||||
height: `${ogImageSize.height}`,
|
||||
},
|
||||
keywords: frontmatter?.tags?.join(", "),
|
||||
datePublished: frontmatter?.date,
|
||||
dateModified: frontmatter?.date,
|
||||
inLanguage: env.NEXT_PUBLIC_SITE_LOCALE,
|
||||
inLanguage: process.env.NEXT_PUBLIC_SITE_LOCALE,
|
||||
license: `https://spdx.org/licenses/${siteConfig.license}.html`,
|
||||
author: {
|
||||
// defined in app/layout.tsx
|
||||
"@id": `${env.NEXT_PUBLIC_BASE_URL}/#person`,
|
||||
"@id": `${process.env.NEXT_PUBLIC_BASE_URL}/#person`,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
@@ -146,7 +146,7 @@ const Page = async ({ params }: { params: Promise<{ slug: string }> }) => {
|
||||
)}
|
||||
|
||||
<Link
|
||||
href={`https://github.com/${env.NEXT_PUBLIC_GITHUB_REPO}/blob/main/${POSTS_DIR}/${frontmatter?.slug}/index.mdx`}
|
||||
href={`https://github.com/${process.env.NEXT_PUBLIC_GITHUB_REPO}/blob/main/${POSTS_DIR}/${frontmatter?.slug}/index.mdx`}
|
||||
title={`Edit "${frontmatter?.title}" on GitHub`}
|
||||
className={
|
||||
"flex flex-nowrap items-center gap-1.5 whitespace-nowrap text-foreground/70 hover:no-underline"
|
||||
|
||||
@@ -4,7 +4,6 @@ import { graphql } from "@octokit/graphql";
|
||||
import type { Repository, User } from "@octokit/graphql-schema";
|
||||
import * as cheerio from "cheerio";
|
||||
import { cacheLife } from "next/cache";
|
||||
import { env } from "@/lib/env";
|
||||
|
||||
export const getContributions = async (): Promise<
|
||||
Array<{
|
||||
@@ -19,10 +18,10 @@ export const getContributions = async (): Promise<
|
||||
// thanks @grubersjoe! :) https://github.com/grubersjoe/github-contributions-api/blob/main/src/scrape.ts
|
||||
try {
|
||||
const response = await fetch(
|
||||
`https://github.com/users/${env.NEXT_PUBLIC_GITHUB_USERNAME}/contributions`,
|
||||
`https://github.com/users/${process.env.NEXT_PUBLIC_GITHUB_USERNAME}/contributions`,
|
||||
{
|
||||
headers: {
|
||||
referer: `https://github.com/${env.NEXT_PUBLIC_GITHUB_USERNAME}`,
|
||||
referer: `https://github.com/${process.env.NEXT_PUBLIC_GITHUB_USERNAME}`,
|
||||
"x-requested-with": "XMLHttpRequest",
|
||||
},
|
||||
},
|
||||
@@ -116,12 +115,12 @@ export const getRepos = async (): Promise<Repository[] | undefined> => {
|
||||
}
|
||||
`,
|
||||
{
|
||||
username: env.NEXT_PUBLIC_GITHUB_USERNAME,
|
||||
username: process.env.NEXT_PUBLIC_GITHUB_USERNAME,
|
||||
sort: "STARGAZERS",
|
||||
limit: 12,
|
||||
headers: {
|
||||
accept: "application/vnd.github.v3+json",
|
||||
authorization: `token ${env.GITHUB_TOKEN}`,
|
||||
authorization: `token ${process.env.GITHUB_TOKEN}`,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
@@ -7,21 +7,21 @@ import { PageTitle } from "@/components/layout/page-title";
|
||||
import { RelativeTime } from "@/components/relative-time";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { env } from "@/lib/env";
|
||||
|
||||
import { createMetadata } from "@/lib/metadata";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { getContributions, getRepos } from "./github";
|
||||
|
||||
export const metadata = createMetadata({
|
||||
title: "Projects",
|
||||
description: `Most-starred repositories by @${env.NEXT_PUBLIC_GITHUB_USERNAME} on GitHub`,
|
||||
description: `Most-starred repositories by @${process.env.NEXT_PUBLIC_GITHUB_USERNAME} on GitHub`,
|
||||
canonical: "/projects",
|
||||
});
|
||||
|
||||
const Page = async () => {
|
||||
// don't fail the entire site build if the required config for this page is missing, just return a 404 since this page
|
||||
// would be mostly blank anyways.
|
||||
if (!env.GITHUB_TOKEN) {
|
||||
if (!process.env.GITHUB_TOKEN) {
|
||||
console.error(
|
||||
"[/projects] I can't fetch anything from GitHub without 'GITHUB_TOKEN' set!",
|
||||
);
|
||||
@@ -40,7 +40,7 @@ const Page = async () => {
|
||||
|
||||
<h2 className="my-3.5 font-medium text-xl">
|
||||
<a
|
||||
href={`https://github.com/${env.NEXT_PUBLIC_GITHUB_USERNAME}`}
|
||||
href={`https://github.com/${process.env.NEXT_PUBLIC_GITHUB_USERNAME}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-secondary-foreground hover:no-underline"
|
||||
@@ -63,7 +63,7 @@ const Page = async () => {
|
||||
|
||||
<h2 className="my-3.5 font-medium text-xl">
|
||||
<a
|
||||
href={`https://github.com/${env.NEXT_PUBLIC_GITHUB_USERNAME}?tab=repositories&sort=stargazers`}
|
||||
href={`https://github.com/${process.env.NEXT_PUBLIC_GITHUB_USERNAME}?tab=repositories&sort=stargazers`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-secondary-foreground hover:no-underline"
|
||||
@@ -113,7 +113,7 @@ const Page = async () => {
|
||||
{repo?.stargazerCount > 0 && (
|
||||
<a
|
||||
href={`${repo?.url}/stargazers`}
|
||||
title={`${Intl.NumberFormat(env.NEXT_PUBLIC_SITE_LOCALE).format(repo?.stargazerCount)} ${repo?.stargazerCount === 1 ? "star" : "stars"}`}
|
||||
title={`${Intl.NumberFormat(process.env.NEXT_PUBLIC_SITE_LOCALE).format(repo?.stargazerCount)} ${repo?.stargazerCount === 1 ? "star" : "stars"}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-flex flex-nowrap items-center gap-2 text-muted-foreground hover:text-primary hover:no-underline"
|
||||
@@ -123,9 +123,9 @@ const Page = async () => {
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span>
|
||||
{Intl.NumberFormat(env.NEXT_PUBLIC_SITE_LOCALE).format(
|
||||
repo?.stargazerCount,
|
||||
)}
|
||||
{Intl.NumberFormat(
|
||||
process.env.NEXT_PUBLIC_SITE_LOCALE,
|
||||
).format(repo?.stargazerCount)}
|
||||
</span>
|
||||
</a>
|
||||
)}
|
||||
@@ -135,7 +135,7 @@ const Page = async () => {
|
||||
href={`${repo?.url}/network/members`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
title={`${Intl.NumberFormat(env.NEXT_PUBLIC_SITE_LOCALE).format(repo?.forkCount)} ${repo?.forkCount === 1 ? "fork" : "forks"}`}
|
||||
title={`${Intl.NumberFormat(process.env.NEXT_PUBLIC_SITE_LOCALE).format(repo?.forkCount)} ${repo?.forkCount === 1 ? "fork" : "forks"}`}
|
||||
className="inline-flex flex-nowrap items-center gap-2 text-muted-foreground hover:text-primary hover:no-underline"
|
||||
>
|
||||
<GitForkIcon
|
||||
@@ -143,9 +143,9 @@ const Page = async () => {
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span>
|
||||
{Intl.NumberFormat(env.NEXT_PUBLIC_SITE_LOCALE).format(
|
||||
repo?.forkCount,
|
||||
)}
|
||||
{Intl.NumberFormat(
|
||||
process.env.NEXT_PUBLIC_SITE_LOCALE,
|
||||
).format(repo?.forkCount)}
|
||||
</span>
|
||||
</a>
|
||||
)}
|
||||
@@ -170,7 +170,7 @@ const Page = async () => {
|
||||
<p className="mt-6 mb-0 text-center font-medium text-base">
|
||||
<Button variant="secondary" asChild>
|
||||
<a
|
||||
href={`https://github.com/${env.NEXT_PUBLIC_GITHUB_USERNAME}?tab=repositories&type=source&sort=stargazers`}
|
||||
href={`https://github.com/${process.env.NEXT_PUBLIC_GITHUB_USERNAME}?tab=repositories&type=source&sort=stargazers`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { MetadataRoute } from "next";
|
||||
import { env } from "@/lib/env";
|
||||
|
||||
const robots = (): MetadataRoute.Robots => ({
|
||||
rules: [
|
||||
@@ -8,7 +7,7 @@ const robots = (): MetadataRoute.Robots => ({
|
||||
disallow: ["/api/", "/404", "/500"],
|
||||
},
|
||||
],
|
||||
sitemap: `${env.NEXT_PUBLIC_BASE_URL}/sitemap.xml`,
|
||||
sitemap: `${process.env.NEXT_PUBLIC_BASE_URL}/sitemap.xml`,
|
||||
});
|
||||
|
||||
export default robots;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import path from "node:path";
|
||||
import glob from "fast-glob";
|
||||
import type { MetadataRoute } from "next";
|
||||
import { env } from "@/lib/env";
|
||||
|
||||
import { getFrontMatter } from "@/lib/posts";
|
||||
|
||||
// routes in /app (in other words, directories containing a page.tsx/mdx file) are automatically included; add a route
|
||||
@@ -19,12 +19,13 @@ const sitemap = async (): Promise<MetadataRoute.Sitemap> => {
|
||||
const routes: MetadataRoute.Sitemap = [
|
||||
{
|
||||
// homepage
|
||||
url: env.NEXT_PUBLIC_BASE_URL,
|
||||
// biome-ignore lint/style/noNonNullAssertion: expected to be set in env
|
||||
url: process.env.NEXT_PUBLIC_BASE_URL!,
|
||||
priority: 1.0,
|
||||
lastModified: new Date(),
|
||||
},
|
||||
{ url: `${env.NEXT_PUBLIC_BASE_URL}/tweets` },
|
||||
{ url: `${env.NEXT_PUBLIC_BASE_URL}/y2k` },
|
||||
{ url: `${process.env.NEXT_PUBLIC_BASE_URL}/tweets` },
|
||||
{ url: `${process.env.NEXT_PUBLIC_BASE_URL}/y2k` },
|
||||
];
|
||||
|
||||
const [staticRoutes, frontmatter] = await Promise.all([
|
||||
@@ -46,7 +47,7 @@ const sitemap = async (): Promise<MetadataRoute.Sitemap> => {
|
||||
staticRoutes.forEach((route) => {
|
||||
routes.push({
|
||||
// remove matching page.(tsx|mdx) file and make all URLs absolute
|
||||
url: `${env.NEXT_PUBLIC_BASE_URL}/${route.replace(/\/page\.(tsx|mdx)$/, "")}`,
|
||||
url: `${process.env.NEXT_PUBLIC_BASE_URL}/${route.replace(/\/page\.(tsx|mdx)$/, "")}`,
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { env } from "@/lib/env";
|
||||
|
||||
import { getCommentCount } from "@/lib/server/comments";
|
||||
|
||||
const CommentCount = ({ slug }: { slug: string }) => {
|
||||
@@ -29,9 +29,9 @@ const CommentCount = ({ slug }: { slug: string }) => {
|
||||
|
||||
return (
|
||||
<span
|
||||
title={`${Intl.NumberFormat(env.NEXT_PUBLIC_SITE_LOCALE).format(count)} ${count === 1 ? "comment" : "comments"}`}
|
||||
title={`${Intl.NumberFormat(process.env.NEXT_PUBLIC_SITE_LOCALE).format(count)} ${count === 1 ? "comment" : "comments"}`}
|
||||
>
|
||||
{Intl.NumberFormat(env.NEXT_PUBLIC_SITE_LOCALE).format(count)}
|
||||
{Intl.NumberFormat(process.env.NEXT_PUBLIC_SITE_LOCALE).format(count)}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -6,7 +6,6 @@ import { toast } from "sonner";
|
||||
import { GitHubIcon } from "@/components/icons";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { signIn } from "@/lib/auth-client";
|
||||
import { env } from "@/lib/env";
|
||||
|
||||
const SignIn = ({ callbackPath }: { callbackPath?: string }) => {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
@@ -17,7 +16,7 @@ const SignIn = ({ callbackPath }: { callbackPath?: string }) => {
|
||||
try {
|
||||
await signIn.social({
|
||||
provider: "github",
|
||||
callbackURL: `${env.NEXT_PUBLIC_BASE_URL}${callbackPath ? callbackPath : "/"}`,
|
||||
callbackURL: `${process.env.NEXT_PUBLIC_BASE_URL}${callbackPath ? callbackPath : "/"}`,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error signing in:", error);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import Link from "next/link";
|
||||
import siteConfig from "@/lib/config/site";
|
||||
import { env } from "@/lib/env";
|
||||
|
||||
const Footer = () => (
|
||||
<footer className="mt-8 w-full py-6 text-center text-[13px] text-muted-foreground leading-loose">
|
||||
@@ -10,7 +9,7 @@ const Footer = () => (
|
||||
</Link>
|
||||
. View source on{" "}
|
||||
<a
|
||||
href={`https://github.com/${env.NEXT_PUBLIC_GITHUB_REPO}`}
|
||||
href={`https://github.com/${process.env.NEXT_PUBLIC_GITHUB_REPO}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="underline underline-offset-4"
|
||||
|
||||
@@ -11,11 +11,13 @@ import {
|
||||
} from "react";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { env } from "@/lib/env";
|
||||
|
||||
import { getAllCommentCounts } from "@/lib/server/comments";
|
||||
import { getAllViewCounts } from "@/lib/server/views";
|
||||
|
||||
const numberFormatter = new Intl.NumberFormat(env.NEXT_PUBLIC_SITE_LOCALE);
|
||||
const numberFormatter = new Intl.NumberFormat(
|
||||
process.env.NEXT_PUBLIC_SITE_LOCALE,
|
||||
);
|
||||
|
||||
type Stats = {
|
||||
views: Record<string, number>;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { CountUp } from "@/components/count-up";
|
||||
import { env } from "@/lib/env";
|
||||
|
||||
import { incrementViews } from "@/lib/server/views";
|
||||
|
||||
const ViewCounter = ({ slug }: { slug: string }) => {
|
||||
@@ -31,7 +31,7 @@ const ViewCounter = ({ slug }: { slug: string }) => {
|
||||
|
||||
return (
|
||||
<span
|
||||
title={`${Intl.NumberFormat(env.NEXT_PUBLIC_SITE_LOCALE).format(views)} ${views === 1 ? "view" : "views"}`}
|
||||
title={`${Intl.NumberFormat(process.env.NEXT_PUBLIC_SITE_LOCALE).format(views)} ${views === 1 ? "view" : "views"}`}
|
||||
>
|
||||
<CountUp start={0} end={views} delay={0} duration={1.5} />
|
||||
</span>
|
||||
|
||||
@@ -6,7 +6,7 @@ export default defineConfig({
|
||||
out: "./drizzle",
|
||||
dialect: "postgresql",
|
||||
dbCredentials: {
|
||||
// biome-ignore lint/style/noNonNullAssertion: runs outside Next.js; can't use env helper from @t3-oss/env-nextjs
|
||||
// biome-ignore lint/style/noNonNullAssertion: expected to be set in .env
|
||||
url: process.env.DATABASE_URL!,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { createAuthClient } from "better-auth/react";
|
||||
import { env } from "@/lib/env";
|
||||
|
||||
export const authClient = createAuthClient({
|
||||
baseURL: env.NEXT_PUBLIC_BASE_URL,
|
||||
baseURL: process.env.NEXT_PUBLIC_BASE_URL,
|
||||
});
|
||||
|
||||
export const { signIn, signUp, useSession } = authClient;
|
||||
|
||||
@@ -3,10 +3,9 @@ import { drizzleAdapter } from "better-auth/adapters/drizzle";
|
||||
import { nextCookies } from "better-auth/next-js";
|
||||
import { db } from "@/lib/db";
|
||||
import * as schema from "@/lib/db/schema";
|
||||
import { env } from "@/lib/env";
|
||||
|
||||
export const auth = betterAuth({
|
||||
baseURL: env.NEXT_PUBLIC_BASE_URL,
|
||||
baseURL: process.env.NEXT_PUBLIC_BASE_URL,
|
||||
database: drizzleAdapter(db, {
|
||||
provider: "pg",
|
||||
schema,
|
||||
@@ -14,8 +13,10 @@ export const auth = betterAuth({
|
||||
plugins: [nextCookies()],
|
||||
socialProviders: {
|
||||
github: {
|
||||
clientId: env.AUTH_GITHUB_CLIENT_ID,
|
||||
clientSecret: env.AUTH_GITHUB_CLIENT_SECRET,
|
||||
// biome-ignore lint/style/noNonNullAssertion: expected to be set in env
|
||||
clientId: process.env.AUTH_GITHUB_CLIENT_ID!,
|
||||
// biome-ignore lint/style/noNonNullAssertion: expected to be set in env
|
||||
clientSecret: process.env.AUTH_GITHUB_CLIENT_SECRET!,
|
||||
mapProfileToUser: (profile) => ({
|
||||
name: profile.login,
|
||||
email: profile.email,
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Feed, type Item as FeedItem } from "feed";
|
||||
import ogImage from "@/app/opengraph-image.jpg";
|
||||
import authorConfig from "@/lib/config/author";
|
||||
import siteConfig from "@/lib/config/site";
|
||||
import { env } from "@/lib/env";
|
||||
|
||||
import { getContent, getFrontMatter } from "@/lib/posts";
|
||||
|
||||
/**
|
||||
@@ -11,20 +11,20 @@ import { getContent, getFrontMatter } from "@/lib/posts";
|
||||
*/
|
||||
export const buildFeed = async (): Promise<Feed> => {
|
||||
const feed = new Feed({
|
||||
id: `${env.NEXT_PUBLIC_BASE_URL}`,
|
||||
link: `${env.NEXT_PUBLIC_BASE_URL}`,
|
||||
id: `${process.env.NEXT_PUBLIC_BASE_URL}`,
|
||||
link: `${process.env.NEXT_PUBLIC_BASE_URL}`,
|
||||
title: siteConfig.name,
|
||||
description: siteConfig.description,
|
||||
copyright: `https://spdx.org/licenses/${siteConfig.license}.html`,
|
||||
updated: new Date(),
|
||||
image: `${env.NEXT_PUBLIC_BASE_URL}${ogImage.src}`,
|
||||
image: `${process.env.NEXT_PUBLIC_BASE_URL}${ogImage.src}`,
|
||||
feedLinks: {
|
||||
rss: `${env.NEXT_PUBLIC_BASE_URL}/feed.xml`,
|
||||
atom: `${env.NEXT_PUBLIC_BASE_URL}/feed.atom`,
|
||||
rss: `${process.env.NEXT_PUBLIC_BASE_URL}/feed.xml`,
|
||||
atom: `${process.env.NEXT_PUBLIC_BASE_URL}/feed.atom`,
|
||||
},
|
||||
author: {
|
||||
name: authorConfig.name,
|
||||
link: env.NEXT_PUBLIC_BASE_URL,
|
||||
link: process.env.NEXT_PUBLIC_BASE_URL,
|
||||
email: authorConfig.email,
|
||||
},
|
||||
});
|
||||
@@ -40,7 +40,7 @@ export const buildFeed = async (): Promise<Feed> => {
|
||||
author: [
|
||||
{
|
||||
name: authorConfig.name,
|
||||
link: `${env.NEXT_PUBLIC_BASE_URL}`,
|
||||
link: `${process.env.NEXT_PUBLIC_BASE_URL}`,
|
||||
},
|
||||
],
|
||||
date: new Date(post.date),
|
||||
|
||||
@@ -2,11 +2,10 @@ import { attachDatabasePool } from "@vercel/functions";
|
||||
import { drizzle } from "drizzle-orm/node-postgres";
|
||||
import { Pool } from "pg";
|
||||
import * as schema from "@/lib/db/schema";
|
||||
import { env } from "@/lib/env";
|
||||
|
||||
// Create explicit pool instance for better connection management
|
||||
const pool = new Pool({
|
||||
connectionString: env.DATABASE_URL,
|
||||
connectionString: process.env.DATABASE_URL,
|
||||
});
|
||||
|
||||
// Attach to Vercel's pool management to ensure idle connections are properly
|
||||
|
||||
142
lib/env.ts
142
lib/env.ts
@@ -1,142 +0,0 @@
|
||||
import { createEnv } from "@t3-oss/env-nextjs";
|
||||
import { z } from "zod";
|
||||
|
||||
export const env = createEnv({
|
||||
server: {
|
||||
/**
|
||||
* Required. A random value used for authentication encryption.
|
||||
*
|
||||
* @see https://www.better-auth.com/docs/installation#set-environment-variables
|
||||
*/
|
||||
AUTH_SECRET: z.string().min(1),
|
||||
|
||||
/**
|
||||
* Required. The client ID from the GitHub Developer Portal for this site's OAuth App.
|
||||
*
|
||||
* @see https://www.better-auth.com/docs/authentication/github
|
||||
*/
|
||||
AUTH_GITHUB_CLIENT_ID: z.string().min(1),
|
||||
|
||||
/**
|
||||
* Required. A client secret from the GitHub Developer Portal for this site's OAuth App.
|
||||
*
|
||||
* @see https://www.better-auth.com/docs/authentication/github
|
||||
*/
|
||||
AUTH_GITHUB_CLIENT_SECRET: z.string().min(1),
|
||||
|
||||
/**
|
||||
* Required. Database connection string for a Postgres database.
|
||||
*/
|
||||
DATABASE_URL: z.string().startsWith("postgresql://"),
|
||||
|
||||
/**
|
||||
* Required. GitHub API token used for [/projects](../app/projects/page.tsx) grid. Only needs the `public_repo`
|
||||
* scope since we don't need/want to change anything, obviously.
|
||||
*
|
||||
* @see https://github.com/settings/tokens/new?scopes=public_repo
|
||||
*/
|
||||
GITHUB_TOKEN: z.string().startsWith("ghp_").optional(),
|
||||
|
||||
/**
|
||||
* Required. Uses Resend API to send contact form submissions via a [server action](../app/contact/action.ts). May
|
||||
* be set automatically by Vercel's Resend integration.
|
||||
*
|
||||
* @see https://resend.com/api-keys
|
||||
* @see https://vercel.com/integrations/resend
|
||||
*/
|
||||
RESEND_API_KEY: z.string().startsWith("re_"),
|
||||
|
||||
/**
|
||||
* Optional, but will throw a warning if unset. Use an approved domain (or subdomain) on the Resend account to send
|
||||
* submissions from. Sender's real email is passed via a Reply-To header, so setting this makes zero difference to
|
||||
* the user, only for deliverability success. Defaults to `onboarding@resend.dev`.
|
||||
*
|
||||
* @see https://resend.com/domains
|
||||
*/
|
||||
RESEND_FROM_EMAIL: z.string().email().default("onboarding@resend.dev"),
|
||||
|
||||
/** Required. The destination email for contact form submissions. */
|
||||
RESEND_TO_EMAIL: z.string().email(),
|
||||
},
|
||||
client: {
|
||||
/**
|
||||
* Optional. We try to make an educated guess for the most appropriate URL based on the current deployment's
|
||||
* environment and platform (if Vercel or Netlify), but you can override it by simply setting this manually. Must be
|
||||
* a fully-qualified URL if set beginning with `http(s)://`, and should not end with a trailing slash.
|
||||
*
|
||||
* @see https://nextjs.org/docs/app/api-reference/functions/generate-metadata#default-value
|
||||
*/
|
||||
NEXT_PUBLIC_BASE_URL: z
|
||||
.string()
|
||||
.url()
|
||||
.default(
|
||||
((): string =>
|
||||
(process.env.VERCEL || process.env.NEXT_PUBLIC_VERCEL
|
||||
? process.env.NEXT_PUBLIC_VERCEL_ENV === "production"
|
||||
? `https://${process.env.NEXT_PUBLIC_VERCEL_PROJECT_PRODUCTION_URL}`
|
||||
: process.env.NEXT_PUBLIC_VERCEL_ENV === "preview"
|
||||
? `https://${process.env.NEXT_PUBLIC_VERCEL_BRANCH_URL}`
|
||||
: process.env.NEXT_PUBLIC_VERCEL_URL
|
||||
? `https://${process.env.NEXT_PUBLIC_VERCEL_URL}`
|
||||
: undefined
|
||||
: undefined) ||
|
||||
(process.env.NETLIFY
|
||||
? process.env.CONTEXT === "production"
|
||||
? `${process.env.URL}`
|
||||
: process.env.DEPLOY_PRIME_URL
|
||||
? `${process.env.DEPLOY_PRIME_URL}`
|
||||
: process.env.DEPLOY_URL
|
||||
? `${process.env.DEPLOY_URL}`
|
||||
: undefined
|
||||
: undefined) ||
|
||||
`http://localhost:${process.env.PORT || 3000}`)(),
|
||||
),
|
||||
|
||||
/**
|
||||
* Optional. Set this to override the best guess as to the environment the site is running in.
|
||||
*/
|
||||
NEXT_PUBLIC_ENV: z
|
||||
.enum(["production", "development"])
|
||||
.default(
|
||||
((): "production" | "development" =>
|
||||
(process.env.VERCEL && process.env.VERCEL_ENV === "production") ||
|
||||
(process.env.NETLIFY && process.env.CONTEXT === "production")
|
||||
? "production"
|
||||
: "development")(),
|
||||
),
|
||||
|
||||
/** Required. GitHub repository for the site in the format of `{username}/{repo}`. */
|
||||
NEXT_PUBLIC_GITHUB_REPO: z.string().refine((val) => val.includes("/"), {
|
||||
message: "Must be in the format {username}/{repo}",
|
||||
}),
|
||||
|
||||
/** Required. GitHub username of the author, used to generate [/projects](../app/projects/page.tsx). */
|
||||
NEXT_PUBLIC_GITHUB_USERNAME: z.string().min(1),
|
||||
|
||||
/**
|
||||
* Optional. Sets an `Onion-Location` header in responses to advertise a URL for the same page but hosted on a
|
||||
* hidden service on the Tor network. Browsers like Brave and Tor Browser will automatically pick this up and offer
|
||||
* to redirect users to it.
|
||||
*
|
||||
* @see https://community.torproject.org/onion-services/advanced/onion-location/
|
||||
*/
|
||||
NEXT_PUBLIC_ONION_DOMAIN: z.string().endsWith(".onion").optional(),
|
||||
|
||||
/**
|
||||
* Optional. Locale code to define the site's language in ISO-639 format. Defaults to `en-US`.
|
||||
*
|
||||
* @see https://www.loc.gov/standards/iso639-2/php/code_list.php
|
||||
*/
|
||||
NEXT_PUBLIC_SITE_LOCALE: z.string().default("en-US"),
|
||||
},
|
||||
experimental__runtimeEnv: {
|
||||
NEXT_PUBLIC_BASE_URL: process.env.NEXT_PUBLIC_BASE_URL,
|
||||
NEXT_PUBLIC_ENV: process.env.NEXT_PUBLIC_ENV,
|
||||
NEXT_PUBLIC_GITHUB_REPO: process.env.NEXT_PUBLIC_GITHUB_REPO,
|
||||
NEXT_PUBLIC_GITHUB_USERNAME: process.env.NEXT_PUBLIC_GITHUB_USERNAME,
|
||||
NEXT_PUBLIC_ONION_DOMAIN: process.env.NEXT_PUBLIC_ONION_DOMAIN,
|
||||
NEXT_PUBLIC_SITE_LOCALE: process.env.NEXT_PUBLIC_SITE_LOCALE,
|
||||
},
|
||||
emptyStringAsUndefined: true,
|
||||
skipValidation: !!process.env.SKIP_ENV_VALIDATION,
|
||||
});
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { Metadata } from "next";
|
||||
import authorConfig from "@/lib/config/author";
|
||||
import siteConfig from "@/lib/config/site";
|
||||
import { env } from "@/lib/env";
|
||||
|
||||
export const defaultMetadata: Metadata = {
|
||||
metadataBase: new URL(env.NEXT_PUBLIC_BASE_URL),
|
||||
// biome-ignore lint/style/noNonNullAssertion: expected to be set in env
|
||||
metadataBase: new URL(process.env.NEXT_PUBLIC_BASE_URL!),
|
||||
title: {
|
||||
template: `%s – ${siteConfig.name}`,
|
||||
default: `${siteConfig.name} – ${siteConfig.tagline}`,
|
||||
@@ -17,7 +17,7 @@ export const defaultMetadata: Metadata = {
|
||||
default: `${siteConfig.name} – ${siteConfig.tagline}`,
|
||||
},
|
||||
url: "/",
|
||||
locale: env.NEXT_PUBLIC_SITE_LOCALE.replace("-", "_"),
|
||||
locale: process.env.NEXT_PUBLIC_SITE_LOCALE?.replace("-", "_"),
|
||||
type: "website",
|
||||
},
|
||||
twitter: {
|
||||
|
||||
@@ -3,7 +3,7 @@ import path from "node:path";
|
||||
import glob from "fast-glob";
|
||||
import { decode } from "html-entities";
|
||||
import { unified } from "unified";
|
||||
import { env } from "@/lib/env";
|
||||
|
||||
import { rehypeSanitize, rehypeStringify } from "@/lib/rehype";
|
||||
import {
|
||||
remarkFrontmatter,
|
||||
@@ -89,7 +89,7 @@ export const getFrontMatter: {
|
||||
slug,
|
||||
// validate/normalize the date string provided from front matter
|
||||
date: new Date(frontmatter.date).toISOString(),
|
||||
permalink: `${env.NEXT_PUBLIC_BASE_URL}/${POSTS_DIR}/${slug}`,
|
||||
permalink: `${process.env.NEXT_PUBLIC_BASE_URL}/${POSTS_DIR}/${slug}`,
|
||||
} as FrontMatter;
|
||||
} catch (error) {
|
||||
console.error(
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { checkBotId } from "botid/server";
|
||||
import { Resend } from "resend";
|
||||
import siteConfig from "@/lib/config/site";
|
||||
import { env } from "@/lib/env";
|
||||
|
||||
import { ContactSchema } from "@/lib/schemas/contact";
|
||||
|
||||
export type ContactResult = {
|
||||
@@ -39,18 +39,19 @@ export const sendContactForm = async (
|
||||
}
|
||||
|
||||
try {
|
||||
if (env.RESEND_FROM_EMAIL === "onboarding@resend.dev") {
|
||||
if (process.env.RESEND_FROM_EMAIL === "onboarding@resend.dev") {
|
||||
// https://resend.com/docs/api-reference/emails/send-email
|
||||
console.warn(
|
||||
"[server/contact] 'RESEND_FROM_EMAIL' is not set, falling back to onboarding@resend.dev.",
|
||||
);
|
||||
}
|
||||
|
||||
const resend = new Resend(env.RESEND_API_KEY);
|
||||
const resend = new Resend(process.env.RESEND_API_KEY);
|
||||
await resend.emails.send({
|
||||
from: `${parsed.data.name} <${env.RESEND_FROM_EMAIL || "onboarding@resend.dev"}>`,
|
||||
from: `${parsed.data.name} <${process.env.RESEND_FROM_EMAIL || "onboarding@resend.dev"}>`,
|
||||
replyTo: `${parsed.data.name} <${parsed.data.email}>`,
|
||||
to: [env.RESEND_TO_EMAIL],
|
||||
// biome-ignore lint/style/noNonNullAssertion: expected to be set in env
|
||||
to: [process.env.RESEND_TO_EMAIL!],
|
||||
subject: `[${siteConfig.name}] Contact Form Submission`,
|
||||
text: parsed.data.message,
|
||||
});
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
import type { NextConfig } from "next";
|
||||
|
||||
// check environment variables at build time
|
||||
// https://env.t3.gg/docs/nextjs#validate-schema-on-build-(recommended)
|
||||
import "./lib/env";
|
||||
|
||||
const nextConfig = {
|
||||
cacheComponents: true,
|
||||
reactCompiler: true,
|
||||
|
||||
@@ -46,7 +46,6 @@
|
||||
"@radix-ui/react-toggle": "^1.1.10",
|
||||
"@radix-ui/react-toggle-group": "^1.1.11",
|
||||
"@radix-ui/react-tooltip": "^1.2.8",
|
||||
"@t3-oss/env-nextjs": "^0.13.10",
|
||||
"@tanstack/react-form": "^1.28.3",
|
||||
"@vercel/analytics": "^1.6.1",
|
||||
"@vercel/functions": "^3.4.2",
|
||||
|
||||
49
pnpm-lock.yaml
generated
49
pnpm-lock.yaml
generated
@@ -86,9 +86,6 @@ importers:
|
||||
'@radix-ui/react-tooltip':
|
||||
specifier: ^1.2.8
|
||||
version: 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@t3-oss/env-nextjs':
|
||||
specifier: ^0.13.10
|
||||
version: 0.13.10(typescript@5.9.3)(zod@4.3.6)
|
||||
'@tanstack/react-form':
|
||||
specifier: ^1.28.3
|
||||
version: 1.28.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
@@ -1631,40 +1628,6 @@ packages:
|
||||
'@swc/helpers@0.5.18':
|
||||
resolution: {integrity: sha512-TXTnIcNJQEKwThMMqBXsZ4VGAza6bvN4pa41Rkqoio6QBKMvo+5lexeTMScGCIxtzgQJzElcvIltani+adC5PQ==}
|
||||
|
||||
'@t3-oss/env-core@0.13.10':
|
||||
resolution: {integrity: sha512-NNFfdlJ+HmPHkLi2HKy7nwuat9SIYOxei9K10lO2YlcSObDILY7mHZNSHsieIM3A0/5OOzw/P/b+yLvPdaG52g==}
|
||||
peerDependencies:
|
||||
arktype: ^2.1.0
|
||||
typescript: '>=5.0.0'
|
||||
valibot: ^1.0.0-beta.7 || ^1.0.0
|
||||
zod: ^3.24.0 || ^4.0.0
|
||||
peerDependenciesMeta:
|
||||
arktype:
|
||||
optional: true
|
||||
typescript:
|
||||
optional: true
|
||||
valibot:
|
||||
optional: true
|
||||
zod:
|
||||
optional: true
|
||||
|
||||
'@t3-oss/env-nextjs@0.13.10':
|
||||
resolution: {integrity: sha512-JfSA2WXOnvcc/uMdp31paMsfbYhhdvLLRxlwvrnlPE9bwM/n0Z+Qb9xRv48nPpvfMhOrkrTYw1I5Yc06WIKBJQ==}
|
||||
peerDependencies:
|
||||
arktype: ^2.1.0
|
||||
typescript: '>=5.0.0'
|
||||
valibot: ^1.0.0-beta.7 || ^1.0.0
|
||||
zod: ^3.24.0 || ^4.0.0
|
||||
peerDependenciesMeta:
|
||||
arktype:
|
||||
optional: true
|
||||
typescript:
|
||||
optional: true
|
||||
valibot:
|
||||
optional: true
|
||||
zod:
|
||||
optional: true
|
||||
|
||||
'@tailwindcss/node@4.2.0':
|
||||
resolution: {integrity: sha512-Yv+fn/o2OmL5fh/Ir62VXItdShnUxfpkMA4Y7jdeC8O81WPB8Kf6TT6GSHvnqgSwDzlB5iT7kDpeXxLsUS0T6Q==}
|
||||
|
||||
@@ -4521,18 +4484,6 @@ snapshots:
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
|
||||
'@t3-oss/env-core@0.13.10(typescript@5.9.3)(zod@4.3.6)':
|
||||
optionalDependencies:
|
||||
typescript: 5.9.3
|
||||
zod: 4.3.6
|
||||
|
||||
'@t3-oss/env-nextjs@0.13.10(typescript@5.9.3)(zod@4.3.6)':
|
||||
dependencies:
|
||||
'@t3-oss/env-core': 0.13.10(typescript@5.9.3)(zod@4.3.6)
|
||||
optionalDependencies:
|
||||
typescript: 5.9.3
|
||||
zod: 4.3.6
|
||||
|
||||
'@tailwindcss/node@4.2.0':
|
||||
dependencies:
|
||||
'@jridgewell/remapping': 2.3.5
|
||||
|
||||
Reference in New Issue
Block a user