Skip to content

feat(lint): add import plugins and configure rules - #798

Merged
steebchen merged 3 commits into
mainfrom
feat/lint
Sep 13, 2025
Merged

steebchen merged 3 commits into
mainfrom
feat/lint

Conversation

@steebchen

@steebchen steebchen commented Sep 13, 2025

Copy link
Copy Markdown
Member

Add eslint-import-resolver-typescript and eslint-plugin-import to enhance TypeScript import management. Configure rules for import order, path handling, and resolver settings in eslint.config.mjs.

Summary by CodeRabbit

  • Style

    • Standardized and alphabetized import order across the codebase; removed duplicate imports. No functional changes.
  • Chores

    • Adopted stricter ESLint rules for import organization with TypeScript-aware resolution.
    • Applied targeted lint overrides where necessary to preserve correct initialization.
  • Tests

    • Updated test setups to load environment variables earlier and comply with new import rules. No test logic changes.
  • Refactor

    • Simplified a few internal import paths and adjusted module initialization order for consistency, without altering behavior.

@coderabbitai

coderabbitai Bot commented Sep 13, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

Project-wide import reordering and deduplication aligned with new eslint-plugin-import rules. Introduces dotenv/config at top-level in API and Gateway entry points; adjusts some relative import paths. One UI component (OrganizationSwitcher) adds two callback props. No functional logic changes elsewhere; tests updated for env preload. Minor .npmrc and cache import tweaks.

Changes

Cohort / File(s) Summary
ESLint import rules integration
eslint.config.mjs
Adds eslint-plugin-import, TS resolver settings, and strict import/order + no-useless-path-segments rules; test-file override disables no-console.
Env bootstrap at entrypoints
apps/api/src/index.ts, apps/gateway/src/index.ts, apps/gateway/src/api.e2e.ts
Adds top-of-file dotenv/config (and disables import/order for that line); reorders related imports to ensure env loads before other modules.
Serve/import path simplification
apps/api/src/serve.ts, apps/gateway/src/serve.ts, apps/gateway/src/models/models.spec.ts, apps/api/src/routes/beacon.spec.ts
Changes import path from ./index to . (or ..) to reference package entry; no logic changes.
API import normalization
apps/api/src/auth/config.ts, apps/api/src/auth/config.spec.ts, apps/api/src/lib/beacon.ts, apps/api/src/routes/*.{ts,spec.ts,e2e.ts}, apps/api/src/scripts/generate-openapi.ts, apps/api/src/stripe.ts
Reorders and deduplicates imports across API source and tests (db/logger/zod/OpenAPI/Hono grouping). No runtime changes.
Gateway import normalization
apps/gateway/src/api.spec.ts, apps/gateway/src/chat/chat.ts, apps/gateway/src/lib/costs.ts, apps/gateway/src/models/models.ts, apps/gateway/src/scripts/generate-openapi.ts
Moves/deduplicates imports (tokenizer, HTTPException, streaming, logger). No logic changes.
UI import/order alignment
apps/ui/src/app/**/*.{tsx,ts}, apps/ui/src/components/**/*.tsx
Broad import reordering, type-only import splits, and deduplication to satisfy new rules. No behavior changes.
UI component public API change
apps/ui/src/components/dashboard/organization-switcher.tsx, apps/ui/src/components/dashboard/dashboard-sidebar.tsx
OrganizationSwitcher props extended with onSelectOrganization and onOrganizationCreated; wiring added. Sidebar import order adjusted.
Docs import tweaks
apps/docs/app/layout.tsx, apps/docs/app/llms.txt/route.ts
Moves global.css to top with lint directive; reorders route imports.
Config and utility minor edits
.npmrc, packages/cache/src/cache.ts
Reorders lines in .npmrc; moves crypto import to top and removes duplicate.

Sequence Diagram(s)

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • smakosh

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title succinctly and accurately summarizes the main change — adding ESLint import plugins and configuring import-related rules — and directly reflects the edits to eslint.config.mjs and related import-order adjustments in the diff, so it meaningfully describes the primary intent of the PR.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/lint

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Add `eslint-import-resolver-typescript` and `eslint-plugin-import` to enhance TypeScript import management. Configure rules for import order, path handling, and resolver settings in `eslint.config.mjs`.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (6)
apps/api/src/routes/user.ts (1)

115-118: Critical: passkey deletion uses JS && instead of SQL and() — can delete all user passkeys

Using && short-circuits and drops the id predicate, risking broader deletes. Use Drizzle's and() and import it.

-import { db, eq, tables } from "@llmgateway/db";
+import { db, eq, tables, and } from "@llmgateway/db";

-  await db
-    .delete(tables.passkey)
-    .where(eq(tables.passkey.id, id) && eq(tables.passkey.userId, authUser.id));
+  await db
+    .delete(tables.passkey)
+    .where(and(eq(tables.passkey.id, id), eq(tables.passkey.userId, authUser.id)));

If and isn’t re-exported from @llmgateway/db, import it from drizzle-orm instead.

Also applies to: 5-6

apps/ui/src/components/landing/cta.tsx (1)

33-35: Add rel="noopener noreferrer" to external link opened with target="_blank".

Prevents reverse‑tabnabbing and removes window.opener access.

Apply this diff:

-            <a href={config.githubUrl ?? ""} target="_blank">
+            <a href={config.githubUrl ?? ""} target="_blank" rel="noopener noreferrer">
apps/api/src/stripe.ts (2)

179-181: Don’t log full Stripe event payloads (PII/secret leakage risk).

Log only minimal metadata (id, type) or gate behind a debug flag.

Apply this diff:

-    logger.info(JSON.stringify({ kind: "stripe-event", payload: event }));
+    logger.info({ kind: "stripe-event", id: event.id, type: event.type });
+    // Optionally behind a flag:
+    // if (process.env.DEBUG_STRIPE === "true") logger.debug({ event });

675-679: Use subscription.current_period_end, not item-level fields.

current_period_end is on the Subscription, not on items; current code may leave planExpiresAt undefined.

Apply this diff:

-  const currentPeriodEnd =
-    subscription.items.data.length > 0
-      ? subscription.items.data[0].current_period_end
-      : undefined;
+  const currentPeriodEnd = subscription.current_period_end;
apps/gateway/src/lib/costs.ts (2)

61-66: Bug: falsy checks treat 0 tokens as “missing”.

Use nullish checks, not truthiness.

