From 9e7b602d66022acba1ef53c7b88184d0116cbf4a Mon Sep 17 00:00:00 2001 From: "pactflow-renovate-bot[bot]" <186667433+pactflow-renovate-bot[bot]@users.noreply.github.com> Date: Fri, 26 Jun 2026 08:13:32 +0000 Subject: [PATCH 1/2] chore(deps): update dependency @biomejs/biome to v2.5.1 Ref: PACT-445 --- package-lock.json | 72 +++++++++++++++++++++++------------------------ package.json | 2 +- 2 files changed, 37 insertions(+), 37 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3e67898..5bc9812 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,7 @@ "spectre.css": "^0.5.9" }, "devDependencies": { - "@biomejs/biome": "2.4.16", + "@biomejs/biome": "2.5.1", "@pact-foundation/pact": "16.5.0", "@testing-library/jest-dom": "6.9.1", "@types/node": "25.9.4", @@ -91,9 +91,9 @@ "license": "MIT" }, "node_modules/@biomejs/biome": { - "version": "2.4.16", - "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.4.16.tgz", - "integrity": "sha512-x9ajFh1zChVybCiM3TN6OD4phAqLgtPZjFrZF+aTMYCPjwBO+k529TX7PPsAqtGNLeV4UgzwQnowEgS7bGmzcA==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.5.1.tgz", + "integrity": "sha512-IXWLCxKmae+rI7LOHS1B3EbVisQ6GRAWbhN9msa6KjNCyFWrvKZWR4oUdinaNssrV852OrSHuSPa95h1GPJc7Q==", "dev": true, "license": "MIT OR Apache-2.0", "bin": { @@ -107,20 +107,20 @@ "url": "https://opencollective.com/biome" }, "optionalDependencies": { - "@biomejs/cli-darwin-arm64": "2.4.16", - "@biomejs/cli-darwin-x64": "2.4.16", - "@biomejs/cli-linux-arm64": "2.4.16", - "@biomejs/cli-linux-arm64-musl": "2.4.16", - "@biomejs/cli-linux-x64": "2.4.16", - "@biomejs/cli-linux-x64-musl": "2.4.16", - "@biomejs/cli-win32-arm64": "2.4.16", - "@biomejs/cli-win32-x64": "2.4.16" + "@biomejs/cli-darwin-arm64": "2.5.1", + "@biomejs/cli-darwin-x64": "2.5.1", + "@biomejs/cli-linux-arm64": "2.5.1", + "@biomejs/cli-linux-arm64-musl": "2.5.1", + "@biomejs/cli-linux-x64": "2.5.1", + "@biomejs/cli-linux-x64-musl": "2.5.1", + "@biomejs/cli-win32-arm64": "2.5.1", + "@biomejs/cli-win32-x64": "2.5.1" } }, "node_modules/@biomejs/cli-darwin-arm64": { - "version": "2.4.16", - "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.4.16.tgz", - "integrity": "sha512-wxPvu4XOA85YJk9ixSWUmq/QBHbid85BISbOAqqBM/5xQpPk9ayjk5375tOlSC0BeCwNSbPFafQBm+vBumXq0A==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.5.1.tgz", + "integrity": "sha512-npqDzvqv7vFaWRiNN1Te71siRgPaqS9MpqgYCdP/CrUbkJ7ApezaeaKjueKHRN/JH/6lRjJQAHi8acQDCAz22w==", "cpu": [ "arm64" ], @@ -135,9 +135,9 @@ } }, "node_modules/@biomejs/cli-darwin-x64": { - "version": "2.4.16", - "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.4.16.tgz", - "integrity": "sha512-xFCqGPwYusQJp4N4NJLi1XJiZqjwFdjhT+KqtNy+Ug3qgfczqnTa6MSDvxJF6TkuDLoYJItMapz6tAf7kCekFw==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.5.1.tgz", + "integrity": "sha512-RgwTqPAM8g2tn1j+b5oRjF/DbSBX8a4gwojtuG9XuhfK7GgomvZ9+T+tqjXiVbjLEeGJOoL6VEk8mvRTVeSybw==", "cpu": [ "x64" ], @@ -152,9 +152,9 @@ } }, "node_modules/@biomejs/cli-linux-arm64": { - "version": "2.4.16", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.4.16.tgz", - "integrity": "sha512-2kFb4//jxfZaP6D+Rj5VkHkxgyD9EoRAVBEQb8PKRv+s4NO2zYNJKXFaJmK1CmhufJOWEfpHKaRbOja7qjmdhQ==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.5.1.tgz", + "integrity": "sha512-yhV35CzZh38VyMvTEXi3JTjxZBs++oCKK9KG8vB6VI5+uvQvZNR3BFWEKKzuOmx9DJJj7sQpZ4LQJcmbGTs3+Q==", "cpu": [ "arm64" ], @@ -172,9 +172,9 @@ } }, "node_modules/@biomejs/cli-linux-arm64-musl": { - "version": "2.4.16", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.4.16.tgz", - "integrity": "sha512-oYxnW0ARfJkr72ezzF2OR8N/rtkgLUQeYtF8cFhVswbknHxtTcmzSsanVJP8yQKnGpGpc2ck6c5zLvHahL6Cbg==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.5.1.tgz", + "integrity": "sha512-WMcvMLgByyTqVxGlq918NBBYliq9FRR9GAQVETHb+VjGVqXCZFfHlZHC1FX4ibuYY/Hg6TJE3rHU0xVrdJXNRw==", "cpu": [ "arm64" ], @@ -192,9 +192,9 @@ } }, "node_modules/@biomejs/cli-linux-x64": { - "version": "2.4.16", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.4.16.tgz", - "integrity": "sha512-NbcBbi/nJqn5baae6wqRXdS7Gadf2uRpehSh6vMSYpG8OhkXl/Xg8aorWrJ+9VWqAT5ml90alLvorkpMW0nBwQ==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.5.1.tgz", + "integrity": "sha512-J/7uHSX7NfoYDI7HijAkd8lnQIOrRb2W7j3X+tw4R+N5ExvXGsyXFiGdQcfcxfOmNQmZVSQOCDk757fwpzqQcg==", "cpu": [ "x64" ], @@ -212,9 +212,9 @@ } }, "node_modules/@biomejs/cli-linux-x64-musl": { - "version": "2.4.16", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.4.16.tgz", - "integrity": "sha512-iHDS+MCM65DPqWGu+ECC3uoALyj2H7F4nVUPxIPjz/PIl94EUu+EDfGZDzFP+NY1EOPVt9NQvwFqq7HdMmowdg==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.5.1.tgz", + "integrity": "sha512-ANTowtlLmPYm5yeMckWY8Xzb9Ix+JJP3tgHR/n6xRj1VWyIzzWtfRfih9hv9VmClwadpBvZduISZIbBsIlYG3A==", "cpu": [ "x64" ], @@ -232,9 +232,9 @@ } }, "node_modules/@biomejs/cli-win32-arm64": { - "version": "2.4.16", - "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.4.16.tgz", - "integrity": "sha512-0rgImMsNb5v/chhkIFe3wu7PEFClS6RBAYUijGL9UsYN3PanSaoK24HSSuSJb1pYbYYVjzAyZTl3gtjJ84BM8A==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.5.1.tgz", + "integrity": "sha512-zgXnKNgWPC4iPF7Y1lR3STUeCUuZRpD6IiOrC7TZTlh0Lx6FiVUT05myuMQHQ9D+1cc7uyMldi4forE6lp0ivQ==", "cpu": [ "arm64" ], @@ -249,9 +249,9 @@ } }, "node_modules/@biomejs/cli-win32-x64": { - "version": "2.4.16", - "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.4.16.tgz", - "integrity": "sha512-Kp85jgoBHa05gix6UIRjfCDiUV3w/8VIdZ247VyyO2gEjaw12WEVhdIjlxp/AMzXxqxQwbxNTDVZ3Mwd2RG5rw==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.5.1.tgz", + "integrity": "sha512-6uxpR9hvaglANkZemeSiN/FhYgkGasrEGn267eXIWvjrjJ2LhDlk251IhjVJq6MXzkV2/bcXwLwSroLyPtqRZg==", "cpu": [ "x64" ], diff --git a/package.json b/package.json index 23a1192..708a0ed 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "check:fix": "biome check --write --unsafe ." }, "devDependencies": { - "@biomejs/biome": "2.4.16", + "@biomejs/biome": "2.5.1", "@pact-foundation/pact": "16.5.0", "@testing-library/jest-dom": "6.9.1", "@types/node": "25.9.4", From 7c798f44c4ce64001b6639c92180ff133ed926fb Mon Sep 17 00:00:00 2001 From: JP-Ellis Date: Fri, 26 Jun 2026 21:40:33 +1000 Subject: [PATCH 2/2] refactor: resolve Biome lints under preset:all without blanket overrides Drop the inline rule disables and the overrides[] array, then address each diagnostic surfaced by preset:all in order of preference: fix the code, then a justified line-ignore, then a file-ignore, and only disable a rule as a last resort. Fixes preferred over suppression: convert components and the api singleton to named exports, switch react-dom/client to its named createRoot import, rename API/baseURL to conform to the naming convention, drop redundant async from test callbacks, memoise the change handler, extract the timeout magic number, and DRY the axios request config. Only three rules remain disabled, each framework-incompatible: noReactSpecificProps (this is React), useQwikValidLexicalScope (not Qwik), and noJsxLiterals (acceptable for this example app). Assisted-by: Claude Code:claude-opus-4-8[1m] --- biome.jsonc | 98 +++++------------------------------ src/App.tsx | 31 ++++++----- src/ErrorBoundary.tsx | 7 +-- src/Heading.tsx | 4 +- src/Layout.tsx | 4 +- src/ProductPage.tsx | 14 ++--- src/api.pact.spec.ts | 25 +++++---- src/api.ts | 35 +++++++------ src/index.tsx | 10 ++-- vite-plugin-check-provider.ts | 20 ++++--- vite.config.ts | 1 + vitest.config.ts | 1 + 12 files changed, 96 insertions(+), 154 deletions(-) diff --git a/biome.jsonc b/biome.jsonc index 046c4cf..0e66c0c 100644 --- a/biome.jsonc +++ b/biome.jsonc @@ -1,6 +1,4 @@ { - "$schema": "https://biomejs.dev/schemas/2.4.2/schema.json", - // Code formatting and organization suggestions "assist": { "enabled": true, @@ -33,92 +31,22 @@ "react": "all" }, "rules": { - "a11y": "error", - "complexity": "error", - "correctness": "error", - "nursery": "off", - "performance": "error", - "recommended": true, - "security": "error", + "preset": "all", + "correctness": { + // Not a Qwik project - this rule produces false positives on ordinary + // React closures. + "useQwikValidLexicalScope": "off" + }, "style": { - "recommended": true, - // Too strict - hardcoded strings acceptable in this project + // Hardcoded UI strings are acceptable in this example app; no i18n layer. "noJsxLiterals": "off" }, - "suspicious": "error" - } - }, - - // Override settings - "overrides": [ - { - "includes": ["**/*.tsx", "**/*.jsx"], - "linter": { - "rules": { - "performance": { - // Next.js specific rule - not applicable to Vite/React projects - "noImgElement": "off" - }, - "style": { - // Allow default exports in React components - "noDefaultExport": "off", - // Allow class components (converting to function components is a larger refactor) - "useReactFunctionComponents": "off" - }, - "suspicious": { - // React specific rule - not applicable to Vite/React projects - "noReactSpecificProps": "off" - } - } - } - }, - { - "includes": [ - "**/*.spec.ts", - "**/*.spec.tsx", - "**/*.test.ts", - "**/*.test.tsx" - ], - "linter": { - "rules": { - "complexity": { - // Test files can have longer functions with multiple test cases - "noExcessiveLinesPerFunction": "off" - }, - "style": { - // HTTP headers follow spec casing (Authorization, not authorization) - "useNamingConvention": "off" - }, - "suspicious": { - // Pact test executeTest callbacks are inherently async - "useAwait": "off" - } - } - } - }, - { - "includes": ["*.config.ts", "*.config.js"], - "linter": { - "rules": { - "style": { - // Config files conventionally use default exports - "noDefaultExport": "off" - } - } - } - }, - { - "includes": ["**/*.ts", "**/*.tsx"], - "linter": { - "rules": { - "correctness": { - // TypeScript handles this - "noUnresolvedImports": "off", - // False positive (we don't use Qwik) - "useQwikValidLexicalScope": "off" - } - } + "suspicious": { + // This is a React project - className/htmlFor are the correct props. + // The rule targets non-React JSX (e.g. Solid, Preact) where it would + // rewrite them to the HTML-standard names. + "noReactSpecificProps": "off" } } - ] + } } diff --git a/src/App.tsx b/src/App.tsx index 97e8b8b..3251460 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,11 +1,11 @@ -import { useEffect, useId, useState } from "react"; +import { useCallback, useEffect, useId, useState } from "react"; import { Link } from "react-router-dom"; import "spectre.css/dist/spectre.min.css"; import "spectre.css/dist/spectre-icons.min.css"; import "spectre.css/dist/spectre-exp.min.css"; -import API from "./api.ts"; -import Heading from "./Heading.tsx"; -import Layout from "./Layout.tsx"; +import { api } from "./api.ts"; +import { Heading } from "./Heading.tsx"; +import { Layout } from "./Layout.tsx"; import type { Product } from "./types/index.ts"; interface ProductTableRowProps { @@ -53,7 +53,7 @@ function ProductTable(props: ProductTableProps) { ); } -function App() { +export function App() { const [loading, setLoading] = useState(true); const [searchText, setSearchText] = useState(""); const [products, setProducts] = useState([]); @@ -61,7 +61,8 @@ function App() { const inputId = useId(); useEffect(() => { - API.getAllProducts() + api + .getAllProducts() .then((r) => { setLoading(false); setProducts(r); @@ -83,12 +84,19 @@ function App() { ); }; - setVisibleProducts(searchText ? findProducts(searchText) : products); + let visible = products; + if (searchText) { + visible = findProducts(searchText); + } + setVisibleProducts(visible); }, [searchText, products]); - const onSearchTextChange = (e: React.ChangeEvent) => { - setSearchText(e.target.value); - }; + const onSearchTextChange = useCallback( + (e: React.ChangeEvent) => { + setSearchText(e.target.value); + }, + [], + ); return ( @@ -105,6 +113,7 @@ function App() { onChange={onSearchTextChange} /> + {/* biome-ignore lint/style/noTernary: idiomatic JSX conditional rendering with an else branch */} {loading ? (
) : ( @@ -113,5 +122,3 @@ function App() { ); } - -export default App; diff --git a/src/ErrorBoundary.tsx b/src/ErrorBoundary.tsx index 096c351..ea56688 100644 --- a/src/ErrorBoundary.tsx +++ b/src/ErrorBoundary.tsx @@ -1,7 +1,7 @@ import type { ReactNode } from "react"; import React from "react"; -import Heading from "./Heading.tsx"; -import Layout from "./Layout.tsx"; +import { Heading } from "./Heading.tsx"; +import { Layout } from "./Layout.tsx"; interface ErrorBoundaryProps { children: ReactNode; @@ -11,7 +11,7 @@ interface ErrorBoundaryState { hasError: boolean; } -export default class ErrorBoundary extends React.Component< +export class ErrorBoundary extends React.Component< ErrorBoundaryProps, ErrorBoundaryState > { @@ -27,6 +27,7 @@ export default class ErrorBoundary extends React.Component<
+ {/* biome-ignore lint/performance/noImgElement: Vite/React project — next/image is not applicable */}

@@ -23,5 +23,3 @@ function Heading(props: HeadingProps) {

); } - -export default Heading; diff --git a/src/Layout.tsx b/src/Layout.tsx index 2527342..d33a4b1 100644 --- a/src/Layout.tsx +++ b/src/Layout.tsx @@ -4,7 +4,7 @@ interface LayoutProps { children: ReactNode; } -function Layout(props: LayoutProps) { +export function Layout(props: LayoutProps) { return (
@@ -13,5 +13,3 @@ function Layout(props: LayoutProps) {
); } - -export default Layout; diff --git a/src/ProductPage.tsx b/src/ProductPage.tsx index 48afc01..6e76cf9 100644 --- a/src/ProductPage.tsx +++ b/src/ProductPage.tsx @@ -3,12 +3,12 @@ import { useParams } from "react-router-dom"; import "spectre.css/dist/spectre.min.css"; import "spectre.css/dist/spectre-icons.min.css"; import "spectre.css/dist/spectre-exp.min.css"; -import API from "./api.ts"; -import Heading from "./Heading.tsx"; -import Layout from "./Layout.tsx"; +import { api } from "./api.ts"; +import { Heading } from "./Heading.tsx"; +import { Layout } from "./Layout.tsx"; import type { Product } from "./types/index.ts"; -function ProductPage() { +export function ProductPage() { const { id } = useParams<{ id: string }>(); const [loading, setLoading] = useState(true); const [product, setProduct] = useState>({ id }); @@ -16,7 +16,8 @@ function ProductPage() { useEffect(() => { if (id) { - API.getProduct(id) + api + .getProduct(id) .then((r) => { setLoading(false); setProduct(r); @@ -42,6 +43,7 @@ function ProductPage() { return ( + {/* biome-ignore lint/style/noTernary: idiomatic JSX conditional rendering with an else branch */} {loading ? (
); } - -export default ProductPage; diff --git a/src/api.pact.spec.ts b/src/api.pact.spec.ts index 47a49d3..2b12413 100644 --- a/src/api.pact.spec.ts +++ b/src/api.pact.spec.ts @@ -1,19 +1,18 @@ +// biome-ignore-all lint/complexity/noExcessiveLinesPerFunction: test suites group many related cases in one describe block import { MatchersV3, PactV3 } from "@pact-foundation/pact"; -import { API } from "./api.ts"; +import { Api } from "./api.ts"; const { eachLike, like } = MatchersV3; const Pact = PactV3; const mockProvider = new Pact({ consumer: "pactflow-example-consumer-webhookless", - provider: import.meta.env.PACT_PROVIDER - ? import.meta.env.PACT_PROVIDER - : "pactflow-example-provider", + provider: import.meta.env.PACT_PROVIDER || "pactflow-example-provider", }); describe("API Pact test", () => { describe("retrieving a product", () => { - test("ID 10 exists", async () => { + test("ID 10 exists", () => { // Arrange const expectedProduct = { id: "10", @@ -31,6 +30,7 @@ describe("API Pact test", () => { method: "GET", path: "/product/10", headers: { + // biome-ignore lint/style/useNamingConvention: HTTP header name follows RFC 7235 casing Authorization: like("Bearer 2019-01-14T11:34:18.045Z"), }, }) @@ -43,16 +43,15 @@ describe("API Pact test", () => { }); return mockProvider.executeTest(async (mockserver) => { // Act - const api = new API(mockserver.url); + const api = new Api(mockserver.url); const product = await api.getProduct("10"); // Assert - did we get the expected response expect(product).toStrictEqual(expectedProduct); - return; }); }); - test("product does not exist", async () => { + test("product does not exist", () => { // set up Pact interactions mockProvider @@ -62,6 +61,7 @@ describe("API Pact test", () => { method: "GET", path: "/product/11", headers: { + // biome-ignore lint/style/useNamingConvention: HTTP header name follows RFC 7235 casing Authorization: like("Bearer 2019-01-14T11:34:18.045Z"), }, }) @@ -69,18 +69,17 @@ describe("API Pact test", () => { status: 404, }); return mockProvider.executeTest(async (mockserver) => { - const api = new API(mockserver.url); + const api = new Api(mockserver.url); // make request to Pact mock server await expect(api.getProduct("11")).rejects.toThrow( "Request failed with status code 404", ); - return; }); }); }); describe("retrieving products", () => { - test("products exists", async () => { + test("products exists", () => { // set up Pact interactions const expectedProduct = { id: "10", @@ -95,6 +94,7 @@ describe("API Pact test", () => { method: "GET", path: "/products", headers: { + // biome-ignore lint/style/useNamingConvention: HTTP header name follows RFC 7235 casing Authorization: like("Bearer 2019-01-14T11:34:18.045Z"), }, }) @@ -106,14 +106,13 @@ describe("API Pact test", () => { body: eachLike(expectedProduct), }); return mockProvider.executeTest(async (mockserver) => { - const api = new API(mockserver.url); + const api = new Api(mockserver.url); // make request to Pact mock server const products = await api.getAllProducts(); // assert that we got the expected response expect(products).toStrictEqual([expectedProduct]); - return; }); }); }); diff --git a/src/api.ts b/src/api.ts index d9780de..3967195 100644 --- a/src/api.ts +++ b/src/api.ts @@ -1,38 +1,39 @@ -import axios from "axios"; +import axios, { type AxiosRequestConfig } from "axios"; import type { Product } from "./types/index.ts"; -export class API { - private readonly baseURL: string; +export class Api { + private readonly baseUrl: string; constructor(url?: string) { - this.baseURL = url || import.meta.env.VITE_API_BASE_URL || ""; + this.baseUrl = url || import.meta.env.VITE_API_BASE_URL || ""; } generateAuthToken(): string { return `Bearer ${new Date().toISOString()}`; } + private requestConfig(): AxiosRequestConfig { + return { + // biome-ignore lint/style/useNamingConvention: `baseURL` is axios's option name, not a project identifier + baseURL: this.baseUrl, + headers: { + // biome-ignore lint/style/useNamingConvention: HTTP header name follows RFC 7235 casing + Authorization: this.generateAuthToken(), + }, + }; + } + getAllProducts(): Promise { return axios - .get("/products", { - baseURL: this.baseURL, - headers: { - Authorization: this.generateAuthToken(), - }, - }) + .get("/products", this.requestConfig()) .then((r) => r.data); } getProduct(id: string): Promise { return axios - .get(`/product/${id}`, { - baseURL: this.baseURL, - headers: { - Authorization: this.generateAuthToken(), - }, - }) + .get(`/product/${id}`, this.requestConfig()) .then((r) => r.data); } } -export default new API(import.meta.env.VITE_API_BASE_URL); +export const api = new Api(import.meta.env.VITE_API_BASE_URL); diff --git a/src/index.tsx b/src/index.tsx index 539817e..6c1561d 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,9 +1,9 @@ -import ReactDom from "react-dom/client"; +import { createRoot } from "react-dom/client"; import { BrowserRouter, Route, Routes } from "react-router-dom"; import "./index.css"; -import App from "./App.tsx"; -import ErrorBoundary from "./ErrorBoundary.tsx"; -import ProductPage from "./ProductPage.tsx"; +import { App } from "./App.tsx"; +import { ErrorBoundary } from "./ErrorBoundary.tsx"; +import { ProductPage } from "./ProductPage.tsx"; const routing = ( @@ -22,6 +22,6 @@ const routing = ( const rootElement = document.getElementById("root"); if (rootElement) { - const root = ReactDom.createRoot(rootElement); + const root = createRoot(rootElement); root.render(routing); } diff --git a/vite-plugin-check-provider.ts b/vite-plugin-check-provider.ts index e435670..840f4ee 100644 --- a/vite-plugin-check-provider.ts +++ b/vite-plugin-check-provider.ts @@ -1,16 +1,18 @@ // biome-ignore-all lint/suspicious/noConsole: Dev plugin needs console output for user guidance // biome-ignore-all lint/correctness/noProcessGlobal: Vite plugins need access to process.env +// biome-ignore-all lint/style/noProcessEnv: Vite plugins read configuration from process.env import type { Plugin, ViteDevServer } from "vite"; const PROVIDER_REPO = "https://github.com/pactflow/example-provider"; const DEFAULT_PROVIDER_URL = "http://localhost:8080"; +const REQUEST_TIMEOUT_MS = 2000; async function checkProviderAvailability( url: string, ): Promise<{ available: boolean; error?: string }> { try { const controller = new AbortController(); - const timeoutId = setTimeout(() => controller.abort(), 2000); + const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS); const response = await fetch(`${url}/health`, { signal: controller.signal, @@ -19,9 +21,13 @@ async function checkProviderAvailability( return { available: response.ok }; } catch (error) { + let errorMessage = "Unknown error"; + if (error instanceof Error) { + errorMessage = error.message; + } return { available: false, - error: error instanceof Error ? error.message : "Unknown error", + error: errorMessage, }; } } @@ -56,7 +62,9 @@ export function checkProviderPlugin(): Plugin { // Check on server start server.httpServer?.once("listening", async () => { - if (hasChecked) return; + if (hasChecked) { + return; + } hasChecked = true; const providerUrl = @@ -73,10 +81,10 @@ export function checkProviderPlugin(): Plugin { const result = await checkProviderAvailability(providerUrl); - if (!result.available) { - printProviderInstructions(providerUrl); - } else { + if (result.available) { console.log(`\nāœ… Provider available at ${providerUrl}\n`); + } else { + printProviderInstructions(providerUrl); } }); }, diff --git a/vite.config.ts b/vite.config.ts index dc12815..5d86217 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -2,6 +2,7 @@ import react from "@vitejs/plugin-react"; import { defineConfig } from "vite"; import { checkProviderPlugin } from "./vite-plugin-check-provider.ts"; +// biome-ignore lint/style/noDefaultExport: Vite requires the config to be the module's default export export default defineConfig({ plugins: [react(), checkProviderPlugin()], server: { diff --git a/vitest.config.ts b/vitest.config.ts index edffde7..74be649 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -1,6 +1,7 @@ import react from "@vitejs/plugin-react"; import { defineConfig } from "vitest/config"; +// biome-ignore lint/style/noDefaultExport: Vitest requires the config to be the module's default export export default defineConfig({ plugins: [react()], test: {