diff --git a/.cspell/custom-dictionary-workspace.txt b/.cspell/custom-dictionary-workspace.txt index 50c2ec0..a2b8baa 100644 --- a/.cspell/custom-dictionary-workspace.txt +++ b/.cspell/custom-dictionary-workspace.txt @@ -1,4 +1,5 @@ Abaixo +aberta abertas abordagem aborta @@ -23,6 +24,7 @@ acesso acessos achar acidental +aciona acompanhamento acompanhar acompanhe @@ -106,6 +108,7 @@ anos Anos Anotações anteriores +antiga antigas antigo antigos @@ -239,6 +242,7 @@ cadastrar Cadastro cadvisor caixa +calcula calculável Camada camadas @@ -299,6 +303,7 @@ Ciclos Círculo cirúrgica clampeia +classifica clicar clientes cmdmeta @@ -363,6 +368,7 @@ configuradas configurado configurados Configurar +confirmada conflita conflitam conflito @@ -400,6 +406,7 @@ controla Controle Controles conversão +conversar Converte convites cooperado @@ -441,6 +448,7 @@ currículo curta customuser daqui +datas datname davecgh debuglib @@ -783,6 +791,7 @@ frança França freela frequentemente +frequentes fulano Fulano funciona @@ -829,6 +838,8 @@ habilitado habilitar hashedpassword híbrid +híbridas +Híbridas híbrido Hierarquia hintrc @@ -1131,6 +1142,7 @@ movimente muda mudam mude +mudou múltiplas múltiplos mundiais @@ -1200,6 +1212,7 @@ oportunidade oportunidades optar ordem +ordenação Orfaos órfãos organicamente @@ -1261,6 +1274,7 @@ perfeitamente perfis performático pergunta +perguntas periodo permanecem permanente @@ -1290,6 +1304,7 @@ plataformas plugar pmezard pôde +Podemos poderem poderia poderosa @@ -1314,6 +1329,7 @@ possivelmente Possuam possui possuir +poucas pouco prática práticas @@ -1335,6 +1351,7 @@ prefixo Prefixo prefs preparar +presenciais presencial presente presentes @@ -1428,6 +1445,7 @@ reconcilia reconecta reconhece recorre +recrutador Recrutadores recrutamento recupera @@ -1557,6 +1575,7 @@ seguro seguros sejam SELECIONADA +selecionar seletivos seletor Selo diff --git a/backend/src/lib/cache.ts b/backend/src/lib/cache.ts index 8b0d0be..088f360 100644 --- a/backend/src/lib/cache.ts +++ b/backend/src/lib/cache.ts @@ -197,13 +197,35 @@ export type CacheJobIndexFilters = { country?: string; state?: string; city?: string; - type?: string; - model?: string; + type?: string | string[]; + model?: string | string[]; contract?: string; }; -export function cacheJobIndexKeys(filters: CacheJobIndexFilters): string[] { - const entries: Array<[string, string | undefined]> = [ +function filterValues(value: string | string[] | undefined): string[] { + const values = Array.isArray(value) ? value : [value]; + + return values + .flatMap((item) => item?.split(",") ?? []) + .map((item) => item.trim()) + .filter(Boolean); +} + +function cacheJobIndexKey(kind: string, value: string): string { + const normalized = + kind === "level" + ? normalizeLevelIndexValue(value) + : normalizeIndexValue(value); + + if (!normalized || normalized === "todos" || normalized === "all") { + return ""; + } + + return `scraper:jobs:${kind}:${normalized}`; +} + +function cacheJobIndexKeyGroups(filters: CacheJobIndexFilters): string[][] { + const entries: Array<[string, string | string[] | undefined]> = [ ["level", filters.level], ["location", filters.location], ["continent", filters.continent], @@ -215,17 +237,16 @@ export function cacheJobIndexKeys(filters: CacheJobIndexFilters): string[] { ]; return entries - .map(([kind, value]) => { - const normalized = - kind === "level" - ? normalizeLevelIndexValue(value ?? "") - : normalizeIndexValue(value ?? ""); - if (!normalized || normalized === "todos" || normalized === "all") { - return ""; - } - return `scraper:jobs:${kind}:${normalized}`; - }) - .filter(Boolean); + .map(([kind, value]) => + filterValues(value) + .map((item) => cacheJobIndexKey(kind, item)) + .filter(Boolean), + ) + .filter((group) => group.length > 0); +} + +export function cacheJobIndexKeys(filters: CacheJobIndexFilters): string[] { + return cacheJobIndexKeyGroups(filters).flatMap((group) => group); } export async function cacheSearchJobIds( @@ -233,26 +254,39 @@ export async function cacheSearchJobIds( ): Promise { const client = await getCache(); const keywordKeys = keywordSearchKeys(filters.keywords ?? []); - const filterKeys = cacheJobIndexKeys(filters); + const tempKeys: string[] = []; - if (keywordKeys.length === 0 && filterKeys.length === 0) { - return await client.sMembers("scraper:jobs:index"); - } + const filterKeys = await Promise.all( + cacheJobIndexKeyGroups(filters).map(async (group) => { + if (group.length === 1) return group[0]; - if (keywordKeys.length === 0) { - if (filterKeys.length === 1) return await client.sMembers(filterKeys[0]); - return (await client.sendCommand(["SINTER", ...filterKeys])) as string[]; - } + const tempKey = `scraper:jobs:filter:${randomUUID()}`; + tempKeys.push(tempKey); + await client.sendCommand(["SUNIONSTORE", tempKey, ...group]); + await client.expire(tempKey, 30); + return tempKey; + }), + ); - if (keywordKeys.length === 1) { - const keys = [keywordKeys[0], ...filterKeys]; - if (keys.length === 1) return await client.sMembers(keys[0]); - return (await client.sendCommand(["SINTER", ...keys])) as string[]; - } + try { + if (keywordKeys.length === 0 && filterKeys.length === 0) { + return await client.sMembers("scraper:jobs:index"); + } - const tempKey = `scraper:jobs:search:${randomUUID()}`; + if (keywordKeys.length === 0) { + if (filterKeys.length === 1) return await client.sMembers(filterKeys[0]); + return (await client.sendCommand(["SINTER", ...filterKeys])) as string[]; + } + + if (keywordKeys.length === 1) { + const keys = [keywordKeys[0], ...filterKeys]; + if (keys.length === 1) return await client.sMembers(keys[0]); + return (await client.sendCommand(["SINTER", ...keys])) as string[]; + } + + const tempKey = `scraper:jobs:search:${randomUUID()}`; + tempKeys.push(tempKey); - try { await client.sendCommand(["SUNIONSTORE", tempKey, ...keywordKeys]); await client.expire(tempKey, 30); @@ -260,7 +294,7 @@ export async function cacheSearchJobIds( if (keys.length === 1) return await client.sMembers(keys[0]); return (await client.sendCommand(["SINTER", ...keys])) as string[]; } finally { - await client.del(tempKey); + await Promise.all(tempKeys.map((key) => client.del(key))); } } diff --git a/backend/src/routes/jobs.routes.ts b/backend/src/routes/jobs.routes.ts index 635c2a3..335cf14 100644 --- a/backend/src/routes/jobs.routes.ts +++ b/backend/src/routes/jobs.routes.ts @@ -35,6 +35,15 @@ function firstQueryValue(value: unknown): string { return typeof value === "string" ? value.trim() : ""; } +function queryValues(value: unknown): string[] { + const values = Array.isArray(value) ? value : [value]; + + return values + .flatMap((item) => (typeof item === "string" ? item.split(",") : [])) + .map((item) => item.trim()) + .filter(Boolean); +} + function normalizeComparable(value: string): string { return value .normalize("NFD") @@ -209,16 +218,22 @@ function matchesLocationFilter(jobLocation: string, location: string): boolean { return normalizedLocation.includes(location); } +function getTypeFilters(query: Request["query"]): string[] { + const rawTypes = queryValues(query.model).length > 0 + ? queryValues(query.model) + : queryValues(query.type); + + return [...new Set(rawTypes.map(normalizeComparable).filter(Boolean))]; +} + function filterJobs(jobs: unknown[], query: Request["query"]): unknown[] { const level = normalizeLevelFilter(firstQueryValue(query.level)); const location = normalizeComparable( firstQueryValue(query.country) || firstQueryValue(query.location), ); - const type = normalizeComparable( - firstQueryValue(query.model) || firstQueryValue(query.type), - ); + const types = getTypeFilters(query); - if (!level && !location && !type) return jobs; + if (!level && !location && types.length === 0) return jobs; return jobs.filter((job) => { const candidate = job as SearchJob; @@ -227,7 +242,8 @@ function filterJobs(jobs: unknown[], query: Request["query"]): unknown[] { const matchesLevel = !level || inferJobLevel(title) === level; const matchesLocation = matchesLocationFilter(jobLocation, location); - const matchesType = !type || inferJobType(candidate) === type; + const matchesType = + types.length === 0 || types.includes(inferJobType(candidate)); return matchesLevel && matchesLocation && matchesType; }); @@ -387,8 +403,8 @@ jobsRoutes.get("/search", async (req: Request, res: Response) => { country: firstQueryValue(req.query.country), state: firstQueryValue(req.query.state), city: firstQueryValue(req.query.city), - type: firstQueryValue(req.query.type), - model: firstQueryValue(req.query.model), + type: queryValues(req.query.type), + model: queryValues(req.query.model), contract: firstQueryValue(req.query.contract) || firstQueryValue(req.query.contractType) || diff --git a/backend/tests/unit/app.test.ts b/backend/tests/unit/app.test.ts index 0d005a0..1c6cb6a 100644 --- a/backend/tests/unit/app.test.ts +++ b/backend/tests/unit/app.test.ts @@ -321,8 +321,8 @@ describe("jobsApiApp", () => { country: "", state: "", city: "", - type: "Remoto", - model: "", + type: ["Remoto"], + model: [], contract: "", }); expect(mocks.cacheSearchKeywords).not.toHaveBeenCalled(); diff --git a/backend/tests/unit/libs/cache.test.ts b/backend/tests/unit/libs/cache.test.ts index d6b25b4..9e9f19b 100644 --- a/backend/tests/unit/libs/cache.test.ts +++ b/backend/tests/unit/libs/cache.test.ts @@ -286,6 +286,33 @@ describe("Valkey Cache Lib", () => { ); expect(result).toEqual(["job_1", "job_2"]); }); + + it("deve unir múltiplos modelos antes de intersectar com outros filtros", async () => { + mockClientInstance.sendCommand + .mockResolvedValueOnce(2) + .mockResolvedValueOnce(["job_1", "job_2"]); + + const result = await cacheSearchJobIds({ + model: ["Remoto", "Híbrido"], + level: "Sênior", + }); + + expect(mockClientInstance.sendCommand).toHaveBeenNthCalledWith(1, [ + "SUNIONSTORE", + expect.stringMatching(/^scraper:jobs:filter:/), + "scraper:jobs:model:remoto", + "scraper:jobs:model:hibrido", + ]); + expect(mockClientInstance.sendCommand).toHaveBeenNthCalledWith(2, [ + "SINTER", + "scraper:jobs:level:senior", + expect.stringMatching(/^scraper:jobs:filter:/), + ]); + expect(mockClientInstance.del).toHaveBeenCalledWith( + expect.stringMatching(/^scraper:jobs:filter:/), + ); + expect(result).toEqual(["job_1", "job_2"]); + }); }); describe("cacheGetJobsByIds", () => { diff --git a/frontend/src/domains/auth/presentation/components/LoginFormPanel.tsx b/frontend/src/domains/auth/presentation/components/LoginFormPanel.tsx index adde4bd..e9e3898 100644 --- a/frontend/src/domains/auth/presentation/components/LoginFormPanel.tsx +++ b/frontend/src/domains/auth/presentation/components/LoginFormPanel.tsx @@ -168,7 +168,15 @@ export default function RightSide() { ! > - +

+ Novo por aqui?{" "} + + Cadastre-se + +

{isLoading ? "Entrando..." : "Entrar"} -

- Novo por aqui?{" "} - - Cadastre-se - -

diff --git a/frontend/src/domains/marketing/presentation/components/Footer.tsx b/frontend/src/domains/marketing/presentation/components/Footer.tsx index dd5abdd..bea5f3c 100644 --- a/frontend/src/domains/marketing/presentation/components/Footer.tsx +++ b/frontend/src/domains/marketing/presentation/components/Footer.tsx @@ -1,4 +1,6 @@ import { Github } from "lucide-react"; +import LogoWhite from "@/shared/assets/logo-painel-vagas.svg"; +import LogoBlack from "@/shared/assets/black.svg"; export function Footer() { return ( @@ -26,7 +28,7 @@ export function Footer() {
diff --git a/frontend/src/domains/marketing/presentation/components/HeroSection.tsx b/frontend/src/domains/marketing/presentation/components/HeroSection.tsx index 1adcced..5e6d1ed 100644 --- a/frontend/src/domains/marketing/presentation/components/HeroSection.tsx +++ b/frontend/src/domains/marketing/presentation/components/HeroSection.tsx @@ -1,25 +1,73 @@ -import { motion } from "framer-motion"; +import { motion, useScroll, useTransform } from "framer-motion"; import { ArrowRight, Play } from "lucide-react"; import { useRef } from "react"; import { Link } from "react-router-dom"; +const STATIC_STARS = Array.from({ length: 80 }).map((_, i) => { + const random = (min: number, max: number) => Math.random() * (max - min) + min; + return { + id: i, + top: `${random(0, 100)}%`, + left: `${random(0, 100)}%`, + size: random(1, 3), + delay: random(0, 5), + duration: random(2, 6), + }; +}); + +function StarsBackground() { + return ( +
+ {STATIC_STARS.map((star) => ( + + ))} +
+ ); +} + export function HeroSection() { const ref = useRef(null); + const { scrollYProgress } = useScroll({ + target: ref, + offset: ["start start", "end start"], + }); + + const backgroundY = useTransform(scrollYProgress, [0, 1], ["0%", "50%"]); + const textY = useTransform(scrollYProgress, [0, 1], ["0%", "80%"]); return ( -
+
+ > + +
- + O < - Cand!Date - ! - > varre dezenas - de plataformas automaticamente, transforma candidatos em profissionais - preparados e entrega as melhores oportunidades filtradas para você. + Cand!Date! + > varre dezenas de plataformas automaticamente, transforma candidatos em profissionais preparados e entrega as melhores oportunidades filtradas para você. + + +
); } diff --git a/frontend/src/domains/new_dashboard/NewDashboardPage.tsx b/frontend/src/domains/new_dashboard/NewDashboardPage.tsx index 3eb44d8..6e14fe6 100644 --- a/frontend/src/domains/new_dashboard/NewDashboardPage.tsx +++ b/frontend/src/domains/new_dashboard/NewDashboardPage.tsx @@ -164,6 +164,8 @@ export default function NewDashboardPage() { const [selectedJobId, setSelectedJobId] = useState(null); const [isAddJobOpen, setIsAddJobOpen] = useState(false); const [toast, setToast] = useState(""); + const [hasUserChangedJobFilters, setHasUserChangedJobFilters] = + useState(false); const checklistSaveTimeout = useRef(null); const hasUserSelectedModelFilter = useRef(false); const showToast = useCallback((message: string) => setToast(message), []); @@ -211,6 +213,14 @@ export default function NewDashboardPage() { ), [filterType, matchedRecommendedJobs], ); + const preferredModelFilter = useMemo( + () => getModelFilterFromJobTypes(searchPreferences.jobTypes), + [searchPreferences.jobTypes], + ); + const showPreferenceNotice = + !hasUserChangedJobFilters && + preferredModelFilter !== "Todos" && + filterType === preferredModelFilter; const selectedJob = [...matchedTrackedJobs, ...matchedRecommendedJobs].find( (job) => job.id === selectedJobId, @@ -246,6 +256,7 @@ export default function NewDashboardPage() { try { await saveSearchPreferences(preferences); hasUserSelectedModelFilter.current = false; + setHasUserChangedJobFilters(false); setFilterType(getModelFilterFromJobTypes(preferences.jobTypes)); showToast("Preferências de busca atualizadas."); } catch { @@ -255,9 +266,38 @@ export default function NewDashboardPage() { const handleFilterTypeChange = useCallback((value: JobModelFilter) => { hasUserSelectedModelFilter.current = true; + setHasUserChangedJobFilters(true); setFilterType(value); }, []); + const handleSearchQueryChange = useCallback((value: string) => { + setHasUserChangedJobFilters(true); + setSearchQuery(value); + }, []); + + const handleFilterLevelChange = useCallback((value: string) => { + setHasUserChangedJobFilters(true); + setFilterLevel(value); + }, []); + + const handleContinentFilterChange = useCallback( + (value: ContinentFilter) => { + setHasUserChangedJobFilters(true); + setContinentFilter(value); + }, + [], + ); + + const handleCountryFilterChange = useCallback((value: CountryFilter) => { + setHasUserChangedJobFilters(true); + setCountryFilter(value); + }, []); + + const handleMatchSortChange = useCallback((value: MatchSort) => { + setHasUserChangedJobFilters(true); + setMatchSort(value); + }, []); + const handleCareerChecklistChange = useCallback( (careerChecklist: CareerChecklist[]) => { const nextPreferences = { @@ -370,8 +410,9 @@ export default function NewDashboardPage() { useEffect(() => { if (isLoadingUserData || hasUserSelectedModelFilter.current) return; - setFilterType(getModelFilterFromJobTypes(searchPreferences.jobTypes)); - }, [isLoadingUserData, searchPreferences.jobTypes]); + setFilterType(preferredModelFilter); + setHasUserChangedJobFilters(false); + }, [isLoadingUserData, preferredModelFilter]); // Busca automática: dispara ao digitar (com debounce) ou ao trocar // qualquer filtro, sem precisar clicar em "Buscar vagas". @@ -426,18 +467,19 @@ export default function NewDashboardPage() { void; searchPreferences?: SearchPreferences; + showPreferenceNotice?: boolean; isSearching?: boolean; pagination?: { total: number; @@ -61,6 +62,7 @@ export function JobTab({ matchSort, setMatchSort, searchPreferences, + showPreferenceNotice = false, isSearching = false, pagination, onSearchJobs, @@ -114,7 +116,9 @@ export function JobTab({ setMatchSort={setMatchSort} /> - {preferredModelFilter !== "Todos" && preferredModelLabel && ( + {showPreferenceNotice && + preferredModelFilter !== "Todos" && + preferredModelLabel && (

• Preferência ativa: {preferredModelLabel.toLowerCase()} como padrão nesta busca. diff --git a/frontend/src/domains/new_dashboard/utils/jobModelFilters.ts b/frontend/src/domains/new_dashboard/utils/jobModelFilters.ts index cc96334..1dd837b 100644 --- a/frontend/src/domains/new_dashboard/utils/jobModelFilters.ts +++ b/frontend/src/domains/new_dashboard/utils/jobModelFilters.ts @@ -50,9 +50,9 @@ export function modelFilterMatchesJob(job: Job, filterType: JobModelFilter) { export function modelFilterToApiFilter(filterType: JobModelFilter) { const jobTypes = getJobTypesFromModelFilter(filterType); - if (jobTypes.length !== 1) return {}; + if (jobTypes.length === 0) return {}; - const [type] = jobTypes; + const type = jobTypes.join(","); return { type, model: type, diff --git a/frontend/tests/unit/new_dashboard/page.test.tsx b/frontend/tests/unit/new_dashboard/page.test.tsx index 42ef32a..906656b 100644 --- a/frontend/tests/unit/new_dashboard/page.test.tsx +++ b/frontend/tests/unit/new_dashboard/page.test.tsx @@ -266,8 +266,15 @@ describe("NewDashboardPage", () => { expect(screen.getByDisplayValue("Somente remotas")).toBeInTheDocument(); }); + expect(screen.getByText(/preferência ativa/i)).toBeInTheDocument(); expect(screen.getByText("Remote Node")).toBeInTheDocument(); expect(screen.queryByText("Onsite Node")).not.toBeInTheDocument(); + + fireEvent.change(screen.getByDisplayValue("Match (padrão)"), { + target: { value: "desc" }, + }); + + expect(screen.queryByText(/preferência ativa/i)).not.toBeInTheDocument(); }); it("aciona a busca e paginação de vagas", () => { diff --git a/package-lock.json b/package-lock.json index df15455..02c8f26 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2051,13 +2051,11 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "android" ], - "peer": true, "engines": { "node": ">=18" } @@ -2222,13 +2220,11 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -2461,13 +2457,11 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "win32" ], - "peer": true, "engines": { "node": ">=18" } @@ -10108,13 +10102,11 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "aix" ], - "peer": true, "engines": { "node": ">=18" } @@ -10126,13 +10118,11 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "android" ], - "peer": true, "engines": { "node": ">=18" } @@ -10144,13 +10134,11 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "android" ], - "peer": true, "engines": { "node": ">=18" } @@ -10162,13 +10150,11 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "darwin" ], - "peer": true, "engines": { "node": ">=18" } @@ -10180,13 +10166,11 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "darwin" ], - "peer": true, "engines": { "node": ">=18" } @@ -10198,13 +10182,11 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "freebsd" ], - "peer": true, "engines": { "node": ">=18" } @@ -10216,13 +10198,11 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "freebsd" ], - "peer": true, "engines": { "node": ">=18" } @@ -10234,13 +10214,11 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -10252,13 +10230,11 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -10270,13 +10246,11 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -10288,13 +10262,11 @@ "cpu": [ "mips64el" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -10306,13 +10278,11 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -10324,13 +10294,11 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -10342,13 +10310,11 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -10360,13 +10326,11 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -10378,13 +10342,11 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "netbsd" ], - "peer": true, "engines": { "node": ">=18" } @@ -10396,13 +10358,11 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "netbsd" ], - "peer": true, "engines": { "node": ">=18" } @@ -10414,13 +10374,11 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "openbsd" ], - "peer": true, "engines": { "node": ">=18" } @@ -10432,13 +10390,11 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "openbsd" ], - "peer": true, "engines": { "node": ">=18" } @@ -10450,13 +10406,11 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "openharmony" ], - "peer": true, "engines": { "node": ">=18" } @@ -10468,13 +10422,11 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "sunos" ], - "peer": true, "engines": { "node": ">=18" } @@ -10486,13 +10438,11 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "win32" ], - "peer": true, "engines": { "node": ">=18" } @@ -10504,13 +10454,11 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "win32" ], - "peer": true, "engines": { "node": ">=18" } @@ -12888,7 +12836,6 @@ "os": [ "android" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -12909,7 +12856,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -12930,7 +12876,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -12951,7 +12896,6 @@ "os": [ "freebsd" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -12972,7 +12916,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -12993,7 +12936,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -13014,7 +12956,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -13035,7 +12976,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -13056,7 +12996,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -13077,7 +13016,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -13098,7 +13036,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">= 12.0.0" },