- if ((!promptTokens || !completionTokens) && fullOutput) {
+ if ((promptTokens == null || completionTokens == null) && fullOutput) {
@@
- if (!promptTokens && fullOutput) {
+ if (promptTokens == null && fullOutput) {

99-112: Bug: early return when tokens are 0.

Allow zero values.

- if (!calculatedPromptTokens || !calculatedCompletionTokens) {
+ if (calculatedPromptTokens == null || calculatedCompletionTokens == null) {
🧹 Nitpick comments (27)
apps/ui/src/components/usage/cost-breakdown-chart.tsx (2)

139-141: Use stable keys for Cells instead of array index

Index keys can cause unnecessary remounts when data order changes. Prefer a deterministic key like provider name.

- {chartData.map((entry, index) => (
-   <Cell key={`cell-${index}`} fill={entry.color} />
- ))}
+ {chartData.map((entry) => (
+   <Cell key={`cell-${entry.name}`} fill={entry.color} />
+ ))}

144-145: Prefer locale-aware currency formatting

Using Intl.NumberFormat improves readability and i18n, and avoids manual string building.

+ const currency = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', maximumFractionDigits: 4 });
...
- <Tooltip
-   formatter={(value) => [`$${Number(value).toFixed(4)}`, "Cost"]}
- />
+ <Tooltip formatter={(value) => [currency.format(Number(value)), "Cost"]} />
...
- <span className="font-medium">${totalCost.toFixed(4)}</span>
+ <span className="font-medium">{currency.format(totalCost)}</span>

Also applies to: 152-152

apps/ui/src/components/dashboard/activity-chart.tsx (3)

6-15: Co-locate the Recharts type import with the existing Recharts import (import/order)

Most import-order configs group all imports from the same package together. Merge the type into the existing Recharts import to prevent lint churn.

Apply:

-import {
-	Bar,
-	BarChart,
-	CartesianGrid,
-	Legend,
-	ResponsiveContainer,
-	Tooltip,
-	XAxis,
-	YAxis,
-} from "recharts";
+import {
+	Bar,
+	BarChart,
+	CartesianGrid,
+	Legend,
+	ResponsiveContainer,
+	Tooltip,
+	XAxis,
+	YAxis,
+	type TooltipProps,
+} from "recharts";
-
-import type { TooltipProps } from "recharts";

Also applies to: 35-35


77-95: Avoid redeclaring TooltipProps fields; rely on Recharts’ types

You extend TooltipProps and then re-declare active/payload/label with a custom TooltipPayload. That risks incompatibility with Recharts’ own TooltipPayload and adds maintenance overhead. Prefer composing TooltipProps and adding only your extra field.

Apply:

-interface TooltipPayload {
-	dataKey: string;
-	name: string;
-	value: number;
-	color: string;
-	payload: {
-		requestCount: number;
-		totalTokens: number;
-		cost: number;
-		modelBreakdown: ActivityModelUsage[];
-	};
-}
-
-interface CustomTooltipProps extends TooltipProps<number, string> {
-	active?: boolean;
-	payload?: TooltipPayload[];
-	label?: string;
-	breakdownField?: "requests" | "cost" | "tokens";
-}
+type CustomTooltipProps = TooltipProps<number, string> & {
+	breakdownField?: "requests" | "cost" | "tokens";
+};

346-346: Minor: avoid repeated getUniqueModels computation in render

Compute once to reduce work during render and simplify the JSX.

Apply:

 const chartData = dateRange.map((date) => {
   ...
 });
 
+const uniqueModels = getUniqueModels(data.activity);
+
 return (
   <Card>
     ...
-    {getUniqueModels(data.activity).length > 0 ? (
-      getUniqueModels(data.activity).map((model, index) => (
+    {uniqueModels.length > 0 ? (
+      uniqueModels.map((model, index) => (
         <Bar
           key={`${model}-${index}`}
           dataKey={model}
           name={model}
           stackId="models"
           fill={getModelColor(model, index)}
           radius={
-            index === getUniqueModels(data.activity).length - 1
+            index === uniqueModels.length - 1
               ? [4, 4, 0, 0]
               : [0, 0, 0, 0]
           }
         />
       ))

Also applies to: 411-425

apps/ui/src/components/playground/sidebar.tsx (1)

48-49: Prefer alias import for consistency with repo-wide path conventions

Switch to "@/components/mode-toggle" to match other UI imports and new import/resolver rules.

-import { ModeToggle } from "../mode-toggle";
+import { ModeToggle } from "@/components/mode-toggle";
apps/ui/src/components/compare/hero-compare.tsx (2)

69-71: Fix typo: “Docomentation” → “Documentation”.

Minor copy polish.

Apply this diff:

-      text: "View Docomentation",
+      text: "View Documentation",

168-169: Invalid Tailwind class “h-10.5”.

Tailwind doesn’t support decimal sizes without arbitrary values; this won’t compile.

Use a supported size or an arbitrary value:

-    className="h-10.5 rounded-xl px-5"
+    className="h-10 rounded-xl px-5"
+    // or: className="h-[42px] rounded-xl px-5"
apps/api/src/routes/projects.ts (1)

196-206: Avoid any for update payload; use a typed partial.

Improves type safety for updates.

Apply this diff:

-  const updateData: any = {};
+  type ProjectUpdate = {
+    cachingEnabled?: boolean;
+    cacheDurationSeconds?: number;
+    mode?: "api-keys" | "credits" | "hybrid";
+  };
+  const updateData: ProjectUpdate = {};
apps/ui/src/app/layout.tsx (1)

1-3: Prefer config over inline disable for CSS side‑effect import.

Keeping the CSS import at the top is fine for Next.js, but avoid per-file disables by teaching import/order about CSS. Example flat-config snippet:

+// eslint.config.mjs (excerpt)
+import importPlugin from "eslint-plugin-import";
+
+export default [
+  {
+    plugins: { import: importPlugin },
+    rules: {
+      "import/order": ["error", {
+        "pathGroups": [
+          { pattern: "**/*.css", group: "unknown", position: "before" }
+        ],
+        "pathGroupsExcludedImportTypes": ["builtin"]
+      }]
+    }
+  }
+];

If you keep the directive, limit it to a single line as done here.

apps/ui/src/components/dashboard/new-project-dialog.tsx (1)

71-91: Harden error handling in onError.

error is often unknown; accessing .message can be unsafe. Normalize first.

-  } else {
-    // Generic error toast
-    toast({
-      title: "Failed to create project",
-      description:
-        error.message || "An unexpected error occurred. Please try again.",
-      variant: "destructive",
-    });
-  }
+  } else {
+    const msg =
+      error instanceof Error ? error.message : String(error ?? "Unknown error");
+    toast({
+      title: "Failed to create project",
+      description: msg || "An unexpected error occurred. Please try again.",
+      variant: "destructive",
+    });
+  }
apps/ui/src/app/dashboard/[orgId]/[projectId]/settings/billing/page.tsx (1)

18-26: Page props should not be Promises.

Next.js passes params/searchParams as plain objects. Typing them as Promise<...> is misleading.

-interface BillingPageProps {
-  params: Promise<{
-    orgId: string;
-    projectId: string;
-  }>;
-  searchParams: Promise<{
-    success?: string;
-    canceled?: string;
-  }>;
-}
+interface BillingPageProps {
+  params: { orgId: string; projectId: string };
+  searchParams: { success?: string; canceled?: string };
+}

Also drop the await when destructuring them.

apps/api/src/lib/beacon.ts (1)

44-46: Nit: unmatched parenthesis in log message.

Close the opening parenthesis.

- logger.info(
-   "Sending installation beacon (for anonymous tracking of self-hosted installs. To disable, set TELEMETRY_ACTIVE=false in your environment variables.",
- );
+ logger.info(
+   "Sending installation beacon (for anonymous tracking of self-hosted installs). To disable, set TELEMETRY_ACTIVE=false in your environment variables.",
+ );
apps/gateway/src/lib/costs.ts (1)

7-11: Optional: include 'tool' role for future‑proofing.

Tokenizer/chat ecosystems frequently use a 'tool' role.

-interface ChatMessage {
-  role: "user" | "system" | "assistant" | undefined;
+interface ChatMessage {
+  role: "user" | "system" | "assistant" | "tool" | undefined;
apps/ui/src/components/providers/hero.tsx (1)

52-58: Add rel to external link opened in new tab.

Prevent tab‑nabbing.

-            <a
-              href={`${provider.website}?utm_source=llmgateway-models`}
-              target="_blank"
-            >
+            <a
+              href={`${provider.website}?utm_source=llmgateway-models`}
+              target="_blank"
+              rel="noopener noreferrer"
+            >
apps/ui/src/app/changelog/page.tsx (1)

4-5: Fix import order to satisfy import/order.

Place external package imports before internal alias imports to resolve the failing lint check.

Apply this diff:

-import { ChangelogComponent } from "@/components/changelog";
-import { HeroRSC } from "@/components/landing/hero-rsc";
-
-import { allChangelogs, type Changelog } from "content-collections";
+import { allChangelogs, type Changelog } from "content-collections";
+import { ChangelogComponent } from "@/components/changelog";
+import { HeroRSC } from "@/components/landing/hero-rsc";
apps/ui/src/components/landing/footer.tsx (1)

9-10: Confirm grouping for workspace imports (@llmgateway/models) vs internal (@/...)

With typical import/order groups (builtin, external, internal), a workspace package is “external” and would be placed above "@/…". If your eslint config treats @llmgateway/* as internal, this is fine; otherwise consider moving it into the external group.

apps/ui/src/app/providers/[id]/page.tsx (1)

8-12: Check import-order grouping for monorepo packages

Similar to footer.tsx: if @llmgateway/* is configured as internal, this order is correct; if not, import/order may expect it in the external group above "@/…".

apps/ui/src/app/blog/[slug]/page.tsx (1)

11-12: Fix import/order: place content-collections in the external group (before lucide-react) and alphabetize

import/order expects external imports first and sorted; content-collections should be above lucide-react. Move this import into the external block.

Apply this diff to remove from the current position:

- import { allBlogs, type Blog } from "content-collections";

Then add it at the top external group (example target ordering):

import { allBlogs, type Blog } from "content-collections";
import { ArrowLeftIcon } from "lucide-react";
import Markdown from "markdown-to-jsx";
import Image from "next/image";
import Link from "next/link";
import { notFound } from "next/navigation";

import Footer from "@/components/landing/footer";
import { HeroRSC } from "@/components/landing/hero-rsc";
import { getMarkdownOptions } from "@/lib/utils/markdown";
apps/ui/src/app/changelog/[slug]/page.tsx (1)

11-12: Fix import/order: move content-collections to the external group and sort

Like the blog page, place this external import before lucide-react and other internals.

Apply this diff to remove from the current position:

- import { allChangelogs, type Changelog } from "content-collections";

Then add to the top external block (example):

import { allChangelogs, type Changelog } from "content-collections";
import { ArrowLeftIcon } from "lucide-react";
import Markdown from "markdown-to-jsx";
import Image from "next/image";
import Link from "next/link";
import { notFound } from "next/navigation";

import Footer from "@/components/landing/footer";
import { HeroRSC } from "@/components/landing/hero-rsc";
import { getMarkdownOptions } from "@/lib/utils/markdown";
apps/ui/src/components/api-keys/create-api-key-dialog.tsx (1)

23-23: Minor type-import polish

Consider importing named types to avoid React. prefixes.

-import type React from "react";
+import type { ReactNode, FormEvent } from "react";

And update usages:

  • children: ReactNode
  • const handleSubmit = (e: FormEvent) => { … }
apps/ui/src/components/onboarding/provider-key-step.tsx (1)

23-35: Normalize import paths to alias to satisfy import rules

This file mixes "@/..." and relative "../../..." paths, which will trip import/no-useless-path-segments and import/order with the new plugins. Switch to alias paths for consistency.

Apply:

-import { Button } from "../../lib/components/button";
+import { Button } from "@/lib/components/button";
@@
-} from "../../lib/components/card";
+} from "@/lib/components/card";
@@
-import { Step } from "../../lib/components/stepper";
+import { Step } from "@/lib/components/stepper";
@@
-import { ProviderSelect } from "../provider-keys/provider-select";
+import { ProviderSelect } from "@/components/provider-keys/provider-select";
apps/ui/src/app/models/[name]/page.tsx (1)

27-33: Nit: params typing doesn’t need to be a Promise

Next.js passes params as a plain object. Consider simplifying the prop type and dropping the await.

-interface PageProps {
-  params: Promise<{ name: string }>;
-}
+interface PageProps {
+  params: { name: string };
+}
@@
-export default async function ModelPage({ params }: PageProps) {
-  const { name } = await params;
+export default async function ModelPage({ params }: PageProps) {
+  const { name } = params;
apps/api/src/routes/keys-provider.ts (1)

171-175: Nit: error message says “project” but scope is organization

This endpoint de-duplicates per organization. Consider adjusting wording for clarity.

- message: `A key for provider '${provider}' already exists for this project`,
+ message: `A key for provider '${provider}' already exists for this organization`,
apps/api/src/routes/keys-provider.e2e.ts (1)

158-199: Remove duplicate skipped test block

There are two identical test.skip("POST /keys/provider with custom baseUrl", ...) blocks. Keep one to reduce noise.

- test.skip("POST /keys/provider with custom baseUrl", async () => {
-   // ...duplicate content...
- });
apps/ui/src/components/dashboard/new-organization-dialog.tsx (1)

19-19: Type‑only React import is appropriate; consider narrowing the event type.

Optional nit: type the submit handler as React.FormEvent<HTMLFormElement> for stricter inference.

-const handleSubmit = async (e: React.FormEvent) => {
+const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
apps/api/src/index.ts (1)

1-3: Top-level dotenv load is correct; avoid per-file ESLint disable via config.

Side-effect import is appropriate here. Prefer configuring import/order to allow it instead of an inline disable.

Add this to eslint.config.mjs (flat config) so side‑effect imports like "dotenv/config" are accepted at the top without disable:

// inside your shared config object
settings: {
  'import/resolver': {
    typescript: {
      // point to all tsconfigs in the monorepo
      project: ['apps/*/tsconfig.json', 'packages/*/tsconfig.json'],
      alwaysTryTypes: true,
    },
    node: { extensions: ['.ts', '.tsx', '.js', '.jsx'] },
  },
},
rules: {
  'import/order': ['error', {
    groups: ['type', 'builtin', 'external', 'internal', 'parent', 'sibling', 'index', 'object', 'unknown'],
    pathGroups: [
      { pattern: 'dotenv/config', group: 'builtin', position: 'before' },
    ],
    pathGroupsExcludedImportTypes: ['builtin'],
    'newlines-between': 'always',
    alphabetize: { order: 'asc', caseInsensitive: true },
  }],
}

Comment thread apps/api/src/index.ts
Comment on lines +10 to +12
import { db } from "@llmgateway/db";
import { logger } from "@llmgateway/logger";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Heads-up: resolver errors in CI need addressing (EACCES from eslint-import-resolver-typescript).

Static analysis shows EACCES from get-tsconfig via the TS resolver when rules like import/order run. Likely a resolver/tsconfig discovery issue in CI.

Try:

  • Pin and/or upgrade resolver to a stable version and ensure projects are explicit:
    • "eslint-import-resolver-typescript": "^3.7.0" or latest 4.x known-good in your CI.
  • In eslint.config.mjs, set resolver project globs (apps/, packages/) as above.
  • Ensure ESLint runs with repo root as CWD; if not, add workingDirectories in editor and CI.
  • If the CI still fails, temporarily limit resolver usage by disabling rules that invoke resolution (e.g., import/no-unresolved, import/no-useless-path-segments) until resolver is stable.

I can generate a minimal CI repro script to iterate resolver versions if helpful.

🤖 Prompt for AI Agents
In apps/api/src/index.ts around lines 10-12 the CI is failing with EACCES from
the TypeScript import resolver during ESLint import rules; to fix it pin or
upgrade eslint-import-resolver-typescript in package.json to a known-good
version (e.g., "^3.7.0" or a stable 4.x), update eslint.config.mjs to provide
explicit resolver.project globs covering your monorepo (e.g., "apps/*",
"packages/*"), ensure ESLint is executed with the repository root as CWD in CI
(or set workingDirectories in the CI/editor), and if CI still fails temporarily
disable resolver-heavy rules (import/no-unresolved,
import/no-useless-path-segments) until the resolver version is stabilized.

Comment on lines +5 to 10
import { db } from "@llmgateway/db";
import { logger } from "@llmgateway/logger";

import { ensureStripeCustomer } from "../stripe";
import { stripe } from "./payments";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Import reshuffle looks good; check for circular deps with payments.

Bringing in stripe from "./payments" alongside ensureStripeCustomer is fine, but confirm there’s no cycle (payments → subscriptions).


🏁 Script executed:

#!/bin/bash
rg -nP 'from\s+"\.\/subscriptions"' apps/api/src/routes/payments.ts || true
rg -nP 'from\s+"\.{1,2}\/stripe"' apps/api/src/routes/{payments,subscriptions}.ts

Length of output: 172


🏁 Script executed:

#!/bin/bash
set -uo pipefail

echo "1) List any stripe*.ts files under apps/"
rg -n --hidden -S --glob '!**/node_modules/**' -g 'apps/**/src/**/stripe*.ts' || true

echo
echo "2) Search for ensureStripeCustomer definition/usage across apps/"
rg -n --hidden -S --glob '!**/node_modules/**' 'ensureStripeCustomer' apps || true

echo
echo "3) Show top of subscriptions.ts and payments.ts to confirm imports"
if [[ -f apps/api/src/routes/subscriptions.ts ]]; then
  echo "---- apps/api/src/routes/subscriptions.ts ----"
  sed -n '1,120p' apps/api/src/routes/subscriptions.ts || true
else
  echo "subscriptions.ts not found"
fi
echo
if [[ -f apps/api/src/routes/payments.ts ]]; then
  echo "---- apps/api/src/routes/payments.ts ----"
  sed -n '1,160p' apps/api/src/routes/payments.ts || true
else
  echo "payments.ts not found"
fi

echo
echo "4) Print stripe module file if present at apps/api/src/stripe.ts or apps/api/src/stripe/index.ts"
if [[ -f apps/api/src/stripe.ts ]]; then
  echo "---- apps/api/src/stripe.ts ----"
  sed -n '1,300p' apps/api/src/stripe.ts || true
elif [[ -f apps/api/src/stripe/index.ts ]]; then
  echo "---- apps/api/src/stripe/index.ts ----"
  sed -n '1,300p' apps/api/src/stripe/index.ts || true
else
  echo "stripe module not found at apps/api/src/stripe.ts or apps/api/src/stripe/index.ts"
fi

Length of output: 15276


Break circular import between payments and stripe

Found cycle: apps/api/src/routes/payments.ts imports ensureStripeCustomer from ../stripe, while apps/api/src/stripe.ts imports { stripe } from ./routes/payments — this creates a payments ↔ stripe circular dependency and can produce partially-initialized exports at runtime.
Fix: break the cycle — either move the Stripe client initialization into apps/api/src/stripe.ts and have payments/subscriptions import it from there (update payments.ts/subscriptions.ts to import stripe from ../stripe), or extract ensureStripeCustomer into a small shared module both can import. Also remove the reciprocal import in apps/api/src/stripe.ts (stop importing from ./routes/payments).

🤖 Prompt for AI Agents
In apps/api/src/routes/subscriptions.ts around lines 5 to 10 there is a circular
import: routes/payments.ts imports ensureStripeCustomer from ../stripe while
stripe.ts imports { stripe } from ./routes/payments, leading to
partially-initialized exports at runtime; break the cycle by moving Stripe
client initialization into apps/api/src/stripe.ts and update payments.ts and
subscriptions.ts to import the stripe client from ../stripe (or alternatively
extract ensureStripeCustomer into a small shared module both can import), and
remove any reciprocal import from apps/api/src/stripe.ts so stripe.ts no longer
imports from ./routes/payments.

Comment thread apps/docs/app/layout.tsx
Comment on lines +1 to +2
// eslint-disable-next-line import/order
import "./global.css";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Address resolver EACCES failures in CI by scoping TS project globs

Static analysis shows EACCES from eslint-import-resolver-typescript when auto-discovering tsconfigs. Explicitly set resolver projects to workspace globs to avoid tmp writes.

Add to eslint.config.mjs (or equivalent):

 export default [
   {
     settings: {
       'import/resolver': {
-        typescript: {},
+        typescript: {
+          alwaysTryTypes: true,
+          project: [
+            'apps/*/tsconfig.json',
+            'packages/*/tsconfig.json',
+            'tools/*/tsconfig.json'
+          ],
+        },
+        node: true,
       },
     },
   },
 ];

Then verify locally:


🏁 Script executed:

#!/bin/bash
# Run ESLint with resolver debug to ensure no EACCES and paths resolve.
export DEBUG=eslint-plugin-import:resolver:typescript,eslint-module-utils:resolve
pnpm exec eslint apps/docs/app/layout.tsx

Length of output: 4480


Fix EACCES in CI: scope TypeScript resolver projects in ESLint

ESLint reproduced EACCES errors when auto-discovering tsconfigs; lock the import/resolver typescript project to workspace globs to avoid tmp writes. Edit eslint.config.mjs (or equivalent):

 export default [
   {
     settings: {
       'import/resolver': {
-        typescript: {},
+        typescript: {
+          alwaysTryTypes: true,
+          project: [
+            'apps/*/tsconfig.json',
+            'packages/*/tsconfig.json',
+            'tools/*/tsconfig.json'
+          ],
+        },
+        node: true,
       },
     },
   },
 ];

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 ESLint

[error] 1-1: Resolve error: EACCES: permission denied, open '/OzKKBLsyBT'
at Object.writeFileSync (node:fs:2409:20)
at l (/home/jailuser/git/node_modules/.pnpm/get-tsconfig@4.10.1/node_modules/get-tsconfig/dist/index.cjs:7:13685)
at createFilesMatcher (/home/jailuser/git/node_modules/.pnpm/get-tsconfig@4.10.1/node_modules/get-tsconfig/dist/index.cjs:7:14437)
at Object.resolve (/home/jailuser/git/node_modules/.pnpm/eslint-import-resolver-typescript@4.4.4_eslint-plugin-import@2.32.0_eslint@9.34.0_jiti@2.5.1_/node_modules/eslint-import-resolver-typescript/lib/index.cjs:298:107)
at withResolver (/home/jailuser/git/node_modules/.pnpm/eslint-module-utils@2.12.1_@typescript-eslint+parser@8.39.1_eslint@9.34.0_jiti@2.5.1__t_0245afd672d291ebf09c60ebb405cf51/node_modules/eslint-module-utils/resolve.js:180:23)
at fullResolve (/home/jailuser/git/node_modules/.pnpm/eslint-module-utils@2.12.1_@typescript-eslint+parser@8.39.1_eslint@9.34.0_jiti@2.5.1__t_0245afd672d291ebf09c60ebb405cf51/node_modules/eslint-module-utils/resolve.js:201:22)
at relative (/home/jailuser/git/node_modules/.pnpm/eslint-module-utils@2.12.1_@typescript-eslint+parser@8.39.1_eslint@9.34.0_jiti@2.5.1__t_0245afd672d291ebf09c60ebb405cf51/node_modules/eslint-module-utils/resolve.js:217:10)
at resolve (/home/jailuser/git/node_modules/.pnpm/eslint-module-utils@2.12.1_@typescript-eslint+parser@8.39.1_eslint@9.34.0_jiti@2.5.1__t_0245afd672d291ebf09c60ebb405cf51/node_modules/eslint-module-utils/resolve.js:233:12)
at resolveImportType (/home/jailuser/git/node_modules/.pnpm/eslint-plugin-import@2.32.0_@typescript-eslint+parser@8.39.1_eslint@9.34.0_jiti@2.5.1___504bceda7a403f00735dcb51d6a813c7/node_modules/eslint-plugin-import/lib/core/importType.js:126:2822)
at computeRank (/home/jailuser/git/node_modules/.pnpm/eslint-plugin-import@2.32.0_@typescript-eslint+parser@8.39.1_eslint@9.34.0_jiti@2.5.1___504bceda7a403f00735dcb51d6a813c7/node_modules/eslint-plugin-import/lib/rules/order.js:529:43)

(import/order)


[error] 1-1: Resolve error: EACCES: permission denied, open '/cbXESOWCNQ'
at Object.writeFileSync (node:fs:2409:20)
at l (/home/jailuser/git/node_modules/.pnpm/get-tsconfig@4.10.1/node_modules/get-tsconfig/dist/index.cjs:7:13685)
at createFilesMatcher (/home/jailuser/git/node_modules/.pnpm/get-tsconfig@4.10.1/node_modules/get-tsconfig/dist/index.cjs:7:14437)
at Object.resolve (/home/jailuser/git/node_modules/.pnpm/eslint-import-resolver-typescript@4.4.4_eslint-plugin-import@2.32.0_eslint@9.34.0_jiti@2.5.1_/node_modules/eslint-import-resolver-typescript/lib/index.cjs:298:107)
at withResolver (/home/jailuser/git/node_modules/.pnpm/eslint-module-utils@2.12.1_@typescript-eslint+parser@8.39.1_eslint@9.34.0_jiti@2.5.1__t_0245afd672d291ebf09c60ebb405cf51/node_modules/eslint-module-utils/resolve.js:180:23)
at fullResolve (/home/jailuser/git/node_modules/.pnpm/eslint-module-utils@2.12.1_@typescript-eslint+parser@8.39.1_eslint@9.34.0_jiti@2.5.1__t_0245afd672d291ebf09c60ebb405cf51/node_modules/eslint-module-utils/resolve.js:201:22)
at relative (/home/jailuser/git/node_modules/.pnpm/eslint-module-utils@2.12.1_@typescript-eslint+parser@8.39.1_eslint@9.34.0_jiti@2.5.1__t_0245afd672d291ebf09c60ebb405cf51/node_modules/eslint-module-utils/resolve.js:217:10)
at resolve (/home/jailuser/git/node_modules/.pnpm/eslint-module-utils@2.12.1_@typescript-eslint+parser@8.39.1_eslint@9.34.0_jiti@2.5.1__t_0245afd672d291ebf09c60ebb405cf51/node_modules/eslint-module-utils/resolve.js:233:12)
at checkSourceValue (/home/jailuser/git/node_modules/.pnpm/eslint-plugin-import@2.32.0_@typescript-eslint+parser@8.39.1_eslint@9.34.0_jiti@2.5.1___504bceda7a403f00735dcb51d6a813c7/node_modules/eslint-plugin-import/lib/rules/no-useless-path-segments.js:85:53)
at checkSourceValue (/home/jailuser/git/node_modules/.pnpm/eslint-module-utils@2.12.1_@typescript-eslint+parser@8.39.1_eslint@9.34.0_jiti@2.5.1__t_0245afd672d291ebf09c60ebb405cf51/node_modules/eslint-module-utils/moduleVisitor.js:32:5)

(import/no-useless-path-segments)

🤖 Prompt for AI Agents
In apps/docs/app/layout.tsx around lines 1-2: ESLint's TypeScript resolver is
auto-discovering tsconfig files and causing EACCES in CI; update your ESLint
config (e.g., eslint.config.mjs) to explicitly scope the import/resolver
typescript "project" option to workspace globs (for example the repo tsconfig
paths like "apps/*/tsconfig.json" and "packages/*/tsconfig.json" or the exact
tsconfig locations used by this monorepo) instead of leaving it unset so the
resolver doesn't scan tmp dirs; save and re-run CI to confirm the EACCES is
gone.

import { describe, expect, test } from "vitest";

import { app } from "../index";
import { app } from "..";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Import root shortcut looks fine; verify resolver handles directory imports.

Switching from "../index" to ".." is OK with Vitest/Vite resolution. With the new eslint-plugin-import + TS resolver, make sure directory imports aren’t flagged as unresolved in CI.

Run this to confirm no import resolution errors are reported on this file:


🏁 Script executed:

#!/bin/bash
rg -n 'import\s+\{\s*app\s*\}\s+from\s+"\.\."' apps/gateway/src/models/models.spec.ts
pnpm -w eslint apps/gateway/src/models/models.spec.ts || true

Length of output: 4522


Import root shortcut OK — ESLint resolver failing (EACCES); fix CI permissions/config

  • Running the supplied eslint command produced Resolve error: EACCES: permission denied, open '/HVPkYalrcW' and '/oMygdkRBlX' (from get-tsconfig / eslint-import-resolver-typescript), which prevents verifying that the ".." directory import isn’t flagged.
  • Action: ensure the linter can write to a writable temp dir in CI (set TMPDIR or fix container permissions) or configure import/resolver/typescript with an explicit project (pointing to the repo/app tsconfig(s)) to avoid get-tsconfig creating temp files; re-run eslint and confirm no unresolved-directory-import errors.
🤖 Prompt for AI Agents
In apps/gateway/src/models/models.spec.ts around line 3, ESLint import
resolution is failing with EACCES when get-tsconfig attempts to create temp
files for the ".." import; fix by making the linter able to write temp files in
CI (set TMPDIR to a writable directory or adjust container filesystem
permissions) or, preferably, configure eslint-import-resolver-typescript with an
explicit "project" pointing to the relevant tsconfig.json(s) for the repo/apps
so get-tsconfig doesn't create temp files; update the ESLint resolver config,
ensure CI has the writable temp dir (or set TMPDIR in CI), re-run eslint and
verify the unresolved-directory-import error is gone.

@@ -1,4 +1,6 @@
// eslint-disable-next-line import/order

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Resolver EACCES from eslint-import-resolver-typescript — verify resolver config.

CI hint shows get-tsconfig attempting FS writes causing EACCES. Ensure resolver is scoped to workspace tsconfigs and not probing root. Confirm with:

Common fixes:

  • Set resolver projects explicitly: { typescript: { project: ["tsconfig.json","apps//tsconfig.json","packages//tsconfig.json"] } }
  • Ensure parserOptions.tsconfigRootDir is repo root.

🏁 Script executed:

#!/bin/bash
# Print resolved ESLint config for one of the affected files
pnpm -w eslint --print-config apps/ui/src/app/layout.tsx | jq '."settings"."import/resolver"'

# Run lint in verbose to surface resolver issues
DEBUG=eslint-plugin-import:* pnpm -w eslint apps/ui --ext .ts,.tsx

Length of output: 524286


Fix eslint-import-resolver-typescript EACCES: scope resolver projects & set tsconfigRootDir

Print-config shows import/resolver.typescript uses globs (["./tsconfig.json","./apps//tsconfig.json","./packages//tsconfig.json"]) and the lint run shows get-tsconfig is trying to write temp files at root (EACCES). Actionable fixes:

  • Replace broad/globbed projects with explicit repo tsconfig paths (or use a single composite tsconfig with references) in settings.import/resolver.typescript.project.
  • Ensure parserOptions.tsconfigRootDir is set to the repository root (e.g. parserOptions: { tsconfigRootDir: __dirname } in your .eslintrc.js/.cjs) so the resolver resolves relative to the workspace instead of probing/creating files at /.
  • If you must support multiple tsconfigs, consider using a single referenced tsconfig, set typescript.noWarnOnMultipleProjects, or ensure CI’s TMPDIR is writable as a temporary workaround.
🧰 Tools
🪛 ESLint

[error] 1-1: Resolve error: EACCES: permission denied, open '/zslAFUfEHV'
at Object.writeFileSync (node:fs:2409:20)
at l (/home/jailuser/git/node_modules/.pnpm/get-tsconfig@4.10.1/node_modules/get-tsconfig/dist/index.cjs:7:13685)
at createFilesMatcher (/home/jailuser/git/node_modules/.pnpm/get-tsconfig@4.10.1/node_modules/get-tsconfig/dist/index.cjs:7:14437)
at Object.resolve (/home/jailuser/git/node_modules/.pnpm/eslint-import-resolver-typescript@4.4.4_eslint-plugin-import@2.32.0_eslint@9.34.0_jiti@2.5.1_/node_modules/eslint-import-resolver-typescript/lib/index.cjs:298:107)
at withResolver (/home/jailuser/git/node_modules/.pnpm/eslint-module-utils@2.12.1_@typescript-eslint+parser@8.39.1_eslint@9.34.0_jiti@2.5.1__t_0245afd672d291ebf09c60ebb405cf51/node_modules/eslint-module-utils/resolve.js:180:23)
at fullResolve (/home/jailuser/git/node_modules/.pnpm/eslint-module-utils@2.12.1_@typescript-eslint+parser@8.39.1_eslint@9.34.0_jiti@2.5.1__t_0245afd672d291ebf09c60ebb405cf51/node_modules/eslint-module-utils/resolve.js:201:22)
at relative (/home/jailuser/git/node_modules/.pnpm/eslint-module-utils@2.12.1_@typescript-eslint+parser@8.39.1_eslint@9.34.0_jiti@2.5.1__t_0245afd672d291ebf09c60ebb405cf51/node_modules/eslint-module-utils/resolve.js:217:10)
at resolve (/home/jailuser/git/node_modules/.pnpm/eslint-module-utils@2.12.1_@typescript-eslint+parser@8.39.1_eslint@9.34.0_jiti@2.5.1__t_0245afd672d291ebf09c60ebb405cf51/node_modules/eslint-module-utils/resolve.js:233:12)
at resolveImportType (/home/jailuser/git/node_modules/.pnpm/eslint-plugin-import@2.32.0_@typescript-eslint+parser@8.39.1_eslint@9.34.0_jiti@2.5.1___504bceda7a403f00735dcb51d6a813c7/node_modules/eslint-plugin-import/lib/core/importType.js:126:2822)
at computeRank (/home/jailuser/git/node_modules/.pnpm/eslint-plugin-import@2.32.0_@typescript-eslint+parser@8.39.1_eslint@9.34.0_jiti@2.5.1___504bceda7a403f00735dcb51d6a813c7/node_modules/eslint-plugin-import/lib/rules/order.js:529:43)

(import/order)


[error] 1-1: Resolve error: EACCES: permission denied, open '/MsbzaujiEK'
at Object.writeFileSync (node:fs:2409:20)
at l (/home/jailuser/git/node_modules/.pnpm/get-tsconfig@4.10.1/node_modules/get-tsconfig/dist/index.cjs:7:13685)
at createFilesMatcher (/home/jailuser/git/node_modules/.pnpm/get-tsconfig@4.10.1/node_modules/get-tsconfig/dist/index.cjs:7:14437)
at Object.resolve (/home/jailuser/git/node_modules/.pnpm/eslint-import-resolver-typescript@4.4.4_eslint-plugin-import@2.32.0_eslint@9.34.0_jiti@2.5.1_/node_modules/eslint-import-resolver-typescript/lib/index.cjs:298:107)
at withResolver (/home/jailuser/git/node_modules/.pnpm/eslint-module-utils@2.12.1_@typescript-eslint+parser@8.39.1_eslint@9.34.0_jiti@2.5.1__t_0245afd672d291ebf09c60ebb405cf51/node_modules/eslint-module-utils/resolve.js:180:23)
at fullResolve (/home/jailuser/git/node_modules/.pnpm/eslint-module-utils@2.12.1_@typescript-eslint+parser@8.39.1_eslint@9.34.0_jiti@2.5.1__t_0245afd672d291ebf09c60ebb405cf51/node_modules/eslint-module-utils/resolve.js:201:22)
at relative (/home/jailuser/git/node_modules/.pnpm/eslint-module-utils@2.12.1_@typescript-eslint+parser@8.39.1_eslint@9.34.0_jiti@2.5.1__t_0245afd672d291ebf09c60ebb405cf51/node_modules/eslint-module-utils/resolve.js:217:10)
at resolve (/home/jailuser/git/node_modules/.pnpm/eslint-module-utils@2.12.1_@typescript-eslint+parser@8.39.1_eslint@9.34.0_jiti@2.5.1__t_0245afd672d291ebf09c60ebb405cf51/node_modules/eslint-module-utils/resolve.js:233:12)
at checkSourceValue (/home/jailuser/git/node_modules/.pnpm/eslint-plugin-import@2.32.0_@typescript-eslint+parser@8.39.1_eslint@9.34.0_jiti@2.5.1___504bceda7a403f00735dcb51d6a813c7/node_modules/eslint-plugin-import/lib/rules/no-useless-path-segments.js:85:53)
at checkSourceValue (/home/jailuser/git/node_modules/.pnpm/eslint-module-utils@2.12.1_@typescript-eslint+parser@8.39.1_eslint@9.34.0_jiti@2.5.1__t_0245afd672d291ebf09c60ebb405cf51/node_modules/eslint-module-utils/moduleVisitor.js:32:5)

(import/no-useless-path-segments)

🤖 Prompt for AI Agents
In apps/ui/src/app/layout.tsx around lines 1 to 1, the repo-level ESLint config
is causing the typescript resolver to probe/create temp tsconfig files at the
filesystem root (EACCES) because projects are specified with broad globs and
tsconfigRootDir isn’t set; update your ESLint/TypeScript config to list explicit
tsconfig paths (or use one composite referenced tsconfig) in
settings["import/resolver"].typescript.project, set
parserOptions.tsconfigRootDir to the repository root (e.g. __dirname in the
.eslintrc.* file) so resolution is rooted to the workspace, and if multiple
tsconfigs are required either create a single referenced tsconfig, enable
typescript.noWarnOnMultipleProjects, or ensure CI TMPDIR is writable as a
temporary workaround.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
apps/api/src/routes/keys-provider.ts (1)

156-176: Add unique DB constraint on (organizationId, provider) and make inserts conflict-safe

packages/db/src/schema.ts already defines unique().on(table.organizationId, table.name) (~line 272) but there is no unique constraint on (organizationId, provider). Add a unique().on(table.organizationId, table.provider) in the schema and change the insert(s) to use an atomic ON CONFLICT targeting those columns. Update both insert locations in apps/api/src/routes/keys-provider.ts (~156–176 and ~220–229).

Suggested insert change:

 const [providerKey] = await db
   .insert(tables.providerKey)
+  .onConflictDoNothing({
+    target: [tables.providerKey.organizationId, tables.providerKey.provider],
+  })
   .values({
     token: userToken,
     organizationId,
     provider,
     name,
     baseUrl,
   })
   .returning();
apps/ui/src/components/landing/hero.tsx (2)

146-149: Add rel="noopener noreferrer" to external link with target="_blank".

Prevents reverse‑tabnabbing; also prefer avoiding empty hrefs.

-										<a
+										<a
 											href={config.githubUrl ?? ""}
-											target="_blank"
+											target="_blank" rel="noopener noreferrer"
 											className="mx-auto lg:mx-0 hover:bg-background dark:hover:border-t-border bg-muted group flex w-fit items-center gap-4 rounded-full border p-1 pl-4 shadow-md shadow-black/5 transition-all duration-300 dark:border-t-white/5 dark:shadow-zinc-950"
 										>

Optionally avoid empty hrefs:

-											href={config.githubUrl ?? ""}
+											href={config.githubUrl || undefined}

221-226: Also add rel on the docs link opened in a new tab.

Same security concern as above.

-												<a href={config.docsUrl ?? ""} target="_blank">
+												<a href={config.docsUrl ?? ""} target="_blank" rel="noopener noreferrer">
🧹 Nitpick comments (14)
apps/api/src/routes/keys-provider.ts (2)

5-7: Optionally de-duplicate '@llmgateway/models' imports.

If import/no-duplicates is configured to flag type/value duplicates, merge into a single statement.

-import { providers, validateProviderKey } from "@llmgateway/models";
+import { providers, validateProviderKey, type ProviderId } from "@llmgateway/models";
-
-import type { ProviderId } from "@llmgateway/models";

Also applies to: 11-12


307-313: Avoid spreading the raw token then overriding with undefined.

Prevents accidental leakage and aligns the shape with the schema without relying on JSON.stringify behavior.

-return c.json({
-  providerKeys: providerKeys.map((key) => ({
-    ...key,
-    maskedToken: maskToken(key.token),
-    token: undefined,
-  })),
-});
+return c.json({
+  providerKeys: providerKeys.map(({ token, ...rest }) => ({
+    ...rest,
+    maskedToken: maskToken(token),
+  })),
+});
packages/cache/src/cache.ts (3)

1-1: Prefer node: specifier and a named import from crypto.

Improves clarity, avoids polyfill ambiguity, and typically plays nicer with bundlers and eslint-plugin-import resolution.

Apply this diff:

-import crypto from "crypto";
+import { createHash } from "node:crypto";

And update usage:

-  return crypto
-    .createHash("sha256")
+  return createHash("sha256")
     .update(JSON.stringify(payload))
     .digest("hex");

3-9: Consolidate imports from @llmgateway/db and mark types as type‑only.

Reduces duplicate module loads and aligns with import/no-duplicates and import/order when using TS type imports.

Apply this diff:

-import { db, type InferSelectModel } from "@llmgateway/db";
+import { db } from "@llmgateway/db";
+import type { InferSelectModel, tables } from "@llmgateway/db";
 
-import type { tables } from "@llmgateway/db";

10-15: Cache key may vary with object key order; consider stable stringify.

If callers pass semantically identical payloads with different key orders, keys will differ. Optional improvement.

If you want this, add a stable stringify helper and use it:

+function stableStringify(value: unknown): string {
+  if (value === null || typeof value !== "object") return JSON.stringify(value);
+  if (Array.isArray(value)) return `[${value.map(stableStringify).join(",")}]`;
+  const entries = Object.entries(value as Record<string, unknown>).sort(([a],[b]) => a.localeCompare(b));
+  return `{${entries.map(([k,v]) => `${JSON.stringify(k)}:${stableStringify(v)}`).join(",")}}`;
+}
 
 export function generateCacheKey(payload: Record<string, any>): string {
-  return crypto
-    .createHash("sha256")
-    .update(JSON.stringify(payload))
+  return createHash("sha256")
+    .update(stableStringify(payload))
     .digest("hex");
 }
apps/ui/src/components/landing/hero.tsx (1)

379-385: Nit: drop explicit prefetch unless you have a measured need.

Reduces unnecessary network work; Next.js defaults are generally sufficient.

-								<Link
-									href="/providers"
-									className="block text-sm duration-150 hover:opacity-75"
-									prefetch={true}
-								>
+								<Link
+									href="/providers"
+									className="block text-sm duration-150 hover:opacity-75"
+								>
eslint.config.mjs (2)

8-22: Resolver config: add PNPM support and internal-regex; consider Node resolver + performance.

  • Add import/external-module-folders to handle PNPM’s .pnpm layout.
  • Mark @/ and @llmgateway/ as internal via import/internal-regex (keeps grouping stable without relying only on pathGroups).
  • Optionally add node resolver with extensions for mixed ESM/TS.
  • Large project globs can slow lint; if you hit perf issues, consider a central tsconfig.eslint.json or narrower globs.

Apply within this block:

 		settings: {
 			"import/resolver": {
 				typescript: {
 					alwaysTryTypes: true,
 					project: [
 						"./tsconfig.json",
 						"./apps/*/tsconfig.json",
 						"./packages/*/tsconfig.json",
 					],
 				},
+				node: {
+					extensions: [".js", ".mjs", ".ts", ".tsx", ".d.ts"],
+				},
 			},
+			"import/internal-regex": "^(@|@llmgateway)/",
+			"import/external-module-folders": ["node_modules", "node_modules/.pnpm"],
 		},

85-98: Disabling no-console for entire UI/Docs is risky; scope to tests.

Turning it off across apps/ui/** and apps/docs/** can mask production logs. Limit to test files, or downgrade to “warn” for UI/Docs.

Apply this minimal change now:

 		files: [
 			"**/*.spec.ts",
 			"**/*.spec.tsx",
 			"**/*.test.ts",
 			"**/*.test.tsx",
 			"**/*.e2e.ts",
 			"**/test-utils/**",
-			"apps/ui/**",
-			"apps/docs/**",
 		],

Then add another override (outside this hunk) if desired:

{
  files: ["apps/ui/**", "apps/docs/**"],
  rules: { "no-console": "warn" },
},
apps/ui/src/app/blog/[slug]/page.tsx (3)

15-21: Fix Next.js props typing: params is not a Promise.

Next passes params as a plain object. Align the type and drop the unnecessary await.

-interface BlogEntryPageProps {
-  params: Promise<{ slug: string }>;
-}
+interface BlogEntryPageProps {
+  params: { slug: string };
+}

 export default async function BlogEntryPage({ params }: BlogEntryPageProps) {
-  const { slug } = await params;
+  const { slug } = params;

51-57: Stabilize date rendering across time zones.

If entry.date is YYYY-MM-DD, local TZ can shift it by a day. Force UTC in formatting.

- {new Date(entry.date).toLocaleDateString("en-US", {
+ {new Date(entry.date).toLocaleDateString("en-US", {
     year: "numeric",
     month: "long",
     day: "numeric",
+    timeZone: "UTC",
   })}

111-116: Use nullish coalescing for numeric fallbacks.

Avoid treating 0 as falsy; use ??.

-  width: entry.image.width || 800,
-  height: entry.image.height || 400,
+  width: entry.image.width ?? 800,
+  height: entry.image.height ?? 400,
apps/ui/src/app/changelog/[slug]/page.tsx (3)

15-23: Fix Next.js props typing: params should not be Promise.

Align with Next’s Page props and drop the await.

-interface ChangelogEntryPageProps {
-  params: Promise<{ slug: string }>;
-}
+interface ChangelogEntryPageProps {
+  params: { slug: string };
+}

 export default async function ChangelogEntryPage({
   params,
 }: ChangelogEntryPageProps) {
-  const { slug } = await params;
+  const { slug } = params;

55-60: Same TZ stability nit: force UTC when formatting dates.

- {new Date(entry.date).toLocaleDateString("en-US", {
+ {new Date(entry.date).toLocaleDateString("en-US", {
     year: "numeric",
     month: "long",
     day: "numeric",
+    timeZone: "UTC",
   })}

115-118: Prefer ?? over || for numeric defaults.

-  width: entry.image.width || 800,
-  height: entry.image.height || 400,
+  width: entry.image.width ?? 800,
+  height: entry.image.height ?? 400,
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dc5b784 and 8211767.

📒 Files selected for processing (19)
  • apps/api/src/routes/keys-provider.ts (1 hunks)
  • apps/ui/src/app/blog/[slug]/page.tsx (2 hunks)
  • apps/ui/src/app/changelog/[slug]/page.tsx (2 hunks)
  • apps/ui/src/app/changelog/page.tsx (1 hunks)
  • apps/ui/src/app/onboarding/page.tsx (1 hunks)
  • apps/ui/src/app/playground/page.tsx (1 hunks)
  • apps/ui/src/components/activity/recent-logs.tsx (1 hunks)
  • apps/ui/src/components/api-keys/api-keys-list.tsx (1 hunks)
  • apps/ui/src/components/dashboard/dashboard-sidebar.tsx (1 hunks)
  • apps/ui/src/components/dashboard/organization-switcher.tsx (1 hunks)
  • apps/ui/src/components/dashboard/project-switcher.tsx (1 hunks)
  • apps/ui/src/components/landing/hero.tsx (1 hunks)
  • apps/ui/src/components/models/all-models.tsx (1 hunks)
  • apps/ui/src/components/models/models-list.tsx (1 hunks)
  • apps/ui/src/components/provider-keys/create-provider-key-dialog.tsx (1 hunks)
  • apps/ui/src/components/provider-keys/provider-keys-list.tsx (1 hunks)
  • apps/ui/src/components/usage/cost-breakdown-chart.tsx (1 hunks)
  • eslint.config.mjs (3 hunks)
  • packages/cache/src/cache.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (11)
  • apps/ui/src/components/provider-keys/provider-keys-list.tsx
  • apps/ui/src/components/provider-keys/create-provider-key-dialog.tsx
  • apps/ui/src/components/models/models-list.tsx
  • apps/ui/src/components/dashboard/project-switcher.tsx
  • apps/ui/src/components/activity/recent-logs.tsx
  • apps/ui/src/app/playground/page.tsx
  • apps/ui/src/app/onboarding/page.tsx
  • apps/ui/src/components/api-keys/api-keys-list.tsx
  • apps/ui/src/components/usage/cost-breakdown-chart.tsx
  • apps/ui/src/components/dashboard/dashboard-sidebar.tsx
  • apps/ui/src/components/dashboard/organization-switcher.tsx
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Always use top-level import; never use require() or dynamic import()

Always use top-level import; never use require or dynamic imports

Files:

  • apps/ui/src/components/landing/hero.tsx
  • apps/ui/src/components/models/all-models.tsx
  • apps/api/src/routes/keys-provider.ts
  • apps/ui/src/app/blog/[slug]/page.tsx
  • apps/ui/src/app/changelog/[slug]/page.tsx
  • apps/ui/src/app/changelog/page.tsx
  • packages/cache/src/cache.ts
apps/ui/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/ui/**/*.{ts,tsx}: In the Next.js UI, use next/link for links and next/navigation's router for programmatic navigation
Use cookies for user settings that aren’t stored in the database to ensure SSR works

Use cookies for user-settings not stored in the database to ensure SSR works in the UI

Files:

  • apps/ui/src/components/landing/hero.tsx
  • apps/ui/src/components/models/all-models.tsx
  • apps/ui/src/app/blog/[slug]/page.tsx
  • apps/ui/src/app/changelog/[slug]/page.tsx
  • apps/ui/src/app/changelog/page.tsx
apps/ui/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use next/link for links and next/navigation's router for programmatic navigation in the Next.js UI app

Files:

  • apps/ui/src/components/landing/hero.tsx
  • apps/ui/src/components/models/all-models.tsx
  • apps/ui/src/app/blog/[slug]/page.tsx
  • apps/ui/src/app/changelog/[slug]/page.tsx
  • apps/ui/src/app/changelog/page.tsx
{apps/{api,gateway}/**/*.ts,packages/db/**/*.ts}

📄 CodeRabbit inference engine (CLAUDE.md)

{apps/{api,gateway}/**/*.ts,packages/db/**/*.ts}: For database reads, use Drizzle’s db().query.

.findMany() or db().query.
.findFirst()
Use Drizzle ORM with the latest object syntax

Files:

  • apps/api/src/routes/keys-provider.ts
apps/{api,gateway}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

apps/{api,gateway}/**/*.{ts,tsx}: Use Drizzle ORM with the latest object syntax for database access
For reads, use db().query.

.findMany() or db().query.
.findFirst()
After API route changes, run pnpm generate to update OpenAPI schemas

Files:

  • apps/api/src/routes/keys-provider.ts
🧠 Learnings (1)
📚 Learning: 2025-09-13T16:25:00.704Z
Learnt from: CR
PR: theopenco/llmgateway#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-13T16:25:00.704Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Always use top-level import; never use require() or dynamic import()

Applied to files:

  • eslint.config.mjs
🪛 ESLint
apps/ui/src/app/blog/[slug]/page.tsx

[error] 1-1: Resolve error: EACCES: permission denied, open '/YsFKGGajyO'
at Object.writeFileSync (node:fs:2409:20)
at l (/home/jailuser/git/node_modules/.pnpm/get-tsconfig@4.10.1/node_modules/get-tsconfig/dist/index.cjs:7:13685)
at createFilesMatcher (/home/jailuser/git/node_modules/.pnpm/get-tsconfig@4.10.1/node_modules/get-tsconfig/dist/index.cjs:7:14437)
at Object.resolve (/home/jailuser/git/node_modules/.pnpm/eslint-import-resolver-typescript@4.4.4_eslint-plugin-import@2.32.0_eslint@9.34.0_jiti@2.5.1_/node_modules/eslint-import-resolver-typescript/lib/index.cjs:298:107)
at withResolver (/home/jailuser/git/node_modules/.pnpm/eslint-module-utils@2.12.1_@typescript-eslint+parser@8.39.1_eslint@9.34.0_jiti@2.5.1__t_0245afd672d291ebf09c60ebb405cf51/node_modules/eslint-module-utils/resolve.js:180:23)
at fullResolve (/home/jailuser/git/node_modules/.pnpm/eslint-module-utils@2.12.1_@typescript-eslint+parser@8.39.1_eslint@9.34.0_jiti@2.5.1__t_0245afd672d291ebf09c60ebb405cf51/node_modules/eslint-module-utils/resolve.js:201:22)
at relative (/home/jailuser/git/node_modules/.pnpm/eslint-module-utils@2.12.1_@typescript-eslint+parser@8.39.1_eslint@9.34.0_jiti@2.5.1__t_0245afd672d291ebf09c60ebb405cf51/node_modules/eslint-module-utils/resolve.js:217:10)
at resolve (/home/jailuser/git/node_modules/.pnpm/eslint-module-utils@2.12.1_@typescript-eslint+parser@8.39.1_eslint@9.34.0_jiti@2.5.1__t_0245afd672d291ebf09c60ebb405cf51/node_modules/eslint-module-utils/resolve.js:233:12)
at resolveImportType (/home/jailuser/git/node_modules/.pnpm/eslint-plugin-import@2.32.0_@typescript-eslint+parser@8.39.1_eslint@9.34.0_jiti@2.5.1___504bceda7a403f00735dcb51d6a813c7/node_modules/eslint-plugin-import/lib/core/importType.js:126:2822)
at computeRank (/home/jailuser/git/node_modules/.pnpm/eslint-plugin-import@2.32.0_@typescript-eslint+parser@8.39.1_eslint@9.34.0_jiti@2.5.1___504bceda7a403f00735dcb51d6a813c7/node_modules/eslint-plugin-import/lib/rules/order.js:529:43)

(import/order)

apps/ui/src/app/changelog/[slug]/page.tsx

[error] 1-1: Resolve error: EACCES: permission denied, open '/wgBhZjlNky'
at Object.writeFileSync (node:fs:2409:20)
at l (/home/jailuser/git/node_modules/.pnpm/get-tsconfig@4.10.1/node_modules/get-tsconfig/dist/index.cjs:7:13685)
at createFilesMatcher (/home/jailuser/git/node_modules/.pnpm/get-tsconfig@4.10.1/node_modules/get-tsconfig/dist/index.cjs:7:14437)
at Object.resolve (/home/jailuser/git/node_modules/.pnpm/eslint-import-resolver-typescript@4.4.4_eslint-plugin-import@2.32.0_eslint@9.34.0_jiti@2.5.1_/node_modules/eslint-import-resolver-typescript/lib/index.cjs:298:107)
at withResolver (/home/jailuser/git/node_modules/.pnpm/eslint-module-utils@2.12.1_@typescript-eslint+parser@8.39.1_eslint@9.34.0_jiti@2.5.1__t_0245afd672d291ebf09c60ebb405cf51/node_modules/eslint-module-utils/resolve.js:180:23)
at fullResolve (/home/jailuser/git/node_modules/.pnpm/eslint-module-utils@2.12.1_@typescript-eslint+parser@8.39.1_eslint@9.34.0_jiti@2.5.1__t_0245afd672d291ebf09c60ebb405cf51/node_modules/eslint-module-utils/resolve.js:201:22)
at relative (/home/jailuser/git/node_modules/.pnpm/eslint-module-utils@2.12.1_@typescript-eslint+parser@8.39.1_eslint@9.34.0_jiti@2.5.1__t_0245afd672d291ebf09c60ebb405cf51/node_modules/eslint-module-utils/resolve.js:217:10)
at resolve (/home/jailuser/git/node_modules/.pnpm/eslint-module-utils@2.12.1_@typescript-eslint+parser@8.39.1_eslint@9.34.0_jiti@2.5.1__t_0245afd672d291ebf09c60ebb405cf51/node_modules/eslint-module-utils/resolve.js:233:12)
at resolveImportType (/home/jailuser/git/node_modules/.pnpm/eslint-plugin-import@2.32.0_@typescript-eslint+parser@8.39.1_eslint@9.34.0_jiti@2.5.1___504bceda7a403f00735dcb51d6a813c7/node_modules/eslint-plugin-import/lib/core/importType.js:126:2822)
at computeRank (/home/jailuser/git/node_modules/.pnpm/eslint-plugin-import@2.32.0_@typescript-eslint+parser@8.39.1_eslint@9.34.0_jiti@2.5.1___504bceda7a403f00735dcb51d6a813c7/node_modules/eslint-plugin-import/lib/rules/order.js:529:43)

(import/order)

apps/ui/src/app/changelog/page.tsx

[error] 1-1: Resolve error: EACCES: permission denied, open '/NdjbLSIpnP'
at Object.writeFileSync (node:fs:2409:20)
at l (/home/jailuser/git/node_modules/.pnpm/get-tsconfig@4.10.1/node_modules/get-tsconfig/dist/index.cjs:7:13685)
at createFilesMatcher (/home/jailuser/git/node_modules/.pnpm/get-tsconfig@4.10.1/node_modules/get-tsconfig/dist/index.cjs:7:14437)
at Object.resolve (/home/jailuser/git/node_modules/.pnpm/eslint-import-resolver-typescript@4.4.4_eslint-plugin-import@2.32.0_eslint@9.34.0_jiti@2.5.1_/node_modules/eslint-import-resolver-typescript/lib/index.cjs:298:107)
at withResolver (/home/jailuser/git/node_modules/.pnpm/eslint-module-utils@2.12.1_@typescript-eslint+parser@8.39.1_eslint@9.34.0_jiti@2.5.1__t_0245afd672d291ebf09c60ebb405cf51/node_modules/eslint-module-utils/resolve.js:180:23)
at fullResolve (/home/jailuser/git/node_modules/.pnpm/eslint-module-utils@2.12.1_@typescript-eslint+parser@8.39.1_eslint@9.34.0_jiti@2.5.1__t_0245afd672d291ebf09c60ebb405cf51/node_modules/eslint-module-utils/resolve.js:201:22)
at relative (/home/jailuser/git/node_modules/.pnpm/eslint-module-utils@2.12.1_@typescript-eslint+parser@8.39.1_eslint@9.34.0_jiti@2.5.1__t_0245afd672d291ebf09c60ebb405cf51/node_modules/eslint-module-utils/resolve.js:217:10)
at resolve (/home/jailuser/git/node_modules/.pnpm/eslint-module-utils@2.12.1_@typescript-eslint+parser@8.39.1_eslint@9.34.0_jiti@2.5.1__t_0245afd672d291ebf09c60ebb405cf51/node_modules/eslint-module-utils/resolve.js:233:12)
at resolveImportType (/home/jailuser/git/node_modules/.pnpm/eslint-plugin-import@2.32.0_@typescript-eslint+parser@8.39.1_eslint@9.34.0_jiti@2.5.1___504bceda7a403f00735dcb51d6a813c7/node_modules/eslint-plugin-import/lib/core/importType.js:126:2822)
at computeRank (/home/jailuser/git/node_modules/.pnpm/eslint-plugin-import@2.32.0_@typescript-eslint+parser@8.39.1_eslint@9.34.0_jiti@2.5.1___504bceda7a403f00735dcb51d6a813c7/node_modules/eslint-plugin-import/lib/rules/order.js:529:43)

(import/order)

packages/cache/src/cache.ts

[error] 1-1: Resolve error: EACCES: permission denied, open '/qFyabQvaVN'
at Object.writeFileSync (node:fs:2409:20)
at l (/home/jailuser/git/node_modules/.pnpm/get-tsconfig@4.10.1/node_modules/get-tsconfig/dist/index.cjs:7:13685)
at createFilesMatcher (/home/jailuser/git/node_modules/.pnpm/get-tsconfig@4.10.1/node_modules/get-tsconfig/dist/index.cjs:7:14437)
at Object.resolve (/home/jailuser/git/node_modules/.pnpm/eslint-import-resolver-typescript@4.4.4_eslint-plugin-import@2.32.0_eslint@9.34.0_jiti@2.5.1_/node_modules/eslint-import-resolver-typescript/lib/index.cjs:298:107)
at withResolver (/home/jailuser/git/node_modules/.pnpm/eslint-module-utils@2.12.1_@typescript-eslint+parser@8.39.1_eslint@9.34.0_jiti@2.5.1__t_0245afd672d291ebf09c60ebb405cf51/node_modules/eslint-module-utils/resolve.js:180:23)
at fullResolve (/home/jailuser/git/node_modules/.pnpm/eslint-module-utils@2.12.1_@typescript-eslint+parser@8.39.1_eslint@9.34.0_jiti@2.5.1__t_0245afd672d291ebf09c60ebb405cf51/node_modules/eslint-module-utils/resolve.js:201:22)
at relative (/home/jailuser/git/node_modules/.pnpm/eslint-module-utils@2.12.1_@typescript-eslint+parser@8.39.1_eslint@9.34.0_jiti@2.5.1__t_0245afd672d291ebf09c60ebb405cf51/node_modules/eslint-module-utils/resolve.js:217:10)
at resolve (/home/jailuser/git/node_modules/.pnpm/eslint-module-utils@2.12.1_@typescript-eslint+parser@8.39.1_eslint@9.34.0_jiti@2.5.1__t_0245afd672d291ebf09c60ebb405cf51/node_modules/eslint-module-utils/resolve.js:233:12)
at resolveImportType (/home/jailuser/git/node_modules/.pnpm/eslint-plugin-import@2.32.0_@typescript-eslint+parser@8.39.1_eslint@9.34.0_jiti@2.5.1___504bceda7a403f00735dcb51d6a813c7/node_modules/eslint-plugin-import/lib/core/importType.js:126:2822)
at computeRank (/home/jailuser/git/node_modules/.pnpm/eslint-plugin-import@2.32.0_@typescript-eslint+parser@8.39.1_eslint@9.34.0_jiti@2.5.1___504bceda7a403f00735dcb51d6a813c7/node_modules/eslint-plugin-import/lib/rules/order.js:529:43)

(import/order)


[error] 1-1: Resolve error: EACCES: permission denied, open '/qhMtTvcapn'
at Object.writeFileSync (node:fs:2409:20)
at l (/home/jailuser/git/node_modules/.pnpm/get-tsconfig@4.10.1/node_modules/get-tsconfig/dist/index.cjs:7:13685)
at createFilesMatcher (/home/jailuser/git/node_modules/.pnpm/get-tsconfig@4.10.1/node_modules/get-tsconfig/dist/index.cjs:7:14437)
at Object.resolve (/home/jailuser/git/node_modules/.pnpm/eslint-import-resolver-typescript@4.4.4_eslint-plugin-import@2.32.0_eslint@9.34.0_jiti@2.5.1_/node_modules/eslint-import-resolver-typescript/lib/index.cjs:298:107)
at withResolver (/home/jailuser/git/node_modules/.pnpm/eslint-module-utils@2.12.1_@typescript-eslint+parser@8.39.1_eslint@9.34.0_jiti@2.5.1__t_0245afd672d291ebf09c60ebb405cf51/node_modules/eslint-module-utils/resolve.js:180:23)
at fullResolve (/home/jailuser/git/node_modules/.pnpm/eslint-module-utils@2.12.1_@typescript-eslint+parser@8.39.1_eslint@9.34.0_jiti@2.5.1__t_0245afd672d291ebf09c60ebb405cf51/node_modules/eslint-module-utils/resolve.js:201:22)
at relative (/home/jailuser/git/node_modules/.pnpm/eslint-module-utils@2.12.1_@typescript-eslint+parser@8.39.1_eslint@9.34.0_jiti@2.5.1__t_0245afd672d291ebf09c60ebb405cf51/node_modules/eslint-module-utils/resolve.js:217:10)
at resolve (/home/jailuser/git/node_modules/.pnpm/eslint-module-utils@2.12.1_@typescript-eslint+parser@8.39.1_eslint@9.34.0_jiti@2.5.1__t_0245afd672d291ebf09c60ebb405cf51/node_modules/eslint-module-utils/resolve.js:233:12)
at checkSourceValue (/home/jailuser/git/node_modules/.pnpm/eslint-plugin-import@2.32.0_@typescript-eslint+parser@8.39.1_eslint@9.34.0_jiti@2.5.1___504bceda7a403f00735dcb51d6a813c7/node_modules/eslint-plugin-import/lib/rules/no-useless-path-segments.js:85:53)
at checkSourceValue (/home/jailuser/git/node_modules/.pnpm/eslint-module-utils@2.12.1_@typescript-eslint+parser@8.39.1_eslint@9.34.0_jiti@2.5.1__t_0245afd672d291ebf09c60ebb405cf51/node_modules/eslint-module-utils/moduleVisitor.js:32:5)

(import/no-useless-path-segments)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: e2e / run
  • GitHub Check: build / run
  • GitHub Check: test-build-split (ui)
  • GitHub Check: test-build-split (docs)
  • GitHub Check: test-build-split (api)
  • GitHub Check: test-build-split (worker)
  • GitHub Check: test-build-split (gateway)
  • GitHub Check: test-build-unified
🔇 Additional comments (13)
apps/ui/src/components/models/all-models.tsx (1)

65-66: Import order OK — verify @llmgateway/models resolution & client-safety

  • apps/ui/src/components/models/all-models.tsx (import at lines 65–66): import style looks fine.
  • apps/ui/package.json: @llmgateway/models is present (script showed ✔ Dependency present).
  • Could not locate a package.json named "@llmgateway/models" in the repo — confirm whether that package is published or a workspace package and ensure apps/ui's tsconfig paths / resolver and bundler resolve the alias.
  • Verify @llmgateway/models is browser/client-safe (no node:, fs, path, child_process imports or process.env usage) because this file is a client component.
apps/api/src/routes/keys-provider.ts (1)

5-7: Imports reordered to match eslint-plugin-import — LGTM.

Grouping external modules first and colocating db/model imports is consistent with resolver rules.

apps/ui/src/components/landing/hero.tsx (2)

10-14: Imports consolidated and grouped correctly.

Top-level alias imports placed with the "@/..." group ahead of relative imports; aligns with eslint-plugin-import/order. No duplication left behind.


219-219: Verify Tailwind class "h-10.5".

Search returned no evidence this utility exists in the repo; define it in tailwind.config.js or replace with an explicit/arbitrary value.

File: apps/ui/src/components/landing/hero.tsx (around line 219)

  • className="h-10.5 rounded-xl px-5"
  • className="h-[42px] rounded-xl px-5"
eslint.config.mjs (3)

42-47: Good call enabling no-useless-path-segments.

noUselessIndex: true will clean up noisy index segments.


99-104: Commented rule: decide policy before enabling no-relative-parent-imports.

Enabling this without complete path aliases will break imports. If you want this later, pair it with path aliases (tsconfig paths) and resolver config, then enable behind a short-lived branch flag.


2-2: Plugin import OK — pinned in root package.json

Root package.json lists eslint-plugin-import@2.32.0 and eslint-import-resolver-typescript@4.4.4; workspace package.json files do not include them. Confirm root-only devDeps are intentional for monorepo linting, or add the packages to workspace package.jsons if lint runs per-package.

apps/ui/src/app/blog/[slug]/page.tsx (2)

13-14: Type‑only import style LGTM.

Consistent with TS best practices; keep this paired with @typescript-eslint/consistent-type-imports (separateTypeImports=true).


1-2: Remove inline eslint-disable-next-line import/order; configure ESLint to handle TypeScript + content-collections

  • Update eslint.config.mjs: set import/resolver.typescript.project to ['apps//tsconfig.json','packages//tsconfig.json'] and alwaysTryTypes: true; add 'import/order' with groups ['builtin','external','internal','type','parent','sibling','index'] and pathGroups for 'content-collections' (group: 'external', position: 'before') and '@/**' (group: 'internal', position: 'after'); enable 'import/no-duplicates' with considerSeparateImports: true.
  • Use a writable ESLint cache in CI to avoid EACCES: run eslint --cache --cache-location .eslintcache (or set TMPDIR="$PWD/.tmp/eslint").
  • CI/local lint ran no results because the codeframe formatter is missing; install eslint-formatter-codeframe or run with a supported formatter, then re-run ESLint and paste output to confirm the inline disable can be removed.

Location: apps/ui/src/app/blog/[slug]/page.tsx

apps/ui/src/app/changelog/page.tsx (2)

6-7: Type‑only import separation LGTM.

Matches the chosen style and works well with consistent-type-imports.


1-2: Same: remove per-line disable — configure import/order pathGroups/resolver for content-collections

File: apps/ui/src/app/changelog/page.tsx (lines 1–2). Verification blocked: npx eslint returned "The codeframe formatter is no longer part of core ESLint" and produced no lint output. Install the formatter or run eslint with a supported formatter, then re-run the verification script and attach the ESLint output:
npm install -D eslint-formatter-codeframe
TMPDIR="$PWD/.tmp/eslint" npx eslint apps/ui/src/app/changelog/page.tsx -f codeframe --cache --cache-location .eslintcache

apps/ui/src/app/changelog/[slug]/page.tsx (2)

13-14: Type‑only import style LGTM.


1-2: Remove the inline eslint-disable-next-line import/order; configure import/order instead.

Delete the inline disable and mirror the project's import/order configuration so imports from "content-collections" are ordered deterministically; run ESLint to confirm no violations.

// eslint-disable-next-line import/order
import { allChangelogs } from "content-collections";

Comment thread eslint.config.mjs
Comment on lines +48 to +81
"import/order": [
"error",
{
groups: [
["builtin"],
["external"],
// Internals
["internal", "unknown", "parent", "sibling", "index"],
// Types
["object", "type"],
],
"newlines-between": "always",
alphabetize: { order: "asc", caseInsensitive: true },
warnOnUnassignedImports: true,
pathGroups: [
{
pattern: "^\\u0000",
group: "builtin",
position: "before",
},
{
pattern: "@/**",
group: "internal",
position: "before",
},
{
pattern: "@llmgateway/**",
group: "internal",
position: "before",
},
],
pathGroupsExcludedImportTypes: ["builtin", "type"],
},
],

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

warnOnUnassignedImports will flag import "dotenv/config" introduced in this PR.

With import/order at "error", this will fail CI on side‑effect imports. Either disable that warning or handle via an allowlist.

Apply this change and add an explicit allow for the env preload:

 					"newlines-between": "always",
 					alphabetize: { order: "asc", caseInsensitive: true },
-					warnOnUnassignedImports: true,
+					warnOnUnassignedImports: false,
 					pathGroups: [
+						{
+							pattern: "dotenv/config",
+							group: "builtin",
+							position: "before",
+						},
 						{
 							pattern: "^\\u0000",
 							group: "builtin",
 							position: "before",
 						},

Optionally enforce the “no require/dynamic import” learning:

 		rules: {
+			"import/no-dynamic-require": "error",
+			"import/no-amd": "error",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"import/order": [
"error",
{
groups: [
["builtin"],
["external"],
// Internals
["internal", "unknown", "parent", "sibling", "index"],
// Types
["object", "type"],
],
"newlines-between": "always",
alphabetize: { order: "asc", caseInsensitive: true },
warnOnUnassignedImports: true,
pathGroups: [
{
pattern: "^\\u0000",
group: "builtin",
position: "before",
},
{
pattern: "@/**",
group: "internal",
position: "before",
},
{
pattern: "@llmgateway/**",
group: "internal",
position: "before",
},
],
pathGroupsExcludedImportTypes: ["builtin", "type"],
},
],
"import/order": [
"error",
{
groups: [
["builtin"],
["external"],
// Internals
["internal", "unknown", "parent", "sibling", "index"],
// Types
["object", "type"],
],
"newlines-between": "always",
alphabetize: { order: "asc", caseInsensitive: true },
warnOnUnassignedImports: false,
pathGroups: [
{
pattern: "dotenv/config",
group: "builtin",
position: "before",
},
{
pattern: "^\\u0000",
group: "builtin",
position: "before",
},
{
pattern: "@/**",
group: "internal",
position: "before",
},
{
pattern: "@llmgateway/**",
group: "internal",
position: "before",
},
],
pathGroupsExcludedImportTypes: ["builtin", "type"],
},
],
🤖 Prompt for AI Agents
In eslint.config.mjs around lines 48–81, the import/order option
warnOnUnassignedImports will flag the side‑effect preload import (import
"dotenv/config") added in this PR; either set warnOnUnassignedImports to false
or add an explicit allowlist for the env preload (e.g., add a pathGroups entry
that matches "dotenv/config" and treats it as an internal/builtin/preferred
group so it’s exempted from the unassigned import warning), and optionally add a
separate rule to forbid dynamic require/imports if you want to enforce no
runtime imports.

@@ -1,6 +1,7 @@
import crypto from "crypto";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Verify new eslint import resolver config; resolver throws EACCES in analysis.

Static analysis shows eslint-import-resolver-typescript hitting EACCES during resolution, which can fail CI. Likely a misconfigured project glob resolving to / in a mono‑repo.

Run to debug resolver paths:

If it points to root or an invalid path, update flat config:

// eslint.config.mjs (settings snippet)
export default [
  {
    settings: {
      'import/resolver': {
        typescript: {
          project: ['tsconfig.json', 'packages/*/tsconfig.json'],
          alwaysTryTypes: true,
        },
      },
    },
  },
];

Also ensure import/order and import/no-useless-path-segments are configured with appropriate groups for "type" and monorepo path aliases.


🏁 Script executed:

#!/bin/bash
# Inspect resolver behavior on this file
pnpm exec eslint packages/cache/src/cache.ts --debug 2>&1 | rg -n "import/resolver|typescript|project|tsconfig" -C2

Length of output: 4858


Fix eslint-import-resolver-typescript EACCES by narrowing typescript.project globs

ESLint debug shows get-tsconfig failing to write temp files to / (Resolve error: EACCES open '/RzIpQSnmkq' and '/jLdeFbbUqx') and "Multiple projects found" — the resolver's project glob is matching too broadly and causing permission errors.

  • Update ESLint resolver settings (e.g., eslint.config.mjs) to explicitly list tsconfig files instead of a broad glob. Example: settings['import/resolver'] = { typescript: { project: ['tsconfig.json','packages/*/tsconfig.json'], alwaysTryTypes: true } }.
  • Or switch to a single root tsconfig with references, or restrict the resolver/parser (avoid scanning /) so get-tsconfig cannot attempt writes to root.
  • Re-run: pnpm exec eslint packages/cache/src/cache.ts --debug and confirm EACCES is resolved.
🧰 Tools
🪛 ESLint

[error] 1-1: Resolve error: EACCES: permission denied, open '/qFyabQvaVN'
at Object.writeFileSync (node:fs:2409:20)
at l (/home/jailuser/git/node_modules/.pnpm/get-tsconfig@4.10.1/node_modules/get-tsconfig/dist/index.cjs:7:13685)
at createFilesMatcher (/home/jailuser/git/node_modules/.pnpm/get-tsconfig@4.10.1/node_modules/get-tsconfig/dist/index.cjs:7:14437)
at Object.resolve (/home/jailuser/git/node_modules/.pnpm/eslint-import-resolver-typescript@4.4.4_eslint-plugin-import@2.32.0_eslint@9.34.0_jiti@2.5.1_/node_modules/eslint-import-resolver-typescript/lib/index.cjs:298:107)
at withResolver (/home/jailuser/git/node_modules/.pnpm/eslint-module-utils@2.12.1_@typescript-eslint+parser@8.39.1_eslint@9.34.0_jiti@2.5.1__t_0245afd672d291ebf09c60ebb405cf51/node_modules/eslint-module-utils/resolve.js:180:23)
at fullResolve (/home/jailuser/git/node_modules/.pnpm/eslint-module-utils@2.12.1_@typescript-eslint+parser@8.39.1_eslint@9.34.0_jiti@2.5.1__t_0245afd672d291ebf09c60ebb405cf51/node_modules/eslint-module-utils/resolve.js:201:22)
at relative (/home/jailuser/git/node_modules/.pnpm/eslint-module-utils@2.12.1_@typescript-eslint+parser@8.39.1_eslint@9.34.0_jiti@2.5.1__t_0245afd672d291ebf09c60ebb405cf51/node_modules/eslint-module-utils/resolve.js:217:10)
at resolve (/home/jailuser/git/node_modules/.pnpm/eslint-module-utils@2.12.1_@typescript-eslint+parser@8.39.1_eslint@9.34.0_jiti@2.5.1__t_0245afd672d291ebf09c60ebb405cf51/node_modules/eslint-module-utils/resolve.js:233:12)
at resolveImportType (/home/jailuser/git/node_modules/.pnpm/eslint-plugin-import@2.32.0_@typescript-eslint+parser@8.39.1_eslint@9.34.0_jiti@2.5.1___504bceda7a403f00735dcb51d6a813c7/node_modules/eslint-plugin-import/lib/core/importType.js:126:2822)
at computeRank (/home/jailuser/git/node_modules/.pnpm/eslint-plugin-import@2.32.0_@typescript-eslint+parser@8.39.1_eslint@9.34.0_jiti@2.5.1___504bceda7a403f00735dcb51d6a813c7/node_modules/eslint-plugin-import/lib/rules/order.js:529:43)

(import/order)


[error] 1-1: Resolve error: EACCES: permission denied, open '/qhMtTvcapn'
at Object.writeFileSync (node:fs:2409:20)
at l (/home/jailuser/git/node_modules/.pnpm/get-tsconfig@4.10.1/node_modules/get-tsconfig/dist/index.cjs:7:13685)
at createFilesMatcher (/home/jailuser/git/node_modules/.pnpm/get-tsconfig@4.10.1/node_modules/get-tsconfig/dist/index.cjs:7:14437)
at Object.resolve (/home/jailuser/git/node_modules/.pnpm/eslint-import-resolver-typescript@4.4.4_eslint-plugin-import@2.32.0_eslint@9.34.0_jiti@2.5.1_/node_modules/eslint-import-resolver-typescript/lib/index.cjs:298:107)
at withResolver (/home/jailuser/git/node_modules/.pnpm/eslint-module-utils@2.12.1_@typescript-eslint+parser@8.39.1_eslint@9.34.0_jiti@2.5.1__t_0245afd672d291ebf09c60ebb405cf51/node_modules/eslint-module-utils/resolve.js:180:23)
at fullResolve (/home/jailuser/git/node_modules/.pnpm/eslint-module-utils@2.12.1_@typescript-eslint+parser@8.39.1_eslint@9.34.0_jiti@2.5.1__t_0245afd672d291ebf09c60ebb405cf51/node_modules/eslint-module-utils/resolve.js:201:22)
at relative (/home/jailuser/git/node_modules/.pnpm/eslint-module-utils@2.12.1_@typescript-eslint+parser@8.39.1_eslint@9.34.0_jiti@2.5.1__t_0245afd672d291ebf09c60ebb405cf51/node_modules/eslint-module-utils/resolve.js:217:10)
at resolve (/home/jailuser/git/node_modules/.pnpm/eslint-module-utils@2.12.1_@typescript-eslint+parser@8.39.1_eslint@9.34.0_jiti@2.5.1__t_0245afd672d291ebf09c60ebb405cf51/node_modules/eslint-module-utils/resolve.js:233:12)
at checkSourceValue (/home/jailuser/git/node_modules/.pnpm/eslint-plugin-import@2.32.0_@typescript-eslint+parser@8.39.1_eslint@9.34.0_jiti@2.5.1___504bceda7a403f00735dcb51d6a813c7/node_modules/eslint-plugin-import/lib/rules/no-useless-path-segments.js:85:53)
at checkSourceValue (/home/jailuser/git/node_modules/.pnpm/eslint-module-utils@2.12.1_@typescript-eslint+parser@8.39.1_eslint@9.34.0_jiti@2.5.1__t_0245afd672d291ebf09c60ebb405cf51/node_modules/eslint-module-utils/moduleVisitor.js:32:5)

(import/no-useless-path-segments)

🤖 Prompt for AI Agents
In packages/cache/src/cache.ts around lines 1 to 1, ESLint's typescript resolver
is matching too broadly causing get-tsconfig to try writing temp files to root
(EACCES); fix the ESLint resolver config (e.g., eslint.config.mjs) to replace
the broad project glob with an explicit list of tsconfig paths (for example
['tsconfig.json','packages/*/tsconfig.json']) or point to a single root tsconfig
with references, and set alwaysTryTypes to true; alternatively restrict
parser/resolver scanning so it cannot traverse /; after updating, re-run pnpm
exec eslint packages/cache/src/cache.ts --debug to confirm the EACCES is
resolved.

@steebchen
steebchen merged commit 9570c10 into main Sep 13, 2025
24 of 25 checks passed
@steebchen
steebchen deleted the feat/lint branch September 13, 2025 19:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant