From 87e20f4a472d163004998377c688cc5c936dfffe Mon Sep 17 00:00:00 2001 From: Jake Jarvis Date: Fri, 18 Sep 2026 14:21:15 -0400 Subject: [PATCH] chore: replace Biome with oxlint and oxfmt --- .github/workflows/publish.yml | 5 +- .github/workflows/test.yml | 10 +- .oxfmtrc.json | 4 + .oxlintrc.json | 11 + README.md | 186 +++--- bin/cli.mjs | 10 +- biome.json | 34 -- package-lock.json | 1074 ++++++++++++++++++++++++++++----- package.json | 59 +- src/index.smoke.test.ts | 12 +- src/index.test.ts | 30 +- src/index.ts | 37 +- src/lib/dates.ts | 18 +- src/lib/domain.test.ts | 8 +- src/lib/domain.ts | 15 +- src/lib/text.ts | 7 +- src/rdap/bootstrap.test.ts | 14 +- src/rdap/client.ts | 5 +- src/rdap/links.ts | 4 +- src/rdap/merge.ts | 29 +- src/rdap/normalize.test.ts | 12 +- src/rdap/normalize.ts | 63 +- src/types.ts | 5 +- src/whois/merge.test.ts | 8 +- src/whois/merge.ts | 20 +- src/whois/normalize.test.ts | 14 +- src/whois/normalize.ts | 80 +-- src/whois/referral.test.ts | 9 +- 28 files changed, 1154 insertions(+), 629 deletions(-) create mode 100644 .oxfmtrc.json create mode 100644 .oxlintrc.json delete mode 100644 biome.json diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index c83fa37..ff94bd4 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -12,11 +12,12 @@ jobs: npm: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 - - uses: actions/setup-node@v6 + - uses: actions/checkout@v7 + - uses: actions/setup-node@v7 with: node-version: "24" registry-url: "https://registry.npmjs.org" + package-manager-cache: false - run: npm install -g npm@latest - run: npm ci - run: npm publish diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9ac9aa1..72556c6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,14 +11,14 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node-version: [18, 20, 22, 24] + node-version: [18, 20, 22, 24, 26] steps: - - uses: actions/checkout@v6 - - uses: actions/setup-node@v6 + - uses: actions/checkout@v7 + - uses: actions/setup-node@v7 with: node-version: ${{ matrix.node-version }} cache: "npm" - run: npm ci - - run: npm run typecheck - run: npm run lint - - run: npm run test:run + - run: npm run fmt:check + - run: npm run test diff --git a/.oxfmtrc.json b/.oxfmtrc.json new file mode 100644 index 0000000..3bffec0 --- /dev/null +++ b/.oxfmtrc.json @@ -0,0 +1,4 @@ +{ + "$schema": "./node_modules/oxfmt/configuration_schema.json", + "ignorePatterns": ["**/dist"] +} diff --git a/.oxlintrc.json b/.oxlintrc.json new file mode 100644 index 0000000..10ea945 --- /dev/null +++ b/.oxlintrc.json @@ -0,0 +1,11 @@ +{ + "$schema": "./node_modules/oxlint/configuration_schema.json", + "options": { + "typeAware": true, + "typeCheck": true + }, + "categories": { + "correctness": "error" + }, + "ignorePatterns": ["dist"] +} diff --git a/README.md b/README.md index 26d340a..8994a69 100644 --- a/README.md +++ b/README.md @@ -37,8 +37,8 @@ Normalize arbitrary input (domain or URL) to its registrable domain (eTLD+1): import { toRegistrableDomain } from "rdapper"; toRegistrableDomain("https://sub.example.co.uk/page"); // => "example.co.uk" -toRegistrableDomain("spark-public.s3.amazonaws.com"); // => "amazonaws.com" (ICANN-only default) -toRegistrableDomain("192.168.0.1"); // => null +toRegistrableDomain("spark-public.s3.amazonaws.com"); // => "amazonaws.com" (ICANN-only default) +toRegistrableDomain("192.168.0.1"); // => null ``` Convenience helpers to quickly check availability: @@ -102,7 +102,7 @@ For production applications that perform many domain lookups, you can take contr #### Example: In-memory caching with TTL ```ts -import { lookup, type BootstrapData } from 'rdapper'; +import { lookup, type BootstrapData } from "rdapper"; // Simple in-memory cache with TTL let cachedBootstrap: BootstrapData | null = null; @@ -111,43 +111,43 @@ const CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours async function getBootstrapData(): Promise { const now = Date.now(); - + // Return cached data if still valid if (cachedBootstrap && now < cacheExpiry) { return cachedBootstrap; } - + // Fetch fresh data - const response = await fetch('https://data.iana.org/rdap/dns.json'); + const response = await fetch("https://data.iana.org/rdap/dns.json"); if (!response.ok) { throw new Error(`Failed to load bootstrap data: ${response.status} ${response.statusText}`); } const data: BootstrapData = await response.json(); - + // Update cache cachedBootstrap = data; cacheExpiry = now + CACHE_TTL_MS; - + return data; } // Use the cached bootstrap data in lookups const bootstrapData = await getBootstrapData(); -const result = await lookup('example.com', { - customBootstrapData: bootstrapData +const result = await lookup("example.com", { + customBootstrapData: bootstrapData, }); ``` #### Example: Redis caching ```ts -import { lookup, type BootstrapData } from 'rdapper'; -import { createClient } from 'redis'; +import { lookup, type BootstrapData } from "rdapper"; +import { createClient } from "redis"; const redis = createClient(); await redis.connect(); -const CACHE_KEY = 'rdap:bootstrap:dns'; +const CACHE_KEY = "rdap:bootstrap:dns"; const CACHE_TTL_SECONDS = 24 * 60 * 60; // 24 hours async function getBootstrapData(): Promise { @@ -156,34 +156,34 @@ async function getBootstrapData(): Promise { if (cached) { return JSON.parse(cached); } - + // Fetch fresh data - const response = await fetch('https://data.iana.org/rdap/dns.json'); + const response = await fetch("https://data.iana.org/rdap/dns.json"); if (!response.ok) { throw new Error(`Failed to load bootstrap data: ${response.status} ${response.statusText}`); } const data: BootstrapData = await response.json(); - + // Store in Redis with TTL await redis.setEx(CACHE_KEY, CACHE_TTL_SECONDS, JSON.stringify(data)); - + return data; } // Use the cached bootstrap data in lookups const bootstrapData = await getBootstrapData(); -const result = await lookup('example.com', { - customBootstrapData: bootstrapData +const result = await lookup("example.com", { + customBootstrapData: bootstrapData, }); ``` #### Example: Filesystem caching ```ts -import { lookup, type BootstrapData } from 'rdapper'; -import { readFile, writeFile, stat } from 'node:fs/promises'; +import { lookup, type BootstrapData } from "rdapper"; +import { readFile, writeFile, stat } from "node:fs/promises"; -const CACHE_FILE = './cache/rdap-bootstrap.json'; +const CACHE_FILE = "./cache/rdap-bootstrap.json"; const CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours async function getBootstrapData(): Promise { @@ -191,32 +191,32 @@ async function getBootstrapData(): Promise { // Check if cache file exists and is fresh const stats = await stat(CACHE_FILE); const age = Date.now() - stats.mtimeMs; - + if (age < CACHE_TTL_MS) { - const cached = await readFile(CACHE_FILE, 'utf-8'); + const cached = await readFile(CACHE_FILE, "utf-8"); return JSON.parse(cached); } } catch { // Cache file doesn't exist or is unreadable, will fetch fresh } - + // Fetch fresh data - const response = await fetch('https://data.iana.org/rdap/dns.json'); + const response = await fetch("https://data.iana.org/rdap/dns.json"); if (!response.ok) { throw new Error(`Failed to load bootstrap data: ${response.status} ${response.statusText}`); } const data: BootstrapData = await response.json(); - + // Write to cache file - await writeFile(CACHE_FILE, JSON.stringify(data, null, 2), 'utf-8'); - + await writeFile(CACHE_FILE, JSON.stringify(data, null, 2), "utf-8"); + return data; } // Use the cached bootstrap data in lookups const bootstrapData = await getBootstrapData(); -const result = await lookup('example.com', { - customBootstrapData: bootstrapData +const result = await lookup("example.com", { + customBootstrapData: bootstrapData, }); ``` @@ -226,10 +226,10 @@ The `BootstrapData` type matches IANA's published format: ```ts interface BootstrapData { - version: string; // e.g., "1.0" - publication: string; // ISO 8601 timestamp + version: string; // e.g., "1.0" + publication: string; // ISO 8601 timestamp description?: string; - services: string[][][]; // Array of [TLDs, base URLs] tuples + services: string[][][]; // Array of [TLDs, base URLs] tuples } ``` @@ -244,6 +244,7 @@ For advanced use cases, rdapper allows you to provide a custom `fetch` implement #### What requests are affected? Your custom fetch will be used for: + - **RDAP bootstrap registry requests** (fetching `dns.json` from IANA, unless `customBootstrapData` is provided) - **RDAP domain lookups** (querying RDAP servers for domain data) - **RDAP related/entity link requests** (following links to registrar information) @@ -260,40 +261,40 @@ Your custom fetch will be used for: #### Example 1: Simple in-memory cache ```ts -import { lookup } from 'rdapper'; +import { lookup } from "rdapper"; const cache = new Map(); const cachedFetch: typeof fetch = async (input, init) => { - const url = typeof input === 'string' ? input : input.toString(); - + const url = typeof input === "string" ? input : input.toString(); + // Check cache first if (cache.has(url)) { - console.log('[Cache Hit]', url); + console.log("[Cache Hit]", url); return cache.get(url)!.clone(); } - + // Fetch and cache - console.log('[Cache Miss]', url); + console.log("[Cache Miss]", url); const response = await fetch(input, init); cache.set(url, response.clone()); return response; }; -const result = await lookup('example.com', { customFetch: cachedFetch }); +const result = await lookup("example.com", { customFetch: cachedFetch }); ``` #### Example 2: Request logging and monitoring ```ts -import { lookup } from 'rdapper'; +import { lookup } from "rdapper"; const loggingFetch: typeof fetch = async (input, init) => { - const url = typeof input === 'string' ? input : input.toString(); + const url = typeof input === "string" ? input : input.toString(); const start = Date.now(); - - console.log(`[→] ${init?.method || 'GET'} ${url}`); - + + console.log(`[→] ${init?.method || "GET"} ${url}`); + try { const response = await fetch(input, init); const duration = Date.now() - start; @@ -306,54 +307,54 @@ const loggingFetch: typeof fetch = async (input, init) => { } }; -const result = await lookup('example.com', { customFetch: loggingFetch }); +const result = await lookup("example.com", { customFetch: loggingFetch }); ``` #### Example 3: Retry logic with exponential backoff ```ts -import { lookup } from 'rdapper'; +import { lookup } from "rdapper"; async function fetchWithRetry( input: RequestInfo | URL, init?: RequestInit, - maxRetries = 3 + maxRetries = 3, ): Promise { let lastError: Error | undefined; - + for (let attempt = 0; attempt <= maxRetries; attempt++) { try { const response = await fetch(input, init); - + // Retry on 5xx errors if (response.status >= 500 && attempt < maxRetries) { const delay = Math.min(1000 * 2 ** attempt, 10000); console.log(`Retrying after ${delay}ms (attempt ${attempt + 1}/${maxRetries})`); - await new Promise(resolve => setTimeout(resolve, delay)); + await new Promise((resolve) => setTimeout(resolve, delay)); continue; } - + return response; } catch (error) { lastError = error as Error; if (attempt < maxRetries) { const delay = Math.min(1000 * 2 ** attempt, 10000); - await new Promise(resolve => setTimeout(resolve, delay)); + await new Promise((resolve) => setTimeout(resolve, delay)); continue; } } } - - throw lastError || new Error('Max retries exceeded'); + + throw lastError || new Error("Max retries exceeded"); } -const result = await lookup('example.com', { customFetch: fetchWithRetry }); +const result = await lookup("example.com", { customFetch: fetchWithRetry }); ``` #### Example 4: HTTP caching with cache-control headers ```ts -import { lookup } from 'rdapper'; +import { lookup } from "rdapper"; interface CachedResponse { response: Response; @@ -363,20 +364,20 @@ interface CachedResponse { const httpCache = new Map(); const httpCachingFetch: typeof fetch = async (input, init) => { - const url = typeof input === 'string' ? input : input.toString(); + const url = typeof input === "string" ? input : input.toString(); const now = Date.now(); - + // Check if we have a valid cached response const cached = httpCache.get(url); if (cached && cached.expiresAt > now) { return cached.response.clone(); } - + // Fetch fresh response const response = await fetch(input, init); - + // Parse Cache-Control header - const cacheControl = response.headers.get('cache-control'); + const cacheControl = response.headers.get("cache-control"); if (cacheControl) { const maxAgeMatch = cacheControl.match(/max-age=(\d+)/); if (maxAgeMatch) { @@ -387,11 +388,11 @@ const httpCachingFetch: typeof fetch = async (input, init) => { }); } } - + return response; }; -const result = await lookup('example.com', { customFetch: httpCachingFetch }); +const result = await lookup("example.com", { customFetch: httpCachingFetch }); ``` #### Example 5: Combining with customBootstrapData @@ -399,10 +400,10 @@ const result = await lookup('example.com', { customFetch: httpCachingFetch }); You can use both `customFetch` and `customBootstrapData` together for maximum control: ```ts -import { lookup, type BootstrapData } from 'rdapper'; +import { lookup, type BootstrapData } from "rdapper"; // Pre-load bootstrap data (no fetch needed for this) -const bootstrapData: BootstrapData = await getFromCache('bootstrap'); +const bootstrapData: BootstrapData = await getFromCache("bootstrap"); // Use custom fetch for all other RDAP requests const cachedFetch: typeof fetch = async (input, init) => { @@ -410,7 +411,7 @@ const cachedFetch: typeof fetch = async (input, init) => { return fetch(input, init); }; -const result = await lookup('example.com', { +const result = await lookup("example.com", { customBootstrapData: bootstrapData, customFetch: cachedFetch, }); @@ -441,13 +442,13 @@ The exact presence of fields depends on registry/registrar data and whether RDAP ```ts interface DomainRecord { - domain: string; // normalized name (unicode when available) - tld: string; // public suffix (can be multi-label, e.g., "com", "co.uk") - isRegistered: boolean; // availability heuristic (WHOIS) or true (RDAP) - isIDN?: boolean; // uses punycode labels (xn--) - unicodeName?: string; // RDAP unicodeName when provided - punycodeName?: string; // RDAP ldhName when provided - registry?: string; // registry operator (rarely available) + domain: string; // normalized name (unicode when available) + tld: string; // public suffix (can be multi-label, e.g., "com", "co.uk") + isRegistered: boolean; // availability heuristic (WHOIS) or true (RDAP) + isIDN?: boolean; // uses punycode labels (xn--) + unicodeName?: string; // RDAP unicodeName when provided + punycodeName?: string; // RDAP ldhName when provided + registry?: string; // registry operator (rarely available) registrar?: { name?: string; ianaId?: string; @@ -461,11 +462,11 @@ interface DomainRecord { description?: string; raw?: string; }>; - creationDate?: string; // ISO 8601 (UTC) - updatedDate?: string; // ISO 8601 (UTC) - expirationDate?: string; // ISO 8601 (UTC) - deletionDate?: string; // ISO 8601 (UTC) - transferLock?: boolean; // derived from EPP statuses + creationDate?: string; // ISO 8601 (UTC) + updatedDate?: string; // ISO 8601 (UTC) + expirationDate?: string; // ISO 8601 (UTC) + deletionDate?: string; // ISO 8601 (UTC) + transferLock?: boolean; // derived from EPP statuses dnssec?: { enabled: boolean; dsRecords?: Array<{ @@ -481,7 +482,8 @@ interface DomainRecord { ipv6?: string[]; }>; contacts?: Array<{ - type: "registrant" | "admin" | "tech" | "billing" | "abuse" | "registrar" | "reseller" | "unknown"; + type: + "registrant" | "admin" | "tech" | "billing" | "abuse" | "registrar" | "reseller" | "unknown"; name?: string; organization?: string; email?: string | string[]; @@ -494,12 +496,12 @@ interface DomainRecord { country?: string; countryCode?: string; }>; - privacyEnabled?: boolean; // registrant appears privacy-redacted based on keyword heuristics - whoisServer?: string; // authoritative WHOIS queried (if any) - rdapServers?: string[]; // RDAP URLs tried (bootstrap bases and related/entity links) - rawRdap?: unknown; // raw RDAP JSON (only when options.includeRaw) - rawWhois?: string; // raw WHOIS text (only when options.includeRaw) - source: "rdap" | "whois"; // which path produced data + privacyEnabled?: boolean; // registrant appears privacy-redacted based on keyword heuristics + whoisServer?: string; // authoritative WHOIS queried (if any) + rdapServers?: string[]; // RDAP URLs tried (bootstrap bases and related/entity links) + rawRdap?: unknown; // raw RDAP JSON (only when options.includeRaw) + rawWhois?: string; // raw WHOIS text (only when options.includeRaw) + source: "rdap" | "whois"; // which path produced data warnings?: string[]; } ``` @@ -511,7 +513,10 @@ interface DomainRecord { "domain": "example.com", "tld": "com", "isRegistered": true, - "registrar": { "name": "Internet Assigned Numbers Authority", "ianaId": "376" }, + "registrar": { + "name": "Internet Assigned Numbers Authority", + "ianaId": "376" + }, "statuses": [{ "status": "clientTransferProhibited" }], "nameservers": [{ "host": "a.iana-servers.net" }, { "host": "b.iana-servers.net" }], "dnssec": { "enabled": true }, @@ -539,9 +544,10 @@ Timeouts are enforced per request using a simple race against `timeoutMs` (defau - Test: `npm test` ([Vitest](https://vitest.dev/)) - By default, tests are offline/deterministic. - Watch mode: `npm run dev` - - Coverage: `npm run test:run -- --coverage` + - Coverage: `npm run test -- --coverage` - Smoke tests that hit the network are gated by `SMOKE=1`, e.g. `SMOKE=1 npm test`. -- Lint/format: `npm run lint` ([Biome](https://biomejs.dev/)) +- Lint: `npm run lint` ([Oxlint](https://oxc.rs/docs/guide/usage/linter)) +- Format: `npm run fmt` ([Oxfmt](https://oxc.rs/docs/guide/usage/formatter)) Project layout: diff --git a/bin/cli.mjs b/bin/cli.mjs index a98846c..9a24d46 100755 --- a/bin/cli.mjs +++ b/bin/cli.mjs @@ -11,13 +11,7 @@ import { lookup } from "../dist/index.mjs"; async function main() { if (process.argv.length > 2) { // URL(s) specified in the command arguments - console.log( - JSON.stringify( - await lookup(process.argv[process.argv.length - 1]), - null, - 2, - ), - ); + console.log(JSON.stringify(await lookup(process.argv[process.argv.length - 1]), null, 2)); } else { // No domain passed as argument, read from each line of stdin const rlInterface = createInterface({ @@ -29,4 +23,4 @@ async function main() { } } -main(); +void main(); diff --git a/biome.json b/biome.json deleted file mode 100644 index 1435f87..0000000 --- a/biome.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "$schema": "./node_modules/@biomejs/biome/configuration_schema.json", - "vcs": { - "enabled": true, - "clientKind": "git" - }, - "files": { - "includes": ["**", "!**/dist"], - "ignoreUnknown": false - }, - "formatter": { - "enabled": true, - "indentStyle": "space" - }, - "linter": { - "enabled": true, - "rules": { - "recommended": true - } - }, - "javascript": { - "formatter": { - "quoteStyle": "double" - } - }, - "assist": { - "enabled": true, - "actions": { - "source": { - "organizeImports": "on" - } - } - } -} diff --git a/package-lock.json b/package-lock.json index b7c41be..1f47a55 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,8 +15,10 @@ "rdapper": "bin/cli.mjs" }, "devDependencies": { - "@biomejs/biome": "2.3.11", "@types/node": "^26.6.1", + "oxfmt": "^0.68.0", + "oxlint": "^1.83.0", + "oxlint-tsgolint": "^7.0.2002", "tsdown": "^0.23.0", "typescript": "^6.0.3", "vite": "^8.3.0", @@ -26,169 +28,6 @@ "node": ">=18.17" } }, - "node_modules/@biomejs/biome": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.3.11.tgz", - "integrity": "sha512-/zt+6qazBWguPG6+eWmiELqO+9jRsMZ/DBU3lfuU2ngtIQYzymocHhKiZRyrbra4aCOoyTg/BmY+6WH5mv9xmQ==", - "dev": true, - "license": "MIT OR Apache-2.0", - "bin": { - "biome": "bin/biome" - }, - "engines": { - "node": ">=14.21.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/biome" - }, - "optionalDependencies": { - "@biomejs/cli-darwin-arm64": "2.3.11", - "@biomejs/cli-darwin-x64": "2.3.11", - "@biomejs/cli-linux-arm64": "2.3.11", - "@biomejs/cli-linux-arm64-musl": "2.3.11", - "@biomejs/cli-linux-x64": "2.3.11", - "@biomejs/cli-linux-x64-musl": "2.3.11", - "@biomejs/cli-win32-arm64": "2.3.11", - "@biomejs/cli-win32-x64": "2.3.11" - } - }, - "node_modules/@biomejs/cli-darwin-arm64": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.3.11.tgz", - "integrity": "sha512-/uXXkBcPKVQY7rc9Ys2CrlirBJYbpESEDme7RKiBD6MmqR2w3j0+ZZXRIL2xiaNPsIMMNhP1YnA+jRRxoOAFrA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT OR Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=14.21.3" - } - }, - "node_modules/@biomejs/cli-darwin-x64": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.3.11.tgz", - "integrity": "sha512-fh7nnvbweDPm2xEmFjfmq7zSUiox88plgdHF9OIW4i99WnXrAC3o2P3ag9judoUMv8FCSUnlwJCM1B64nO5Fbg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT OR Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=14.21.3" - } - }, - "node_modules/@biomejs/cli-linux-arm64": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.3.11.tgz", - "integrity": "sha512-l4xkGa9E7Uc0/05qU2lMYfN1H+fzzkHgaJoy98wO+b/7Gl78srbCRRgwYSW+BTLixTBrM6Ede5NSBwt7rd/i6g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT OR Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=14.21.3" - } - }, - "node_modules/@biomejs/cli-linux-arm64-musl": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.3.11.tgz", - "integrity": "sha512-XPSQ+XIPZMLaZ6zveQdwNjbX+QdROEd1zPgMwD47zvHV+tCGB88VH+aynyGxAHdzL+Tm/+DtKST5SECs4iwCLg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT OR Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=14.21.3" - } - }, - "node_modules/@biomejs/cli-linux-x64": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.3.11.tgz", - "integrity": "sha512-/1s9V/H3cSe0r0Mv/Z8JryF5x9ywRxywomqZVLHAoa/uN0eY7F8gEngWKNS5vbbN/BsfpCG5yeBT5ENh50Frxg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT OR Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=14.21.3" - } - }, - "node_modules/@biomejs/cli-linux-x64-musl": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.3.11.tgz", - "integrity": "sha512-vU7a8wLs5C9yJ4CB8a44r12aXYb8yYgBn+WeyzbMjaCMklzCv1oXr8x+VEyWodgJt9bDmhiaW/I0RHbn7rsNmw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT OR Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=14.21.3" - } - }, - "node_modules/@biomejs/cli-win32-arm64": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.3.11.tgz", - "integrity": "sha512-PZQ6ElCOnkYapSsysiTy0+fYX+agXPlWugh6+eQ6uPKI3vKAqNp6TnMhoM3oY2NltSB89hz59o8xIfOdyhi9Iw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT OR Apache-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=14.21.3" - } - }, - "node_modules/@biomejs/cli-win32-x64": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.3.11.tgz", - "integrity": "sha512-43VrG813EW+b5+YbDbz31uUsheX+qFKCpXeY9kfdAx+ww3naKxeVkTD9zLIWxUPfJquANMHrmW3wbe/037G0Qg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT OR Apache-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=14.21.3" - } - }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", @@ -227,6 +66,784 @@ "url": "https://github.com/sponsors/oxc-project" } }, + "node_modules/@oxfmt/binding-android-arm-eabi": { + "version": "0.68.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-android-arm-eabi/-/binding-android-arm-eabi-0.68.0.tgz", + "integrity": "sha512-dhfYPbzv/h9JgHjNkl2R6sOjUfxDyLGOZVb3g8/ScaTNwwJcYgmHh8kcYFDUhinuy1QAoANCWUvw1jlk+z6gAg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-android-arm64": { + "version": "0.68.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-android-arm64/-/binding-android-arm64-0.68.0.tgz", + "integrity": "sha512-v3Njdi6qY0O/5eGfg01ww2w6gTn2mUvZ72Bnx1/UN53A9wruh3Nk6otc3WkgJLkXD4Qgz1SOcVQieH1oD03V9Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-darwin-arm64": { + "version": "0.68.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-darwin-arm64/-/binding-darwin-arm64-0.68.0.tgz", + "integrity": "sha512-ei4MCMzHFREmZwPJ7KuWUB4kBuHdsgDrnXGJVcEAopU7fj7S42I8BChdFILWdHvhFqR08FLJtOfbZIr2CDw0cA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-darwin-x64": { + "version": "0.68.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-darwin-x64/-/binding-darwin-x64-0.68.0.tgz", + "integrity": "sha512-UrKgzZxYhwB9DSvTX+vdgl9M32wLUNKJcAKIoiyx/Kzn/zveqi7W6kYVcRroFMhS3Kwz0KhTk3WBeSuQn4YCTg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-freebsd-x64": { + "version": "0.68.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-freebsd-x64/-/binding-freebsd-x64-0.68.0.tgz", + "integrity": "sha512-6jrEKgpJbilM1QaRv7hEtKXr4p4AK4jvvyOtajwyhu0kOz3e0O7OLnSTk6tBotRqCcUC4ehZRJ1Zx+Y99wieLw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-linux-arm-gnueabihf": { + "version": "0.68.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.68.0.tgz", + "integrity": "sha512-YOIVnKOBaLeGullskS179N12hjSAdFYnzLjOaKiLhAKNWgnShq9w4xRdtmUm6BlnP65l2/EA9Aw/KlftNxDM7Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-linux-arm-musleabihf": { + "version": "0.68.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-0.68.0.tgz", + "integrity": "sha512-xW5XoEHVNqydPBv2KXvk9lmEzyAlOQHVEazKoXUuAacqekjya+OdiaFjjEBl0oJD02raG8g3TRl9OVCh9PDIHA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-linux-arm64-gnu": { + "version": "0.68.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.68.0.tgz", + "integrity": "sha512-QCvYwVVQieu6oyJglAgV9vH/YMDxZyR4cwVSYtoq9oOXd5N+D3TDUBjNwxFrLn5AdcJZOcvn/7IB17vJGp+2Og==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-linux-arm64-musl": { + "version": "0.68.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.68.0.tgz", + "integrity": "sha512-4TVz5iFQ8ndrHnhX50UXiz9BIWAtUSOHJ6Nus4qWFfJBXq/Ed/krXbF/ehJu42BXM5tIvBs99jNIM22s9agY5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-linux-ppc64-gnu": { + "version": "0.68.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-0.68.0.tgz", + "integrity": "sha512-qLe3ao0RP84bnPxBvRI+GnlK/jybo538NWu0Xrm+zYeTmJtpzqLhnnd5BH21NafqKnplbGZjtN1cnrOlWF73lw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-linux-riscv64-gnu": { + "version": "0.68.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-0.68.0.tgz", + "integrity": "sha512-Yvyl7a6gbb0vM6r925KW2dO+/CmXySO5TVbcX7o/uZJ+d108HFOG0TxIyHApmn5USuj5mhDPxzhiVwOlGP7uSA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-linux-riscv64-musl": { + "version": "0.68.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-0.68.0.tgz", + "integrity": "sha512-mJlFuFVCxzrYM5sFStN433D/s/mb6Wq4aCQAM02vs/OudHywnaSAd2rb1vlYUJtqdYIciJtiasuxvfbYkv5fLg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-linux-s390x-gnu": { + "version": "0.68.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-0.68.0.tgz", + "integrity": "sha512-RlfSg++qs1hbKltRR6lYvV9EoI3MdlfSQD9w1hdHVYjHqjIn1tkH4FWOpMSmjKGN20zr+nI+W9o4ARogCDudGQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-linux-x64-gnu": { + "version": "0.68.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.68.0.tgz", + "integrity": "sha512-nyzRB9U+dlYUKu3pMo3afHzZBUv/oTHZMG36ZfJViNVfOIzp70Q4GS8FFRGgYJ/p0zcyDCgpBvYISOdJOMh+jQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-linux-x64-musl": { + "version": "0.68.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-x64-musl/-/binding-linux-x64-musl-0.68.0.tgz", + "integrity": "sha512-iCx3sbZRIvGrL1RafphEiUKBaW1lc0/tAjKOIB/Wjw2+STRBEdu5+fH1Gc1faEWEmc2k5Ks4iUUV54Zd5C9a1A==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-openharmony-arm64": { + "version": "0.68.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-openharmony-arm64/-/binding-openharmony-arm64-0.68.0.tgz", + "integrity": "sha512-x2X5AZez7OgyLLFpwIgItoXBUqudDM7yiaTsxv8R8vKQ6e81l0jVw0NFeUCXzcl1sAJq8h+tC8N4mY8EiMeL4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-win32-arm64-msvc": { + "version": "0.68.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.68.0.tgz", + "integrity": "sha512-AHVPjXkenLPQUh6kB8zSC8pX2ct9r4T1Edk9r/RNJyov6wPsS5uAfYtipwG4chn6+3bPFG5rI/3DxEeH8vib1w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-win32-ia32-msvc": { + "version": "0.68.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-0.68.0.tgz", + "integrity": "sha512-n09SjEk5VH7z8Hl4WVP7hho+cCwGViENkQFiM45vbW85dJd7kEhWaHRUobaPzEmrWyu6uumd4EuNfNyDKLtzDA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-win32-x64-msvc": { + "version": "0.68.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.68.0.tgz", + "integrity": "sha512-gPe+dJLXaPuWPWqlpklDAJp0k+K9KhQPYiQLHfb+i2rmFuUGfJ/5Qlj6tr1mO6of5g0DiLjG/XCFHIaPhotqqA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint-tsgolint/darwin-arm64": { + "version": "7.0.2002", + "resolved": "https://registry.npmjs.org/@oxlint-tsgolint/darwin-arm64/-/darwin-arm64-7.0.2002.tgz", + "integrity": "sha512-E3yYb/tI+5M3SqGgKwrwUD0nmjA2FMGD1xFbma46qi8Y7+nPTlGY6bZk/hqxijc9Vda/tdtUxQ2d5zGIEX9qlg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oxlint-tsgolint/darwin-x64": { + "version": "7.0.2002", + "resolved": "https://registry.npmjs.org/@oxlint-tsgolint/darwin-x64/-/darwin-x64-7.0.2002.tgz", + "integrity": "sha512-qEaMySdvzs8faaJwMO4dFtPFCa+degFCz+HbkFQ0REcIJqJ16KmC0smt30FAI9TeEHCoAtz6sU/6acJvi4uaxg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oxlint-tsgolint/linux-arm64": { + "version": "7.0.2002", + "resolved": "https://registry.npmjs.org/@oxlint-tsgolint/linux-arm64/-/linux-arm64-7.0.2002.tgz", + "integrity": "sha512-bqPObCNIR6tDQSc0Xn9Nu7EfWRXFB+qFzADRl/q6YA8zT9YUg3pGUbmicV2hV91uLpiot74so9ocGjx35SaX6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxlint-tsgolint/linux-x64": { + "version": "7.0.2002", + "resolved": "https://registry.npmjs.org/@oxlint-tsgolint/linux-x64/-/linux-x64-7.0.2002.tgz", + "integrity": "sha512-E5CcRALAYdiQMbdYjEqlLOiYuEdt21RtXiyxIoD8bfypdfkpWppacijKOR4i0DtLXFRVwCGQggZBWk5bOIpU1A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxlint-tsgolint/win32-arm64": { + "version": "7.0.2002", + "resolved": "https://registry.npmjs.org/@oxlint-tsgolint/win32-arm64/-/win32-arm64-7.0.2002.tgz", + "integrity": "sha512-L+ul0ZFDiz/ZmTwCQ1J/qS7S6s45ktiaFqb+ZIzx+1sSdtdRxm8/H0+p4VDQYrCKBFzKNb6VjUlm48bNEe3BUg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@oxlint-tsgolint/win32-x64": { + "version": "7.0.2002", + "resolved": "https://registry.npmjs.org/@oxlint-tsgolint/win32-x64/-/win32-x64-7.0.2002.tgz", + "integrity": "sha512-zkZeJfo4UmYiCzj77u85eCDpdxOiQiSAsV6j9Pvj5e7Vu5+MvwLtK1xmVutE77tJmUJ3eBOSXHR6WgY4AwHHGQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@oxlint/binding-android-arm-eabi": { + "version": "1.83.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm-eabi/-/binding-android-arm-eabi-1.83.0.tgz", + "integrity": "sha512-0yGY24EwsLk5YDe6F+VkmZyRHSwJDALa3nIrPpq7FXmp2lV2d0TzvBCGeZk+wgiULRGr5blhyr4QMp5KCXJUqA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-android-arm64": { + "version": "1.83.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm64/-/binding-android-arm64-1.83.0.tgz", + "integrity": "sha512-hHfJ0vc17A4iUjH5p9BsTUPYbYRNxGpvD2lbu1aBRk54bzNIx9o5TtYF39QPZcV95DagZd+4DEAw2RH3G2ZsMg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-darwin-arm64": { + "version": "1.83.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-arm64/-/binding-darwin-arm64-1.83.0.tgz", + "integrity": "sha512-hsOjYjszLb/3zym/TkzUMPAoQlTJcuzSyEPOAyA+skXJIX9M0o+4JfOtqopX/Vf4hSLrJ98j0nvFo23gzk8auQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-darwin-x64": { + "version": "1.83.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-x64/-/binding-darwin-x64-1.83.0.tgz", + "integrity": "sha512-mjh5oH2EA+wl5yRJYT9K9G61O2zFlpuv+yf2JwZOi0+dq2FnTUtm1h8i+5Ik0fXPWIu/k84I1psZR9aQsLAnyA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-freebsd-x64": { + "version": "1.83.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-freebsd-x64/-/binding-freebsd-x64-1.83.0.tgz", + "integrity": "sha512-fNHr64/YaO8YssuoDVC8+F4Uk5enR86q5uxfHkQrjAPs1dbAILOrD2uaud+J7MO8Fx774g44ERLD0IGIvZE48w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm-gnueabihf": { + "version": "1.83.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.83.0.tgz", + "integrity": "sha512-Qpwy3zzAwMj+8/lyYItHmkSMwbkprFNWTK7jPYDOxSyxEhaSLOWYUTCMkjF334J8/WD0nznCCsoBbIH6hpsuIw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm-musleabihf": { + "version": "1.83.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-1.83.0.tgz", + "integrity": "sha512-s+BirYLFq7JL2k9sP0XI3ZXJ9dYvJ8sX3jLCLoag7tt+zrSHpZxP0jqznfL+Gdgwu7ay0dYgGYJXrQvq3iWloA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm64-gnu": { + "version": "1.83.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.83.0.tgz", + "integrity": "sha512-7lihXt3vKr+GIyapNbHrnFHm/biiW30le6Zv/DExbAFPF6YwCQXVFlONPFehxs0CpGO4CBfYPM9rdDT+XMoIlg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm64-musl": { + "version": "1.83.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.83.0.tgz", + "integrity": "sha512-q63JalLYVkZiZvls1z3PPUnpmQluOMXp0khqQMznCeAPLGydfNY8JhvuA4WlK57JfrvikU8wB5lPVveqpIXvew==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-ppc64-gnu": { + "version": "1.83.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.83.0.tgz", + "integrity": "sha512-krQmDF+dRbxvdqVPV88ZuOoPPu8X5BuqDA8Hd+qcS4YMRQCb+nexA57DazgGsc/rGdKBe3QmV0mnv0bdpW/p5g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-riscv64-gnu": { + "version": "1.83.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-1.83.0.tgz", + "integrity": "sha512-MmOl8Y6txEAXZU1RG8Rr264jQ6D7VPmqFsU/45x/FeWsGe32hklTqGrLE6UxHzp5Rjt0wP+20tY8YXKgSFB3mw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-riscv64-musl": { + "version": "1.83.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-1.83.0.tgz", + "integrity": "sha512-u1rMymh0W3JZkq370kzQsYPULGWqhE09pZRqnZvUSoYaI9pVO5yVX+iYIslmWuEgwuzH9YAaOsScJiobWCHoOw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-s390x-gnu": { + "version": "1.83.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.83.0.tgz", + "integrity": "sha512-y0zK3HNwGysu7rqtE+BQG/d0bx5gh/KwlOtghN8oWeK1KcWzeaLqtZrbm8owqdma1lFyrce/hTO5ismuNu+INQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-x64-gnu": { + "version": "1.83.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.83.0.tgz", + "integrity": "sha512-rS5gM0NgD7ngmuJmbIehsidtrOwKkLFwCQbKEeb9KuyQrrWNq5Zkn0uV6AYdXOMJ0grrWEiLwBuvMxt8w5vsNw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-x64-musl": { + "version": "1.83.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-musl/-/binding-linux-x64-musl-1.83.0.tgz", + "integrity": "sha512-W2IH4EtpcPaWcvNGCA95YoDg4vxqE/ZiPCi3arrxEEpsK7+JQN9WYwrlYFx9pcdP6KPXqRqkv3zdQPHcx7b6YQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-openharmony-arm64": { + "version": "1.83.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-openharmony-arm64/-/binding-openharmony-arm64-1.83.0.tgz", + "integrity": "sha512-6LyKkUyoajssTPLlZmDbZIbu4IZ5B4bGuRUnBgCGpEvHP3FQMaYITncHA/unPUo7q+Z+pIu2HhdkQ+8d1SG7iA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-win32-arm64-msvc": { + "version": "1.83.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.83.0.tgz", + "integrity": "sha512-Uz/fObEtF0jmNJQJ8CGRBKfefYstS0/wjD3s6IGzP8nUwsJykHQJBiN3npHwKiGRGn/vvBEgNr4B3cCzmmatvg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-win32-ia32-msvc": { + "version": "1.83.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.83.0.tgz", + "integrity": "sha512-u7XcvPW6Bk58tY5iWs2ESb0vJjoE/kuSpHxopbwp/p3ZtWVQXZ6wor5w3ssVTHOqd/v8b+QdhSFWQ4grEUNWpA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-win32-x64-msvc": { + "version": "1.83.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.83.0.tgz", + "integrity": "sha512-LZRubd7ph13QmAg4fFecTYVZkiYbROR2Htaxh/ufWRkDhPOm2wrwaEYR89e0YpPFD3dqBrPoxS7myBw5hmYA7Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, "node_modules/@quansync/fs": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@quansync/fs/-/fs-1.1.0.tgz", @@ -1462,6 +2079,125 @@ "node": ">=12.20.0" } }, + "node_modules/oxfmt": { + "version": "0.68.0", + "resolved": "https://registry.npmjs.org/oxfmt/-/oxfmt-0.68.0.tgz", + "integrity": "sha512-Z0XMofcXCGUXbcpBHnWyUiX93BGiw1B+lcHNbQDWEtOhX06ewoFfu4zXkyiLhRrNnMq0twqXRHUcJetf+GsiQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinypool": "2.1.2" + }, + "bin": { + "oxfmt": "bin/oxfmt" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/sponsors/oxc-project" + }, + "optionalDependencies": { + "@oxfmt/binding-android-arm-eabi": "0.68.0", + "@oxfmt/binding-android-arm64": "0.68.0", + "@oxfmt/binding-darwin-arm64": "0.68.0", + "@oxfmt/binding-darwin-x64": "0.68.0", + "@oxfmt/binding-freebsd-x64": "0.68.0", + "@oxfmt/binding-linux-arm-gnueabihf": "0.68.0", + "@oxfmt/binding-linux-arm-musleabihf": "0.68.0", + "@oxfmt/binding-linux-arm64-gnu": "0.68.0", + "@oxfmt/binding-linux-arm64-musl": "0.68.0", + "@oxfmt/binding-linux-ppc64-gnu": "0.68.0", + "@oxfmt/binding-linux-riscv64-gnu": "0.68.0", + "@oxfmt/binding-linux-riscv64-musl": "0.68.0", + "@oxfmt/binding-linux-s390x-gnu": "0.68.0", + "@oxfmt/binding-linux-x64-gnu": "0.68.0", + "@oxfmt/binding-linux-x64-musl": "0.68.0", + "@oxfmt/binding-openharmony-arm64": "0.68.0", + "@oxfmt/binding-win32-arm64-msvc": "0.68.0", + "@oxfmt/binding-win32-ia32-msvc": "0.68.0", + "@oxfmt/binding-win32-x64-msvc": "0.68.0" + }, + "peerDependencies": { + "svelte": "^5.0.0", + "vite-plus": "*" + }, + "peerDependenciesMeta": { + "svelte": { + "optional": true + }, + "vite-plus": { + "optional": true + } + } + }, + "node_modules/oxlint": { + "version": "1.83.0", + "resolved": "https://registry.npmjs.org/oxlint/-/oxlint-1.83.0.tgz", + "integrity": "sha512-cyDzSzaw3uzP0TeCeq3lLRPPoaUxkbB4ZOXj+kn+5r+BX9V+4bNVGk9lxer+WrgcpebH4JxLlJ3KQjveVztOLQ==", + "dev": true, + "license": "MIT", + "bin": { + "oxlint": "bin/oxlint" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/sponsors/oxc-project" + }, + "optionalDependencies": { + "@oxlint/binding-android-arm-eabi": "1.83.0", + "@oxlint/binding-android-arm64": "1.83.0", + "@oxlint/binding-darwin-arm64": "1.83.0", + "@oxlint/binding-darwin-x64": "1.83.0", + "@oxlint/binding-freebsd-x64": "1.83.0", + "@oxlint/binding-linux-arm-gnueabihf": "1.83.0", + "@oxlint/binding-linux-arm-musleabihf": "1.83.0", + "@oxlint/binding-linux-arm64-gnu": "1.83.0", + "@oxlint/binding-linux-arm64-musl": "1.83.0", + "@oxlint/binding-linux-ppc64-gnu": "1.83.0", + "@oxlint/binding-linux-riscv64-gnu": "1.83.0", + "@oxlint/binding-linux-riscv64-musl": "1.83.0", + "@oxlint/binding-linux-s390x-gnu": "1.83.0", + "@oxlint/binding-linux-x64-gnu": "1.83.0", + "@oxlint/binding-linux-x64-musl": "1.83.0", + "@oxlint/binding-openharmony-arm64": "1.83.0", + "@oxlint/binding-win32-arm64-msvc": "1.83.0", + "@oxlint/binding-win32-ia32-msvc": "1.83.0", + "@oxlint/binding-win32-x64-msvc": "1.83.0" + }, + "peerDependencies": { + "oxlint-tsgolint": ">=7.0.2001", + "vite-plus": "*" + }, + "peerDependenciesMeta": { + "oxlint-tsgolint": { + "optional": true + }, + "vite-plus": { + "optional": true + } + } + }, + "node_modules/oxlint-tsgolint": { + "version": "7.0.2002", + "resolved": "https://registry.npmjs.org/oxlint-tsgolint/-/oxlint-tsgolint-7.0.2002.tgz", + "integrity": "sha512-rQAj2RAQM57nCwTd3PhtnAkREGW2StqI47tzudNlJF8WwGommM7rVpeP4O1O8/ZiuDZ9f2GnTESzLHGTluwkvQ==", + "dev": true, + "license": "MIT", + "bin": { + "tsgolint": "bin/tsgolint.js" + }, + "optionalDependencies": { + "@oxlint-tsgolint/darwin-arm64": "7.0.2002", + "@oxlint-tsgolint/darwin-x64": "7.0.2002", + "@oxlint-tsgolint/linux-arm64": "7.0.2002", + "@oxlint-tsgolint/linux-x64": "7.0.2002", + "@oxlint-tsgolint/win32-arm64": "7.0.2002", + "@oxlint-tsgolint/win32-x64": "7.0.2002" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -1700,6 +2436,16 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, + "node_modules/tinypool": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-2.1.2.tgz", + "integrity": "sha512-9YodfrxS9g9IbFr/KOjE5bAeJ0p61n3bW6mqvy0jtoeKd1kTW1Cxm0oulm6KX2lyM9Gl6WIe8nEbY7LWv5ZJww==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.0.0 || >=22.0.0" + } + }, "node_modules/tldts": { "version": "7.4.13", "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.4.13.tgz", diff --git a/package.json b/package.json index e6e3788..a3702dc 100644 --- a/package.json +++ b/package.json @@ -1,30 +1,39 @@ { "name": "rdapper", "version": "0.13.0", - "license": "MIT", "description": "🎩 RDAP/WHOIS fetcher, parser, and normalizer for Node", - "repository": { - "type": "git", - "url": "git+https://github.com/jakejarvis/rdapper.git" - }, + "keywords": [ + "domain", + "lookup", + "normalizer", + "parser", + "rdap", + "registration", + "whois" + ], "bugs": { "url": "https://github.com/jakejarvis/rdapper/issues" }, + "license": "MIT", "author": { "name": "Jake Jarvis", "email": "jake@jarv.is", "url": "https://jarv.is" }, - "publishConfig": { - "access": "public" + "repository": { + "type": "git", + "url": "git+https://github.com/jakejarvis/rdapper.git" }, + "bin": { + "rdapper": "bin/cli.mjs" + }, + "files": [ + "dist" + ], "type": "module", "main": "./dist/index.mjs", "module": "./dist/index.mjs", "types": "./dist/index.d.mts", - "bin": { - "rdapper": "bin/cli.mjs" - }, "exports": { ".": { "types": "./dist/index.d.mts", @@ -32,25 +41,28 @@ "default": "./dist/index.mjs" } }, - "files": [ - "dist" - ], + "publishConfig": { + "access": "public" + }, "scripts": { "build": "tsdown", "dev": "tsdown --watch", "cli": "node bin/cli.mjs", - "typecheck": "tsc --noEmit", - "test": "vitest", - "test:run": "vitest run", - "lint": "biome check", + "lint": "oxlint", + "lint:fix": "oxlint --fix", + "fmt": "oxfmt", + "fmt:check": "oxfmt --check", + "test": "vitest run", "prepublishOnly": "npm run build" }, "dependencies": { "tldts": "~7.4.13" }, "devDependencies": { - "@biomejs/biome": "2.3.11", "@types/node": "^26.6.1", + "oxfmt": "^0.68.0", + "oxlint": "^1.83.0", + "oxlint-tsgolint": "^7.0.2002", "tsdown": "^0.23.0", "typescript": "^6.0.3", "vite": "^8.3.0", @@ -58,14 +70,5 @@ }, "engines": { "node": ">=18.17" - }, - "keywords": [ - "rdap", - "whois", - "domain", - "registration", - "lookup", - "parser", - "normalizer" - ] + } } diff --git a/src/index.smoke.test.ts b/src/index.smoke.test.ts index 92ad46b..d93dc39 100644 --- a/src/index.smoke.test.ts +++ b/src/index.smoke.test.ts @@ -15,9 +15,7 @@ maybeTest("lookup smoke test (example.com)", async () => { expect(res.ok, res.error).toBe(true); expect(Boolean(res.record?.domain)).toBe(true); expect(Boolean(res.record?.tld)).toBe(true); - expect(res.record?.source === "rdap" || res.record?.source === "whois").toBe( - true, - ); + expect(res.record?.source === "rdap" || res.record?.source === "whois").toBe(true); }); // RDAP-only smoke for reserved example domains (.com/.net/.org) @@ -42,9 +40,7 @@ for (const c of rdapCases) { if (c.tld !== "org") { // .com/.net often include the IANA reserved name explicitly expect( - (rec.registrar?.name || "") - .toLowerCase() - .includes("internet assigned numbers authority"), + (rec.registrar?.name || "").toLowerCase().includes("internet assigned numbers authority"), ).toBe(true); } // IANA nameservers @@ -112,9 +108,7 @@ maybeTest("WHOIS-only lookup for example.io", async () => { }); maybeTest("isRegistered true for example.com", async () => { - await expect(isRegistered("example.com", { timeoutMs: 15000 })).resolves.toBe( - true, - ); + await expect(isRegistered("example.com", { timeoutMs: 15000 })).resolves.toBe(true); }); maybeTest("isAvailable true for an unlikely .com", async () => { diff --git a/src/index.test.ts b/src/index.test.ts index 5e0bb89..b6e5a94 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -30,18 +30,13 @@ vi.mock("./whois/referral.js", async () => { const client = await import("./whois/client.js"); return { followWhoisReferrals: vi.fn( - async ( - server: string, - domain: string, - opts?: import("./types").LookupOptions, - ) => client.whoisQuery(server, domain, opts), + async (server: string, domain: string, opts?: import("./types").LookupOptions) => + client.whoisQuery(server, domain, opts), ), collectWhoisReferralChain: vi.fn( - async ( - server: string, - domain: string, - opts?: import("./types").LookupOptions, - ) => [await client.whoisQuery(server, domain, opts)], + async (server: string, domain: string, opts?: import("./types").LookupOptions) => [ + await client.whoisQuery(server, domain, opts), + ], ), }; }); @@ -55,8 +50,7 @@ vi.mock("./whois/discovery.js", async () => { }); vi.mock("./lib/domain.js", async () => { - const actual = - await vi.importActual("./lib/domain.js"); + const actual = await vi.importActual("./lib/domain.js"); return { ...actual, // Default to actual behavior; specific tests can override @@ -75,9 +69,7 @@ import * as whoisReferral from "./whois/referral"; describe("lookup orchestration", () => { beforeEach(() => { vi.clearAllMocks(); - vi.mocked(discovery.ianaWhoisServerForTld).mockResolvedValue( - "whois.verisign-grs.com", - ); + vi.mocked(discovery.ianaWhoisServerForTld).mockResolvedValue("whois.verisign-grs.com"); }); it("uses RDAP when available and does not call WHOIS", async () => { @@ -89,9 +81,7 @@ describe("lookup orchestration", () => { }); it("falls back to WHOIS when RDAP fails", async () => { - vi.mocked(rdapClient.fetchRdapDomain).mockRejectedValueOnce( - new Error("rdap down"), - ); + vi.mocked(rdapClient.fetchRdapDomain).mockRejectedValueOnce(new Error("rdap down")); const res = await lookup("example.com", { timeoutMs: 200 }); expect(res.ok, res.error).toBe(true); expect(res.record?.source).toBe("whois"); @@ -155,9 +145,7 @@ describe("RDAP 404 handling", () => { describe("WHOIS referral & includeRaw", () => { beforeEach(() => { vi.clearAllMocks(); - vi.mocked(discovery.ianaWhoisServerForTld).mockResolvedValue( - "whois.verisign-grs.com", - ); + vi.mocked(discovery.ianaWhoisServerForTld).mockResolvedValue("whois.verisign-grs.com"); }); it("does not follow referral when followWhoisReferral is false", async () => { diff --git a/src/index.ts b/src/index.ts index a8391cd..c53da8c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -11,19 +11,13 @@ import { } from "./whois/discovery"; import { mergeWhoisRecords } from "./whois/merge"; import { normalizeWhois } from "./whois/normalize"; -import { - collectWhoisReferralChain, - followWhoisReferrals, -} from "./whois/referral"; +import { collectWhoisReferralChain, followWhoisReferrals } from "./whois/referral"; /** * High-level lookup that prefers RDAP and falls back to WHOIS. * Ensures a standardized DomainRecord, independent of the source. */ -export async function lookup( - domain: string, - opts?: LookupOptions, -): Promise { +export async function lookup(domain: string, opts?: LookupOptions): Promise { try { if (!isLikelyDomain(domain)) { return { ok: false, error: "Input does not look like a domain" }; @@ -61,11 +55,7 @@ export async function lookup( return { ok: true, record }; } - const rdapEnriched = await fetchAndMergeRdapRelated( - domain, - json, - opts, - ); + const rdapEnriched = await fetchAndMergeRdapRelated(domain, json, opts); const record: DomainRecord = normalizeRdap( domain, tld, @@ -92,9 +82,7 @@ export async function lookup( if (!whoisServer) { // Provide a clearer, actionable message const ianaText = await getIanaWhoisTextForTld(tld, opts); - const regUrl = ianaText - ? parseIanaRegistrationInfoUrl(ianaText) - : undefined; + const regUrl = ianaText ? parseIanaRegistrationInfoUrl(ianaText) : undefined; const hint = regUrl ? ` See registration info at ${regUrl}.` : ""; return { ok: false, @@ -138,10 +126,7 @@ export async function lookup( * Determine if a domain appears available (not registered). * Performs a lookup and resolves to a boolean. Rejects on lookup error. */ -export async function isAvailable( - domain: string, - opts?: LookupOptions, -): Promise { +export async function isAvailable(domain: string, opts?: LookupOptions): Promise { const res = await lookup(domain, opts); if (!res.ok || !res.record) throw new Error(res.error || "Lookup failed"); return res.record.isRegistered === false; @@ -151,10 +136,7 @@ export async function isAvailable( * Determine if a domain appears registered. * Performs a lookup and resolves to a boolean. Rejects on lookup error. */ -export async function isRegistered( - domain: string, - opts?: LookupOptions, -): Promise { +export async function isRegistered(domain: string, opts?: LookupOptions): Promise { const res = await lookup(domain, opts); if (!res.ok || !res.record) throw new Error(res.error || "Lookup failed"); return res.record.isRegistered === true; @@ -165,10 +147,5 @@ export async function isRegistered( */ export const lookupDomain = lookup; -export { - getDomainParts, - getDomainTld, - isLikelyDomain, - toRegistrableDomain, -} from "./lib/domain"; +export { getDomainParts, getDomainTld, isLikelyDomain, toRegistrableDomain } from "./lib/domain"; export type * from "./types"; diff --git a/src/lib/dates.ts b/src/lib/dates.ts index 000678d..677d807 100644 --- a/src/lib/dates.ts +++ b/src/lib/dates.ts @@ -1,8 +1,6 @@ // Lightweight date parsing helpers to avoid external dependencies. // We aim to parse common RDAP and WHOIS date representations and return a UTC ISO string. -export function toISO( - dateLike: string | number | Date | undefined | null, -): string | undefined { +export function toISO(dateLike: string | number | Date | undefined | null): string | undefined { if (dateLike == null) return undefined; if (dateLike instanceof Date) return toIsoFromDate(dateLike); if (typeof dateLike === "number") return toIsoFromDate(new Date(dateLike)); @@ -53,10 +51,7 @@ function toIsoFromDate(d: Date): string | undefined { } } -function parseDateWithRegex( - m: RegExpMatchArray, - _re: RegExp, -): Date | undefined { +function parseDateWithRegex(m: RegExpMatchArray, _re: RegExp): Date | undefined { const monthMap: Record = { jan: 0, feb: 1, @@ -77,14 +72,7 @@ function parseDateWithRegex( const [_, y, mo, d, hh, mm, ss, offH, offM] = m; if (!y || !mo || !d || !hh || !mm || !ss) return undefined; // Base time as UTC - let dt = Date.UTC( - Number(y), - Number(mo) - 1, - Number(d), - Number(hh), - Number(mm), - Number(ss), - ); + let dt = Date.UTC(Number(y), Number(mo) - 1, Number(d), Number(hh), Number(mm), Number(ss)); // Apply timezone offset if present (e.g., +0000, -0500, +05:30) if (offH) { const sign = offH.startsWith("-") ? -1 : 1; diff --git a/src/lib/domain.test.ts b/src/lib/domain.test.ts index b498228..2d01f28 100644 --- a/src/lib/domain.test.ts +++ b/src/lib/domain.test.ts @@ -14,15 +14,11 @@ test("isLikelyDomain", () => { test("toRegistrableDomain normalizes eTLD+1 and rejects non-ICANN", () => { // Basic domains expect(toRegistrableDomain("example.com")).toBe("example.com"); - expect(toRegistrableDomain("http://www.writethedocs.org/conf")).toBe( - "writethedocs.org", - ); + expect(toRegistrableDomain("http://www.writethedocs.org/conf")).toBe("writethedocs.org"); // Private/public SLDs should collapse to ICANN TLD + SLD by default // (ICANN-only behavior; private suffixes ignored) - expect(toRegistrableDomain("spark-public.s3.amazonaws.com")).toBe( - "amazonaws.com", - ); + expect(toRegistrableDomain("spark-public.s3.amazonaws.com")).toBe("amazonaws.com"); // Reject IPs and invalid inputs expect(toRegistrableDomain("192.168.0.1")).toBeNull(); diff --git a/src/lib/domain.ts b/src/lib/domain.ts index 92956f7..73cb0a3 100644 --- a/src/lib/domain.ts +++ b/src/lib/domain.ts @@ -6,10 +6,7 @@ type ParseOptions = Parameters[1]; * Parse a domain into its parts. Passes options to `tldts.parse()`. * @see https://github.com/remusao/tldts/blob/master/packages/tldts-core/src/options.ts */ -export function getDomainParts( - domain: string, - opts?: ParseOptions, -): ReturnType { +export function getDomainParts(domain: string, opts?: ParseOptions): ReturnType { return parse(domain, { ...opts }); } @@ -17,10 +14,7 @@ export function getDomainParts( * Get the TLD (ICANN-only public suffix) of a domain. Passes options to `tldts.parse()`. * @see https://github.com/remusao/tldts/blob/master/packages/tldts-core/src/options.ts */ -export function getDomainTld( - domain: string, - opts?: ParseOptions, -): string | null { +export function getDomainTld(domain: string, opts?: ParseOptions): string | null { const result = getDomainParts(domain, { allowPrivateDomains: false, ...opts, @@ -54,10 +48,7 @@ export function punyToUnicode(domain: string): string { * Returns null when the input is not a valid ICANN domain (e.g., invalid TLD, IPs) * @see https://github.com/remusao/tldts/blob/master/packages/tldts-core/src/options.ts */ -export function toRegistrableDomain( - input: string, - opts?: ParseOptions, -): string | null { +export function toRegistrableDomain(input: string, opts?: ParseOptions): string | null { const raw = (input ?? "").trim(); if (raw === "") return null; diff --git a/src/lib/text.ts b/src/lib/text.ts index 9dafc18..07ebb30 100644 --- a/src/lib/text.ts +++ b/src/lib/text.ts @@ -69,11 +69,6 @@ export function asStringArray(value: unknown): string[] | undefined { } export function asDateLike(value: unknown): string | number | Date | undefined { - if ( - typeof value === "string" || - typeof value === "number" || - value instanceof Date - ) - return value; + if (typeof value === "string" || typeof value === "number" || value instanceof Date) return value; return undefined; } diff --git a/src/rdap/bootstrap.test.ts b/src/rdap/bootstrap.test.ts index 7b1accf..4c76512 100644 --- a/src/rdap/bootstrap.test.ts +++ b/src/rdap/bootstrap.test.ts @@ -1,12 +1,4 @@ -import { - afterAll, - beforeAll, - beforeEach, - describe, - expect, - it, - vi, -} from "vitest"; +import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import type { BootstrapData } from "../types"; import { getRdapBaseUrlsForTld } from "./bootstrap"; @@ -110,9 +102,7 @@ describe("getRdapBaseUrlsForTld with customBootstrapData", () => { const dataWithDuplicates: BootstrapData = { version: "1.0", publication: "2025-01-15T12:00:00Z", - services: [ - [["test"], ["https://rdap.example.com/", "https://rdap.example.com"]], - ], + services: [[["test"], ["https://rdap.example.com/", "https://rdap.example.com"]]], }; const urls = await getRdapBaseUrlsForTld("test", { diff --git a/src/rdap/client.ts b/src/rdap/client.ts index 54e6530..50062bf 100644 --- a/src/rdap/client.ts +++ b/src/rdap/client.ts @@ -24,10 +24,7 @@ export async function fetchRdapDomain( baseUrl: string, options?: LookupOptions, ): Promise { - const url = new URL( - `domain/${encodeURIComponent(domain)}`, - baseUrl, - ).toString(); + const url = new URL(`domain/${encodeURIComponent(domain)}`, baseUrl).toString(); const fetchFn = resolveFetch(options); const res = await withTimeout( fetchFn(url, { diff --git a/src/rdap/links.ts b/src/rdap/links.ts index adaf4b7..87b89aa 100644 --- a/src/rdap/links.ts +++ b/src/rdap/links.ts @@ -13,9 +13,7 @@ export function extractRdapRelatedLinks( opts?: Pick, ): string[] { const rels = ( - opts?.rdapLinkRels?.length - ? opts.rdapLinkRels - : ["related", "entity", "registrar", "alternate"] + opts?.rdapLinkRels?.length ? opts.rdapLinkRels : ["related", "entity", "registrar", "alternate"] ).map((r) => r.toLowerCase()); const d = (doc ?? {}) as Record & { links?: RdapLink[] }; const arr = Array.isArray(d?.links) ? (d.links as RdapLink[]) : []; diff --git a/src/rdap/merge.ts b/src/rdap/merge.ts index affcad8..99f2b91 100644 --- a/src/rdap/merge.ts +++ b/src/rdap/merge.ts @@ -6,38 +6,40 @@ import { extractRdapRelatedLinks } from "./links"; type Json = Record; +/** Coerce a loosely-typed JSON field to a string ("" for null/objects/etc.). */ +function str(val: unknown): string { + return typeof val === "string" || typeof val === "number" || typeof val === "boolean" + ? String(val) + : ""; +} + /** Merge RDAP documents with a conservative, additive strategy. */ export function mergeRdapDocs(baseDoc: unknown, others: unknown[]): unknown { const merged: Json = { ...(baseDoc as Json) }; for (const doc of others) { const cur = (doc ?? {}) as Json; // status: array of strings - merged.status = uniqStrings([ - ...toStringArray(merged.status), - ...toStringArray(cur.status), - ]); + merged.status = uniqStrings([...toStringArray(merged.status), ...toStringArray(cur.status)]); // events: array of objects; dedupe by eventAction + eventDate merged.events = uniqBy( [...toArray(merged.events), ...toArray(cur.events)], - (e) => - `${String(e?.eventAction ?? "").toLowerCase()}|${String(e?.eventDate ?? "")}`, + (e) => `${str(e?.eventAction).toLowerCase()}|${str(e?.eventDate)}`, ); // nameservers: array of objects; dedupe by ldhName/unicodeName merged.nameservers = uniqBy( [...toArray(merged.nameservers), ...toArray(cur.nameservers)], - (n) => `${String(n?.ldhName ?? n?.unicodeName ?? "").toLowerCase()}`, + (n) => `${str(n?.ldhName ?? n?.unicodeName).toLowerCase()}`, ); // entities: array; dedupe by handle if present, else by roles+vcard hash merged.entities = uniqBy( [...toArray(merged.entities), ...toArray(cur.entities)], (e) => - `${String(e?.handle ?? "").toLowerCase()}|${String( + `${str(e?.handle).toLowerCase()}|${String( JSON.stringify(e?.roles || []), ).toLowerCase()}|${String(JSON.stringify(e?.vcardArray || [])).toLowerCase()}`, ); // secureDNS: prefer existing; fill if missing - if (merged.secureDNS == null && cur.secureDNS != null) - merged.secureDNS = cur.secureDNS; + if (merged.secureDNS == null && cur.secureDNS != null) merged.secureDNS = cur.secureDNS; // port43 (authoritative WHOIS): prefer existing; fill if missing if (merged.port43 == null && cur.port43 != null) merged.port43 = cur.port43; // remarks: concat simple strings if present @@ -59,8 +61,7 @@ export async function fetchAndMergeRdapRelated( opts?: LookupOptions, ): Promise<{ merged: unknown; serversTried: string[] }> { const tried: string[] = []; - if (opts?.rdapFollowLinks === false) - return { merged: baseDoc, serversTried: tried }; + if (opts?.rdapFollowLinks === false) return { merged: baseDoc, serversTried: tried }; const maxHops = Math.max(0, opts?.maxRdapLinkHops ?? 2); if (maxHops === 0) return { merged: baseDoc, serversTried: tried }; @@ -83,8 +84,8 @@ export async function fetchAndMergeRdapRelated( tried.push(url); // only accept docs that appear related to the same domain when possible // if ldhName/unicodeName present, they should match the queried domain (case-insensitive) - const ldh = String((json as Json)?.ldhName ?? "").toLowerCase(); - const uni = String((json as Json)?.unicodeName ?? "").toLowerCase(); + const ldh = str((json as Json)?.ldhName).toLowerCase(); + const uni = str((json as Json)?.unicodeName).toLowerCase(); if (ldh && !sameDomain(ldh, domain)) continue; if (uni && !sameDomain(uni, domain)) continue; fetchedDocs.push(json); diff --git a/src/rdap/normalize.test.ts b/src/rdap/normalize.test.ts index 094e221..3a43486 100644 --- a/src/rdap/normalize.test.ts +++ b/src/rdap/normalize.test.ts @@ -47,9 +47,7 @@ test("normalizeRdap maps registrar, contacts, nameservers, events, dnssec", () = ], secureDNS: { delegationSigned: true, - dsData: [ - { keyTag: 12345, algorithm: 13, digestType: 2, digest: "ABCDEF" }, - ], + dsData: [{ keyTag: 12345, algorithm: 13, digestType: 2, digest: "ABCDEF" }], }, events: [ { eventAction: "registration", eventDate: "2020-01-02T03:04:05Z" }, @@ -59,9 +57,7 @@ test("normalizeRdap maps registrar, contacts, nameservers, events, dnssec", () = status: ["clientTransferProhibited"], port43: "whois.example-registrar.test", }; - const rec = normalizeRdap("example.com", "com", rdap, [ - "https://rdap.example/", - ]); + const rec = normalizeRdap("example.com", "com", rdap, ["https://rdap.example/"]); expect(rec.domain).toBe("example.com"); expect(rec.tld).toBe("com"); expect(rec.registrar?.name).toBe("Registrar LLC"); @@ -95,9 +91,7 @@ test("normalizeRdap derives privacyEnabled from registrant keywords", () => { }, ], }; - const rec = normalizeRdap("example.com", "com", rdap, [ - "https://rdap.example/", - ]); + const rec = normalizeRdap("example.com", "com", rdap, ["https://rdap.example/"]); expect(rec.privacyEnabled).toBe(true); }); diff --git a/src/rdap/normalize.ts b/src/rdap/normalize.ts index 3e2c9c4..cc17878 100644 --- a/src/rdap/normalize.ts +++ b/src/rdap/normalize.ts @@ -1,12 +1,7 @@ import { toISO } from "../lib/dates"; import { isPrivacyName } from "../lib/privacy"; import { asDateLike, asString, asStringArray, uniq } from "../lib/text"; -import type { - Contact, - DomainRecord, - Nameserver, - RegistrarInfo, -} from "../types"; +import type { Contact, DomainRecord, Nameserver, RegistrarInfo } from "../types"; type RdapDoc = Record; @@ -24,24 +19,17 @@ export function normalizeRdap( const doc = (rdap ?? {}) as RdapDoc; // Prefer ldhName (punycode) and unicodeName if provided - const ldhName: string | undefined = - asString(doc.ldhName) || asString(doc.handle); + const ldhName: string | undefined = asString(doc.ldhName) || asString(doc.handle); const unicodeName: string | undefined = asString(doc.unicodeName); // Registrar entity can be provided with role "registrar" - const registrar: RegistrarInfo | undefined = extractRegistrar( - doc.entities as unknown, - ); + const registrar: RegistrarInfo | undefined = extractRegistrar(doc.entities as unknown); // Nameservers: normalize host + IPs const nameservers: Nameserver[] | undefined = Array.isArray(doc.nameservers) ? (doc.nameservers as RdapDoc[]) .map((ns) => { - const host = ( - asString(ns.ldhName) ?? - asString(ns.unicodeName) ?? - "" - ).toLowerCase(); + const host = (asString(ns.ldhName) ?? asString(ns.unicodeName) ?? "").toLowerCase(); const ip = ns.ipAddresses as RdapDoc | undefined; const ipv4 = asStringArray(ip?.v4); const ipv6 = asStringArray(ip?.v6); @@ -54,17 +42,13 @@ export function normalizeRdap( : undefined; // Contacts: RDAP entities include roles like registrant, administrative, technical, billing, abuse - const contacts: Contact[] | undefined = extractContacts( - doc.entities as unknown, - ); + const contacts: Contact[] | undefined = extractContacts(doc.entities as unknown); // Derive privacy flag from registrant name/org keywords const registrant = contacts?.find((c) => c.type === "registrant"); const privacyEnabled = !!( registrant && - ( - [registrant.name, registrant.organization].filter(Boolean) as string[] - ).some(isPrivacyName) + ([registrant.name, registrant.organization].filter(Boolean) as string[]).some(isPrivacyName) ); // RDAP uses IANA EPP status values. Preserve raw plus a description if any remarks are present. @@ -99,21 +83,16 @@ export function normalizeRdap( : []; const byAction = (action: string) => events.find( - (e) => - typeof e?.eventAction === "string" && - e.eventAction.toLowerCase().includes(action), + (e) => typeof e?.eventAction === "string" && e.eventAction.toLowerCase().includes(action), ); const creationDate = toISO( - asDateLike(byAction("registration")?.eventDate) ?? - asDateLike(doc.registrationDate), + asDateLike(byAction("registration")?.eventDate) ?? asDateLike(doc.registrationDate), ); const updatedDate = toISO( - asDateLike(byAction("last changed")?.eventDate) ?? - asDateLike(doc.lastChangedDate), + asDateLike(byAction("last changed")?.eventDate) ?? asDateLike(doc.lastChangedDate), ); const expirationDate = toISO( - asDateLike(byAction("expiration")?.eventDate) ?? - asDateLike(doc.expirationDate), + asDateLike(byAction("expiration")?.eventDate) ?? asDateLike(doc.expirationDate), ); const deletionDate = toISO( asDateLike(byAction("deletion")?.eventDate) ?? asDateLike(doc.deletionDate), @@ -164,9 +143,7 @@ function extractRegistrar(entities: unknown): RegistrarInfo | undefined { if (!Array.isArray(entities)) return undefined; for (const ent of entities) { const roles: string[] = Array.isArray((ent as RdapDoc)?.roles) - ? ((ent as RdapDoc).roles as unknown[]).filter( - (r): r is string => typeof r === "string", - ) + ? ((ent as RdapDoc).roles as unknown[]).filter((r): r is string => typeof r === "string") : []; if (!roles.some((r) => /registrar/i.test(r))) continue; const v = parseVcard((ent as RdapDoc)?.vcardArray); @@ -191,9 +168,7 @@ function extractContacts(entities: unknown): Contact[] | undefined { const out: Contact[] = []; for (const ent of entities) { const roles: string[] = Array.isArray((ent as RdapDoc)?.roles) - ? ((ent as RdapDoc).roles as unknown[]).filter( - (r): r is string => typeof r === "string", - ) + ? ((ent as RdapDoc).roles as unknown[]).filter((r): r is string => typeof r === "string") : []; const v = parseVcard((ent as RdapDoc)?.vcardArray); const type = roles.find((r) => @@ -245,15 +220,9 @@ interface ParsedVCard { // Parse a minimal subset of vCard 4.0 arrays as used in RDAP "vcardArray" fields function parseVcard(vcardArray: unknown): ParsedVCard { // vcardArray is typically ["vcard", [["version",{} ,"text","4.0"], ["fn",{} ,"text","Example"], ...]] - if ( - !Array.isArray(vcardArray) || - vcardArray[0] !== "vcard" || - !Array.isArray(vcardArray[1]) - ) + if (!Array.isArray(vcardArray) || vcardArray[0] !== "vcard" || !Array.isArray(vcardArray[1])) return {}; - const entries = vcardArray[1] as Array< - [string, Record, string, unknown] - >; + const entries = vcardArray[1] as Array<[string, Record, string, unknown]>; const out: ParsedVCard = {}; for (const e of entries) { const key = e?.[0]; @@ -264,9 +233,7 @@ function parseVcard(vcardArray: unknown): ParsedVCard { out.fn = asString(value); break; case "org": - out.org = Array.isArray(value) - ? value.map((x) => String(x)).join(" ") - : asString(value); + out.org = Array.isArray(value) ? value.map((x) => String(x)).join(" ") : asString(value); break; case "email": out.email = asString(value); diff --git a/src/types.ts b/src/types.ts index e4b0d67..18e87b3 100644 --- a/src/types.ts +++ b/src/types.ts @@ -372,7 +372,4 @@ export interface LookupResult { * Used internally for dependency injection and testing. Matches the signature * of the global `fetch` function available in Node.js 18+ and browsers. */ -export type FetchLike = ( - input: string | URL, - init?: RequestInit, -) => Promise; +export type FetchLike = (input: string | URL, init?: RequestInit) => Promise; diff --git a/src/whois/merge.test.ts b/src/whois/merge.test.ts index 9eb6fe9..01f767f 100644 --- a/src/whois/merge.test.ts +++ b/src/whois/merge.test.ts @@ -31,13 +31,7 @@ describe("WHOIS coalescing", () => { const [first] = chain; if (!first) throw new Error("Expected first record"); - const base = normalizeWhois( - "gitpod.io", - "io", - first.text, - first.serverQueried, - false, - ); + const base = normalizeWhois("gitpod.io", "io", first.text, first.serverQueried, false); const merged = mergeWhoisRecords(base, []); expect(merged.isRegistered).toBe(true); expect(merged.creationDate).toBeDefined(); diff --git a/src/whois/merge.ts b/src/whois/merge.ts index 2fb2550..a841ed9 100644 --- a/src/whois/merge.ts +++ b/src/whois/merge.ts @@ -1,10 +1,7 @@ import { uniq } from "../lib/text"; import type { Contact, DomainRecord, Nameserver } from "../types"; -function dedupeStatuses( - a?: DomainRecord["statuses"], - b?: DomainRecord["statuses"], -) { +function dedupeStatuses(a?: DomainRecord["statuses"], b?: DomainRecord["statuses"]) { const list = [...(a || []), ...(b || [])]; const seen = new Set(); const out: NonNullable = []; @@ -48,10 +45,7 @@ function dedupeContacts(a?: Contact[], b?: Contact[]) { } /** Conservative merge: start with base; fill missing scalars; union arrays; prefer more informative dates. */ -export function mergeWhoisRecords( - base: DomainRecord, - others: DomainRecord[], -): DomainRecord { +export function mergeWhoisRecords(base: DomainRecord, others: DomainRecord[]): DomainRecord { const merged: DomainRecord = { ...base }; for (const cur of others) { merged.isRegistered = merged.isRegistered || cur.isRegistered; @@ -60,15 +54,9 @@ export function mergeWhoisRecords( merged.reseller = merged.reseller ?? cur.reseller; merged.statuses = dedupeStatuses(merged.statuses, cur.statuses); // Dates: prefer earliest creation, latest updated/expiration when available - merged.creationDate = preferEarliestIso( - merged.creationDate, - cur.creationDate, - ); + merged.creationDate = preferEarliestIso(merged.creationDate, cur.creationDate); merged.updatedDate = preferLatestIso(merged.updatedDate, cur.updatedDate); - merged.expirationDate = preferLatestIso( - merged.expirationDate, - cur.expirationDate, - ); + merged.expirationDate = preferLatestIso(merged.expirationDate, cur.expirationDate); merged.deletionDate = merged.deletionDate ?? cur.deletionDate; merged.transferLock = Boolean(merged.transferLock || cur.transferLock); merged.dnssec = merged.dnssec ?? cur.dnssec; diff --git a/src/whois/normalize.test.ts b/src/whois/normalize.test.ts index 750e159..24bdb66 100644 --- a/src/whois/normalize.test.ts +++ b/src/whois/normalize.test.ts @@ -159,12 +159,7 @@ Name Server: NS2.EXAMPLE.COM DNSSEC: unsigned Domain Status: clientTransferProhibited https://icann.org/epp#clientTransferProhibited `; - const rec = normalizeWhois( - "example.com", - "com", - text, - "whois.verisign-grs.com", - ); + const rec = normalizeWhois("example.com", "com", text, "whois.verisign-grs.com"); expect(Boolean(rec.creationDate)).toBe(true); expect(Boolean(rec.expirationDate)).toBe(true); expect(rec.source).toBe("whois"); @@ -178,12 +173,7 @@ Registrar URL: http://www.registrar.test Registrant Name: REDACTED FOR PRIVACY Registrant Organization: Example Org `; - const rec = normalizeWhois( - "example.com", - "com", - text, - "whois.verisign-grs.com", - ); + const rec = normalizeWhois("example.com", "com", text, "whois.verisign-grs.com"); expect(rec.privacyEnabled).toBe(true); }); diff --git a/src/whois/normalize.ts b/src/whois/normalize.ts index e2d7d4e..1e97940 100644 --- a/src/whois/normalize.ts +++ b/src/whois/normalize.ts @@ -1,12 +1,7 @@ import { toISO } from "../lib/dates"; import { isPrivacyName } from "../lib/privacy"; import { parseKeyValueLines, uniq } from "../lib/text"; -import type { - Contact, - DomainRecord, - Nameserver, - RegistrarInfo, -} from "../types"; +import type { Contact, DomainRecord, Nameserver, RegistrarInfo } from "../types"; // Common WHOIS availability phrases seen across registries/registrars const WHOIS_AVAILABLE_PATTERNS: RegExp[] = [ @@ -123,11 +118,7 @@ export function normalizeWhois( "organisation", "record maintained by", ]); - const ianaId = anyValue(map, [ - "registrar iana id", - "sponsoring registrar iana id", - "iana id", - ]); + const ianaId = anyValue(map, ["registrar iana id", "sponsoring registrar iana id", "iana id"]); const url = anyValue(map, [ "registrar url", "registrar website", @@ -135,16 +126,9 @@ export function normalizeWhois( "url of the registrar", "referrer", ]); - const abuseEmail = anyValue(map, [ - "registrar abuse contact email", - "abuse contact email", - ]); - const abusePhone = anyValue(map, [ - "registrar abuse contact phone", - "abuse contact phone", - ]); - if (!name && !ianaId && !url && !abuseEmail && !abusePhone) - return undefined; + const abuseEmail = anyValue(map, ["registrar abuse contact email", "abuse contact email"]); + const abusePhone = anyValue(map, ["registrar abuse contact phone", "abuse contact phone"]); + if (!name && !ianaId && !url && !abuseEmail && !abusePhone) return undefined; return { name: name || undefined, ianaId: ianaId || undefined, @@ -222,15 +206,11 @@ export function normalizeWhois( const registrant = contacts?.find((c) => c.type === "registrant"); const privacyEnabled = !!( registrant && - ( - [registrant.name, registrant.organization].filter(Boolean) as string[] - ).some(isPrivacyName) + ([registrant.name, registrant.organization].filter(Boolean) as string[]).some(isPrivacyName) ); const dnssecRaw = (map.dnssec?.[0] || "").toLowerCase(); - const dnssec = dnssecRaw - ? { enabled: /signed|yes|true/.test(dnssecRaw) } - : undefined; + const dnssec = dnssecRaw ? { enabled: /signed|yes|true/.test(dnssecRaw) } : undefined; // Simple lock derivation from statuses const transferLock = !!statuses?.some((s) => @@ -268,10 +248,7 @@ export function normalizeWhois( return record; } -function anyValue( - map: Record, - keys: string[], -): string | undefined { +function anyValue(map: Record, keys: string[]): string | undefined { for (const k of keys) { const v = map[k]; if (v?.length) return v[0]; @@ -327,11 +304,7 @@ function collectContacts(map: Record): Contact[] | undefined { nameKeys.push("owner name"); // .tm } - orgKeys.push( - `${prefix} organization`, - `${prefix} organisation`, - `${prefix} org`, - ); + orgKeys.push(`${prefix} organization`, `${prefix} organisation`, `${prefix} org`); if (prefix === "registrant") { orgKeys.push("trading as"); // .uk, .co.uk orgKeys.push("org"); // .ru @@ -340,42 +313,22 @@ function collectContacts(map: Record): Contact[] | undefined { orgKeys.push("owner orgname"); // .tm } - emailKeys.push( - `${prefix} email`, - `${prefix} contact email`, - `${prefix} e-mail`, - ); + emailKeys.push(`${prefix} email`, `${prefix} contact email`, `${prefix} e-mail`); - phoneKeys.push( - `${prefix} phone`, - `${prefix} contact phone`, - `${prefix} telephone`, - ); + phoneKeys.push(`${prefix} phone`, `${prefix} contact phone`, `${prefix} telephone`); faxKeys.push(`${prefix} fax`, `${prefix} facsimile`); - streetKeys.push( - `${prefix} street`, - `${prefix} address`, - `${prefix}'s address`, - ); + streetKeys.push(`${prefix} street`, `${prefix} address`, `${prefix}'s address`); if (prefix === "owner") { streetKeys.push("owner addr"); // .tm } cityKeys.push(`${prefix} city`); - stateKeys.push( - `${prefix} state`, - `${prefix} province`, - `${prefix} state/province`, - ); + stateKeys.push(`${prefix} state`, `${prefix} province`, `${prefix} state/province`); - postalCodeKeys.push( - `${prefix} postal code`, - `${prefix} postcode`, - `${prefix} zip`, - ); + postalCodeKeys.push(`${prefix} postal code`, `${prefix} postcode`, `${prefix} zip`); countryKeys.push(`${prefix} country`); } @@ -410,10 +363,7 @@ function collectContacts(map: Record): Contact[] | undefined { return contacts.length ? contacts : undefined; } -function multi( - map: Record, - keys: string[], -): string[] | undefined { +function multi(map: Record, keys: string[]): string[] | undefined { for (const k of keys) { const v = map[k]; if (v?.length) return v; diff --git a/src/whois/referral.test.ts b/src/whois/referral.test.ts index ff5ef6b..b78c46c 100644 --- a/src/whois/referral.test.ts +++ b/src/whois/referral.test.ts @@ -32,11 +32,10 @@ describe("WHOIS referral contradiction handling", () => { }); it("collects chain and does not append contradictory registrar", async () => { - const chain = await collectWhoisReferralChain( - "whois.nic.io", - "raindrop.io", - { followWhoisReferral: true, maxWhoisReferralHops: 2 }, - ); + const chain = await collectWhoisReferralChain("whois.nic.io", "raindrop.io", { + followWhoisReferral: true, + maxWhoisReferralHops: 2, + }); expect(Array.isArray(chain)).toBe(true); // Mocked registrar is contradictory, so chain should contain only the TLD response expect(chain.length).toBe(1);