From 37a044a267c46871c219b54fd09c081864cb1e61 Mon Sep 17 00:00:00 2001 From: Nicolas Date: Fri, 21 Aug 2026 10:11:57 +0200 Subject: [PATCH] chore(lint): upgrade Biome vers 2.x, active noExplicitAny/noConsole/noFloatingPromises MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `@biomejs/biome` passe de 1.9.4 à 2.5.9 (config migrée via `biome migrate --write`) — nécessaire pour noFloatingPromises, une règle type-aware apparue en 2.0 (nursery). - noExplicitAny : déjà "recommended", actif depuis toujours, aucun changement. - noConsole (biome.json) : bloque tout `console.*` sauf error/warn/info/ debug/table/assert — équivalent à "pas de console.log" sans interdire les niveaux nommés (voir le nouveau log service dans le prochain commit, qui centralise justement ces appels). - noFloatingPromises (nursery) activé explicitement sous `rules.nursery` sans avoir besoin d'activer le domaine "types" au sens large (ça aurait aussi allumé des dizaines d'autres règles type-aware type noUnresolvedImports/noUnnecessaryConditions, hors scope ici). Le reste du diff, c'est soit du reformatage automatique (import sort, 2.x ordonne différemment de 1.9.4 — `biome check --write --unsafe`), soit les corrections des ~20 promesses flottantes que la nouvelle règle a fait remonter : - La plupart sont des `navigate(...)` non attendus (react-router v7 type `navigate` en `void | Promise`) — préfixés `void navigate(...)`, aucun changement de comportement. - Trois chargements initiaux en useEffect (OnboardingAllergensPage, OnboardingDietPage, OnboardingHouseholdPage, HouseholdSettingsPage) n'avaient jamais de `.catch()` du tout — ajouté (dégradation silencieuse vers un état vide/par défaut, même raisonnement que le `.catch()` déjà présent dans OnboardingSourcesPage). - HouseholdSettingsPage : `loadHouse` était une fonction déclarée à chaque render (donc une référence différente à chaque fois) utilisée comme dépendance de useEffect ET passée en callback à des enfants — le useEffect se re-déclenchait donc à chaque re-render provoqué par son propre fetch, un vrai bug de boucle infinie de requêtes que noFloatingPromises a fait remonter indirectement (via useExhaustiveDependencies). Corrigé avec useCallback([]). - RecipeDetailPanel : une clé de liste `${index}-...}` sur une liste statique (draft.steps, sans id stable — DraftRecipeStepView n'en a pas) — biome-ignore justifié, pas de bug réel. - recipe.test.ts : variable `agent` non utilisée, retirée. Vérifié : `pnpm --filter api test` (295/295), `pnpm lint` et `pnpm build` clean sur tout le repo. Co-Authored-By: Claude Sonnet 5 --- apps/api/src/lib/ingredient-matcher.ts | 2 +- apps/api/src/lib/recipe-translation.ts | 6 +- apps/api/src/modules/auth/auth.routes.ts | 2 +- apps/api/src/modules/house/house.routes.ts | 2 +- .../src/modules/planning/planning.routes.ts | 2 +- apps/api/src/modules/recipe/recipe.routes.ts | 2 +- .../api/src/modules/sources/sources.routes.ts | 2 +- .../src/modules/sources/sources.service.ts | 4 +- apps/api/test/ingredient-matcher.test.ts | 4 +- apps/api/test/recipe-translation.test.ts | 4 +- apps/api/test/recipe.test.ts | 6 +- apps/api/test/tech-step-matcher.test.ts | 2 +- apps/web/cypress/e2e/recipes.cy.ts | 2 +- apps/web/src/App.tsx | 8 +- apps/web/src/components/ui/Tooltip.tsx | 2 +- apps/web/src/features/auth/AuthContext.tsx | 2 +- .../features/planning/RecipePickerDialog.tsx | 8 +- .../src/features/recipes/IngredientPicker.tsx | 2 +- .../src/features/recipes/IngredientRow.tsx | 2 +- .../features/recipes/RecipeDetailPanel.tsx | 9 +-- .../src/features/recipes/RecipeImportForm.tsx | 2 +- .../features/recipes/recipe-import-draft.ts | 2 +- apps/web/src/features/theme/ThemeContext.tsx | 2 +- apps/web/src/layouts/AppLayout.tsx | 2 +- apps/web/src/layouts/nav-icons.tsx | 22 +++--- apps/web/src/pages/ImportRecipePage.tsx | 2 +- apps/web/src/pages/LoginPage.tsx | 2 +- apps/web/src/pages/PlanningPage.tsx | 2 +- apps/web/src/pages/RecipeFormPage.tsx | 4 +- apps/web/src/pages/RecipesPage.tsx | 8 +- apps/web/src/pages/SignupPage.tsx | 2 +- .../onboarding/OnboardingAllergensPage.tsx | 10 ++- .../pages/onboarding/OnboardingDietPage.tsx | 7 +- .../onboarding/OnboardingHouseholdPage.tsx | 7 +- .../onboarding/OnboardingSourcesPage.tsx | 6 +- .../pages/settings/AccountSettingsPage.tsx | 2 +- .../pages/settings/HouseholdSettingsPage.tsx | 37 ++++++---- biome.json | 33 ++++++--- package.json | 2 +- packages/date-tools/src/index.ts | 5 +- pnpm-lock.yaml | 74 +++++++++---------- 41 files changed, 167 insertions(+), 139 deletions(-) diff --git a/apps/api/src/lib/ingredient-matcher.ts b/apps/api/src/lib/ingredient-matcher.ts index 0ba0c3c..2c3c71f 100644 --- a/apps/api/src/lib/ingredient-matcher.ts +++ b/apps/api/src/lib/ingredient-matcher.ts @@ -1,6 +1,6 @@ import { - INGREDIENT_LABELS_EN, INGREDIENT_LABEL_SYNONYMS_EN, + INGREDIENT_LABELS_EN, UNIT_LABELS_EN, } from "@batch-cooking/shared"; import { prisma } from "../db/prisma.js"; diff --git a/apps/api/src/lib/recipe-translation.ts b/apps/api/src/lib/recipe-translation.ts index 257f25c..a3de19e 100644 --- a/apps/api/src/lib/recipe-translation.ts +++ b/apps/api/src/lib/recipe-translation.ts @@ -1,12 +1,12 @@ import type { UnitType } from "@batch-cooking/shared"; import { - type IngredientMatchEntry, - type UnitMatchEntry, extractQuantity, + type IngredientMatchEntry, loadIngredientCatalog, loadUnitCatalog, matchIngredientName, matchUnit, + type UnitMatchEntry, } from "./ingredient-matcher.js"; import type { ParsedRecipe, @@ -14,9 +14,9 @@ import type { ParsedRecipeStep, } from "./recipe-source-adapter.js"; import { - type TechStepMappingRule, loadTechStepMappingRules, matchTechSteps, + type TechStepMappingRule, } from "./tech-step-matcher.js"; /** diff --git a/apps/api/src/modules/auth/auth.routes.ts b/apps/api/src/modules/auth/auth.routes.ts index 67c2440..af7bbcb 100644 --- a/apps/api/src/modules/auth/auth.routes.ts +++ b/apps/api/src/modules/auth/auth.routes.ts @@ -1,7 +1,7 @@ import { wrapAsyncHandler } from "@batch-cooking/express-tools"; import { deleteAccountSchema, loginSchema, signupSchema } from "@batch-cooking/shared"; -import { Router } from "express"; import type { CookieOptions, Response } from "express"; +import { Router } from "express"; import { env } from "../../config/env.js"; import { type AuthLocals, requireAuth } from "../../middlewares/require-auth.js"; import { deleteAccount, login, signup } from "./auth.service.js"; diff --git a/apps/api/src/modules/house/house.routes.ts b/apps/api/src/modules/house/house.routes.ts index 1ffd8b3..04bb18a 100644 --- a/apps/api/src/modules/house/house.routes.ts +++ b/apps/api/src/modules/house/house.routes.ts @@ -1,8 +1,8 @@ import { HttpError } from "@batch-cooking/error-tools"; import { wrapAsyncHandler } from "@batch-cooking/express-tools"; import { - ErrorCode, createHouseSchema, + ErrorCode, joinHouseSchema, renameHouseSchema, updateHouseSourcesSchema, diff --git a/apps/api/src/modules/planning/planning.routes.ts b/apps/api/src/modules/planning/planning.routes.ts index a4b04f5..26ce92d 100644 --- a/apps/api/src/modules/planning/planning.routes.ts +++ b/apps/api/src/modules/planning/planning.routes.ts @@ -1,7 +1,7 @@ import { parseDateOnly } from "@batch-cooking/date-tools"; import { HttpError } from "@batch-cooking/error-tools"; import { wrapAsyncHandler } from "@batch-cooking/express-tools"; -import { ErrorCode, addPlanningItemSchema, getPlanningByDateSchema } from "@batch-cooking/shared"; +import { addPlanningItemSchema, ErrorCode, getPlanningByDateSchema } from "@batch-cooking/shared"; import { Router } from "express"; import { type AuthLocals, requireAuth } from "../../middlewares/require-auth.js"; import { addPlanningItem, getPlanningForDate, removePlanningItem } from "./planning.service.js"; diff --git a/apps/api/src/modules/recipe/recipe.routes.ts b/apps/api/src/modules/recipe/recipe.routes.ts index ece4f9e..669bdc7 100644 --- a/apps/api/src/modules/recipe/recipe.routes.ts +++ b/apps/api/src/modules/recipe/recipe.routes.ts @@ -1,8 +1,8 @@ import { HttpError } from "@batch-cooking/error-tools"; import { wrapAsyncHandler } from "@batch-cooking/express-tools"; import { - ErrorCode, createRecipeSchema, + ErrorCode, listRecipesSchema, updateRecipeSchema, } from "@batch-cooking/shared"; diff --git a/apps/api/src/modules/sources/sources.routes.ts b/apps/api/src/modules/sources/sources.routes.ts index edba393..083b911 100644 --- a/apps/api/src/modules/sources/sources.routes.ts +++ b/apps/api/src/modules/sources/sources.routes.ts @@ -1,6 +1,6 @@ import { HttpError } from "@batch-cooking/error-tools"; import { wrapAsyncHandler } from "@batch-cooking/express-tools"; -import { ErrorCode, browseSourceSchema, createRecipeSchema } from "@batch-cooking/shared"; +import { browseSourceSchema, createRecipeSchema, ErrorCode } from "@batch-cooking/shared"; import { Router } from "express"; import { type AuthLocals, requireAuth } from "../../middlewares/require-auth.js"; import { browseSource, importSourceItem, previewSourceItem } from "./sources.service.js"; diff --git a/apps/api/src/modules/sources/sources.service.ts b/apps/api/src/modules/sources/sources.service.ts index 1605a46..cb14c01 100644 --- a/apps/api/src/modules/sources/sources.service.ts +++ b/apps/api/src/modules/sources/sources.service.ts @@ -12,11 +12,11 @@ import { prisma } from "../../db/prisma.js"; import { findImportedRecipeIds } from "../../db/recipe-source-sync.js"; import { type IngredientMatchEntry, - type UnitMatchEntry, loadIngredientCatalog, loadUnitCatalog, + type UnitMatchEntry, } from "../../lib/ingredient-matcher.js"; -import { type RecipeSourceAdapter, markAlreadyImported } from "../../lib/recipe-source-adapter.js"; +import { markAlreadyImported, type RecipeSourceAdapter } from "../../lib/recipe-source-adapter.js"; import { RecipeSourceError } from "../../lib/recipe-source-errors.js"; import { getRecipeSource } from "../../lib/recipe-source-registry.js"; import { diff --git a/apps/api/test/ingredient-matcher.test.ts b/apps/api/test/ingredient-matcher.test.ts index 072d0fc..1114001 100644 --- a/apps/api/test/ingredient-matcher.test.ts +++ b/apps/api/test/ingredient-matcher.test.ts @@ -2,13 +2,13 @@ import { INGREDIENT_LABEL_SYNONYMS_EN } from "@batch-cooking/shared"; import { expect } from "chai"; import { prisma } from "../src/db/prisma.js"; import { - type IngredientMatchEntry, - type UnitMatchEntry, extractQuantity, + type IngredientMatchEntry, loadIngredientCatalog, loadUnitCatalog, matchIngredientName, matchUnit, + type UnitMatchEntry, } from "../src/lib/ingredient-matcher.js"; import { resetDatabase } from "../test-support/reset-db.js"; diff --git a/apps/api/test/recipe-translation.test.ts b/apps/api/test/recipe-translation.test.ts index f3d3ffd..6dbebc2 100644 --- a/apps/api/test/recipe-translation.test.ts +++ b/apps/api/test/recipe-translation.test.ts @@ -3,12 +3,12 @@ import { prisma } from "../src/db/prisma.js"; import type { IngredientMatchEntry, UnitMatchEntry } from "../src/lib/ingredient-matcher.js"; import type { ParsedRecipe, ParsedRecipeIngredient } from "../src/lib/recipe-source-adapter.js"; import { - type TranslatedRecipeIngredient, - type UnitConversionEntry, mergeDuplicateIngredients, + type TranslatedRecipeIngredient, translateRecipe, translateRecipeIngredients, translateRecipeSteps, + type UnitConversionEntry, } from "../src/lib/recipe-translation.js"; import type { TechStepMappingRule } from "../src/lib/tech-step-matcher.js"; import { resetDatabase } from "../test-support/reset-db.js"; diff --git a/apps/api/test/recipe.test.ts b/apps/api/test/recipe.test.ts index c78c3f5..90ffd67 100644 --- a/apps/api/test/recipe.test.ts +++ b/apps/api/test/recipe.test.ts @@ -773,8 +773,8 @@ describe("Recipes", () => { const tomate = await ingredientId("tomato"); const piece = await unitId("piece"); const chop = await techStepId("chop"); - const mince = await techStepId("mince"); - const melt = await techStepId("melt"); + const _mince = await techStepId("mince"); + const _melt = await techStepId("melt"); const simmer = await techStepId("simmer"); const bake = await techStepId("bake"); @@ -886,7 +886,7 @@ describe("Recipes", () => { }); it("rejects an edit from anyone other than the recipe's author with 403 NOT_RECIPE_AUTHOR", async () => { - const { agent, profileId } = await signup(); + const { profileId } = await signup(); const { agent: otherAgent } = await signup(); const tomate = await ingredientId("tomato"); const piece = await unitId("piece"); diff --git a/apps/api/test/tech-step-matcher.test.ts b/apps/api/test/tech-step-matcher.test.ts index 624f148..8646b2a 100644 --- a/apps/api/test/tech-step-matcher.test.ts +++ b/apps/api/test/tech-step-matcher.test.ts @@ -1,11 +1,11 @@ import { expect } from "chai"; import { prisma } from "../src/db/prisma.js"; import { - type TechStepMappingRule, loadTechStepMappingRules, matchTechStepSpans, matchTechSteps, normalizeText, + type TechStepMappingRule, } from "../src/lib/tech-step-matcher.js"; import { resetDatabase } from "../test-support/reset-db.js"; diff --git a/apps/web/cypress/e2e/recipes.cy.ts b/apps/web/cypress/e2e/recipes.cy.ts index da874e1..a3190df 100644 --- a/apps/web/cypress/e2e/recipes.cy.ts +++ b/apps/web/cypress/e2e/recipes.cy.ts @@ -16,7 +16,7 @@ const authenticatedProfile = { }; const vegetarien = { id: 1, key: "vegetarian" }; -const gluten = { id: 1, key: "gluten", kind: "INTOLERANCE" }; +const _gluten = { id: 1, key: "gluten", kind: "INTOLERANCE" }; const oeufs = { id: 2, key: "eggs", kind: "ALLERGY" }; const ratatouille = { diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx index 88911dd..b5c7695 100644 --- a/apps/web/src/App.tsx +++ b/apps/web/src/App.tsx @@ -4,15 +4,15 @@ import { RequireAuth } from "./features/auth/RequireAuth"; import { AppLayout } from "./layouts/AppLayout"; import { ImportRecipePage } from "./pages/ImportRecipePage"; import { LoginPage } from "./pages/LoginPage"; +import { OnboardingAllergensPage } from "./pages/onboarding/OnboardingAllergensPage"; +import { OnboardingDietPage } from "./pages/onboarding/OnboardingDietPage"; +import { OnboardingHouseholdPage } from "./pages/onboarding/OnboardingHouseholdPage"; +import { OnboardingSourcesPage } from "./pages/onboarding/OnboardingSourcesPage"; import { PlanningPage } from "./pages/PlanningPage"; import { RecipeFormPage } from "./pages/RecipeFormPage"; import { RecipesPage } from "./pages/RecipesPage"; import { ShoppingListPage } from "./pages/ShoppingListPage"; import { SignupPage } from "./pages/SignupPage"; -import { OnboardingAllergensPage } from "./pages/onboarding/OnboardingAllergensPage"; -import { OnboardingDietPage } from "./pages/onboarding/OnboardingDietPage"; -import { OnboardingHouseholdPage } from "./pages/onboarding/OnboardingHouseholdPage"; -import { OnboardingSourcesPage } from "./pages/onboarding/OnboardingSourcesPage"; import { AccountSettingsPage } from "./pages/settings/AccountSettingsPage"; import { CreditsPage } from "./pages/settings/CreditsPage"; import { HouseholdSettingsPage } from "./pages/settings/HouseholdSettingsPage"; diff --git a/apps/web/src/components/ui/Tooltip.tsx b/apps/web/src/components/ui/Tooltip.tsx index c323850..2db91c7 100644 --- a/apps/web/src/components/ui/Tooltip.tsx +++ b/apps/web/src/components/ui/Tooltip.tsx @@ -1,4 +1,4 @@ -import { type ReactElement, cloneElement, useId } from "react"; +import { cloneElement, type ReactElement, useId } from "react"; import "./tooltip.scss"; /** diff --git a/apps/web/src/features/auth/AuthContext.tsx b/apps/web/src/features/auth/AuthContext.tsx index 1a8b58a..a89c1d6 100644 --- a/apps/web/src/features/auth/AuthContext.tsx +++ b/apps/web/src/features/auth/AuthContext.tsx @@ -1,5 +1,5 @@ import type { LoginInput, SafeUserProfile, SignupInput } from "@batch-cooking/shared"; -import { type ReactNode, createContext, useCallback, useContext, useEffect, useState } from "react"; +import { createContext, type ReactNode, useCallback, useContext, useEffect, useState } from "react"; import { apiClient } from "../../api/client"; /** Shape of the auth state/actions exposed via {@link useAuth}. */ diff --git a/apps/web/src/features/planning/RecipePickerDialog.tsx b/apps/web/src/features/planning/RecipePickerDialog.tsx index 1f6cb9f..7cfcb3c 100644 --- a/apps/web/src/features/planning/RecipePickerDialog.tsx +++ b/apps/web/src/features/planning/RecipePickerDialog.tsx @@ -22,10 +22,10 @@ import { RecipeImportForm } from "../recipes/RecipeImportForm"; import { RecipeSourcesPanel, type SourceItemSelection } from "../recipes/RecipeSourcesPanel"; import { RecipeTable } from "../recipes/RecipeTable"; import { - RecipeTabs, - type RecipesPageTab, isSourceTab, parseSourceTabValue, + type RecipesPageTab, + RecipeTabs, } from "../recipes/RecipeTabs"; import { tryBuildCompleteImport } from "../recipes/recipe-import-draft"; import { useEnabledSources } from "../recipes/useEnabledSources"; @@ -313,7 +313,7 @@ export function RecipePickerDialog({ // for the identical failure — see this dialog's `onImported` handler // below). setIsConfirmingDraft(false); - navigate(`/recettes/${saved.id}`); + void navigate(`/recettes/${saved.id}`); } } @@ -361,7 +361,7 @@ export function RecipePickerDialog({ // to this slot failed. Same fallback as the transparent- // import path above: land on its own page instead of // retrying. - navigate(`/recettes/${recipe.id}`); + void navigate(`/recettes/${recipe.id}`); } }} /> diff --git a/apps/web/src/features/recipes/IngredientPicker.tsx b/apps/web/src/features/recipes/IngredientPicker.tsx index 20ac680..b12450e 100644 --- a/apps/web/src/features/recipes/IngredientPicker.tsx +++ b/apps/web/src/features/recipes/IngredientPicker.tsx @@ -11,8 +11,8 @@ import { CheckboxOption } from "../../components/ui/Checkbox"; import { SettingsIcon } from "../../layouts/nav-icons"; import { AllergenBadges } from "./AllergenBadges"; import { DietBadges } from "./DietBadges"; -import { ReproducibleBadge } from "./ReproducibleBadge"; import { CategoryIcon, IngredientTypeIcon, SubcategoryIcon } from "./ingredient-icons"; +import { ReproducibleBadge } from "./ReproducibleBadge"; import "./recipes.scss"; /** "No filter at this level" — a UI-only pseudo-value, never sent to/received from the API (see {@link INGREDIENT_CATEGORIES}/{@link INGREDIENT_CATEGORY_SUBCATEGORIES} for the real, closed sets). */ diff --git a/apps/web/src/features/recipes/IngredientRow.tsx b/apps/web/src/features/recipes/IngredientRow.tsx index ea2c360..d73e752 100644 --- a/apps/web/src/features/recipes/IngredientRow.tsx +++ b/apps/web/src/features/recipes/IngredientRow.tsx @@ -2,8 +2,8 @@ import type { IngredientView, UnitView } from "@batch-cooking/shared"; import { useTranslation } from "react-i18next"; import { AllergenBadges } from "./AllergenBadges"; import { DietBadges } from "./DietBadges"; -import { ReproducibleBadge } from "./ReproducibleBadge"; import { IngredientTypeIcon } from "./ingredient-icons"; +import { ReproducibleBadge } from "./ReproducibleBadge"; import "./recipes.scss"; /** One selected ingredient line in the recipe form — the ingredient itself (picked via `IngredientPicker`) plus its quantity/unit for this recipe. Quantity is kept as a raw string while editing (not parsed to a number until submit) so an in-progress/invalid value doesn't fight the input; `unit` picks from `unitsCatalog` (a closed reference list, see `Unit` in schema.prisma) rather than free text. */ diff --git a/apps/web/src/features/recipes/RecipeDetailPanel.tsx b/apps/web/src/features/recipes/RecipeDetailPanel.tsx index d5c6ceb..2f3da78 100644 --- a/apps/web/src/features/recipes/RecipeDetailPanel.tsx +++ b/apps/web/src/features/recipes/RecipeDetailPanel.tsx @@ -135,6 +135,7 @@ export function RecipeDetailPanel({

{t("recipes.stepsTitle")}

    {draft.steps.map((step, index) => ( + // biome-ignore lint/suspicious/noArrayIndexKey: DraftRecipeStepView (packages/shared) has no id — it's an unsaved source preview, nothing to key on yet — and this list is a static read-only render, never reordered/edited here.
  1. {step.picture && } @@ -218,13 +219,7 @@ export function RecipeDetailPanel({ } /** Delete action with an inline two-step confirmation, same pattern as `HouseholdSettingsPage`'s danger zone. */ -function DeleteRecipeButton({ - recipeId, - onDeleted, -}: { - recipeId: number; - onDeleted: () => void; -}) { +function DeleteRecipeButton({ recipeId, onDeleted }: { recipeId: number; onDeleted: () => void }) { const { t } = useTranslation(); const [isConfirming, setIsConfirming] = useState(false); const [isDeleting, setIsDeleting] = useState(false); diff --git a/apps/web/src/features/recipes/RecipeImportForm.tsx b/apps/web/src/features/recipes/RecipeImportForm.tsx index e92252b..0202249 100644 --- a/apps/web/src/features/recipes/RecipeImportForm.tsx +++ b/apps/web/src/features/recipes/RecipeImportForm.tsx @@ -1,5 +1,6 @@ import { type CreateRecipeInput, + createRecipeSchema, type DietView, ErrorCode, type IngredientView, @@ -9,7 +10,6 @@ import { type RecipeVisibility, type UnitView, type WeekDay, - createRecipeSchema, } from "@batch-cooking/shared"; import { type FormEvent, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; diff --git a/apps/web/src/features/recipes/recipe-import-draft.ts b/apps/web/src/features/recipes/recipe-import-draft.ts index ebdbff9..29a5bef 100644 --- a/apps/web/src/features/recipes/recipe-import-draft.ts +++ b/apps/web/src/features/recipes/recipe-import-draft.ts @@ -1,7 +1,7 @@ import { type CreateRecipeInput, - type RecipeImportDraftView, createRecipeSchema, + type RecipeImportDraftView, } from "@batch-cooking/shared"; /** diff --git a/apps/web/src/features/theme/ThemeContext.tsx b/apps/web/src/features/theme/ThemeContext.tsx index f8c1021..8a31aec 100644 --- a/apps/web/src/features/theme/ThemeContext.tsx +++ b/apps/web/src/features/theme/ThemeContext.tsx @@ -1,5 +1,5 @@ import type { ThemePreference } from "@batch-cooking/shared"; -import { type ReactNode, createContext, useCallback, useContext, useEffect, useState } from "react"; +import { createContext, type ReactNode, useCallback, useContext, useEffect, useState } from "react"; import { apiClient } from "../../api/client"; import { useAuth } from "../auth/AuthContext"; diff --git a/apps/web/src/layouts/AppLayout.tsx b/apps/web/src/layouts/AppLayout.tsx index e3aef78..a02a536 100644 --- a/apps/web/src/layouts/AppLayout.tsx +++ b/apps/web/src/layouts/AppLayout.tsx @@ -195,7 +195,7 @@ function AccountMenu() { async function handleLogout() { setIsOpen(false); await logout(); - navigate("/login"); + void navigate("/login"); } const initial = user?.firstName?.charAt(0).toUpperCase() ?? ""; diff --git a/apps/web/src/layouts/nav-icons.tsx b/apps/web/src/layouts/nav-icons.tsx index a698dfa..dbfb6fb 100644 --- a/apps/web/src/layouts/nav-icons.tsx +++ b/apps/web/src/layouts/nav-icons.tsx @@ -13,18 +13,18 @@ // every consumer passes `aria-hidden="true"` itself (unlike the old custom // `Icon` wrapper, lucide doesn't set this by default). export { - Calendar as PlanningIcon, BookOpen as RecipesIcon, - ShoppingCart as ShoppingListIcon, - Settings as SettingsIcon, - User as AccountIcon, - Leaf as DietPreferencesIcon, - Home as HouseholdIcon, - Palette as UserPreferencesIcon, - Info as CreditsIcon, + Calendar as PlanningIcon, ChevronLeft as ChevronLeftIcon, - Star as FavoriteIcon, - Globe as PublicIcon, - Rss as SourcesIcon, ExternalLink as SourceLinkIcon, + Globe as PublicIcon, + Home as HouseholdIcon, + Info as CreditsIcon, + Leaf as DietPreferencesIcon, + Palette as UserPreferencesIcon, + Rss as SourcesIcon, + Settings as SettingsIcon, + ShoppingCart as ShoppingListIcon, + Star as FavoriteIcon, + User as AccountIcon, } from "lucide-react"; diff --git a/apps/web/src/pages/ImportRecipePage.tsx b/apps/web/src/pages/ImportRecipePage.tsx index 24234de..6314c9c 100644 --- a/apps/web/src/pages/ImportRecipePage.tsx +++ b/apps/web/src/pages/ImportRecipePage.tsx @@ -72,7 +72,7 @@ export function ImportRecipePage() { externalId={externalId} planningSlot={planningSlot ?? undefined} onImported={({ recipe, planningItem }) => { - navigate(planningItem ? "/" : `/recettes/${recipe.id}`); + void navigate(planningItem ? "/" : `/recettes/${recipe.id}`); }} /> diff --git a/apps/web/src/pages/LoginPage.tsx b/apps/web/src/pages/LoginPage.tsx index 98a931b..8961f6e 100644 --- a/apps/web/src/pages/LoginPage.tsx +++ b/apps/web/src/pages/LoginPage.tsx @@ -50,7 +50,7 @@ export function LoginPage() { setIsSubmitting(true); try { await login(result.data); - navigate("/"); + void navigate("/"); } catch (err) { // ApiError.code is looked up through ErrorMessageService so the // label is centralized and localized — never display err.message diff --git a/apps/web/src/pages/PlanningPage.tsx b/apps/web/src/pages/PlanningPage.tsx index 0771e23..e9e082e 100644 --- a/apps/web/src/pages/PlanningPage.tsx +++ b/apps/web/src/pages/PlanningPage.tsx @@ -1,7 +1,7 @@ import { - DateTime, addWeeks, buildCalendarMonth, + DateTime, formatDateOnly, getWeekStart, toDateOnly, diff --git a/apps/web/src/pages/RecipeFormPage.tsx b/apps/web/src/pages/RecipeFormPage.tsx index fd0fab9..66de3cd 100644 --- a/apps/web/src/pages/RecipeFormPage.tsx +++ b/apps/web/src/pages/RecipeFormPage.tsx @@ -1,11 +1,11 @@ import { type CreateRecipeInput, + createRecipeSchema, type DietView, ErrorCode, type IngredientView, type RecipeVisibility, type UnitView, - createRecipeSchema, } from "@batch-cooking/shared"; import { type FormEvent, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; @@ -196,7 +196,7 @@ export function RecipeFormPage() { recipeId !== null ? await apiClient.updateRecipe(recipeId, result.data) : await apiClient.createRecipe(result.data); - navigate(`/recettes/${saved.id}`); + void navigate(`/recettes/${saved.id}`); } catch (err) { const code = err instanceof ApiError ? err.code : ErrorCode.INTERNAL_ERROR; setFormError(errorMessageService.getLabel(code)); diff --git a/apps/web/src/pages/RecipesPage.tsx b/apps/web/src/pages/RecipesPage.tsx index c7ac2fa..7d963b2 100644 --- a/apps/web/src/pages/RecipesPage.tsx +++ b/apps/web/src/pages/RecipesPage.tsx @@ -10,10 +10,10 @@ import { } from "../features/recipes/RecipeSourcesPanel"; import { RecipeTable } from "../features/recipes/RecipeTable"; import { - RecipeTabs, - type RecipesPageTab, isSourceTab, parseSourceTabValue, + type RecipesPageTab, + RecipeTabs, sourceTabValue, } from "../features/recipes/RecipeTabs"; import { useEnabledSources } from "../features/recipes/useEnabledSources"; @@ -164,7 +164,7 @@ export function RecipesPage() { /** After a delete, the removed recipe can no longer be selected, and the table must drop it too. */ function handleDeleted(recipeId: number) { - navigate("/recettes"); + void navigate("/recettes"); setListState((prev) => prev.status === "loaded" ? { status: "loaded", recipes: prev.recipes.filter((r) => r.id !== recipeId) } @@ -219,7 +219,7 @@ export function RecipesPage() { } onSelectImportedRecipe={(recipe) => { setActiveTab("favoris"); - navigate(`/recettes/${recipe.id}`); + void navigate(`/recettes/${recipe.id}`); }} /> ) : ( diff --git a/apps/web/src/pages/SignupPage.tsx b/apps/web/src/pages/SignupPage.tsx index 6776e88..8c481db 100644 --- a/apps/web/src/pages/SignupPage.tsx +++ b/apps/web/src/pages/SignupPage.tsx @@ -52,7 +52,7 @@ export function SignupPage() { setIsSubmitting(true); try { await signup(result.data); - navigate("/onboarding/regime"); + void navigate("/onboarding/regime"); } catch (err) { // ApiError.code is looked up through ErrorMessageService so the // label is centralized and localized — never display err.message diff --git a/apps/web/src/pages/onboarding/OnboardingAllergensPage.tsx b/apps/web/src/pages/onboarding/OnboardingAllergensPage.tsx index 7c5868b..6052d9f 100644 --- a/apps/web/src/pages/onboarding/OnboardingAllergensPage.tsx +++ b/apps/web/src/pages/onboarding/OnboardingAllergensPage.tsx @@ -42,6 +42,14 @@ export function OnboardingAllergensPage() { setAllergies(allergiesResult); setTotalSteps(house !== null ? 4 : 3); }) + .catch(() => { + // Neither piece is essential for this last step to render — the + // allergy list just stays empty (nothing to pick from) and the + // step count keeps its 3-of-3 default, same "don't strand the + // visitor over a transient failure" reasoning as + // OnboardingSourcesPage's own catch, just with nothing to + // navigate away to since this already is the last step. + }) .finally(() => { if (!cancelled) setIsLoading(false); }); @@ -57,7 +65,7 @@ export function OnboardingAllergensPage() { setIsSubmitting(true); try { await apiClient.updateAllergyIds(allergyIds); - navigate("/"); + void navigate("/"); } catch (err) { const code = err instanceof ApiError ? err.code : ErrorCode.INTERNAL_ERROR; setFormError(errorMessageService.getLabel(code)); diff --git a/apps/web/src/pages/onboarding/OnboardingDietPage.tsx b/apps/web/src/pages/onboarding/OnboardingDietPage.tsx index 2e92117..e4ca7b8 100644 --- a/apps/web/src/pages/onboarding/OnboardingDietPage.tsx +++ b/apps/web/src/pages/onboarding/OnboardingDietPage.tsx @@ -34,6 +34,11 @@ export function OnboardingDietPage() { .then((result) => { if (!cancelled) setDiets(result); }) + // Not essential for this step to render — the diet list just stays + // empty (nothing to pick, "Aucun régime particulier" still works) + // rather than stranding the visitor over a transient failure, same + // reasoning as OnboardingSourcesPage's own catch. + .catch(() => {}) .finally(() => { if (!cancelled) setIsLoading(false); }); @@ -48,7 +53,7 @@ export function OnboardingDietPage() { setIsSubmitting(true); try { await apiClient.updateDiet(dietId); - navigate("/onboarding/foyer"); + void navigate("/onboarding/foyer"); } catch (err) { const code = err instanceof ApiError ? err.code : ErrorCode.INTERNAL_ERROR; setFormError(errorMessageService.getLabel(code)); diff --git a/apps/web/src/pages/onboarding/OnboardingHouseholdPage.tsx b/apps/web/src/pages/onboarding/OnboardingHouseholdPage.tsx index 3d1a2d0..a332fdf 100644 --- a/apps/web/src/pages/onboarding/OnboardingHouseholdPage.tsx +++ b/apps/web/src/pages/onboarding/OnboardingHouseholdPage.tsx @@ -36,6 +36,11 @@ export function OnboardingHouseholdPage() { .then((result) => { if (!cancelled) setHouse(result); }) + // Not essential for this step to render — `house` just stays `null` + // (same as a genuinely household-less profile) rather than + // stranding the visitor over a transient failure, same reasoning as + // OnboardingSourcesPage's own catch. + .catch(() => {}) .finally(() => { if (!cancelled) setIsLoading(false); }); @@ -51,7 +56,7 @@ export function OnboardingHouseholdPage() { * configure sources for without a household). */ function goToNextStep(hasHousehold: boolean) { - navigate(hasHousehold ? "/onboarding/sources" : "/onboarding/allergenes"); + void navigate(hasHousehold ? "/onboarding/sources" : "/onboarding/allergenes"); } return ( diff --git a/apps/web/src/pages/onboarding/OnboardingSourcesPage.tsx b/apps/web/src/pages/onboarding/OnboardingSourcesPage.tsx index 68f7cd3..973a4f8 100644 --- a/apps/web/src/pages/onboarding/OnboardingSourcesPage.tsx +++ b/apps/web/src/pages/onboarding/OnboardingSourcesPage.tsx @@ -40,7 +40,7 @@ export function OnboardingSourcesPage() { .then((result) => { if (cancelled) return; if (result.length === 0) { - navigate("/onboarding/allergenes", { replace: true }); + void navigate("/onboarding/allergenes", { replace: true }); return; } setSources(result); @@ -50,7 +50,7 @@ export function OnboardingSourcesPage() { // Nothing to configure sources for if we can't even list them — the // wizard shouldn't strand the visitor here over a transient failure // fetching an optional step's own data. - if (!cancelled) navigate("/onboarding/allergenes", { replace: true }); + if (!cancelled) void navigate("/onboarding/allergenes", { replace: true }); }); return () => { cancelled = true; @@ -63,7 +63,7 @@ export function OnboardingSourcesPage() { setIsSubmitting(true); try { await apiClient.updateHouseSourceIds(sourceIds); - navigate("/onboarding/allergenes"); + void navigate("/onboarding/allergenes"); } catch (err) { const code = err instanceof ApiError ? err.code : ErrorCode.INTERNAL_ERROR; setFormError(errorMessageService.getLabel(code)); diff --git a/apps/web/src/pages/settings/AccountSettingsPage.tsx b/apps/web/src/pages/settings/AccountSettingsPage.tsx index 3bd7517..b00b084 100644 --- a/apps/web/src/pages/settings/AccountSettingsPage.tsx +++ b/apps/web/src/pages/settings/AccountSettingsPage.tsx @@ -30,7 +30,7 @@ export function AccountSettingsPage() { setIsSubmitting(true); try { await deleteAccount(password); - navigate("/login"); + void navigate("/login"); } catch (err) { const code = err instanceof ApiError ? err.code : ErrorCode.INTERNAL_ERROR; setError(errorMessageService.getLabel(code)); diff --git a/apps/web/src/pages/settings/HouseholdSettingsPage.tsx b/apps/web/src/pages/settings/HouseholdSettingsPage.tsx index 38b07ba..62f75df 100644 --- a/apps/web/src/pages/settings/HouseholdSettingsPage.tsx +++ b/apps/web/src/pages/settings/HouseholdSettingsPage.tsx @@ -1,10 +1,10 @@ import { ErrorCode, type HouseView, - type SourceView, renameHouseSchema, + type SourceView, } from "@batch-cooking/shared"; -import { type FormEvent, useEffect, useRef, useState } from "react"; +import { type FormEvent, useCallback, useEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import { ApiError, apiClient } from "../../api/client"; import { useAuth } from "../../features/auth/AuthContext"; @@ -48,11 +48,15 @@ export function HouseholdSettingsPage() { const [loadError, setLoadError] = useState(false); const [house, setHouse] = useState(null); - useEffect(() => { - loadHouse(); - }, []); - - function loadHouse() { + // Stable across renders (empty deps — only closes over setters, which + // React itself guarantees are stable) so the effect below only re-runs on + // mount, not on every render. It used to be a plain function declaration, + // a fresh reference every render — its `useEffect` dependency on it then + // re-triggered the effect (and thus the fetch, and thus a state update, + // and thus another render...) forever. `loadHouse` is also handed to + // `NoHousehold`/`HasHousehold` below as their `onChanged` callback, so it + // has to stay this stable regardless of the effect. + const loadHouse = useCallback(() => { setIsLoading(true); setLoadError(false); return apiClient @@ -60,7 +64,11 @@ export function HouseholdSettingsPage() { .then((result) => setHouse(result)) .catch(() => setLoadError(true)) .finally(() => setIsLoading(false)); - } + }, []); + + useEffect(() => { + void loadHouse(); + }, [loadHouse]); if (isLoading) { return ( @@ -300,6 +308,11 @@ function SourcesSection() { setSources(sourcesResult); setSourceIds(sourceIdsResult); }) + // Not essential for the rest of the page to render — the sources + // list just stays empty (nothing to toggle) rather than stranding + // the visitor over a transient failure, same reasoning as + // OnboardingSourcesPage's own catch. + .catch(() => {}) .finally(() => { if (!cancelled) setIsLoading(false); }); @@ -368,13 +381,7 @@ function InviteCode({ code }: { code: string }) { } /** Admin-only button removing one specific member from the household. */ -function RemoveMemberButton({ - memberId, - onChanged, -}: { - memberId: number; - onChanged: () => void; -}) { +function RemoveMemberButton({ memberId, onChanged }: { memberId: number; onChanged: () => void }) { const { t } = useTranslation(); const [isRemoving, setIsRemoving] = useState(false); diff --git a/biome.json b/biome.json index 8b9953f..5d95335 100644 --- a/biome.json +++ b/biome.json @@ -1,5 +1,5 @@ { - "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", + "$schema": "https://biomejs.dev/schemas/2.5.9/schema.json", "vcs": { "enabled": true, "clientKind": "git", @@ -7,13 +7,14 @@ }, "files": { "ignoreUnknown": false, - "ignore": [ - "dist", - "coverage", - "**/node_modules", - ".pnpm-store", - "cypress/videos", - "cypress/screenshots" + "includes": [ + "**", + "!**/dist", + "!**/coverage", + "!**/node_modules", + "!**/.pnpm-store", + "!**/cypress/videos", + "!**/cypress/screenshots" ] }, "formatter": { @@ -22,13 +23,21 @@ "indentWidth": 2, "lineWidth": 100 }, - "organizeImports": { - "enabled": true - }, + "assist": { "actions": { "source": { "organizeImports": "on" } } }, "linter": { "enabled": true, "rules": { - "recommended": true + "preset": "recommended", + "nursery": { + "noFloatingPromises": "error" + }, + "suspicious": { + "noConsole": { + "level": "error", + "options": { "allow": ["error", "warn", "info", "debug", "table", "assert"] } + }, + "noExplicitAny": "error" + } } }, "javascript": { diff --git a/package.json b/package.json index b8a073d..c3bca94 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "dev:web": "pnpm --filter web dev" }, "devDependencies": { - "@biomejs/biome": "^1.9.4", + "@biomejs/biome": "^2.5.9", "typescript": "^5.7.2" }, "pnpm": { diff --git a/packages/date-tools/src/index.ts b/packages/date-tools/src/index.ts index be425aa..f0b064a 100644 --- a/packages/date-tools/src/index.ts +++ b/packages/date-tools/src/index.ts @@ -4,9 +4,8 @@ // this package rather than hand-rolled `Date` arithmetic or a second, // differently-behaved date library creeping into one side only. -export * from "./date-only.js"; -export * from "./week.js"; - // Re-exported so a consumer never needs its own direct `luxon` dependency // just to type a `DateTime` value passed to/from this package's functions. export { DateTime } from "luxon"; +export * from "./date-only.js"; +export * from "./week.js"; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7560769..c06548d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,8 +9,8 @@ importers: .: devDependencies: '@biomejs/biome': - specifier: ^1.9.4 - version: 1.9.4 + specifier: ^2.5.9 + version: 2.5.9 typescript: specifier: ^5.7.2 version: 5.9.3 @@ -347,55 +347,55 @@ packages: peerDependencies: esbuild: '>=0.17.0' - '@biomejs/biome@1.9.4': - resolution: {integrity: sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==, tarball: https://registry.npmjs.org/@biomejs/biome/-/biome-1.9.4.tgz} + '@biomejs/biome@2.5.9': + resolution: {integrity: sha512-KkgCvdHB4IhtpHpF564plA9jo6fDOwWGQ/3jvreLzgOtRLEDoPqr7QO9qejNA8jKwDsSkAKr77hqBHnyUbIw4g==, tarball: https://registry.npmjs.org/@biomejs/biome/-/biome-2.5.9.tgz} engines: {node: '>=14.21.3'} hasBin: true - '@biomejs/cli-darwin-arm64@1.9.4': - resolution: {integrity: sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw==, tarball: https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-1.9.4.tgz} + '@biomejs/cli-darwin-arm64@2.5.9': + resolution: {integrity: sha512-am22pX2aBqznqq1eMyIj/bZ++riF3Lk6ct7cbv+gQK0csFhr+d8O0RkOi2FF2qSgFgANbqNkIZ0/PxlnW2pLFg==, tarball: https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.5.9.tgz} engines: {node: '>=14.21.3'} cpu: [arm64] os: [darwin] - '@biomejs/cli-darwin-x64@1.9.4': - resolution: {integrity: sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg==, tarball: https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-1.9.4.tgz} + '@biomejs/cli-darwin-x64@2.5.9': + resolution: {integrity: sha512-l44KWDHLDvEnD0N/XcrVs7VXb3A18xL7QS3WB0eL93wbmk529ffIG55vleGCqaunpRUjLrdnjK05Qki1dsjylg==, tarball: https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.5.9.tgz} engines: {node: '>=14.21.3'} cpu: [x64] os: [darwin] - '@biomejs/cli-linux-arm64-musl@1.9.4': - resolution: {integrity: sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA==, tarball: https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-1.9.4.tgz} + '@biomejs/cli-linux-arm64-musl@2.5.9': + resolution: {integrity: sha512-7ImVPwBLCtkmpR5esd8RHhTqW94f0JLJQum6AneYcy94jRm18TaPPm7slaigGzFhfgt3QiD1Vj52LKmBAnKizA==, tarball: https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.5.9.tgz} engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] - '@biomejs/cli-linux-arm64@1.9.4': - resolution: {integrity: sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g==, tarball: https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-1.9.4.tgz} + '@biomejs/cli-linux-arm64@2.5.9': + resolution: {integrity: sha512-ICaK+IYaVZvKbBxX2rwrPT0DdUDMnE9Vm3nQGe+mltQPmUg19pONzkPWGdY4FCsoreDETWDynvdt4ysCbF5gNQ==, tarball: https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.5.9.tgz} engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] - '@biomejs/cli-linux-x64-musl@1.9.4': - resolution: {integrity: sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg==, tarball: https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-1.9.4.tgz} + '@biomejs/cli-linux-x64-musl@2.5.9': + resolution: {integrity: sha512-RXGaD0o1/pTTguYw1aeDJh9ad6Lfrui0fI7mBderTyGr7WuUJkBIttgLkR3XJyoxOkkgfBDspaUT8wXArTqLZw==, tarball: https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.5.9.tgz} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] - '@biomejs/cli-linux-x64@1.9.4': - resolution: {integrity: sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg==, tarball: https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-1.9.4.tgz} + '@biomejs/cli-linux-x64@2.5.9': + resolution: {integrity: sha512-z22Q/zFYSvbIJfW1CbfZPu4X8PddS6Qd2ORbc6h+aT6EcwAxUF3m6fA4HjNvA3TU4X0dTJRwNPB165ES3PJXzg==, tarball: https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.5.9.tgz} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] - '@biomejs/cli-win32-arm64@1.9.4': - resolution: {integrity: sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg==, tarball: https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-1.9.4.tgz} + '@biomejs/cli-win32-arm64@2.5.9': + resolution: {integrity: sha512-nHK+/HHC+D0ogAHUxomgoSTdjImb6fmNNVTKmf0tyu4eDL1DqPKIHc+i+UL8+b0RnAu8224qo8F2tCVnaT0A3w==, tarball: https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.5.9.tgz} engines: {node: '>=14.21.3'} cpu: [arm64] os: [win32] - '@biomejs/cli-win32-x64@1.9.4': - resolution: {integrity: sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA==, tarball: https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-1.9.4.tgz} + '@biomejs/cli-win32-x64@2.5.9': + resolution: {integrity: sha512-Yiq0H56LjXSSw/hd9YkXgSLQfzyDJzbzU2TezozxyNw+uKWAqOtqGVvBfzKRRDiaFF5avGAhHdWKx7LtDOShUw==, tarball: https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.5.9.tgz} engines: {node: '>=14.21.3'} cpu: [x64] os: [win32] @@ -3963,39 +3963,39 @@ snapshots: transitivePeerDependencies: - supports-color - '@biomejs/biome@1.9.4': + '@biomejs/biome@2.5.9': optionalDependencies: - '@biomejs/cli-darwin-arm64': 1.9.4 - '@biomejs/cli-darwin-x64': 1.9.4 - '@biomejs/cli-linux-arm64': 1.9.4 - '@biomejs/cli-linux-arm64-musl': 1.9.4 - '@biomejs/cli-linux-x64': 1.9.4 - '@biomejs/cli-linux-x64-musl': 1.9.4 - '@biomejs/cli-win32-arm64': 1.9.4 - '@biomejs/cli-win32-x64': 1.9.4 + '@biomejs/cli-darwin-arm64': 2.5.9 + '@biomejs/cli-darwin-x64': 2.5.9 + '@biomejs/cli-linux-arm64': 2.5.9 + '@biomejs/cli-linux-arm64-musl': 2.5.9 + '@biomejs/cli-linux-x64': 2.5.9 + '@biomejs/cli-linux-x64-musl': 2.5.9 + '@biomejs/cli-win32-arm64': 2.5.9 + '@biomejs/cli-win32-x64': 2.5.9 - '@biomejs/cli-darwin-arm64@1.9.4': + '@biomejs/cli-darwin-arm64@2.5.9': optional: true - '@biomejs/cli-darwin-x64@1.9.4': + '@biomejs/cli-darwin-x64@2.5.9': optional: true - '@biomejs/cli-linux-arm64-musl@1.9.4': + '@biomejs/cli-linux-arm64-musl@2.5.9': optional: true - '@biomejs/cli-linux-arm64@1.9.4': + '@biomejs/cli-linux-arm64@2.5.9': optional: true - '@biomejs/cli-linux-x64-musl@1.9.4': + '@biomejs/cli-linux-x64-musl@2.5.9': optional: true - '@biomejs/cli-linux-x64@1.9.4': + '@biomejs/cli-linux-x64@2.5.9': optional: true - '@biomejs/cli-win32-arm64@1.9.4': + '@biomejs/cli-win32-arm64@2.5.9': optional: true - '@biomejs/cli-win32-x64@1.9.4': + '@biomejs/cli-win32-x64@2.5.9': optional: true '@colors/colors@1.5.0':