chore(lint): upgrade Biome vers 2.x, active noExplicitAny/noConsole/noFloatingPromises
`@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<void>`) — 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 <noreply@anthropic.com>
This commit is contained in:
parent
a53e8aff4a
commit
37a044a267
41 changed files with 167 additions and 139 deletions
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { type ReactElement, cloneElement, useId } from "react";
|
||||
import { cloneElement, type ReactElement, useId } from "react";
|
||||
import "./tooltip.scss";
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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}. */
|
||||
|
|
|
|||
|
|
@ -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}`);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -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). */
|
||||
|
|
|
|||
|
|
@ -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. */
|
||||
|
|
|
|||
|
|
@ -135,6 +135,7 @@ export function RecipeDetailPanel({
|
|||
<h3>{t("recipes.stepsTitle")}</h3>
|
||||
<ol className="recipe-detail-panel__steps">
|
||||
{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.
|
||||
<li key={`${index}-${step.description}`}>
|
||||
{step.picture && <img src={step.picture} alt="" />}
|
||||
<StepDescription description={step.description} techSteps={step.techSteps} />
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import {
|
||||
type CreateRecipeInput,
|
||||
type RecipeImportDraftView,
|
||||
createRecipeSchema,
|
||||
type RecipeImportDraftView,
|
||||
} from "@batch-cooking/shared";
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
||||
|
|
|
|||
|
|
@ -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() ?? "";
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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}`);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import {
|
||||
DateTime,
|
||||
addWeeks,
|
||||
buildCalendarMonth,
|
||||
DateTime,
|
||||
formatDateOnly,
|
||||
getWeekStart,
|
||||
toDateOnly,
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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}`);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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<HouseView | null>(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);
|
||||
|
||||
|
|
|
|||
33
biome.json
33
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": {
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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':
|
||||
|
|
|
|||
Loading…
Reference in a new issue