chore: refine oxlint rules and fix resulting fallout

This commit is contained in:
2026-09-03 16:14:20 -04:00
parent 5bb49738b0
commit e60350f0e0
94 changed files with 357 additions and 201 deletions
-4
View File
@@ -37,10 +37,6 @@ jobs:
continue-on-error: true
run: pnpm lint
- name: Run typecheck
continue-on-error: true
run: pnpm check-types
- name: Run tests with coverage report
continue-on-error: true
run: pnpm test:coverage
+60 -2
View File
@@ -1,11 +1,48 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": ["oxc", "eslint", "typescript", "react", "nextjs", "import", "unicorn", "vitest"],
"plugins": [
"oxc",
"eslint",
"typescript",
"unicorn",
"import",
"node",
"promise",
"jsdoc",
"vitest",
"react",
"jsx-a11y",
"nextjs"
],
"categories": {
"correctness": "error",
"suspicious": "warn",
"perf": "warn"
},
"options": {
"typeAware": true,
"typeCheck": true,
"reportUnusedDisableDirectives": "error"
},
"env": {
"builtin": true,
"node": true
},
"settings": {
"next": {
"rootDir": "apps/web"
},
"react": {
"version": "19.2.8",
"linkComponents": [{ "name": "Link", "attribute": "href" }]
},
"jsx-a11y": {
"components": {
"Link": "a",
"Button": "button"
}
}
},
"rules": {
"import/no-named-as-default-member": "off",
"import/no-unassigned-import": "off",
@@ -21,7 +58,28 @@
"unicorn/consistent-function-scoping": "off",
"unicorn/filename-case": "off",
"unicorn/no-array-sort": "off",
"unicorn/no-null": "off"
"unicorn/no-null": "off",
"typescript/no-unsafe-type-assertion": "off",
"typescript/consistent-return": "off",
"typescript/no-unnecessary-type-conversion": "off",
"typescript/no-unnecessary-type-parameters": "off",
"jsdoc/check-tag-names": "off",
"jsx-a11y/prefer-tag-over-role": "off",
"jsx-a11y/control-has-associated-label": "off",
"jsx-a11y/no-autofocus": "off",
"react/jsx-no-target-blank": "off",
"import/no-amd": "error",
"import/no-cycle": "error",
"import/no-duplicates": "error",
"node/no-new-require": "error",
"node/no-path-concat": "error",
"react/button-has-type": "error",
"react/rules-of-hooks": "error",
"typescript/no-misused-promises": ["error", { "checksVoidReturn": { "attributes": false } }],
"typescript/only-throw-error": "error",
"typescript/switch-exhaustiveness-check": "error",
"unicorn/no-abusive-eslint-disable": "error",
"unicorn/prefer-node-protocol": "error"
},
"overrides": [
{
+4 -6
View File
@@ -2,14 +2,13 @@
## Pre-Commit Checklist
**CRITICAL:** Before declaring victory on any task and before committing to git, the following four commands must pass with NO WARNINGS:
**CRITICAL:** Before declaring victory on any task and before committing to git, the following three commands must pass with NO WARNINGS:
1. `pnpm lint` — Must pass with zero warnings
2. `pnpm fmt:check` — Must pass with zero warnings
3. `pnpm check-types` — Must pass with zero warnings
4. `pnpm test` — Must pass with zero warnings
3. `pnpm test` — Must pass with zero warnings
Do not proceed with commits until all four checks are clean.
Do not proceed with commits until all three checks are clean.
<!-- intent-skills:start -->
## Skill Loading
@@ -28,11 +27,10 @@ Before editing files for a substantial task:
- `pnpm dev` — Start Next.js dev server at http://localhost:3000
- `pnpm build` — Compile production bundle
- `pnpm check-types` — Run `tsc --noEmit` for type diagnostics
### Linting & Formatting
- `pnpm lint` — Run oxlint lint
- `pnpm lint` — Run oxlint (includes type-aware linting and type checking)
- `pnpm fmt` — Apply oxfmt formatting
### Testing
+20 -1
View File
@@ -1,4 +1,23 @@
{
"$schema": "../../node_modules/oxlint/configuration_schema.json",
"extends": ["../../.oxlintrc.json"]
"extends": ["../../.oxlintrc.json"],
"env": {
"browser": true,
"node": true
},
"settings": {
"next": {
"rootDir": "."
},
"react": {
"version": "19.2.8",
"linkComponents": [{ "name": "Link", "attribute": "href" }]
},
"jsx-a11y": {
"components": {
"Link": "a",
"Button": "button"
}
}
}
}
@@ -23,10 +23,10 @@ export function LoginModalClient() {
function AuthorizedLoginContent({ onNavigate }: { onNavigate: () => void }) {
const { data: session } = useSession();
const { replace } = useRouter();
const router = useRouter();
if (session?.user) {
replace("/dashboard");
router.replace("/dashboard");
}
return <LoginContent onNavigate={onNavigate} />;
+7 -1
View File
@@ -86,7 +86,13 @@ export async function GET(
return new NextResponse("Avatar host not allowed", { status: 403 });
case "size_exceeded":
return new NextResponse("Avatar too large", { status: 413 });
default:
case "connection_error":
case "dns_error":
case "invalid_response":
case "invalid_url":
case "protocol_not_allowed":
case "redirect_limit":
case "timeout":
return new NextResponse("Failed to fetch avatar", { status: 502 });
}
}
+2 -3
View File
@@ -87,8 +87,7 @@ export async function POST(
// Only treat as cache hit if we have a definitive result:
// - url is present (string), OR
// - url is null but marked as permanently not found
const isDefinitiveResult =
cachedScreenshot.url !== null || cachedScreenshot.notFound === true;
const isDefinitiveResult = cachedScreenshot.url !== null || cachedScreenshot.notFound;
if (isDefinitiveResult) {
// Check current block status dynamically
@@ -186,7 +185,7 @@ export async function GET(
cached: false,
success: result.success,
data: result.data,
...(result.success === false && { error: result.error }),
...(!result.success && { error: result.error }),
} as ScreenshotStatusResponse,
{ headers: rateLimit.headers },
);
+5 -4
View File
@@ -11,10 +11,11 @@ const handler = async (req: Request) => {
req,
router: appRouter,
createContext: () => ctx,
onError: async ({ path, error }) => {
// Use logger for unhandled errors
const { logger } = await import("@domainstack/logger");
logger.error({ err: error, source: "trpc", path });
onError: ({ path, error }) => {
void (async () => {
const { logger } = await import("@domainstack/logger");
logger.error({ err: error, source: "trpc", path });
})();
},
});
};
@@ -51,7 +51,7 @@ export const PromptInput = ({ className, onSubmit, children, ...props }: PromptI
form.reset();
try {
onSubmit({ text }, event);
void onSubmit({ text }, event);
} catch (error) {
console.warn("Message submission failed:", error);
}
+1 -1
View File
@@ -223,7 +223,7 @@ function CloudChatSession({
(msgParams: { text: string }) => {
const text = msgParams.text.trim();
if (!text) return;
chat.sendMessage({ text });
void chat.sendMessage({ text });
onActiveChange(true);
},
[chat, onActiveChange],
@@ -43,7 +43,8 @@ function getStatusLabel(status: BrowserAIStatus, downloadProgress?: number): str
return `Downloading… ${Math.round((downloadProgress ?? 0) * 100)}%`;
case "error":
return "Error";
default:
case "ready":
case "unavailable":
return "";
}
}
@@ -125,7 +125,7 @@ export function AddDomainContent({
<div className="flex flex-col gap-2">
<Button
onClick={() => void refetchSubscription()}
onClick={() => refetchSubscription()}
disabled={isSubscriptionLoading}
className="w-full"
>
@@ -49,7 +49,7 @@ type DashboardBannerProps = {
};
export function DashboardBanner({
variant = "info",
variant,
icon: Icon,
title,
description,
@@ -291,7 +291,7 @@ export function DashboardClient() {
const handleRetry = useCallback(() => {
refetchSubscription();
domainsQuery.refetch();
void domainsQuery.refetch();
}, [refetchSubscription, domainsQuery]);
if (isLoading) {
@@ -43,7 +43,7 @@ function domainNames() {
}
function getFilterTrigger(name: RegExp) {
return screen.getAllByRole("combobox", { name })[0]!;
return screen.getAllByRole("combobox", { name })[0];
}
async function waitForCatalog() {
@@ -320,7 +320,7 @@ describe("dashboard shell", () => {
await user.click(screen.getByRole("button", { name: "Grid view" }));
// "s" still matches all 12 sites, so clamp alone would leave page 2 empty of site00.
await user.type(screen.getAllByRole("textbox", { name: "Search domains" })[0]!, "s");
await user.type(screen.getAllByRole("textbox", { name: "Search domains" })[0], "s");
await waitFor(() => {
expect(domainNames()).toEqual(expect.arrayContaining(["site00.com", "site11.com"]));
});
@@ -383,7 +383,7 @@ describe("dashboard shell", () => {
expect(screen.getByRole("button", { name: /^Registrar$/ })).toBeInTheDocument();
await user.click(screen.getAllByRole("button", { name: "Toggle columns" })[0]!);
await user.click(screen.getAllByRole("button", { name: "Toggle columns" })[0]);
await user.click(await screen.findByRole("menuitemcheckbox", { name: /Registrar/ }));
await waitFor(() => {
@@ -20,7 +20,8 @@ export function DashboardTableColumnMenu() {
const columnVisibility = useDashboardColumnVisibility();
const setColumnVisibility = usePreferencesStore((s) => s.setColumnVisibility);
const isColumnVisible = (columnId: string) => columnVisibility[columnId] !== false;
// Missing keys default to visible — `{}` means show every column.
const isColumnVisible = (columnId: string) => columnVisibility[columnId] ?? true;
const hiddenCount = HIDEABLE_COLUMNS.filter((column) => !isColumnVisible(column.id)).length;
@@ -51,7 +51,7 @@ export function DashboardTable({ domains }: DashboardTableProps) {
(updater: SortingState | ((old: SortingState) => SortingState)) => {
startSortTransition(() => {
const newSorting = typeof updater === "function" ? updater(sorting) : updater;
setSortParam(serializeSortState(newSorting));
void setSortParam(serializeSortState(newSorting));
resetPage();
});
},
@@ -87,7 +87,7 @@ function getStatusConfig(status: HealthStatus): {
colorClass: "border-danger-border bg-danger/20 text-danger-foreground",
icon: IconAlertOctagon,
};
default:
case "unknown":
return {
label: "Unknown",
colorClass: "border-muted-border bg-muted/20 text-muted-foreground",
@@ -118,7 +118,7 @@ export function getHealthAccent(
return "orange";
case "critical":
return "red";
default:
case "unknown":
return "slate";
}
}
@@ -14,7 +14,7 @@ const EXPLICIT_COLUMNS = ["select", "domainName", "verified", "actions"];
type UnverifiedTableRowProps = {
rowId: string;
cells: Cell<DashboardTableFeatures, TrackedDomainWithDetails, unknown>[];
cells: Cell<DashboardTableFeatures, TrackedDomainWithDetails>[];
original: TrackedDomainWithDetails;
};
@@ -9,7 +9,7 @@ import { cn } from "@domainstack/ui/utils";
type VerifiedTableRowProps = {
rowId: string;
cells: Cell<DashboardTableFeatures, TrackedDomainWithDetails, unknown>[];
cells: Cell<DashboardTableFeatures, TrackedDomainWithDetails>[];
original: TrackedDomainWithDetails;
};
+1 -3
View File
@@ -49,9 +49,7 @@ export function ExportButton({ domain, enabled = true }: { domain: string; enabl
try {
const exportData: Record<string, unknown> = {};
for (const key of Object.keys(queryKeys)) {
const response = queryClient.getQueryData(queryKeys[key as keyof typeof queryKeys]) as
| { success?: boolean; data?: unknown }
| undefined;
const response = queryClient.getQueryData(queryKeys[key as keyof typeof queryKeys]);
if (response?.data) {
exportData[key] = response.data;
@@ -171,6 +171,8 @@ export function RawDataDialog({ domain, format, data, serverName, serverUrl }: R
</DialogTitle>
</DialogHeader>
<div
// Scrollable region needs a tab stop so keyboard users can pan the raw dump.
// oxlint-disable-next-line jsx-a11y/no-noninteractive-tabindex
tabIndex={0}
className="min-h-0 flex-1 overflow-auto overscroll-contain bg-popover/10 outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1"
aria-label={`Raw ${format} data`}
+1 -1
View File
@@ -148,7 +148,7 @@ export function DomainReportClient({ domain }: { domain: string }) {
error: registrationError,
} = useQuery(trpc.domain.getRegistration.queryOptions({ domain }, staticQueryOptions));
const domainId = registration?.data?.domainId;
const lookupFailed = Boolean(registration && registration.success === false);
const lookupFailed = Boolean(registration && !registration.success);
const isRegistered = registration?.success === true && registration.data?.isRegistered === true;
const isUnregistered =
registration?.success === true && registration.data?.isRegistered === false;
+12 -3
View File
@@ -39,7 +39,10 @@ function parseStartResponse(raw: unknown): ScreenshotStartResponse {
const obj = raw as Record<string, unknown>;
if ("error" in obj && !("status" in obj)) {
return { status: "error", error: String(obj.error) };
return {
status: "error",
error: typeof obj.error === "string" ? obj.error : "Invalid response",
};
}
if (obj.status === "running" && typeof obj.runId === "string") {
@@ -68,7 +71,10 @@ function parseStatusResponse(raw: unknown): ScreenshotStatusResponse {
const obj = raw as Record<string, unknown>;
if ("error" in obj && !("status" in obj)) {
return { status: "error", error: String(obj.error) };
return {
status: "error",
error: typeof obj.error === "string" ? obj.error : "Invalid response",
};
}
if (obj.status === "running") {
@@ -76,7 +82,10 @@ function parseStatusResponse(raw: unknown): ScreenshotStatusResponse {
}
if (obj.status === "failed") {
return { status: "failed", error: String(obj.error ?? "Workflow failed") };
return {
status: "failed",
error: typeof obj.error === "string" ? obj.error : "Workflow failed",
};
}
if (obj.status === "completed" && obj.data) {
@@ -195,7 +195,7 @@ export function RobotsSummary({
<span>robots.txt</span>
<IconExternalLink className="relative bottom-px inline-flex size-3" aria-hidden />
</a>
<PillCount count={(counts.allows + counts.disallows) as number} color="blue" />
<PillCount count={counts.allows + counts.disallows} color="blue" />
</div>
<div className="space-y-4">
@@ -244,7 +244,7 @@ export function RobotsSummary({
<ToggleGroupItem value="all" className="h-full">
<IconCircleHalf2 className="size-3.5 text-accent-blue" aria-hidden />
<span className="text-[13px]">All</span>
<PillCount count={(counts.allows + counts.disallows) as number} color="slate" />
<PillCount count={counts.allows + counts.disallows} color="slate" />
</ToggleGroupItem>
<ToggleGroupItem value="allow" className="h-full">
<IconCircleCheck className="size-3.5 text-accent-green" aria-hidden />
@@ -165,6 +165,8 @@ export function AppFooter() {
size="lg"
nativeButton={false}
render={
// Bookmarklet drag target; href is filled in on mount.
// oxlint-disable-next-line jsx-a11y/anchor-is-valid
<a ref={hrefScript} href="#">
<IconWorld />
Inspect Domain
+1 -1
View File
@@ -37,7 +37,7 @@ export function UserMenu() {
const { user } = session;
const avatarUrl = `/api/avatar/${user.id}`;
const { props: imageProps } = getImageProps({
src: avatarUrl as string,
src: avatarUrl,
alt: user.name ?? "User avatar",
width: 32,
height: 32,
@@ -7,7 +7,7 @@ import { HeaderSearchClient } from "./header-search-client";
const nav = vi.hoisted(() => ({
push: vi.fn<(href: string) => void | Promise<void>>(),
params: { domain: "Test.INVALID" as string | undefined },
params: { domain: "Test.INVALID" },
}));
vi.mock("@/hooks/use-router", () => ({
@@ -53,7 +53,7 @@ describe("HeaderSearch", () => {
});
it("does nothing on invalid domain", async () => {
nav.params = { domain: "invalid domain" } as { domain: string };
nav.params = { domain: "invalid domain" };
render(<HeaderSearchClient />);
const input = screen.getByLabelText(/Search any domain/i);
await userEvent.type(input, "{Enter}");
@@ -85,7 +85,7 @@ describe("DomainSearch (form variant)", () => {
await userEvent.type(input, "test.invalid{Enter}");
expect(nav.push).toHaveBeenCalledWith("/test.invalid");
// Input and button should be disabled while loading/submitting
expect((screen.getByLabelText(/Search any domain/i) as HTMLInputElement).disabled).toBe(true);
expect(screen.getByLabelText(/Search any domain/i)).toBeDisabled();
// Submit button shows a loading spinner with accessible name "Loading"
expect(screen.getByRole("button", { name: /loading/i })).toBeDisabled();
@@ -146,8 +146,8 @@ describe("DomainSearch (form variant)", () => {
rerender(<SearchClient variant="lg" />);
// Wait for input to reflect the triggered domain (async due to useEffect)
const input = (await screen.findByLabelText(/Search any domain/i)) as HTMLInputElement;
expect(input.value).toBe("test.invalid");
const input = await screen.findByLabelText(/Search any domain/i);
expect(input).toHaveValue("test.invalid");
// Wait for navigation and store clear to be triggered
await waitFor(() => {
@@ -181,8 +181,8 @@ describe("DomainSearch (header variant)", () => {
render(<SearchClient variant="sm" />);
const input = screen.getByLabelText(/Search any domain/i) as HTMLInputElement;
expect(input.placeholder).toBe("Search any domain\u2026");
const input = screen.getByLabelText(/Search any domain/i);
expect(input).toHaveAttribute("placeholder", "Search any domain\u2026");
});
it("shows short placeholder on mobile screens", async () => {
@@ -190,8 +190,8 @@ describe("DomainSearch (header variant)", () => {
render(<SearchClient variant="sm" />);
const input = screen.getByLabelText(/Search any domain/i) as HTMLInputElement;
expect(input.placeholder).toBe("Search\u2026");
const input = screen.getByLabelText(/Search any domain/i);
expect(input).toHaveAttribute("placeholder", "Search\u2026");
});
it("updates placeholder when window is resized", async () => {
@@ -200,15 +200,15 @@ describe("DomainSearch (header variant)", () => {
const { rerender } = render(<SearchClient variant="sm" />);
// Verify desktop placeholder
let input = screen.getByLabelText(/Search any domain/i) as HTMLInputElement;
expect(input.placeholder).toBe("Search any domain\u2026");
let input = screen.getByLabelText(/Search any domain/i);
expect(input).toHaveAttribute("placeholder", "Search any domain\u2026");
// Simulate resize to mobile
useIsMobile.mockReturnValue(true);
rerender(<SearchClient variant="sm" />);
// Verify mobile placeholder
input = screen.getByLabelText(/Search any domain/i) as HTMLInputElement;
expect(input.placeholder).toBe("Search\u2026");
input = screen.getByLabelText(/Search any domain/i);
expect(input).toHaveAttribute("placeholder", "Search\u2026");
});
});
@@ -78,7 +78,7 @@ export function NotificationMatrix({
<div className="flex w-14 items-center justify-center py-1">
<Checkbox
checked={pref.inApp}
onCheckedChange={(checked) => onToggle(category, "inApp", checked === true)}
onCheckedChange={(checked) => onToggle(category, "inApp", checked)}
disabled={disabled}
aria-label={`Web notifications for ${info.label}`}
/>
@@ -86,7 +86,7 @@ export function NotificationMatrix({
<div className="flex w-14 items-center justify-center py-1">
<Checkbox
checked={pref.email}
onCheckedChange={(checked) => onToggle(category, "email", checked === true)}
onCheckedChange={(checked) => onToggle(category, "email", checked)}
disabled={disabled}
aria-label={`Email notifications for ${info.label}`}
/>
+3 -3
View File
@@ -729,9 +729,9 @@ function MapControls({
const container = map?.getContainer();
if (!container) return;
if (document.fullscreenElement) {
document.exitFullscreen();
void document.exitFullscreen();
} else {
container.requestFullscreen();
void container.requestFullscreen();
}
}, [map]);
@@ -1007,7 +1007,7 @@ function MapRoute({
const source = map.getSource(sourceId) as GeoJSONSource;
if (source) {
source.setData({
void source.setData({
type: "Feature",
properties: {},
geometry: { type: "LineString", coordinates },
+1 -1
View File
@@ -16,7 +16,7 @@ const Toaster = ({ ...props }: ToasterProps) => {
return (
<Sonner
theme={theme as ToasterProps["theme"]}
theme={theme}
className="toaster group"
icons={{
success: <IconCircleCheck className="size-4" />,
+9 -9
View File
@@ -151,7 +151,7 @@ export function useDashboardFilters(
const setSearch = useCallback(
(value: string) => {
startTransition(() => {
setFilters({ search: value || null, domainId: null });
void setFilters({ search: value || null, domainId: null });
});
},
[setFilters],
@@ -160,7 +160,7 @@ export function useDashboardFilters(
const setStatus = useCallback(
(values: StatusFilter[]) => {
startTransition(() => {
setFilters({
void setFilters({
status: values.length > 0 ? values : null,
domainId: null,
});
@@ -172,7 +172,7 @@ export function useDashboardFilters(
const setHealth = useCallback(
(values: HealthFilter[]) => {
startTransition(() => {
setFilters({
void setFilters({
health: values.length > 0 ? values : null,
domainId: null,
});
@@ -184,7 +184,7 @@ export function useDashboardFilters(
const setTlds = useCallback(
(values: string[]) => {
startTransition(() => {
setFilters({ tlds: values.length > 0 ? values : null, domainId: null });
void setFilters({ tlds: values.length > 0 ? values : null, domainId: null });
});
},
[setFilters],
@@ -193,7 +193,7 @@ export function useDashboardFilters(
const setProviders = useCallback(
(values: string[]) => {
startTransition(() => {
setFilters({
void setFilters({
providers: values.length > 0 ? values : null,
domainId: null,
});
@@ -204,7 +204,7 @@ export function useDashboardFilters(
const clearFilters = useCallback(() => {
startTransition(() => {
setFilters({
void setFilters({
search: null,
status: null,
health: null,
@@ -219,9 +219,9 @@ export function useDashboardFilters(
(filter: HealthFilter | "pending") => {
startTransition(() => {
if (filter === "pending") {
setFilters({ status: ["pending"], health: null, domainId: null });
void setFilters({ status: ["pending"], health: null, domainId: null });
} else {
setFilters({ status: null, health: [filter], domainId: null });
void setFilters({ status: null, health: [filter], domainId: null });
}
});
},
@@ -230,7 +230,7 @@ export function useDashboardFilters(
const clearDomainId = useCallback(() => {
startTransition(() => {
setFilters({ domainId: null });
void setFilters({ domainId: null });
});
}, [setFilters]);
+12 -21
View File
@@ -135,9 +135,7 @@ export function useDashboardMutations(): UseDashboardMutationsReturn {
const previousSubscription =
queryClient.getQueryData<SubscriptionData>(subscriptionQueryKey);
const { active, archived } = affectedCounts(previousDomains as [unknown, unknown][], [
trackedDomainId,
]);
const { active, archived } = affectedCounts(previousDomains, [trackedDomainId]);
queryClient.setQueriesData(domainsFilter, (old: DomainsData) =>
old?.filter((d) => d.id !== trackedDomainId),
@@ -155,7 +153,7 @@ export function useDashboardMutations(): UseDashboardMutationsReturn {
});
return {
previousDomains: previousDomains as [unknown, unknown][],
previousDomains,
previousSubscription,
};
},
@@ -187,9 +185,7 @@ export function useDashboardMutations(): UseDashboardMutationsReturn {
const previousSubscription =
queryClient.getQueryData<SubscriptionData>(subscriptionQueryKey);
const { active: toArchive } = affectedCounts(previousDomains as [unknown, unknown][], [
trackedDomainId,
]);
const { active: toArchive } = affectedCounts(previousDomains, [trackedDomainId]);
queryClient.setQueriesData(domainsFilter, (old: DomainsData) =>
old?.map((d) => (d.id === trackedDomainId ? { ...d, archivedAt: new Date() } : d)),
@@ -206,7 +202,7 @@ export function useDashboardMutations(): UseDashboardMutationsReturn {
});
return {
previousDomains: previousDomains as [unknown, unknown][],
previousDomains,
previousSubscription,
};
},
@@ -238,9 +234,7 @@ export function useDashboardMutations(): UseDashboardMutationsReturn {
const previousSubscription =
queryClient.getQueryData<SubscriptionData>(subscriptionQueryKey);
const { archived: toActivate } = affectedCounts(previousDomains as [unknown, unknown][], [
trackedDomainId,
]);
const { archived: toActivate } = affectedCounts(previousDomains, [trackedDomainId]);
queryClient.setQueriesData(domainsFilter, (old: DomainsData) =>
old?.map((d) => (d.id === trackedDomainId ? { ...d, archivedAt: null } : d)),
@@ -257,7 +251,7 @@ export function useDashboardMutations(): UseDashboardMutationsReturn {
});
return {
previousDomains: previousDomains as [unknown, unknown][],
previousDomains,
previousSubscription,
};
},
@@ -290,7 +284,7 @@ export function useDashboardMutations(): UseDashboardMutationsReturn {
old?.map((d) => (d.id === trackedDomainId ? { ...d, muted } : d)),
);
return { previousDomains: previousDomains as [unknown, unknown][] };
return { previousDomains };
},
onError: (_err, _vars, context: { previousDomains: [unknown, unknown][] } | undefined) => {
if (context?.previousDomains) {
@@ -318,10 +312,7 @@ export function useDashboardMutations(): UseDashboardMutationsReturn {
queryClient.getQueryData<SubscriptionData>(subscriptionQueryKey);
const idsSet = new Set(trackedDomainIds);
const { active: archiveCount } = affectedCounts(
previousDomains as [unknown, unknown][],
idsSet,
);
const { active: archiveCount } = affectedCounts(previousDomains, idsSet);
queryClient.setQueriesData(domainsFilter, (old: DomainsData) =>
old?.map((d) =>
@@ -340,7 +331,7 @@ export function useDashboardMutations(): UseDashboardMutationsReturn {
});
return {
previousDomains: previousDomains as [unknown, unknown][],
previousDomains,
previousSubscription,
};
},
@@ -373,7 +364,7 @@ export function useDashboardMutations(): UseDashboardMutationsReturn {
const idsSet = new Set(trackedDomainIds);
const { active: activeDeleted, archived: archivedDeleted } = affectedCounts(
previousDomains as [unknown, unknown][],
previousDomains,
idsSet,
);
@@ -393,7 +384,7 @@ export function useDashboardMutations(): UseDashboardMutationsReturn {
});
return {
previousDomains: previousDomains as [unknown, unknown][],
previousDomains,
previousSubscription,
};
},
@@ -432,7 +423,7 @@ export function useDashboardMutations(): UseDashboardMutationsReturn {
old?.map((d) => (idsSet.has(d.id) ? { ...d, muted } : d)),
);
return { previousDomains: previousDomains as [unknown, unknown][] };
return { previousDomains };
},
onError: (
_err,
+3 -3
View File
@@ -59,7 +59,7 @@ export function useDashboardPagination(): UseDashboardPaginationReturn {
const setPageIndex = useCallback(
(newIndex: number) => {
setPageParam(newIndex + 1);
void setPageParam(newIndex + 1);
},
[setPageParam],
);
@@ -68,13 +68,13 @@ export function useDashboardPagination(): UseDashboardPaginationReturn {
(newSize: DashboardPageSizeOptions) => {
setPageSizePreference(newSize);
// Reset to first page when changing page size
setPageParam(1);
void setPageParam(1);
},
[setPageSizePreference, setPageParam],
);
const resetPage = useCallback(() => {
setPageParam(1);
void setPageParam(1);
}, [setPageParam]);
// ---------------------------------------------------------------------------
+9 -3
View File
@@ -162,11 +162,17 @@ export function useSubscription(options: UseSubscriptionOptions = {}): UseSubscr
isPro: query.data?.plan === "pro",
isSubscriptionLoading: query.isLoading,
isSubscriptionError: query.isError,
refetchSubscription: query.refetch,
refetchSubscription: () => {
void query.refetch();
},
invalidateSubscription: invalidate,
handleCheckout,
handleCheckout: () => {
void handleCheckout();
},
isCheckoutLoading,
handleCustomerPortal,
handleCustomerPortal: () => {
void handleCustomerPortal();
},
isCustomerPortalLoading,
};
}
+1 -1
View File
@@ -57,7 +57,7 @@ export function getAssistantRenderItems(message: UIMessage): AssistantRenderItem
const texts: string[] = [];
let last: ReasoningUIPart = part;
while (i < parts.length && isReasoningUIPart(parts[i]!)) {
while (i < parts.length && isReasoningUIPart(parts[i])) {
last = parts[i] as ReasoningUIPart;
texts.push(last.text);
i += 1;
+6 -6
View File
@@ -27,7 +27,7 @@ export interface SectionDef {
export const sections: Record<Section, SectionDef> = {
registration: {
title: "Registration",
accent: "purple" as SectionAccent,
accent: "purple",
icon: IconIdBadge2,
description: "Registrar and registrant details",
help: "RDAP/WHOIS shows registrar, registration dates, and registrant details.",
@@ -35,7 +35,7 @@ export const sections: Record<Section, SectionDef> = {
},
hosting: {
title: "Hosting & Email",
accent: "blue" as SectionAccent,
accent: "blue",
icon: IconCloudComputing,
description: "Providers and IP geolocation",
help: "Hosting provider serves a site; email provider handles a domain's email.",
@@ -43,7 +43,7 @@ export const sections: Record<Section, SectionDef> = {
},
dns: {
title: "DNS Records",
accent: "green" as SectionAccent,
accent: "green",
icon: IconRoute,
description: "A, AAAA, MX, CNAME, TXT, NS",
help: "DNS records map the domain to services like web (A/AAAA), mail (MX), and aliases (CNAME).",
@@ -51,7 +51,7 @@ export const sections: Record<Section, SectionDef> = {
},
certificates: {
title: "SSL Certificates",
accent: "orange" as SectionAccent,
accent: "orange",
icon: IconCertificate,
description: "Issuer and validity",
help: "SSL/TLS certificates encrypt traffic and verify a domain's identity.",
@@ -59,7 +59,7 @@ export const sections: Record<Section, SectionDef> = {
},
headers: {
title: "HTTP Headers",
accent: "pink" as SectionAccent,
accent: "pink",
icon: IconList,
description: "Server, security, caching",
help: "Headers include server info and security/caching directives returned by a site.",
@@ -67,7 +67,7 @@ export const sections: Record<Section, SectionDef> = {
},
seo: {
title: "SEO & Social",
accent: "cyan" as SectionAccent,
accent: "cyan",
icon: IconShare,
description: "Meta tags, previews, robots.txt",
help: "Open Graph, Twitter, and standard meta inform social previews and search engines.",
+2 -2
View File
@@ -75,7 +75,7 @@ export function getSeverityIconColor(
return "destructive";
case "warning":
return "warning";
default:
case "info":
return "default";
}
}
@@ -136,7 +136,7 @@ export function getUnreadIndicatorColor(severity: NotificationSeverity) {
return "bg-destructive";
case "warning":
return "bg-amber-500";
default:
case "info":
return "bg-blue-500";
}
}
+1 -1
View File
@@ -102,7 +102,7 @@ export function classifyFetchError(
const retryAfterMatch = message.match(/retry[- ]after[:\s]+(\d+)/i);
const retrySeconds = retryAfterMatch ? Number.parseInt(retryAfterMatch[1], 10) : 60; // Default to 1 minute for rate limits
return new RetryableError(`${context}: rate limited`, {
retryAfter: `${retrySeconds}s` as `${number}s`,
retryAfter: `${retrySeconds}s`,
});
}
+2 -3
View File
@@ -8,9 +8,8 @@
"build": "next build",
"start": "next start",
"analyze": "next experimental-analyze",
"check-types": "next typegen && tsc --noEmit",
"lint": "oxlint",
"lint:fix": "oxlint --fix",
"lint": "next typegen && oxlint",
"lint:fix": "next typegen && oxlint --fix",
"fmt": "oxfmt --config ../../.oxfmtrc.json",
"fmt:check": "oxfmt --check --config ../../.oxfmtrc.json",
"test": "vitest run",
+3 -3
View File
@@ -61,7 +61,7 @@ export const domainRouter = createTRPCRouter({
// Fetch fresh data
try {
const result = await fetchRegistration(input.domain);
if (result.success === false) {
if (!result.success) {
return {
success: false,
cached: false,
@@ -168,7 +168,7 @@ export const domainRouter = createTRPCRouter({
// Fetch fresh data
try {
const result = await fetchCertificates(input.domain);
if (result.success === false) {
if (!result.success) {
return {
success: false,
cached: false,
@@ -216,7 +216,7 @@ export const domainRouter = createTRPCRouter({
// Fetch fresh data
try {
const result = await fetchHeaders(input.domain);
if (result.success === false) {
if (!result.success) {
return {
success: false,
cached: false,
@@ -25,7 +25,7 @@ const SORTED_THRESHOLDS = [...SUBSCRIPTION_EXPIRY_THRESHOLDS].sort((a, b) => a -
function getSubscriptionExpiryThreshold(daysRemaining: number): SubscriptionExpiryThreshold | null {
for (const threshold of SORTED_THRESHOLDS) {
if (daysRemaining <= threshold) {
return threshold as SubscriptionExpiryThreshold;
return threshold;
}
}
return null;
+1 -1
View File
@@ -15,7 +15,6 @@
"dev": "turbo run dev",
"build": "turbo run build",
"start": "turbo run start",
"check-types": "turbo run check-types",
"lint": "turbo run lint",
"lint:fix": "turbo run lint:fix",
"fmt": "turbo run fmt",
@@ -31,6 +30,7 @@
"devDependencies": {
"oxfmt": "^0.66.0",
"oxlint": "^1.81.0",
"oxlint-tsgolint": "^7.0.2001",
"shx": "^0.4.0",
"turbo": "^2.10.12",
"typescript": "^7.0.2"
-1
View File
@@ -9,7 +9,6 @@
"./middleware": "./src/middleware/index.ts"
},
"scripts": {
"check-types": "tsc --noEmit",
"lint": "oxlint",
"lint:fix": "oxlint --fix",
"fmt": "oxfmt --config ../../.oxfmtrc.json",
+3 -2
View File
@@ -41,9 +41,10 @@ describe("scheduleBackground", () => {
const work = deferred<boolean>();
let settled = false;
const pending = scheduleBackground(work.promise).then(() => {
const pending = (async () => {
await scheduleBackground(work.promise);
settled = true;
});
})();
expect(waitUntil).not.toHaveBeenCalled();
expect(settled).toBe(false);
-1
View File
@@ -15,7 +15,6 @@
"./types": "./src/types.ts"
},
"scripts": {
"check-types": "tsc --noEmit",
"lint": "oxlint",
"lint:fix": "oxlint --fix",
"fmt": "oxfmt --config ../../.oxfmtrc.json",
+3 -3
View File
@@ -2,7 +2,7 @@ import { drizzleAdapter } from "@better-auth/drizzle-adapter";
import { dash } from "@better-auth/infra";
import { waitUntil } from "@vercel/functions";
import { getSessionCookie } from "better-auth/cookies";
import { type BetterAuthOptions, betterAuth } from "better-auth/minimal";
import { betterAuth } from "better-auth/minimal";
import { nextCookies, toNextJsHandler } from "better-auth/next-js";
import { db } from "@domainstack/db/client";
@@ -116,7 +116,7 @@ export const auth = betterAuth({
logger: {
log: (level, message, ...args) => {
const logFn = logger[level].bind(logger);
logFn({ ...args }, message);
logFn({ extra: args }, message);
},
},
databaseHooks: {
@@ -249,7 +249,7 @@ export const auth = betterAuth({
// must be last: https://www.better-auth.com/docs/integrations/next#server-action-cookies
nextCookies(),
],
} as BetterAuthOptions);
});
export type Session = typeof auth.$Infer.Session;
-1
View File
@@ -8,7 +8,6 @@
".": "./src/index.ts"
},
"scripts": {
"check-types": "tsc --noEmit",
"lint": "oxlint",
"lint:fix": "oxlint --fix",
"fmt": "oxfmt --config ../../.oxfmtrc.json",
-1
View File
@@ -8,7 +8,6 @@
".": "./src/index.ts"
},
"scripts": {
"check-types": "tsc --noEmit",
"lint": "oxlint",
"lint:fix": "oxlint --fix",
"fmt": "oxfmt --config ../../.oxfmtrc.json",
-1
View File
@@ -13,7 +13,6 @@
"./testing": "./src/testing.ts"
},
"scripts": {
"check-types": "tsc --noEmit",
"lint": "oxlint",
"lint:fix": "oxlint --fix",
"fmt": "oxfmt --config ../../.oxfmtrc.json",
+2 -2
View File
@@ -42,7 +42,7 @@ export async function getFaviconById(domainId: string): Promise<CacheResult<Favi
return { data: null, stale: false, fetchedAt: null, expiresAt: null };
}
const isDefinitiveResult = row.url !== null || row.notFound === true;
const isDefinitiveResult = row.url !== null || row.notFound;
if (!isDefinitiveResult) {
return { data: null, stale: false, fetchedAt: null, expiresAt: null };
@@ -80,7 +80,7 @@ export async function getFavicon(domainName: string): Promise<CacheResult<Favico
return { data: null, stale: false, fetchedAt: null, expiresAt: null };
}
const isDefinitiveResult = row.url !== null || row.notFound === true;
const isDefinitiveResult = row.url !== null || row.notFound;
if (!isDefinitiveResult) {
return { data: null, stale: false, fetchedAt: null, expiresAt: null };
+1 -1
View File
@@ -81,7 +81,7 @@ export async function getCachedHeaders(domain: string): Promise<CacheResult<Head
// Status messages are derived from the numeric code at the call site.
return {
data: {
headers: row.headers as Header[],
headers: row.headers,
status: row.status,
statusMessage: undefined,
},
+1 -1
View File
@@ -44,7 +44,7 @@ export async function getProviderLogo(
return { data: null, stale: false, fetchedAt: null, expiresAt: null };
}
const isDefinitiveResult = row.url !== null || row.notFound === true;
const isDefinitiveResult = row.url !== null || row.notFound;
if (!isDefinitiveResult) {
return { data: null, stale: false, fetchedAt: null, expiresAt: null };
+6 -12
View File
@@ -1,13 +1,7 @@
import type { InferInsertModel } from "drizzle-orm";
import { eq } from "drizzle-orm";
import type {
GeneralMeta,
OpenGraphMeta,
RobotsTxt,
SeoResponse,
TwitterMeta,
} from "@domainstack/types";
import type { RobotsTxt, SeoResponse } from "@domainstack/types";
import { db } from "../client";
import { blockedDomains, domains, seo as seoTable } from "../schema";
@@ -82,7 +76,7 @@ export async function getCachedSeo(domain: string): Promise<CacheResult<SeoRespo
: null;
// Normalize robots
const robotsData = row.robots as RobotsTxt;
const robotsData = row.robots;
const normalizedRobots: RobotsTxt =
robotsData && "fetched" in robotsData
? robotsData
@@ -90,9 +84,9 @@ export async function getCachedSeo(domain: string): Promise<CacheResult<SeoRespo
const response: SeoResponse = {
meta: {
openGraph: row.metaOpenGraph as OpenGraphMeta,
twitter: row.metaTwitter as TwitterMeta,
general: row.metaGeneral as GeneralMeta,
openGraph: row.metaOpenGraph,
twitter: row.metaTwitter,
general: row.metaGeneral,
},
robots: normalizedRobots,
preview,
@@ -100,7 +94,7 @@ export async function getCachedSeo(domain: string): Promise<CacheResult<SeoRespo
finalUrl: row.sourceFinalUrl ?? null,
status: row.sourceStatus ?? null,
},
errors: row.errors as { html?: string; robots?: string },
errors: row.errors,
};
return { data: response, stale, fetchedAt, expiresAt };
+2 -2
View File
@@ -32,9 +32,9 @@ export async function makePGliteDb(): Promise<DbBundle> {
const origLog = consoleObj.log;
try {
consoleObj.log = (...args: unknown[]) => {
const s = String(args[0] ?? "");
const s = typeof args[0] === "string" ? args[0] : "";
if (s.includes("Pulling schema from database")) return;
origLog.apply(consoleObj, args as unknown[]);
origLog.apply(consoleObj, args);
};
await apply();
} finally {
-1
View File
@@ -8,7 +8,6 @@
".": "./src/index.ts"
},
"scripts": {
"check-types": "tsc --noEmit",
"lint": "oxlint",
"lint:fix": "oxlint --fix",
"fmt": "oxfmt --config ../../.oxfmtrc.json",
+6 -1
View File
@@ -1,4 +1,9 @@
{
"$schema": "../../node_modules/oxlint/configuration_schema.json",
"extends": ["../../.oxlintrc.json"]
"extends": ["../../.oxlintrc.json"],
"settings": {
"react": {
"version": "19.2.8"
}
}
}
-1
View File
@@ -10,7 +10,6 @@
"./components/*": "./src/components/*.tsx"
},
"scripts": {
"check-types": "tsc --noEmit",
"lint": "oxlint",
"lint:fix": "oxlint --fix",
"fmt": "oxfmt --config ../../.oxfmtrc.json",
@@ -98,7 +98,7 @@ CertificateExpiryEmail.PreviewProps = {
daysRemaining: 7,
issuer: "Let's Encrypt",
baseUrl: "https://domainstack.io",
} as CertificateExpiryEmailProps;
};
export default CertificateExpiryEmail;
@@ -70,6 +70,6 @@ DeleteAccountVerifyEmail.PreviewProps = {
userName: "Jake",
confirmUrl: "https://domainstack.io/api/auth/delete-user?token=abc123",
baseUrl: "https://domainstack.io",
} as DeleteAccountVerifyEmailProps;
};
export default DeleteAccountVerifyEmail;
@@ -64,6 +64,6 @@ function ProUpgradeSuccessEmail({ userName, baseUrl }: ProUpgradeSuccessEmailPro
ProUpgradeSuccessEmail.PreviewProps = {
userName: "Jake",
baseUrl: "https://domainstack.io",
} as ProUpgradeSuccessEmailProps;
};
export default ProUpgradeSuccessEmail;
+1 -1
View File
@@ -82,6 +82,6 @@ function ProWelcomeEmail({ userName, baseUrl }: ProWelcomeEmailProps) {
ProWelcomeEmail.PreviewProps = {
userName: "Jake",
baseUrl: "https://domainstack.io",
} as ProWelcomeEmailProps;
};
export default ProWelcomeEmail;
@@ -72,6 +72,6 @@ SubscriptionCancelingEmail.PreviewProps = {
userName: "Jake",
endDate: "January 15, 2025",
baseUrl: "https://domainstack.io",
} as SubscriptionCancelingEmailProps;
};
export default SubscriptionCancelingEmail;
@@ -86,6 +86,6 @@ SubscriptionExpiredEmail.PreviewProps = {
userName: "Jake",
archivedCount: 12,
baseUrl: "https://domainstack.io",
} as SubscriptionExpiredEmailProps;
};
export default SubscriptionExpiredEmail;
@@ -143,6 +143,6 @@ VerificationInstructionsEmail.PreviewProps = {
htmlFileContent: "domainstack-verify=abc123xyz",
metaTag: '<meta name="domainstack-verify" content="abc123xyz">',
baseUrl: "https://domainstack.io",
} as VerificationInstructionsEmailProps;
};
export default VerificationInstructionsEmail;
@@ -64,6 +64,6 @@ VerificationRevokedEmail.PreviewProps = {
userName: "Jake",
domainName: "example.com",
baseUrl: "https://domainstack.io",
} as VerificationRevokedEmailProps;
};
export default VerificationRevokedEmail;
+4
View File
@@ -0,0 +1,4 @@
{
"$schema": "../../node_modules/oxlint/configuration_schema.json",
"extends": ["../../.oxlintrc.json"]
}
-1
View File
@@ -8,7 +8,6 @@
".": "./src/index.ts"
},
"scripts": {
"check-types": "tsc --noEmit",
"lint": "oxlint",
"lint:fix": "oxlint --fix",
"fmt": "oxfmt --config ../../.oxfmtrc.json",
+1 -1
View File
@@ -99,7 +99,7 @@ export async function optimizeImage(input: Buffer, options: OptimizeImageOptions
return pipeline.png({ quality, compressionLevel: 9 }).toBuffer();
case "jpeg":
return pipeline.jpeg({ quality, mozjpeg: true }).toBuffer();
default:
case "webp":
return pipeline.webp({ quality }).toBuffer();
}
}
-1
View File
@@ -8,7 +8,6 @@
".": "./src/index.ts"
},
"scripts": {
"check-types": "tsc --noEmit",
"lint": "oxlint",
"lint:fix": "oxlint --fix",
"fmt": "oxfmt --config ../../.oxfmtrc.json",
+1 -2
View File
@@ -149,8 +149,7 @@ function toAttributes(record: Record<string, unknown>): Record<string, string |
*/
export function toLogRecord(record: Record<string, unknown>): LogRecord {
const timestamp = toTimestamp(record.time);
const body =
typeof record.msg === "string" ? record.msg : record.msg != null ? String(record.msg) : "";
const body = typeof record.msg === "string" ? record.msg : "";
return {
body,
-1
View File
@@ -17,7 +17,6 @@
"./server": "./src/server.ts"
},
"scripts": {
"check-types": "tsc --noEmit",
"lint": "oxlint",
"lint:fix": "oxlint --fix",
"fmt": "oxfmt --config ../../.oxfmtrc.json",
-1
View File
@@ -9,7 +9,6 @@
"./ratelimit": "./src/ratelimit.ts"
},
"scripts": {
"check-types": "tsc --noEmit",
"lint": "oxlint",
"lint:fix": "oxlint --fix",
"fmt": "oxfmt --config ../../.oxfmtrc.json",
-1
View File
@@ -8,7 +8,6 @@
".": "./src/index.ts"
},
"scripts": {
"check-types": "tsc --noEmit",
"lint": "oxlint",
"lint:fix": "oxlint --fix",
"fmt": "oxfmt --config ../../.oxfmtrc.json",
+1 -1
View File
@@ -625,7 +625,7 @@ describe("safeFetch", () => {
it("times out DNS lookup before fetch starts", async () => {
vi.useFakeTimers();
mockLookup.mockReturnValue(new Promise(() => {}) as ReturnType<typeof lookup>);
mockLookup.mockReturnValue(new Promise(() => {}));
const mockFetch = createMockFetch(mockResponse("OK", { status: 200 }));
const errorPromise = safeFetch({
+1 -1
View File
@@ -301,7 +301,7 @@ async function readBodyWithLimit(
if (partial.length > 0) chunks.push(partial);
try {
reader.cancel();
void reader.cancel();
} catch {
// Ignore cancel errors
}
-1
View File
@@ -8,7 +8,6 @@
".": "./src/index.ts"
},
"scripts": {
"check-types": "tsc --noEmit",
"lint": "oxlint",
"lint:fix": "oxlint --fix",
"fmt": "oxfmt --config ../../.oxfmtrc.json",
+3 -3
View File
@@ -39,7 +39,7 @@ async function createBrowser(): Promise<import("puppeteer-core").Browser> {
const executablePath = await chromium.executablePath();
const baseArgs = Array.isArray((chromium as unknown as { args?: unknown }).args)
? (chromium.args as string[])
? chromium.args
: [];
return launch({
@@ -60,8 +60,8 @@ async function createBrowser(): Promise<import("puppeteer-core").Browser> {
headless: true,
args: mergeArgs(STABILITY_ARGS),
defaultViewport: null,
} as never);
return browser as unknown as import("puppeteer-core").Browser;
});
return browser;
} catch {
// Fallback: require an explicit executable path for a locally installed Chrome/Chromium
const { launch } = await import("puppeteer-core");
-1
View File
@@ -16,7 +16,6 @@
"./cloudflare": "./src/cloudflare.ts"
},
"scripts": {
"check-types": "tsc --noEmit",
"lint": "oxlint",
"lint:fix": "oxlint --fix",
"fmt": "oxfmt --config ../../.oxfmtrc.json",
+2 -2
View File
@@ -7,7 +7,7 @@
* and stripping invisible formatting characters.
*/
export function sanitizeText(input: unknown): string {
let out = String(input ?? "");
let out = typeof input === "string" ? input : "";
out = out.trim().replace(/\s+/g, " ");
let res = "";
for (let i = 0; i < out.length; i++) {
@@ -20,7 +20,7 @@ export function sanitizeText(input: unknown): string {
) {
continue;
}
res += out[i] as string;
res += out[i];
}
// Strip invisible formatting chars (ZWSP, bidi marks, BOM)
return res.replace(/[\u200B-\u200F\u202A-\u202E\u2066-\u2069\uFEFF]/g, "");
+1 -1
View File
@@ -45,7 +45,7 @@ export async function fetchCertificateChain(
},
() => {
socket.setTimeout(0);
const peer = socket.getPeerCertificate(true) as DetailedPeerCertificate;
const peer = socket.getPeerCertificate(true);
const rawChain: RawCertificate[] = [];
let current: DetailedPeerCertificate | null = peer;
+8 -2
View File
@@ -7,13 +7,19 @@ import { type BootstrapData, lookup } from "rdapper";
import type { RdapLookupResult, WhoisLookupOptions } from "./types";
import { RDAP_BOOTSTRAP_URL } from "./types";
function errorText(error: unknown): string {
if (typeof error === "string") return error;
if (error instanceof Error) return error.message;
return "";
}
/**
* Check if error indicates an unsupported TLD.
*/
function isExpectedRegistrationError(error: unknown): boolean {
if (!error) return false;
const errorStr = String(error).toLowerCase();
const errorStr = errorText(error).toLowerCase();
return (
errorStr.includes("no whois server discovered") ||
@@ -30,7 +36,7 @@ function isExpectedRegistrationError(error: unknown): boolean {
function isTimeoutError(error: unknown): boolean {
if (!error) return false;
const errorStr = String(error).toLowerCase();
const errorStr = errorText(error).toLowerCase();
return (
errorStr.includes("whois socket timeout") ||
errorStr.includes("whois timeout") ||
-1
View File
@@ -8,7 +8,6 @@
".": "./src/index.ts"
},
"scripts": {
"check-types": "tsc --noEmit",
"lint": "oxlint",
"lint:fix": "oxlint --fix",
"fmt": "oxfmt --config ../../.oxfmtrc.json",
+15 -1
View File
@@ -1,4 +1,18 @@
{
"$schema": "../../node_modules/oxlint/configuration_schema.json",
"extends": ["../../.oxlintrc.json"]
"extends": ["../../.oxlintrc.json"],
"env": {
"browser": true
},
"settings": {
"react": {
"version": "19.2.8"
},
"jsx-a11y": {
"components": {
"Link": "a",
"Button": "button"
}
}
}
}
-1
View File
@@ -10,7 +10,6 @@
"./*": "./src/components/*.tsx"
},
"scripts": {
"check-types": "tsc --noEmit",
"lint": "oxlint",
"lint:fix": "oxlint --fix",
"fmt": "oxfmt --config ../../.oxfmtrc.json",
@@ -63,6 +63,8 @@ function PaginationLink({
className={className}
nativeButton={false}
render={
// Children are passed through `...props` by PaginationLink callers.
// oxlint-disable-next-line jsx-a11y/anchor-has-content
<a
aria-current={isActive ? "page" : undefined}
data-slot="pagination-link"
@@ -35,11 +35,8 @@ function ResponsiveTooltipTrigger({
nativeButton,
closeDelay,
...props
}: Omit<
TooltipPrimitive.Trigger.Props<unknown> & PopoverPrimitive.Trigger.Props<unknown>,
"handle"
> &
Pick<PopoverPrimitive.Trigger.Props<unknown>, "nativeButton">) {
}: Omit<TooltipPrimitive.Trigger.Props & PopoverPrimitive.Trigger.Props, "handle"> &
Pick<PopoverPrimitive.Trigger.Props, "nativeButton">) {
const ctx = useContext(ResponsiveTooltipContext);
if (!ctx) {
throw new Error("ResponsiveTooltipTrigger must be used within <ResponsiveTooltip>.");
+1 -1
View File
@@ -347,7 +347,7 @@ function StepperTrigger({ render, className, children, tabIndex, ...props }: Ste
return useRender({
defaultTagName: "button",
render: render as useRender.RenderProp<Record<string, unknown>> | undefined,
render: render as useRender.RenderProp | undefined,
ref: btnRef,
state: triggerState as unknown as Record<string, unknown>,
props: mergeProps(defaultProps, props),
@@ -104,6 +104,8 @@ function VideoPlayerMuteButton({
}
function VideoPlayerContent({ className, ...props }: React.ComponentProps<"video">) {
// Captions are supplied by the consumer via `children` / `<track>`.
// oxlint-disable-next-line jsx-a11y/media-has-caption
return <video className={cn("mt-0 mb-0", className)} {...props} />;
}
-1
View File
@@ -16,7 +16,6 @@
"./verification": "./src/verification.ts"
},
"scripts": {
"check-types": "tsc --noEmit",
"lint": "oxlint",
"lint:fix": "oxlint --fix",
"fmt": "oxfmt --config ../../.oxfmtrc.json",
+4 -2
View File
@@ -155,7 +155,8 @@ describe("queryDohProvider", () => {
it("adds cacheBust parameter when option is true", async () => {
let capturedUrl = "";
mockFetchImplementation((input) => {
capturedUrl = input.toString();
capturedUrl =
typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
return Promise.resolve({
ok: true,
json: () => Promise.resolve({ Status: 0 }),
@@ -172,7 +173,8 @@ describe("queryDohProvider", () => {
it("does not add cacheBust parameter by default", async () => {
let capturedUrl = "";
mockFetchImplementation((input) => {
capturedUrl = input.toString();
capturedUrl =
typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
return Promise.resolve({
ok: true,
json: () => Promise.resolve({ Status: 0 }),
+70 -5
View File
@@ -41,7 +41,10 @@ importers:
version: 0.66.0
oxlint:
specifier: ^1.81.0
version: 1.81.0
version: 1.81.0(oxlint-tsgolint@7.0.2001)
oxlint-tsgolint:
specifier: ^7.0.2001
version: 7.0.2001
shx:
specifier: ^0.4.0
version: 0.4.0
@@ -2325,6 +2328,36 @@ packages:
cpu: [x64]
os: [win32]
'@oxlint-tsgolint/darwin-arm64@7.0.2001':
resolution: {integrity: sha512-CUJEdbSZ54+Xy9OXqOhWLTKZKV0BBiV7C2i/ygyVmXtkUNXx5YCzN8DpSSshTAKktoL7S+tnQ/ftFG/i7X896w==}
cpu: [arm64]
os: [darwin]
'@oxlint-tsgolint/darwin-x64@7.0.2001':
resolution: {integrity: sha512-pXfBb5BqONCcgrXQNUZWXgiYmRSWJzd97S8i41VVOh6ut0tyo+cJ5FKFpczDHxiVNfj/3e7c9B4MtztNdpIVCw==}
cpu: [x64]
os: [darwin]
'@oxlint-tsgolint/linux-arm64@7.0.2001':
resolution: {integrity: sha512-roP7zujb/QDPzDwEKsFFpzNHHy91/Y7oX9vQXk78ekyZtcQj1QXDIMH33gjDdHBfRl4K9pZ36xhRgrP4Zr+R8A==}
cpu: [arm64]
os: [linux]
'@oxlint-tsgolint/linux-x64@7.0.2001':
resolution: {integrity: sha512-UDezNqdECVmngu2TPnjaS1YoAmcTaBoI5lV9vk3VahBxoi+I5r9k3iJTT7qZoYWOXTD/7T7bNcwRgrocR6BscQ==}
cpu: [x64]
os: [linux]
'@oxlint-tsgolint/win32-arm64@7.0.2001':
resolution: {integrity: sha512-uJZhqB6pdXLuN+AD1F5082byyQti/NPmJA77GtcFlmT2HzRelqbNls3SaIqxpjdFgvSBF9g0yOKGBkGFg7kX8Q==}
cpu: [arm64]
os: [win32]
'@oxlint-tsgolint/win32-x64@7.0.2001':
resolution: {integrity: sha512-FkDRm8hx9OwzGQqyWG1tO5QrTLRApff9DzSgpz9QZau37BR8d1VYKOxMLGf6shPZntJFoTwIIJYT68VndYDCog==}
cpu: [x64]
os: [win32]
'@oxlint/binding-android-arm-eabi@1.81.0':
resolution: {integrity: sha512-IcCRsXiedJoJopY6mpZUBEeVFsUrutmrG7dZ87zMuKJlhg70Ora9bBl1WcCxZQtyI10YpnVdEso5oCg7YcfSHw==}
engines: {node: ^20.19.0 || >=22.12.0}
@@ -6057,6 +6090,10 @@ packages:
vite-plus:
optional: true
oxlint-tsgolint@7.0.2001:
resolution: {integrity: sha512-KjK/XLcXr1DSyonKhsuFqJRiuKqcyG9j3LJ8nkOsrLzGvodBPqzHOKauy10asLMDI0sUpvb+1sxlzff3udZvfg==}
hasBin: true
oxlint@1.81.0:
resolution: {integrity: sha512-HyrJYqeoOCL0iqaLEzGewGT48ZX99P3hxYh8udAF9RGGIghSamkXE4ClUyBpEDNqasamThgmlPbuMOe7SAZmHg==}
engines: {node: ^20.19.0 || >=22.12.0}
@@ -8690,6 +8727,24 @@ snapshots:
'@oxfmt/binding-win32-x64-msvc@0.66.0':
optional: true
'@oxlint-tsgolint/darwin-arm64@7.0.2001':
optional: true
'@oxlint-tsgolint/darwin-x64@7.0.2001':
optional: true
'@oxlint-tsgolint/linux-arm64@7.0.2001':
optional: true
'@oxlint-tsgolint/linux-x64@7.0.2001':
optional: true
'@oxlint-tsgolint/win32-arm64@7.0.2001':
optional: true
'@oxlint-tsgolint/win32-x64@7.0.2001':
optional: true
'@oxlint/binding-android-arm-eabi@1.81.0':
optional: true
@@ -9454,7 +9509,7 @@ snapshots:
'@types/set-cookie-parser@2.4.10':
dependencies:
'@types/node': 24.13.3
'@types/node': 26.4.1
'@types/statuses@2.0.6': {}
@@ -9683,9 +9738,9 @@ snapshots:
obug: 2.1.4
std-env: 4.2.0
tinyrainbow: 3.1.1
vitest: 4.1.11(@opentelemetry/api@1.9.1)(@types/node@24.13.3)(@vitest/browser-playwright@4.1.11)(@vitest/coverage-v8@4.1.11)(msw@2.15.0(@types/node@24.13.3)(typescript@7.0.2))(vite@8.2.2(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13))
vitest: 4.1.11(@opentelemetry/api@1.9.1)(@types/node@26.4.1)(@vitest/browser-playwright@4.1.11)(@vitest/coverage-v8@4.1.11)(msw@2.15.0(@types/node@26.4.1)(typescript@7.0.2))(vite@8.2.2(@types/node@26.4.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13))
optionalDependencies:
'@vitest/browser': 4.1.11(bufferutil@4.1.0)(msw@2.15.0(@types/node@24.13.3)(typescript@7.0.2))(vite@8.2.2(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13))(vitest@4.1.11)
'@vitest/browser': 4.1.11(bufferutil@4.1.0)(msw@2.15.0(@types/node@26.4.1)(typescript@7.0.2))(vite@8.2.2(@types/node@26.4.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13))(vitest@4.1.11)
'@vitest/expect@4.1.11':
dependencies:
@@ -12602,7 +12657,16 @@ snapshots:
'@oxfmt/binding-win32-ia32-msvc': 0.66.0
'@oxfmt/binding-win32-x64-msvc': 0.66.0
oxlint@1.81.0:
oxlint-tsgolint@7.0.2001:
optionalDependencies:
'@oxlint-tsgolint/darwin-arm64': 7.0.2001
'@oxlint-tsgolint/darwin-x64': 7.0.2001
'@oxlint-tsgolint/linux-arm64': 7.0.2001
'@oxlint-tsgolint/linux-x64': 7.0.2001
'@oxlint-tsgolint/win32-arm64': 7.0.2001
'@oxlint-tsgolint/win32-x64': 7.0.2001
oxlint@1.81.0(oxlint-tsgolint@7.0.2001):
optionalDependencies:
'@oxlint/binding-android-arm-eabi': 1.81.0
'@oxlint/binding-android-arm64': 1.81.0
@@ -12623,6 +12687,7 @@ snapshots:
'@oxlint/binding-win32-arm64-msvc': 1.81.0
'@oxlint/binding-win32-ia32-msvc': 1.81.0
'@oxlint/binding-win32-x64-msvc': 1.81.0
oxlint-tsgolint: 7.0.2001
p-cancelable@4.0.1: {}
-4
View File
@@ -42,10 +42,6 @@
"persistent": true,
"cache": false
},
"check-types": {
"dependsOn": ["^check-types"],
"outputs": []
},
"lint": {
"dependsOn": ["^lint"],
"outputs": []