From c84984278852511c79f49cbfabb43e8bcdf56054 Mon Sep 17 00:00:00 2001 From: Jake Jarvis Date: Thu, 10 Sep 2026 11:01:01 -0400 Subject: [PATCH] refactor: remove date-fns dependency and replace with domainstack/utils - Removed all instances of `date-fns` from the codebase, replacing its usage with utility functions from `@domainstack/utils/date` for date formatting and calculations. - Updated components and workflows to utilize the new date formatting methods, ensuring consistent date handling across the application. --- .../api/cron/cleanup-stale-domains/route.ts | 6 +- .../app/api/transport/[transport]/route.ts | 23 +--- apps/web/components/calendar-instructions.tsx | 9 +- .../dashboard/archived-domains-list.tsx | 5 +- .../dashboard/domain-health-badge.tsx | 7 +- .../dashboard/domain-status-badge.tsx | 7 +- .../dashboard/subscription-ending-banner.tsx | 10 +- apps/web/components/domain/relative-age.tsx | 5 +- .../web/components/domain/relative-expiry.tsx | 8 +- .../notifications/notification-card.tsx | 9 +- apps/web/lib/chat/domain-tools.test.ts | 33 ----- apps/web/lib/chat/domain-tools.ts | 57 +------- apps/web/lib/constants/sections.ts | 16 ++- apps/web/lib/dashboard-utils.ts | 7 +- apps/web/lib/trpc/errors.test.ts | 56 ++++++++ apps/web/lib/trpc/errors.ts | 95 +++++++++++++ apps/web/package.json | 1 - apps/web/trpc/query-client.ts | 58 +------- .../workflows/certificate-expiry/workflow.ts | 8 +- apps/web/workflows/chat/tools.ts | 5 +- apps/web/workflows/domain-expiry/workflow.ts | 8 +- .../workflows/reverify-ownership/workflow.ts | 4 +- apps/web/workflows/shared/notifications.ts | 7 +- .../workflows/subscription-expiry/workflow.ts | 9 +- packages/polar/package.json | 4 +- packages/polar/src/emails.ts | 5 +- packages/safe-fetch/src/dns.test.ts | 6 + packages/server/src/services/favicon.ts | 25 +--- packages/server/src/services/fetch-errors.ts | 33 +++++ packages/server/src/services/provider-logo.ts | 25 +--- packages/server/src/tls/fetch.ts | 7 +- packages/server/src/tls/utils.test.ts | 31 ----- packages/server/src/tls/utils.ts | 29 ---- .../utils/src/change-detection/detection.ts | 6 +- packages/utils/src/date.test.ts | 91 ++++++++++++- packages/utils/src/date.ts | 126 ++++++++++++++++++ packages/utils/src/expiry.test.ts | 35 ++++- packages/utils/src/expiry.ts | 16 +++ packages/utils/src/providers/parser.ts | 107 ++++++++------- pnpm-lock.yaml | 9 +- 40 files changed, 603 insertions(+), 405 deletions(-) create mode 100644 apps/web/lib/trpc/errors.test.ts create mode 100644 apps/web/lib/trpc/errors.ts create mode 100644 packages/server/src/services/fetch-errors.ts diff --git a/apps/web/app/api/cron/cleanup-stale-domains/route.ts b/apps/web/app/api/cron/cleanup-stale-domains/route.ts index 82aea5c5..36fe4e22 100644 --- a/apps/web/app/api/cron/cleanup-stale-domains/route.ts +++ b/apps/web/app/api/cron/cleanup-stale-domains/route.ts @@ -1,4 +1,3 @@ -import { subDays } from "date-fns"; import { NextResponse } from "next/server"; import { deleteStaleUnverifiedDomainsByCutoff } from "@domainstack/db/queries/tracked-domains"; @@ -8,6 +7,7 @@ const logger = createLogger({ source: "cron/cleanup-stale-domains" }); // Domains that remain unverified after this many days will be deleted const STALE_DOMAIN_DAYS = 30; +const MS_PER_DAY = 24 * 60 * 60 * 1000; /** * Cron job to clean up stale unverified domains. @@ -25,7 +25,9 @@ export async function GET(request: Request) { try { logger.info("Starting cleanup stale domains cron job"); - const cutoffDate = subDays(new Date(), STALE_DOMAIN_DAYS); + // Absolute rather than calendar days: an hour either side of a clock + // change is immaterial to a month-long staleness cutoff. + const cutoffDate = new Date(Date.now() - STALE_DOMAIN_DAYS * MS_PER_DAY); // Optimized: Delete directly by cutoff date in a single query const deletedCount = await deleteStaleUnverifiedDomainsByCutoff(cutoffDate); diff --git a/apps/web/app/api/transport/[transport]/route.ts b/apps/web/app/api/transport/[transport]/route.ts index 8c176b26..dbfee42f 100644 --- a/apps/web/app/api/transport/[transport]/route.ts +++ b/apps/web/app/api/transport/[transport]/route.ts @@ -4,6 +4,7 @@ import { createMcpHandler } from "mcp-handler"; import { PostHog } from "posthog-node"; import { z } from "zod"; +import { type Section, SECTION_IDS } from "@/lib/constants/sections"; import { checkRateLimit } from "@/lib/ratelimit/api"; import { createCaller } from "@/server/routers/_app"; import type { Context } from "@/trpc/init"; @@ -27,22 +28,8 @@ const domainSchema = z.object({ domain: z.string().min(1, "Domain is required"), }); -/** - * Available sections for domain_report bundle tool. - */ -const REPORT_SECTIONS = [ - "dns", - "registration", - "hosting", - "certificates", - "headers", - "seo", -] as const; - -type ReportSection = (typeof REPORT_SECTIONS)[number]; - const sectionsSchema = z - .array(z.enum(REPORT_SECTIONS)) + .array(z.enum(SECTION_IDS)) .optional() .describe("Sections to include in the report. If omitted, all sections are included."); @@ -225,12 +212,12 @@ function createMcpHandlerWithContext(request: Request) { }, async ({ domain, sections }) => { // Default to all sections if not specified - const requestedSections: ReportSection[] = - sections && sections.length > 0 ? sections : [...REPORT_SECTIONS]; + const requestedSections: Section[] = + sections && sections.length > 0 ? sections : [...SECTION_IDS]; // Define section fetchers const sectionFetchers: Record< - ReportSection, + Section, () => Promise<{ success: boolean; data?: unknown; error?: string }> > = { registration: () => trpc.domain.getRegistration({ domain }), diff --git a/apps/web/components/calendar-instructions.tsx b/apps/web/components/calendar-instructions.tsx index af231127..5a5b8f98 100644 --- a/apps/web/components/calendar-instructions.tsx +++ b/apps/web/components/calendar-instructions.tsx @@ -8,7 +8,6 @@ import { IconRefresh, IconShieldLock, } from "@tabler/icons-react"; -import { formatDistanceStrict } from "date-fns"; import { useCallback, useState } from "react"; import { useCalendarFeed } from "@/hooks/use-calendar-feed"; @@ -35,7 +34,7 @@ import { } from "@domainstack/ui/dropdown-menu"; import { Skeleton } from "@domainstack/ui/skeleton"; import { Spinner } from "@domainstack/ui/spinner"; -import { toDateTimeAttr } from "@domainstack/utils/date"; +import { formatRelativeTime, toDateTimeAttr } from "@domainstack/utils/date"; /** * Outlook doesn't have an icon in @icons-pack/react-simple-icons, so we draw @@ -140,11 +139,7 @@ export function CalendarInstructions({ className }: { className?: string }) { Last accessed{" "} ) : ( diff --git a/apps/web/components/dashboard/archived-domains-list.tsx b/apps/web/components/dashboard/archived-domains-list.tsx index fd0720b7..662a668e 100644 --- a/apps/web/components/dashboard/archived-domains-list.tsx +++ b/apps/web/components/dashboard/archived-domains-list.tsx @@ -1,5 +1,4 @@ import { IconArchive, IconCircleArrowUp, IconRefresh, IconTrash } from "@tabler/icons-react"; -import { formatDistanceStrict } from "date-fns"; import { DashboardBannerDismissable } from "@/components/dashboard/dashboard-banner-dismissable"; import { Favicon } from "@/components/icons/favicon"; @@ -18,7 +17,7 @@ import { } from "@domainstack/ui/empty"; import { Tooltip, TooltipContent, TooltipTrigger } from "@domainstack/ui/tooltip"; import { cn } from "@domainstack/ui/utils"; -import { toDateTimeAttr } from "@domainstack/utils/date"; +import { formatRelativeTime, toDateTimeAttr } from "@domainstack/utils/date"; type ArchivedDomainsListProps = { domains: TrackedDomainWithDetails[]; @@ -122,7 +121,7 @@ function ArchivedRelativeTime({ archivedAt }: { archivedAt: Date | string | null return recently; } - const label = now ? formatDistanceStrict(new Date(dateTime), now, { addSuffix: true }) : null; + const label = now ? formatRelativeTime(new Date(dateTime), now) : null; return (