diff --git a/Dockerfile b/Dockerfile index 58fa931..9ad3be5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,16 +1,15 @@ FROM node:22-slim -ARG TARGETARCH - -# System deps for Chrome for Testing + build tools for native modules (better-sqlite3) -# On ARM64 we also install system Chromium (Chrome for Testing has no ARM64 binary) +# System deps for CloakBrowser + build tools for native modules (better-sqlite3) +# fonts-noto-color-emoji and fonts-freefont-ttf are required so canvas fingerprint +# hashes match real browsers; missing emoji fonts cause bot detection on Kasada/Akamai. RUN apt-get update && apt-get install -y --no-install-recommends \ curl ca-certificates fonts-liberation libasound2 \ libatk-bridge2.0-0 libatk1.0-0 libcups2 libdbus-1-3 \ libdrm2 libgbm1 libgtk-3-0 libnspr4 libnss3 \ libx11-xcb1 libxcomposite1 libxdamage1 libxrandr2 xdg-utils \ + fonts-noto-color-emoji fonts-freefont-ttf \ python3 make g++ \ - && if [ "$TARGETARCH" = "arm64" ]; then apt-get install -y --no-install-recommends chromium; fi \ && rm -rf /var/lib/apt/lists/* \ && mkdir -p /db /conf /fredy @@ -26,8 +25,8 @@ RUN yarn config set network-timeout 600000 \ && yarn --frozen-lockfile \ && yarn cache clean -# on arm64 use the system Chromium installed above -RUN if [ "$TARGETARCH" != "arm64" ]; then npx puppeteer browsers install chrome; fi +# Pre-download the CloakBrowser stealth Chromium binary (supports x86_64 and arm64) +RUN node -e "import('cloakbrowser').then(({ensureBinary}) => ensureBinary())" # Purge build tools now that native modules are compiled RUN apt-get purge -y python3 make g++ \ diff --git a/index.js b/index.js index 640038d..86fc999 100755 --- a/index.js +++ b/index.js @@ -15,6 +15,15 @@ import { initGeocodingCron } from './lib/services/crons/geocoding-cron.js'; import { getSettings } from './lib/services/storage/settingsStorage.js'; import SqliteConnection, { computeDbPath } from './lib/services/storage/SqliteConnection.js'; import { initJobExecutionService } from './lib/services/jobs/jobExecutionService.js'; +import { ensureValidBinary } from './lib/services/ensureValidBinary.js'; + +// Ensure the CloakBrowser stealth Chromium binary is present and complete before +// jobs run. ensureValidBinary() also detects and auto-heals partial extractions +// (e.g. a newer version that was downloaded but only the chrome executable was +// written) so Chrome never crashes with "Invalid file descriptor to ICU data". +logger.info('Checking CloakBrowser binary...'); +await ensureValidBinary(); +logger.info('CloakBrowser binary ready.'); //in the config, we store the path of the sqlite file, thus we must check if it is available const isConfigAccessible = await checkIfConfigIsAccessible(); diff --git a/lib/provider/immowelt.js b/lib/provider/immowelt.js index 38a509d..09e1ff1 100755 --- a/lib/provider/immowelt.js +++ b/lib/provider/immowelt.js @@ -87,7 +87,19 @@ const config = { crawlContainer: 'div[data-testid="serp-core-scrollablelistview-testid"]:not(div[data-testid="serp-enlargementlist-testid"] div[data-testid="serp-card-testid"]) div[data-testid="serp-core-classified-card-testid"]', sortByDateParam: 'order=DateDesc', - waitForSelector: 'div[data-testid="serp-gridcontainer-testid"]', + // waitForSelector is null: extract the full page via page.content() so the + // Cheerio crawler can search anywhere in the rendered document. + // preNavigateUrl visits the homepage first to establish a trusted session + // before hitting the search URL; this prevents CDN-level bot challenges that + // fire on cold sessions. waitForNetworkIdle (phase 2) then catches React's + // listing API round-trip that fires well after domcontentloaded. + waitForSelector: null, + puppeteerOptions: { + puppeteerTimeout: 60_000, + preNavigateUrl: 'https://www.immowelt.de/', + waitForNetworkIdle: true, + waitForNetworkIdleTimeout: 60_000, + }, crawlFields: { id: 'a@href', price: 'div[data-testid="cardmfe-price-testid"] | removeNewline | trim', diff --git a/lib/services/ensureValidBinary.js b/lib/services/ensureValidBinary.js new file mode 100644 index 0000000..f2617ee --- /dev/null +++ b/lib/services/ensureValidBinary.js @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2026 by Christian Kellner. + * Licensed under Apache-2.0 with Commons Clause and Attribution/Naming Clause + */ + +import { ensureBinary } from 'cloakbrowser'; +import fs from 'fs'; +import path from 'path'; +import os from 'os'; + +/** + * Resource files required on Linux/Windows — they must live next to the chrome binary. + * macOS packages these inside the .app bundle's Frameworks directory so a different + * check is used there (see isBinaryComplete). + */ +const LINUX_WIN_REQUIRED_FILES = ['icudtl.dat', 'resources.pak']; + +/** + * Return the top-level versioned installation directory for any platform. + * + * - Linux/Windows: binaryPath is ~/.cloakbrowser/chromium-X.Y.Z/chrome + * → dirname ~/.cloakbrowser/chromium-X.Y.Z/ + * - macOS: binaryPath is ~/.cloakbrowser/chromium-X.Y.Z/Chromium.app/Contents/MacOS/Chromium + * → 4 levels up ~/.cloakbrowser/chromium-X.Y.Z/ + * + * @param {string} binaryPath + * @returns {string} + */ +function getVersionedDir(binaryPath) { + if (process.platform === 'darwin') { + return path.resolve(path.dirname(binaryPath), '../../..'); + } + return path.dirname(binaryPath); +} + +/** + * Return true when the binary at binaryPath belongs to a complete installation. + * + * On macOS the binary lives inside an .app bundle: + * Chromium.app/Contents/MacOS/Chromium + * Resource files (icudtl.dat etc.) are deep inside + * Chromium.app/Contents/Frameworks/… + * so checking for them next to the binary is wrong. Instead we verify the two + * structural markers that are only present after a full extraction: Info.plist + * and the Frameworks directory inside Contents/. + * + * On Linux/Windows the binary and all resource files are siblings in the same + * directory. + * + * @param {string} binaryPath + * @returns {boolean} + */ +function isBinaryComplete(binaryPath) { + if (process.platform === 'darwin') { + const contentsDir = path.resolve(path.dirname(binaryPath), '..'); + return fs.existsSync(path.join(contentsDir, 'Info.plist')) && fs.existsSync(path.join(contentsDir, 'Frameworks')); + } + const dir = path.dirname(binaryPath); + return LINUX_WIN_REQUIRED_FILES.every((f) => fs.existsSync(path.join(dir, f))); +} + +/** + * Return a human-readable description of which required files/dirs are missing. + * + * @param {string} binaryPath + * @returns {string} + */ +function missingDescription(binaryPath) { + if (process.platform === 'darwin') { + const contentsDir = path.resolve(path.dirname(binaryPath), '..'); + return ['Info.plist', 'Frameworks'].filter((f) => !fs.existsSync(path.join(contentsDir, f))).join(', '); + } + const dir = path.dirname(binaryPath); + return LINUX_WIN_REQUIRED_FILES.filter((f) => !fs.existsSync(path.join(dir, f))).join(', '); +} + +/** + * Remove a corrupt binary installation and all `latest_version*` markers from + * the CloakBrowser cache so the next `ensureBinary()` call falls back to the + * package-bundled version. + * + * Removes the full versioned directory (e.g. chromium-X.Y.Z/) on all platforms, + * not just the subdirectory that contains the binary. + * + * @param {string} binaryPath - Path to the (corrupt) chrome/Chromium binary. + */ +function removeCorruptInstallation(binaryPath) { + const versionedDir = getVersionedDir(binaryPath); + const cacheDir = process.env.CLOAKBROWSER_CACHE_DIR || path.join(os.homedir(), '.cloakbrowser'); + + fs.rmSync(versionedDir, { recursive: true, force: true }); + + try { + for (const entry of fs.readdirSync(cacheDir)) { + if (entry.startsWith('latest_version')) { + fs.rmSync(path.join(cacheDir, entry), { force: true }); + } + } + } catch { + // Cache dir may not exist if versionedDir was the only entry — ignore. + } +} + +/** + * Ensure the CloakBrowser stealth Chromium binary is present **and** complete. + * + * `cloakbrowser`'s own `ensureBinary()` only checks that the chrome/Chromium + * file exists. An incomplete extraction (e.g. interrupted download, disk full) + * can leave a directory that contains the executable but is missing essential + * resource files. Chrome then crashes immediately on launch. + * + * This wrapper validates the path returned by `ensureBinary()`. If the + * installation is incomplete it removes the corrupt directory, clears the + * version marker files, and calls `ensureBinary()` again so it falls back to + * (or re-downloads) a complete build. + * + * The validated path is also pinned via `CLOAKBROWSER_BINARY_PATH` so that + * CloakBrowser's own internal `ensureBinary()` call inside `launch()` always + * picks up the same, verified binary. + * + * @returns {Promise} Absolute path to the validated binary. + * @throws {Error} When even the fallback binary is incomplete. + */ +export async function ensureValidBinary() { + const binaryPath = await ensureBinary(); + + if (isBinaryComplete(binaryPath)) { + process.env.CLOAKBROWSER_BINARY_PATH = binaryPath; + return binaryPath; + } + + console.warn( + `[fredy] CloakBrowser installation at ${getVersionedDir(binaryPath)} is missing: ${missingDescription(binaryPath)}. Removing and retrying.`, + ); + + removeCorruptInstallation(binaryPath); + + const fallbackPath = await ensureBinary(); + if (!isBinaryComplete(fallbackPath)) { + throw new Error( + `CloakBrowser binary at ${getVersionedDir(fallbackPath)} is still missing required files after re-download: ${missingDescription(fallbackPath)}`, + ); + } + + process.env.CLOAKBROWSER_BINARY_PATH = fallbackPath; + return fallbackPath; +} diff --git a/lib/services/extractor/puppeteerExtractor.js b/lib/services/extractor/puppeteerExtractor.js index d8bf076..09f59f8 100644 --- a/lib/services/extractor/puppeteerExtractor.js +++ b/lib/services/extractor/puppeteerExtractor.js @@ -3,121 +3,133 @@ * Licensed under Apache-2.0 with Commons Clause and Attribution/Naming Clause */ -import puppeteer from 'puppeteer-extra'; -import StealthPlugin from 'puppeteer-extra-plugin-stealth'; +import { launch } from 'cloakbrowser/puppeteer'; import { debug, botDetected } from './utils.js'; -import { - getPreLaunchConfig, - applyBotPreventionToPage, - applyLanguagePersistence, - applyPostNavigationHumanSignals, -} from './botPrevention.js'; +import { getPreLaunchConfig } from './botPrevention.js'; import logger from '../logger.js'; -import fs from 'fs'; -import os from 'os'; -import path from 'path'; - -puppeteer.use(StealthPlugin()); +/** + * Launch a CloakBrowser/Puppeteer browser instance with stealth and humanizer enabled. + * + * CloakBrowser applies 49 C++ source-level patches (canvas, WebGL, audio, WebRTC, + * navigator.*, automation signals) that are indistinguishable from a real browser. + * All fingerprinting and human-behaviour simulation is handled natively; no CDP + * overrides (setUserAgent, setExtraHTTPHeaders, evaluateOnNewDocument) are applied + * here because they would create detectable inconsistencies on top of the C++ patches. + * + * @param {string} url - Initial URL (used to derive locale/timezone hints). + * @param {object} [options] + * @param {boolean} [options.puppeteerHeadless] + * @param {number} [options.puppeteerTimeout] + * @param {string} [options.proxyUrl] + * @param {string} [options.timezone] + * @param {string} [options.acceptLanguage] + * @param {object} [options.viewport] + * @returns {Promise} + */ export async function launchBrowser(url, options) { const preCfg = getPreLaunchConfig(url, options || {}); - const launchArgs = [ + + // Docker requires --no-sandbox; CloakBrowser handles all stealth args internally. + // --ignore-certificate-errors is needed because CloakBrowser ships its own Chromium + // binary with an independent CA bundle that may not trust proxies or interceptors + // present in the host environment. + const args = [ '--no-sandbox', - '--disable-gpu', '--disable-setuid-sandbox', '--disable-dev-shm-usage', - '--disable-crash-reporter', '--no-first-run', '--no-default-browser-check', - preCfg.langArg, + '--ignore-certificate-errors', + // Disables the zygote process model. Required in some container environments + // (e.g. limited kernel namespaces) where the zygote cannot acquire the + // locks it needs and exits with "Invalid file descriptor to ICU data received". + '--no-zygote', preCfg.windowSizeArg, - ...preCfg.extraArgs, ]; - if (options?.proxyUrl) { - launchArgs.push(`--proxy-server=${options.proxyUrl}`); - } - let userDataDir; - let removeUserDataDir = false; - if (options && options.userDataDir) { - userDataDir = options.userDataDir; - } else { - const prefix = path.join(os.tmpdir(), 'puppeteer-fredy-'); - userDataDir = fs.mkdtempSync(prefix); - removeUserDataDir = true; - } - - // On ARM64 Docker, Chrome for Testing has no native binary - use system Chromium instead. - const executablePath = - options?.executablePath || - (process.arch === 'arm64' && process.env.IS_DOCKER === 'true' ? '/usr/bin/chromium' : undefined); - - const browser = await puppeteer.launch({ + const browser = await launch({ headless: options?.puppeteerHeadless ?? true, - args: launchArgs, - timeout: options?.puppeteerTimeout || 45_000, - userDataDir, - executablePath, + humanize: true, + args, + // locale sets Accept-Language headers and JS navigator.language consistently + locale: preCfg.langForFlag, + ...(options?.proxyUrl ? { proxy: options.proxyUrl } : {}), + ...(preCfg.timezone ? { timezone: preCfg.timezone } : {}), }); - browser.__fredy_userDataDir = userDataDir; - browser.__fredy_removeUserDataDir = removeUserDataDir; - return browser; } +/** + * Close a browser instance returned by {@link launchBrowser}. + * + * @param {import('puppeteer-core').Browser | null} browser + */ export async function closeBrowser(browser) { if (!browser) return; - const userDataDir = browser.__fredy_userDataDir; - const removeUserDataDir = browser.__fredy_removeUserDataDir; try { await browser.close(); } catch { // ignore } - if (removeUserDataDir && userDataDir) { - try { - await fs.promises.rm(userDataDir, { recursive: true, force: true }); - } catch { - // ignore - } - } } +/** + * Open a page in a (possibly reused) browser, navigate to `url`, and return the HTML source. + * Returns `null` when a bot-detection page is encountered or on timeout. + * + * @param {string} url + * @param {string | null} waitForSelector + * @param {object} [options] + * @returns {Promise} + */ export default async function execute(url, waitForSelector, options) { let browser = options?.browser; let isExternalBrowser = !!browser; let page; let result; try { - debug(`Sending request to ${url} using Puppeteer.`); + debug(`Sending request to ${url} using CloakBrowser.`); if (!isExternalBrowser) { browser = await launchBrowser(url, options); } page = await browser.newPage(); - const preCfg = getPreLaunchConfig(url, options || {}); - await applyBotPreventionToPage(page, preCfg); - // Provide languages value before navigation - await applyLanguagePersistence(page, preCfg); - // Optional cookies if (Array.isArray(options?.cookies) && options.cookies.length > 0) { await page.setCookie(...options.cookies); } - // Navigation + // Warm-up navigation: visit a trusted page first so the site sees an + // established session before the actual target URL. Silently ignored on + // failure so it never blocks the main request. + if (options?.preNavigateUrl) { + try { + await page.goto(options.preNavigateUrl, { waitUntil: 'domcontentloaded', timeout: 30_000 }); + await new Promise((r) => setTimeout(r, 1500 + Math.random() * 2000)); + } catch { + // ignore + } + } + const response = await page.goto(url, { waitUntil: options?.waitUntil || 'domcontentloaded', timeout: options?.puppeteerTimeout || 60000, }); - // Optionally wait and add subtle human-like interactions - await applyPostNavigationHumanSignals(page, preCfg); + // Optional second idle wait: useful for React SPAs that trigger API calls + // after domcontentloaded. Times out silently so we use whatever is rendered. + if (options?.waitForNetworkIdle) { + try { + await page.waitForNetworkIdle({ timeout: options?.waitForNetworkIdleTimeout ?? 60_000 }); + } catch { + // ignore — we proceed with whatever the DOM contains at this point + } + } let pageSource; - // if we're extracting data from a SPA, we must wait for the selector if (waitForSelector != null) { const selectorTimeout = options?.puppeteerSelectorTimeout ?? options?.puppeteerTimeout ?? 30_000; await page.waitForSelector(waitForSelector, { timeout: selectorTimeout }); @@ -139,9 +151,9 @@ export default async function execute(url, waitForSelector, options) { } } catch (error) { if (error?.name?.includes('Timeout')) { - logger.debug('Error executing with puppeteer executor', error); + logger.debug('Error executing with CloakBrowser executor', error); } else { - logger.warn('Error executing with puppeteer executor', error); + logger.warn('Error executing with CloakBrowser executor', error); } result = null; } finally { diff --git a/package.json b/package.json index 598ec42..69f8087 100755 --- a/package.json +++ b/package.json @@ -78,6 +78,7 @@ "better-sqlite3": "^12.9.0", "chart.js": "^4.5.1", "cheerio": "^1.2.0", + "cloakbrowser": "^0.3.27", "fastify": "^5.8.5", "handlebars": "4.7.9", "maplibre-gl": "^5.24.0", @@ -88,9 +89,7 @@ "nodemailer": "^8.0.7", "p-throttle": "^8.1.0", "package-up": "^5.0.0", - "puppeteer": "^24.43.0", - "puppeteer-extra": "^3.3.6", - "puppeteer-extra-plugin-stealth": "^2.11.2", + "puppeteer-core": "^24.43.0", "query-string": "9.3.1", "react": "19.2.6", "react-chartjs-2": "^5.3.1", diff --git a/test/globalSetup.js b/test/globalSetup.js new file mode 100644 index 0000000..9e0314c --- /dev/null +++ b/test/globalSetup.js @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2026 by Christian Kellner. + * Licensed under Apache-2.0 with Commons Clause and Attribution/Naming Clause + */ + +import { ensureValidBinary } from '../lib/services/ensureValidBinary.js'; + +/** + * Vitest global setup — runs once in the main process before any workers start. + * Downloads and validates the CloakBrowser stealth Chromium binary. + * ensureValidBinary() also removes and re-downloads partial/corrupt installations + * so tests never fail with "Invalid file descriptor to ICU data received". + * Skipped in offline mode because the browser is fully mocked there. + */ +export async function setup() { + if (process.env.TEST_MODE === 'offline') return; + await ensureValidBinary(); +} diff --git a/test/provider/immobilienDe.test.js b/test/provider/immobilienDe.test.js index b128bf8..c145559 100644 --- a/test/provider/immobilienDe.test.js +++ b/test/provider/immobilienDe.test.js @@ -6,83 +6,89 @@ import * as similarityCache from '../../lib/services/similarity-check/similarityCache.js'; import { get } from '../mocks/mockNotification.js'; import { providerConfig, mockFredy } from '../utils.js'; -import { expect, vi } from 'vitest'; +import { expect } from 'vitest'; import * as provider from '../../lib/provider/immobilienDe.js'; -import * as mockStore from '../mocks/mockStore.js'; +import { launchBrowser, closeBrowser } from '../../lib/services/extractor/puppeteerExtractor.js'; + +// One browser shared across the whole suite so both requests (search + detail) +// come from the same warm session, avoiding double cold-start bot detection. +const TEST_TIMEOUT = 120_000; describe('#immobilien.de testsuite()', () => { provider.init(providerConfig.immobilienDe, [], []); - it('should test immobilien.de provider', async () => { - const mockedJob = { - id: 'test1', - notificationAdapter: null, - spatialFilter: null, - specFilter: null, - }; - const Fredy = await mockFredy(); - const fredy = new Fredy(provider.config, mockedJob, provider.metaInformation.id, similarityCache, undefined); - const listing = await fredy.execute(); + let browser; + let liveListings; - if (listing == null || listing.length === 0) { - throw new Error('Listings is empty!'); - } + beforeAll(async () => { + browser = await launchBrowser(providerConfig.immobilienDe.url); + }, TEST_TIMEOUT); - expect(listing).toBeInstanceOf(Array); - const notificationObj = get(); - expect(notificationObj).toBeTypeOf('object'); - expect(notificationObj.serviceName).toBe('immobilienDe'); - notificationObj.payload.forEach((notify) => { - /** check the actual structure **/ - expect(notify.id).toBeTypeOf('string'); - expect(notify.price).toBeTypeOf('string'); - expect(notify.size).toBeTypeOf('string'); - expect(notify.title).toBeTypeOf('string'); - expect(notify.link).toBeTypeOf('string'); - expect(notify.address).toBeTypeOf('string'); - /** check the values if possible **/ - expect(notify.price).toContain('€'); - expect(notify.size).toContain('m²'); - expect(notify.title).not.toBe(''); - expect(notify.link).toContain('https://www.immobilien.de'); - expect(notify.address).not.toBe(''); - }); + afterAll(async () => { + await closeBrowser(browser); }); + it( + 'should test immobilien.de provider', + async () => { + const mockedJob = { + id: 'test1', + notificationAdapter: null, + spatialFilter: null, + specFilter: null, + }; + + const Fredy = await mockFredy(); + const fredy = new Fredy(provider.config, mockedJob, provider.metaInformation.id, similarityCache, browser); + liveListings = await fredy.execute(); + + if (liveListings == null || liveListings.length === 0) { + throw new Error('Listings is empty!'); + } + + expect(liveListings).toBeInstanceOf(Array); + const notificationObj = get(); + expect(notificationObj).toBeTypeOf('object'); + expect(notificationObj.serviceName).toBe('immobilienDe'); + notificationObj.payload.forEach((notify) => { + /** check the actual structure **/ + expect(notify.id).toBeTypeOf('string'); + expect(notify.price).toBeTypeOf('string'); + expect(notify.size).toBeTypeOf('string'); + expect(notify.title).toBeTypeOf('string'); + expect(notify.link).toBeTypeOf('string'); + expect(notify.address).toBeTypeOf('string'); + /** check the values if possible **/ + expect(notify.price).toContain('€'); + expect(notify.size).toContain('m²'); + expect(notify.title).not.toBe(''); + expect(notify.link).toContain('https://www.immobilien.de'); + expect(notify.address).not.toBe(''); + }); + }, + TEST_TIMEOUT, + ); + describe('with provider_details enabled', () => { - beforeEach(() => { - vi.spyOn(mockStore, 'getUserSettings').mockReturnValue({ provider_details: [provider.metaInformation.id] }); - vi.spyOn(mockStore, 'getKnownListingHashesForJobAndProvider').mockReturnValue([]); - }); + it( + 'should enrich listings with details', + async () => { + if (!liveListings?.length) throw new Error('No listings from first test to enrich'); - afterEach(() => { - vi.restoreAllMocks(); - }); + // Call fetchDetails directly on the first live listing — no need to + // re-scrape the search page. The shared browser keeps the session warm. + const enriched = await provider.config.fetchDetails(liveListings[0], browser); - it('should enrich listings with details', async () => { - const Fredy = await mockFredy(); - provider.init(providerConfig.immobilienDe, [], []); - const mockedJob = { id: 'test1', notificationAdapter: null, specFilter: null, spatialFilter: null }; - - const fredy = new Fredy( - provider.config, - mockedJob, - provider.metaInformation.id, - { checkAndAddEntry: () => false }, - undefined, - ); - const listings = await fredy.execute(); - if (listings == null) return; - expect(listings).toBeInstanceOf(Array); - listings.forEach((listing) => { - expect(listing.link).toContain('https://www.immobilien.de'); - expect(listing.address).toBeTypeOf('string'); - expect(listing.address).not.toBe(''); - // description may be null if selectors don't match yet - falls back gracefully - if (listing.description != null) { - expect(listing.description).toBeTypeOf('string'); + if (enriched == null) return; + expect(enriched.link).toContain('https://www.immobilien.de'); + expect(enriched.address).toBeTypeOf('string'); + expect(enriched.address).not.toBe(''); + // description may be null if selectors don't match yet — falls back gracefully + if (enriched.description != null) { + expect(enriched.description).toBeTypeOf('string'); } - }); - }); + }, + TEST_TIMEOUT, + ); }); }); diff --git a/test/provider/immoscout.test.js b/test/provider/immoscout.test.js index 6cd094e..aa92fd3 100644 --- a/test/provider/immoscout.test.js +++ b/test/provider/immoscout.test.js @@ -3,85 +3,85 @@ * Licensed under Apache-2.0 with Commons Clause and Attribution/Naming Clause */ -import { expect, vi } from 'vitest'; +import { expect } from 'vitest'; import * as similarityCache from '../../lib/services/similarity-check/similarityCache.js'; import { mockFredy, providerConfig } from '../utils.js'; import { get } from '../mocks/mockNotification.js'; import * as provider from '../../lib/provider/immoscout.js'; -import * as mockStore from '../mocks/mockStore.js'; + +// immoscout uses the mobile REST API (fetch-based, no browser). Both tests share +// the same module-level listings so the API is only queried once, avoiding +// duplicate requests that could trigger rate-limiting. +const TEST_TIMEOUT = 120_000; describe('#immoscout provider testsuite()', () => { provider.init(providerConfig.immoscout, [], []); - it('should test immoscout provider', async () => { - const Fredy = await mockFredy(); - const mockedJob = { - id: '', - notificationAdapter: null, - spatialFilter: null, - specFilter: null, - }; - return await new Promise((resolve, reject) => { - const fredy = new Fredy(provider.config, mockedJob, provider.metaInformation.id, similarityCache, undefined); - fredy.execute().then((listings) => { - if (listings == null || listings.length === 0) { - reject('Listings is empty!'); - return; - } + let liveListings; - expect(listings).toBeInstanceOf(Array); - const notificationObj = get(); - expect(notificationObj).toBeTypeOf('object'); + it( + 'should test immoscout provider', + async () => { + const Fredy = await mockFredy(); + const mockedJob = { + id: '', + notificationAdapter: null, + spatialFilter: null, + specFilter: null, + }; - // check if there is at least one valid notification - const hasValidNotification = notificationObj.payload.some((notify) => { - return ( - typeof notify.id === 'string' && - typeof notify.price === 'string' && - notify.price.includes('€') && - typeof notify.size === 'string' && - notify.size.includes('m²') && - typeof notify.title === 'string' && - notify.title !== '' && - typeof notify.link === 'string' && - notify.link.includes('https://www.immobilienscout24.de/') && - typeof notify.address === 'string' - ); + return await new Promise((resolve, reject) => { + const fredy = new Fredy(provider.config, mockedJob, provider.metaInformation.id, similarityCache, undefined); + fredy.execute().then((listings) => { + if (listings == null || listings.length === 0) { + reject('Listings is empty!'); + return; + } + + liveListings = listings; + expect(listings).toBeInstanceOf(Array); + const notificationObj = get(); + expect(notificationObj).toBeTypeOf('object'); + + // check if there is at least one valid notification + const hasValidNotification = notificationObj.payload.some((notify) => { + return ( + typeof notify.id === 'string' && + typeof notify.price === 'string' && + notify.price.includes('€') && + typeof notify.size === 'string' && + notify.size.includes('m²') && + typeof notify.title === 'string' && + notify.title !== '' && + typeof notify.link === 'string' && + notify.link.includes('https://www.immobilienscout24.de/') && + typeof notify.address === 'string' + ); + }); + + expect(hasValidNotification).toBe(true); + resolve(); }); - - expect(hasValidNotification).toBe(true); - resolve(); }); - }); - }); + }, + TEST_TIMEOUT, + ); describe('with provider_details enabled', () => { - beforeEach(() => { - vi.spyOn(mockStore, 'getUserSettings').mockReturnValue({ provider_details: [provider.metaInformation.id] }); - vi.spyOn(mockStore, 'getKnownListingHashesForJobAndProvider').mockReturnValue([]); - }); + it( + 'should enrich listings with details', + async () => { + if (!liveListings?.length) throw new Error('No listings from first test to enrich'); - afterEach(() => { - vi.restoreAllMocks(); - }); + // Call fetchDetails directly on the first live listing — no need to + // re-query the search API. immoscout uses fetch (no browser). + const enriched = await provider.config.fetchDetails(liveListings[0]); - it('should enrich listings with details', async () => { - const Fredy = await mockFredy(); - provider.init(providerConfig.immoscout, [], []); - const mockedJob = { id: '', notificationAdapter: null, specFilter: null, spatialFilter: null }; - const fredy = new Fredy( - provider.config, - mockedJob, - provider.metaInformation.id, - { checkAndAddEntry: () => false }, - undefined, - ); - const listings = await fredy.execute(); - expect(listings).toBeInstanceOf(Array); - listings.forEach((listing) => { - expect(listing.description).toBeTypeOf('string'); - expect(listing.description).not.toBe(''); - }); - }); + expect(enriched).toBeTruthy(); + expect(enriched.description).toBeTypeOf('string'); + expect(enriched.description).not.toBe(''); + }, + TEST_TIMEOUT, + ); }); }); diff --git a/test/provider/immowelt.test.js b/test/provider/immowelt.test.js index 94e9b11..3059f2e 100644 --- a/test/provider/immowelt.test.js +++ b/test/provider/immowelt.test.js @@ -6,87 +6,95 @@ import * as similarityCache from '../../lib/services/similarity-check/similarityCache.js'; import { get } from '../mocks/mockNotification.js'; import { mockFredy, providerConfig } from '../utils.js'; -import { expect, vi } from 'vitest'; +import { expect } from 'vitest'; import * as provider from '../../lib/provider/immowelt.js'; -import * as mockStore from '../mocks/mockStore.js'; +import { launchBrowser, closeBrowser } from '../../lib/services/extractor/puppeteerExtractor.js'; + +// One browser shared across the whole suite so both requests (search + detail) +// come from the same warm session. Immowelt's CDN challenges cold sessions +// aggressively; a shared warm browser prevents the second request from being +// blocked as a bot hit. +const TEST_TIMEOUT = 180_000; describe('#immowelt testsuite()', () => { - it('should test immowelt provider', async () => { - const Fredy = await mockFredy(); - const mockedJob = { - id: 'immowelt', - notificationAdapter: null, - spatialFilter: null, - specFilter: null, - }; - provider.init(providerConfig.immowelt, [], []); + let browser; + let liveListings; - const fredy = new Fredy(provider.config, mockedJob, provider.metaInformation.id, similarityCache, undefined); + beforeAll(async () => { + browser = await launchBrowser(providerConfig.immowelt.url); + }, TEST_TIMEOUT); - const listing = await fredy.execute(); - - if (listing == null || listing.length === 0) { - throw new Error('Listings is empty!'); - } - - expect(listing).toBeInstanceOf(Array); - const notificationObj = get(); - expect(notificationObj).toBeTypeOf('object'); - expect(notificationObj.serviceName).toBe('immowelt'); - notificationObj.payload.forEach((notify) => { - /** check the actual structure **/ - expect(notify.id).toBeTypeOf('string'); - if (notify.price != null) { - expect(notify.price).toBeTypeOf('string'); - expect(notify.price).toContain('€'); - } - expect(notify.title).toBeTypeOf('string'); - expect(notify.link).toBeTypeOf('string'); - expect(notify.address).toBeTypeOf('string'); - /** check the values if possible **/ - if (notify.size != null && notify.size.trim().toLowerCase() !== 'k.a.') { - expect(notify.size).toBeTypeOf('string'); - expect(notify.size).toContain('m²'); - } - expect(notify.title).not.toBe(''); - expect(notify.link).toContain('https://www.immowelt.de'); - expect(notify.address).not.toBe(''); - }); + afterAll(async () => { + await closeBrowser(browser); }); + it( + 'should test immowelt provider', + async () => { + const Fredy = await mockFredy(); + const mockedJob = { + id: 'immowelt', + notificationAdapter: null, + spatialFilter: null, + specFilter: null, + }; + provider.init(providerConfig.immowelt, [], []); + + const fredy = new Fredy(provider.config, mockedJob, provider.metaInformation.id, similarityCache, browser); + + liveListings = await fredy.execute(); + + if (liveListings == null || liveListings.length === 0) { + throw new Error('Listings is empty!'); + } + + expect(liveListings).toBeInstanceOf(Array); + const notificationObj = get(); + expect(notificationObj).toBeTypeOf('object'); + expect(notificationObj.serviceName).toBe('immowelt'); + notificationObj.payload.forEach((notify) => { + /** check the actual structure **/ + expect(notify.id).toBeTypeOf('string'); + if (notify.price != null) { + expect(notify.price).toBeTypeOf('string'); + expect(notify.price).toContain('€'); + } + expect(notify.title).toBeTypeOf('string'); + expect(notify.link).toBeTypeOf('string'); + expect(notify.address).toBeTypeOf('string'); + /** check the values if possible **/ + if (notify.size != null && notify.size.trim().toLowerCase() !== 'k.a.') { + expect(notify.size).toBeTypeOf('string'); + expect(notify.size).toContain('m²'); + } + expect(notify.title).not.toBe(''); + expect(notify.link).toContain('https://www.immowelt.de'); + expect(notify.address).not.toBe(''); + }); + }, + TEST_TIMEOUT, + ); + describe('with provider_details enabled', () => { - beforeEach(() => { - vi.spyOn(mockStore, 'getUserSettings').mockReturnValue({ provider_details: [provider.metaInformation.id] }); - vi.spyOn(mockStore, 'getKnownListingHashesForJobAndProvider').mockReturnValue([]); - }); + it( + 'should enrich listings with details', + async () => { + if (!liveListings?.length) throw new Error('No listings from first test to enrich'); - afterEach(() => { - vi.restoreAllMocks(); - }); + // Call fetchDetails directly on the first live listing — no need to + // re-scrape the search page. The shared browser keeps the session warm. + const enriched = await provider.config.fetchDetails(liveListings[0], browser); - it('should enrich listings with details', async () => { - const Fredy = await mockFredy(); - provider.init(providerConfig.immowelt, [], []); - const mockedJob = { id: 'immowelt', notificationAdapter: null, specFilter: null, spatialFilter: null }; - - const fredy = new Fredy( - provider.config, - mockedJob, - provider.metaInformation.id, - { checkAndAddEntry: () => false }, - undefined, - ); - const listings = await fredy.execute(); - expect(listings).toBeInstanceOf(Array); - listings.forEach((listing) => { - expect(listing.link).toContain('https://www.immowelt.de'); - expect(listing.address).toBeTypeOf('string'); - expect(listing.address).not.toBe(''); + expect(enriched).toBeTruthy(); + expect(enriched.link).toContain('https://www.immowelt.de'); + expect(enriched.address).toBeTypeOf('string'); + expect(enriched.address).not.toBe(''); // description is enriched from the detail page; falls back gracefully if blocked - if (listing.description != null) { - expect(listing.description).toBeTypeOf('string'); + if (enriched.description != null) { + expect(enriched.description).toBeTypeOf('string'); } - }); - }); + }, + TEST_TIMEOUT, + ); }); }); diff --git a/test/provider/kleinanzeigen.test.js b/test/provider/kleinanzeigen.test.js index b5569fe..f4d5598 100644 --- a/test/provider/kleinanzeigen.test.js +++ b/test/provider/kleinanzeigen.test.js @@ -6,80 +6,88 @@ import * as similarityCache from '../../lib/services/similarity-check/similarityCache.js'; import { get } from '../mocks/mockNotification.js'; import { mockFredy, providerConfig } from '../utils.js'; -import { expect, vi } from 'vitest'; +import { expect } from 'vitest'; import * as provider from '../../lib/provider/kleinanzeigen.js'; -import * as mockStore from '../mocks/mockStore.js'; +import { launchBrowser, closeBrowser } from '../../lib/services/extractor/puppeteerExtractor.js'; + +// One browser shared across the whole suite so both requests (search + detail) +// come from the same warm session. Kleinanzeigen rate-limits cold browser +// sessions; a shared warm browser prevents the second request from being blocked. +const TEST_TIMEOUT = 180_000; describe('#kleinanzeigen testsuite()', () => { - it('should test kleinanzeigen provider', async () => { - const Fredy = await mockFredy(); - const mockedJob = { - id: 'kleinanzeigen', - notificationAdapter: null, - spatialFilter: null, - specFilter: null, - }; - provider.init(providerConfig.kleinanzeigen, [], []); - return await new Promise((resolve, reject) => { - const fredy = new Fredy(provider.config, mockedJob, provider.metaInformation.id, similarityCache, undefined); + let browser; + let liveListings; - fredy.execute().then((listing) => { - if (listing == null || listing.length === 0) { - reject('Listings is empty!'); - return; - } + beforeAll(async () => { + browser = await launchBrowser(providerConfig.kleinanzeigen.url); + }, TEST_TIMEOUT); - expect(listing).toBeInstanceOf(Array); - const notificationObj = get(); - expect(notificationObj).toBeTypeOf('object'); - expect(notificationObj.serviceName).toBe('kleinanzeigen'); - notificationObj.payload.forEach((notify) => { - /** check the actual structure **/ - expect(notify.id).toBeTypeOf('string'); - expect(notify.title).toBeTypeOf('string'); - expect(notify.link).toBeTypeOf('string'); - expect(notify.address).toBeTypeOf('string'); - /** check the values if possible **/ - expect(notify.title).not.toBe(''); - expect(notify.link).toContain('https://www.kleinanzeigen.de'); - expect(notify.address).not.toBe(''); - }); - resolve(); - }); - }); + afterAll(async () => { + await closeBrowser(browser); }); + it( + 'should test kleinanzeigen provider', + async () => { + const Fredy = await mockFredy(); + const mockedJob = { + id: 'kleinanzeigen', + notificationAdapter: null, + spatialFilter: null, + specFilter: null, + }; + provider.init(providerConfig.kleinanzeigen, [], []); + return await new Promise((resolve, reject) => { + const fredy = new Fredy(provider.config, mockedJob, provider.metaInformation.id, similarityCache, browser); + + fredy.execute().then((listing) => { + if (listing == null || listing.length === 0) { + reject('Listings is empty!'); + return; + } + + liveListings = listing; + expect(listing).toBeInstanceOf(Array); + const notificationObj = get(); + expect(notificationObj).toBeTypeOf('object'); + expect(notificationObj.serviceName).toBe('kleinanzeigen'); + notificationObj.payload.forEach((notify) => { + /** check the actual structure **/ + expect(notify.id).toBeTypeOf('string'); + expect(notify.title).toBeTypeOf('string'); + expect(notify.link).toBeTypeOf('string'); + expect(notify.address).toBeTypeOf('string'); + /** check the values if possible **/ + expect(notify.title).not.toBe(''); + expect(notify.link).toContain('https://www.kleinanzeigen.de'); + expect(notify.address).not.toBe(''); + }); + resolve(); + }); + }); + }, + TEST_TIMEOUT, + ); + describe('with provider_details enabled', () => { - beforeEach(() => { - vi.spyOn(mockStore, 'getUserSettings').mockReturnValue({ provider_details: [provider.metaInformation.id] }); - vi.spyOn(mockStore, 'getKnownListingHashesForJobAndProvider').mockReturnValue([]); - }); + it( + 'should enrich listings with details', + async () => { + if (!liveListings?.length) throw new Error('No listings from first test to enrich'); - afterEach(() => { - vi.restoreAllMocks(); - }); + // Call fetchDetails directly on the first live listing — no need to + // re-scrape the search page. The shared browser keeps the session warm. + const enriched = await provider.config.fetchDetails(liveListings[0], browser); - it('should enrich listings with details', async () => { - const Fredy = await mockFredy(); - provider.init(providerConfig.kleinanzeigen, [], []); - const mockedJob = { id: 'kleinanzeigen', notificationAdapter: null, specFilter: null, spatialFilter: null }; - - const fredy = new Fredy( - provider.config, - mockedJob, - provider.metaInformation.id, - { checkAndAddEntry: () => false }, - undefined, - ); - const listings = await fredy.execute(); - expect(listings).toBeInstanceOf(Array); - listings.forEach((listing) => { - expect(listing.link).toContain('https://www.kleinanzeigen.de'); - expect(listing.address).toBeTypeOf('string'); - expect(listing.address).not.toBe(''); - expect(listing.description).toBeTypeOf('string'); - expect(listing.description).not.toBe(''); - }); - }); + expect(enriched).toBeTruthy(); + expect(enriched.link).toContain('https://www.kleinanzeigen.de'); + expect(enriched.address).toBeTypeOf('string'); + expect(enriched.address).not.toBe(''); + expect(enriched.description).toBeTypeOf('string'); + expect(enriched.description).not.toBe(''); + }, + TEST_TIMEOUT, + ); }); }); diff --git a/test/provider/sparkasse.test.js b/test/provider/sparkasse.test.js index 357f071..dfd4eb8 100644 --- a/test/provider/sparkasse.test.js +++ b/test/provider/sparkasse.test.js @@ -9,81 +9,97 @@ import { mockFredy, providerConfig } from '../utils.js'; import { expect, vi } from 'vitest'; import * as provider from '../../lib/provider/sparkasse.js'; import * as mockStore from '../mocks/mockStore.js'; +import { launchBrowser, closeBrowser } from '../../lib/services/extractor/puppeteerExtractor.js'; + +// One browser shared across the whole suite so both requests (search + detail) +// come from the same warm session. This prevents the second request from being +// flagged as a cold-start bot hit. +const TEST_TIMEOUT = 120_000; describe('#sparkasse testsuite()', () => { - it('should test sparkasse provider', async () => { - const Fredy = await mockFredy(); - const mockedJob = { - id: 'sparkasse', - notificationAdapter: null, - spatialFilter: null, - specFilter: null, - }; - provider.init(providerConfig.sparkasse, []); + let browser; + let liveListings; - const fredy = new Fredy(provider.config, mockedJob, provider.metaInformation.id, similarityCache, undefined); + beforeAll(async () => { + browser = await launchBrowser(providerConfig.sparkasse.url); + }, TEST_TIMEOUT); - const listing = await fredy.execute(); - - if (listing == null || listing.length === 0) { - throw new Error('Listings is empty!'); - } - - expect(listing).toBeInstanceOf(Array); - const notificationObj = get(); - expect(notificationObj).toBeTypeOf('object'); - expect(notificationObj.serviceName).toBe('sparkasse'); - notificationObj.payload.forEach((notify) => { - /** check the actual structure **/ - expect(notify.id).toBeTypeOf('string'); - expect(notify.price).toBeTypeOf('string'); - expect(notify.price).toContain('€'); - expect(notify.size).toBeTypeOf('string'); - expect(notify.size).toContain('m²'); - expect(notify.title).toBeTypeOf('string'); - expect(notify.link).toBeTypeOf('string'); - expect(notify.address).toBeTypeOf('string'); - /** check the values if possible **/ - expect(notify.size).toBeTypeOf('string'); - expect(notify.title).not.toBe(''); - expect(notify.address).not.toBe(''); - }); + afterAll(async () => { + await closeBrowser(browser); }); + it( + 'should test sparkasse provider', + async () => { + const Fredy = await mockFredy(); + const mockedJob = { + id: 'sparkasse', + notificationAdapter: null, + spatialFilter: null, + specFilter: null, + }; + provider.init(providerConfig.sparkasse, []); + + const fredy = new Fredy(provider.config, mockedJob, provider.metaInformation.id, similarityCache, browser); + + liveListings = await fredy.execute(); + + if (liveListings == null || liveListings.length === 0) { + throw new Error('Listings is empty!'); + } + + expect(liveListings).toBeInstanceOf(Array); + const notificationObj = get(); + expect(notificationObj).toBeTypeOf('object'); + expect(notificationObj.serviceName).toBe('sparkasse'); + notificationObj.payload.forEach((notify) => { + /** check the actual structure **/ + expect(notify.id).toBeTypeOf('string'); + expect(notify.price).toBeTypeOf('string'); + expect(notify.price).toContain('€'); + expect(notify.size).toBeTypeOf('string'); + expect(notify.size).toContain('m²'); + expect(notify.title).toBeTypeOf('string'); + expect(notify.link).toBeTypeOf('string'); + expect(notify.address).toBeTypeOf('string'); + /** check the values if possible **/ + expect(notify.size).toBeTypeOf('string'); + expect(notify.title).not.toBe(''); + expect(notify.address).not.toBe(''); + }); + }, + TEST_TIMEOUT, + ); + describe('with provider_details enabled', () => { beforeEach(() => { vi.spyOn(mockStore, 'getUserSettings').mockReturnValue({ provider_details: [provider.metaInformation.id] }); - vi.spyOn(mockStore, 'getKnownListingHashesForJobAndProvider').mockReturnValue([]); }); afterEach(() => { vi.restoreAllMocks(); }); - it('should enrich listings with details', async () => { - const Fredy = await mockFredy(); - provider.init(providerConfig.sparkasse, []); - const mockedJob = { id: 'sparkasse', notificationAdapter: null, specFilter: null, spatialFilter: null }; + it( + 'should enrich listings with details', + async () => { + if (!liveListings?.length) throw new Error('No listings from first test to enrich'); - const fredy = new Fredy( - provider.config, - mockedJob, - provider.metaInformation.id, - { checkAndAddEntry: () => false }, - undefined, - ); - const listings = await fredy.execute(); - expect(listings).toBeInstanceOf(Array); - listings.forEach((listing) => { - expect(listing.link).toContain('https://immobilien.sparkasse.de'); - expect(listing.address).toBeTypeOf('string'); - expect(listing.address).not.toBe(''); - // description is enriched from the detail page; falls back gracefully if bot-detected - if (listing.description != null) { - expect(listing.description).toBeTypeOf('string'); - expect(listing.description).not.toBe(''); + // Call fetchDetails directly on the first live listing — no need to + // re-scrape the search page. The shared browser keeps the session warm. + const enriched = await provider.config.fetchDetails(liveListings[0], browser); + + expect(enriched).toBeTruthy(); + expect(enriched.link).toContain('https://immobilien.sparkasse.de'); + expect(enriched.address).toBeTypeOf('string'); + expect(enriched.address).not.toBe(''); + // description is enriched from the detail page; falls back gracefully if blocked + if (enriched.description != null) { + expect(enriched.description).toBeTypeOf('string'); + expect(enriched.description).not.toBe(''); } - }); - }); + }, + TEST_TIMEOUT, + ); }); }); diff --git a/test/provider/wgGesucht.test.js b/test/provider/wgGesucht.test.js index 7d5735f..f32b98b 100644 --- a/test/provider/wgGesucht.test.js +++ b/test/provider/wgGesucht.test.js @@ -6,77 +6,85 @@ import * as similarityCache from '../../lib/services/similarity-check/similarityCache.js'; import { get } from '../mocks/mockNotification.js'; import { mockFredy, providerConfig } from '../utils.js'; -import { expect, vi } from 'vitest'; +import { expect } from 'vitest'; import * as provider from '../../lib/provider/wgGesucht.js'; -import * as mockStore from '../mocks/mockStore.js'; +import { launchBrowser, closeBrowser } from '../../lib/services/extractor/puppeteerExtractor.js'; + +// One browser shared across the whole suite so both requests (search + detail) +// come from the same warm session, avoiding double cold-start bot detection. +const TEST_TIMEOUT = 120_000; describe('#wgGesucht testsuite()', () => { provider.init(providerConfig.wgGesucht, [], []); - it('should test wgGesucht provider', { timeout: 120000 }, async () => { - const Fredy = await mockFredy(); - const mockedJob = { - id: 'wgGesucht', - notificationAdapter: null, - spatialFilter: null, - specFilter: null, - }; - return await new Promise((resolve, reject) => { - const fredy = new Fredy(provider.config, mockedJob, provider.metaInformation.id, similarityCache, undefined); + let browser; + let liveListings; - fredy.execute().then((listing) => { - if (listing == null || listing.length === 0) { - reject('Listings is empty!'); - return; - } + beforeAll(async () => { + browser = await launchBrowser(providerConfig.wgGesucht.url); + }, TEST_TIMEOUT); - expect(listing).toBeInstanceOf(Array); - const notificationObj = get(); - expect(notificationObj.serviceName).toBe('wgGesucht'); - notificationObj.payload.forEach((notify) => { - expect(notify).toBeTypeOf('object'); - /** check the actual structure **/ - expect(notify.id).toBeTypeOf('string'); - expect(notify.title).toBeTypeOf('string'); - // expect(notify.details).toBeTypeOf('string'); - expect(notify.price).toBeTypeOf('string'); - expect(notify.price).toContain('€'); - expect(notify.link).toBeTypeOf('string'); - }); - resolve(); - }); - }); + afterAll(async () => { + await closeBrowser(browser); }); + it( + 'should test wgGesucht provider', + async () => { + const Fredy = await mockFredy(); + const mockedJob = { + id: 'wgGesucht', + notificationAdapter: null, + spatialFilter: null, + specFilter: null, + }; + + return await new Promise((resolve, reject) => { + const fredy = new Fredy(provider.config, mockedJob, provider.metaInformation.id, similarityCache, browser); + + fredy.execute().then((listing) => { + if (listing == null || listing.length === 0) { + reject('Listings is empty!'); + return; + } + + liveListings = listing; + expect(listing).toBeInstanceOf(Array); + const notificationObj = get(); + expect(notificationObj.serviceName).toBe('wgGesucht'); + notificationObj.payload.forEach((notify) => { + expect(notify).toBeTypeOf('object'); + /** check the actual structure **/ + expect(notify.id).toBeTypeOf('string'); + expect(notify.title).toBeTypeOf('string'); + // expect(notify.details).toBeTypeOf('string'); + expect(notify.price).toBeTypeOf('string'); + expect(notify.price).toContain('€'); + expect(notify.link).toBeTypeOf('string'); + }); + resolve(); + }); + }); + }, + TEST_TIMEOUT, + ); + describe('with provider_details enabled', () => { - beforeEach(() => { - vi.spyOn(mockStore, 'getUserSettings').mockReturnValue({ provider_details: [provider.metaInformation.id] }); - vi.spyOn(mockStore, 'getKnownListingHashesForJobAndProvider').mockReturnValue([]); - }); + it( + 'should enrich listings with details', + async () => { + if (!liveListings?.length) throw new Error('No listings from first test to enrich'); - afterEach(() => { - vi.restoreAllMocks(); - }); + // Call fetchDetails directly on the first live listing — no need to + // re-scrape the search page. The shared browser keeps the session warm. + const enriched = await provider.config.fetchDetails(liveListings[0], browser); - it('should enrich listings with details', async () => { - const Fredy = await mockFredy(); - provider.init(providerConfig.wgGesucht, [], []); - const mockedJob = { id: 'wgGesucht', notificationAdapter: null, specFilter: null, spatialFilter: null }; - - const fredy = new Fredy( - provider.config, - mockedJob, - provider.metaInformation.id, - { checkAndAddEntry: () => false }, - undefined, - ); - const listings = await fredy.execute(); - expect(listings).toBeInstanceOf(Array); - listings.forEach((listing) => { - expect(listing.link).toContain('https://www.wg-gesucht.de'); - expect(listing.description).toBeTypeOf('string'); - expect(listing.description).not.toBe(''); - }); - }); + expect(enriched).toBeTruthy(); + expect(enriched.link).toContain('https://www.wg-gesucht.de'); + expect(enriched.description).toBeTypeOf('string'); + expect(enriched.description).not.toBe(''); + }, + TEST_TIMEOUT, + ); }); }); diff --git a/test/utils.js b/test/utils.js index 33cd766..bb2e43a 100644 --- a/test/utils.js +++ b/test/utils.js @@ -29,7 +29,7 @@ vi.mock('../lib/services/extractor/puppeteerExtractor.js', async (importOriginal const { readFixture } = await import('./offlineFixtures.js'); return { default: (url) => readFixture(url), - launchBrowser: async () => ({ close: async () => {}, __fredy_removeUserDataDir: false }), + launchBrowser: async () => ({ close: async () => {}, isConnected: () => true }), closeBrowser: async () => {}, }; }); diff --git a/vitest.config.js b/vitest.config.js index 35ad0ba..b639bc1 100644 --- a/vitest.config.js +++ b/vitest.config.js @@ -10,6 +10,7 @@ export default defineConfig({ globals: true, environment: 'node', include: ['test/**/*.test.js'], + globalSetup: ['./test/globalSetup.js'], testTimeout: 60000, reporters: ['verbose'], }, diff --git a/yarn.lock b/yarn.lock index 5f549a3..cc5a0d2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,7 +2,7 @@ # yarn lockfile v1 -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.28.6", "@babel/code-frame@^7.29.0": +"@babel/code-frame@^7.28.6", "@babel/code-frame@^7.29.0": version "7.29.0" resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz" integrity sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw== @@ -1287,6 +1287,13 @@ resolved "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz" integrity sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ== +"@isaacs/fs-minipass@^4.0.0": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz#2d59ae3ab4b38fb4270bfa23d30f8e2e86c7fe32" + integrity sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w== + dependencies: + minipass "^7.0.4" + "@jridgewell/gen-mapping@^0.3.12", "@jridgewell/gen-mapping@^0.3.5": version "0.3.13" resolved "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz" @@ -2019,7 +2026,7 @@ "@tootallnate/quickjs-emscripten@^0.23.0": version "0.23.0" - resolved "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz" + resolved "https://registry.yarnpkg.com/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz#db4ecfd499a9765ab24002c3b696d02e6d32a12c" integrity sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA== "@turf/boolean-point-in-polygon@^7.3.5": @@ -2102,7 +2109,7 @@ "@types/deep-eql" "*" assertion-error "^2.0.1" -"@types/debug@^4.0.0", "@types/debug@^4.1.0": +"@types/debug@^4.0.0": version "4.1.12" resolved "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz" integrity sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ== @@ -2184,11 +2191,11 @@ integrity sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA== "@types/node@*": - version "24.3.0" - resolved "https://registry.npmjs.org/@types/node/-/node-24.3.0.tgz" - integrity sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow== + version "25.6.2" + resolved "https://registry.yarnpkg.com/@types/node/-/node-25.6.2.tgz#8c491201373690e4ef2a2ffed0dfb510a5830b92" + integrity sha512-sokuT28dxf9JT5Kady1fsXOvI4HVpjZa95NKT5y9PNTIrs2AsobR4GFAA90ZG8M+nxVRLysCXsVj6eGC7Vbrlw== dependencies: - undici-types "~7.10.0" + undici-types "~7.19.0" "@types/supercluster@^7.1.3": version "7.1.3" @@ -2214,7 +2221,7 @@ "@types/yauzl@^2.9.1": version "2.10.3" - resolved "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz" + resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.10.3.tgz#e9b2808b4f109504a03cda958259876f61017999" integrity sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q== dependencies: "@types/node" "*" @@ -2326,7 +2333,7 @@ adm-zip@^0.5.17: agent-base@^7.1.0, agent-base@^7.1.2: version "7.1.4" - resolved "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-7.1.4.tgz#e3cd76d4c548ee895d3c3fd8dc1f6c5b9032e7a8" integrity sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ== ajv-formats@^3.0.1: @@ -2375,7 +2382,7 @@ ansi-escapes@^7.0.0: ansi-regex@^5.0.1: version "5.0.1" - resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== ansi-regex@^6.0.1: @@ -2385,7 +2392,7 @@ ansi-regex@^6.0.1: ansi-styles@^4.0.0: version "4.3.0" - resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== dependencies: color-convert "^2.0.1" @@ -2408,11 +2415,6 @@ argparse@^2.0.1: resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== -arr-union@^3.1.0: - version "3.1.0" - resolved "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz" - integrity sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q== - array-buffer-byte-length@^1.0.1, array-buffer-byte-length@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz" @@ -2498,7 +2500,7 @@ assertion-error@^2.0.1: ast-types@^0.13.4: version "0.13.4" - resolved "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz" + resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.13.4.tgz#ee0d77b343263965ecc3fb62da16e7222b2b6782" integrity sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w== dependencies: tslib "^2.0.1" @@ -2562,9 +2564,9 @@ axios@^1.8.2: proxy-from-env "^1.1.0" b4a@^1.6.4: - version "1.7.3" - resolved "https://registry.npmjs.org/b4a/-/b4a-1.7.3.tgz" - integrity sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q== + version "1.8.1" + resolved "https://registry.yarnpkg.com/b4a/-/b4a-1.8.1.tgz#7f16334ca80127aeb26064a28841acbf174840a4" + integrity sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw== babel-plugin-polyfill-corejs2@^0.4.15: version "0.4.15" @@ -2607,13 +2609,13 @@ balanced-match@^4.0.2: bare-events@^2.5.4, bare-events@^2.7.0: version "2.8.2" - resolved "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz" + resolved "https://registry.yarnpkg.com/bare-events/-/bare-events-2.8.2.tgz#7b3e10bd8e1fc80daf38bb516921678f566ab89f" integrity sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ== -bare-fs@^4.0.1: - version "4.5.3" - resolved "https://registry.npmjs.org/bare-fs/-/bare-fs-4.5.3.tgz" - integrity sha512-9+kwVx8QYvt3hPWnmb19tPnh38c6Nihz8Lx3t0g9+4GoIf3/fTgYwM4Z6NxgI+B9elLQA7mLE9PpqcWtOMRDiQ== +bare-fs@^4.0.1, bare-fs@^4.5.5: + version "4.7.1" + resolved "https://registry.yarnpkg.com/bare-fs/-/bare-fs-4.7.1.tgz#6e81f784761102867c13f0823aa48c942d160f00" + integrity sha512-WDRsyVN52eAx/lBamKD6uyw8H4228h/x0sGGGegOamM2cd7Pag88GfMQalobXI+HaEUxpCkbKQUDOQqt9wawRw== dependencies: bare-events "^2.5.4" bare-path "^3.0.0" @@ -2622,28 +2624,29 @@ bare-fs@^4.0.1: fast-fifo "^1.3.2" bare-os@^3.0.1: - version "3.6.2" - resolved "https://registry.npmjs.org/bare-os/-/bare-os-3.6.2.tgz" - integrity sha512-T+V1+1srU2qYNBmJCXZkUY5vQ0B4FSlL3QDROnKQYOqeiQR8UbjNHlPa+TIbM4cuidiN9GaTaOZgSEgsvPbh5A== + version "3.9.1" + resolved "https://registry.yarnpkg.com/bare-os/-/bare-os-3.9.1.tgz#660228ca7ffc47a72e96b6047cdd9d8342994e2f" + integrity sha512-6M5XjcnsygQNPMCMPXSK379xrJFiZ/AEMNBmFEmQW8d/789VQATvriyi5r0HYTL9TkQ26rn3kgdTG3aisbrXkQ== bare-path@^3.0.0: version "3.0.0" - resolved "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz" + resolved "https://registry.yarnpkg.com/bare-path/-/bare-path-3.0.0.tgz#b59d18130ba52a6af9276db3e96a2e3d3ea52178" integrity sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw== dependencies: bare-os "^3.0.1" bare-stream@^2.6.4: - version "2.7.0" - resolved "https://registry.npmjs.org/bare-stream/-/bare-stream-2.7.0.tgz" - integrity sha512-oyXQNicV1y8nc2aKffH+BUHFRXmx6VrPzlnaEvMhram0nPBrKcEdcyBg5r08D0i8VxngHFAiVyn1QKXpSG0B8A== + version "2.13.1" + resolved "https://registry.yarnpkg.com/bare-stream/-/bare-stream-2.13.1.tgz#acfd787a2983f5feb182ffe4c37ecc2c55b6ec85" + integrity sha512-Vp0cnjYyrEC4whYTymQ+YZi6pBpfiICZO3cfRG8sy67ZNWe951urv1x4eW1BKNngw3U+3fPYb5JQvHbCtxH7Ow== dependencies: - streamx "^2.21.0" + streamx "^2.25.0" + teex "^1.0.1" bare-url@^2.2.2: - version "2.3.2" - resolved "https://registry.npmjs.org/bare-url/-/bare-url-2.3.2.tgz" - integrity sha512-ZMq4gd9ngV5aTMa5p9+UfY0b3skwhHELaDkhEHetMdX0LRkW9kzaym4oo/Eh+Ghm0CCDuMTsRIGM/ytUc1ZYmw== + version "2.4.3" + resolved "https://registry.yarnpkg.com/bare-url/-/bare-url-2.4.3.tgz#99aedf87519225669f15ecc0b910db11cad46930" + integrity sha512-Kccpc7ACfXaxfeInfqKcZtW4pT5YBn1mesc4sCsun6sRwtbJ4h+sNOaksUpYEJUKfN65YWC6Bw2OJEFiKxq8nQ== dependencies: bare-path "^3.0.0" @@ -2658,9 +2661,9 @@ baseline-browser-mapping@^2.9.0: integrity sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg== basic-ftp@^5.0.2: - version "5.0.5" - resolved "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz" - integrity sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg== + version "5.3.1" + resolved "https://registry.yarnpkg.com/basic-ftp/-/basic-ftp-5.3.1.tgz#3148ee9af43c0522514a4f973fecb1d3cbb6d71e" + integrity sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw== better-sqlite3@^12.9.0: version "12.9.0" @@ -2763,7 +2766,7 @@ browserslist@^4.24.0, browserslist@^4.28.1: buffer-crc32@~0.2.3: version "0.2.13" - resolved "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz" + resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ== buffer@^5.5.0: @@ -2805,11 +2808,6 @@ call-bound@^1.0.2, call-bound@^1.0.3, call-bound@^1.0.4: call-bind-apply-helpers "^1.0.2" get-intrinsic "^1.3.0" -callsites@^3.0.0: - version "3.1.0" - resolved "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz" - integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== - caniuse-lite@^1.0.30001759: version "1.0.30001769" resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001769.tgz" @@ -2906,6 +2904,11 @@ chownr@^1.1.1: resolved "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz" integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== +chownr@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-3.0.0.tgz#9855e64ecd240a9cc4267ce8a4aa5d24a1da15e4" + integrity sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g== + chromium-bidi@14.0.0: version "14.0.0" resolved "https://registry.yarnpkg.com/chromium-bidi/-/chromium-bidi-14.0.0.tgz#15a12ab083ae519a49a724e94994ca0a9ced9c8e" @@ -2936,23 +2939,19 @@ cli-truncate@^5.0.0: cliui@^8.0.1: version "8.0.1" - resolved "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== dependencies: string-width "^4.2.0" strip-ansi "^6.0.1" wrap-ansi "^7.0.0" -clone-deep@^0.2.4: - version "0.2.4" - resolved "https://registry.npmjs.org/clone-deep/-/clone-deep-0.2.4.tgz" - integrity sha512-we+NuQo2DHhSl+DP6jlUiAhyAjBQrYnpOk15rN6c6JSPScjiCLh8IbSU+VTcph6YS3o7mASE8a0+gbZ7ChLpgg== +cloakbrowser@^0.3.27: + version "0.3.27" + resolved "https://registry.yarnpkg.com/cloakbrowser/-/cloakbrowser-0.3.27.tgz#ce4635de3a774dc6d360c32eb27e0ca53f2e906d" + integrity sha512-EjTI+Ux8XaCDHKDOLFOt6Tsv2g6AhQPQjIL1GqAazcxk+BAg8FfFxtSHYdVcvldsXSW8RpPEz8N3AX54vEjzBg== dependencies: - for-own "^0.1.3" - is-plain-object "^2.0.1" - kind-of "^3.0.2" - lazy-cache "^1.0.3" - shallow-clone "^0.1.2" + tar "^7.0.0" clsx@^1.1.1: version "1.2.1" @@ -2971,14 +2970,14 @@ collapse-white-space@^2.0.0: color-convert@^2.0.1: version "2.0.1" - resolved "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== dependencies: color-name "~1.1.4" color-name@~1.1.4: version "1.1.4" - resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== colorette@^2.0.20: @@ -3080,16 +3079,6 @@ cors@^2.8.5: object-assign "^4" vary "^1" -cosmiconfig@^9.0.0: - version "9.0.0" - resolved "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz" - integrity sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg== - dependencies: - env-paths "^2.2.1" - import-fresh "^3.3.0" - js-yaml "^4.1.0" - parse-json "^5.2.0" - crelt@^1.0.0: version "1.0.6" resolved "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz" @@ -3127,7 +3116,7 @@ data-uri-to-buffer@^4.0.0: data-uri-to-buffer@^6.0.2: version "6.0.2" - resolved "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz" + resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz#8a58bb67384b261a38ef18bea1810cb01badd28b" integrity sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw== data-view-buffer@^1.0.2: @@ -3230,7 +3219,7 @@ define-properties@^1.1.3, define-properties@^1.2.1: degenerator@^5.0.0: version "5.0.1" - resolved "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz" + resolved "https://registry.yarnpkg.com/degenerator/-/degenerator-5.0.1.tgz#9403bf297c6dad9a1ece409b37db27954f91f2f5" integrity sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ== dependencies: ast-types "^0.13.4" @@ -3347,7 +3336,7 @@ emoji-regex@^10.3.0: emoji-regex@^8.0.0: version "8.0.0" - resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== encodeurl@^2.0.0: @@ -3385,11 +3374,6 @@ entities@^7.0.1: resolved "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz" integrity sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA== -env-paths@^2.2.1: - version "2.2.1" - resolved "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz" - integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A== - environment@^1.0.0: version "1.1.0" resolved "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz" @@ -3402,13 +3386,6 @@ errno@^0.1.1: dependencies: prr "~1.0.1" -error-ex@^1.3.1: - version "1.3.2" - resolved "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz" - integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== - dependencies: - is-arrayish "^0.2.1" - es-abstract@^1.17.5, es-abstract@^1.23.2, es-abstract@^1.23.3, es-abstract@^1.23.5, es-abstract@^1.23.6, es-abstract@^1.23.9, es-abstract@^1.24.0: version "1.24.0" resolved "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz" @@ -3581,7 +3558,7 @@ escape-string-regexp@^5.0.0: escodegen@^2.1.0: version "2.1.0" - resolved "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-2.1.0.tgz#ba93bbb7a43986d29d6041f99f5262da773e2e17" integrity sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w== dependencies: esprima "^4.0.1" @@ -3699,7 +3676,7 @@ espree@^11.2.0: esprima@^4.0.1: version "4.0.1" - resolved "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== esquery@^1.7.0: @@ -3797,7 +3774,7 @@ eventemitter3@^5.0.1: events-universal@^1.0.0: version "1.0.1" - resolved "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz" + resolved "https://registry.yarnpkg.com/events-universal/-/events-universal-1.0.1.tgz#b56a84fd611b6610e0a2d0f09f80fdf931e2dfe6" integrity sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw== dependencies: bare-events "^2.7.0" @@ -3872,7 +3849,7 @@ extend@^3.0.0: extract-zip@^2.0.1: version "2.0.1" - resolved "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz" + resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.1.tgz#663dca56fe46df890d5f131ef4a06d22bb8ba13a" integrity sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg== dependencies: debug "^4.1.1" @@ -3903,7 +3880,7 @@ fast-equals@^5.3.3: fast-fifo@^1.2.0, fast-fifo@^1.3.2: version "1.3.2" - resolved "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz" + resolved "https://registry.yarnpkg.com/fast-fifo/-/fast-fifo-1.3.2.tgz#286e31de96eb96d38a97899815740ba2a4f3640c" integrity sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ== fast-json-stable-stringify@^2.0.0: @@ -3980,7 +3957,7 @@ fastq@^1.17.1: fd-slicer@~1.1.0: version "1.1.0" - resolved "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz" + resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" integrity sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g== dependencies: pend "~1.2.0" @@ -4081,23 +4058,6 @@ for-each@^0.3.3, for-each@^0.3.5: dependencies: is-callable "^1.2.7" -for-in@^0.1.3: - version "0.1.8" - resolved "https://registry.npmjs.org/for-in/-/for-in-0.1.8.tgz" - integrity sha512-F0to7vbBSHP8E3l6dCjxNOLuSFAACIxFy3UehTUlG7svlXi37HHsDkyVcHo0Pq8QwrE+pXvWSVX3ZT1T9wAZ9g== - -for-in@^1.0.1: - version "1.0.2" - resolved "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz" - integrity sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ== - -for-own@^0.1.3: - version "0.1.5" - resolved "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz" - integrity sha512-SKmowqGTJoPzLO1T0BBJpkfp3EMacCMOuH40hOUbrbzElVktk4DioXVM99QkLCyKoiuOmyjgcWMpVz2xjE7LZw== - dependencies: - for-in "^1.0.1" - form-data@^4.0.4: version "4.0.4" resolved "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz" @@ -4131,15 +4091,6 @@ fs-constants@^1.0.0: resolved "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz" integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== -fs-extra@^10.0.0: - version "10.1.0" - resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz" - integrity sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ== - dependencies: - graceful-fs "^4.2.0" - jsonfile "^6.0.1" - universalify "^2.0.0" - fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" @@ -4179,7 +4130,7 @@ gensync@^1.0.0-beta.2: get-caller-file@^2.0.5: version "2.0.5" - resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== get-east-asian-width@^1.0.0: @@ -4218,7 +4169,7 @@ get-proto@^1.0.0, get-proto@^1.0.1: get-stream@^5.1.0: version "5.2.0" - resolved "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== dependencies: pump "^3.0.0" @@ -4234,7 +4185,7 @@ get-symbol-description@^1.1.0: get-uri@^6.0.1: version "6.0.5" - resolved "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz" + resolved "https://registry.yarnpkg.com/get-uri/-/get-uri-6.0.5.tgz#714892aa4a871db671abc5395e5e9447bc306a16" integrity sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg== dependencies: basic-ftp "^5.0.2" @@ -4274,7 +4225,7 @@ glob@^13.0.0: minipass "^7.1.3" path-scurry "^2.0.2" -glob@^7.0.0, glob@^7.1.3: +glob@^7.0.0: version "7.2.3" resolved "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz" integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== @@ -4304,7 +4255,7 @@ gopd@^1.0.1, gopd@^1.2.0: resolved "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz" integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== -graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0: +graceful-fs@^4.1.2: version "4.2.11" resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== @@ -4465,7 +4416,7 @@ http-errors@^2.0.1, http-errors@~2.0.1: http-proxy-agent@^7.0.0, http-proxy-agent@^7.0.1: version "7.0.2" - resolved "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz#9a8b1f246866c028509486585f62b8f2c18c270e" integrity sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig== dependencies: agent-base "^7.1.0" @@ -4473,7 +4424,7 @@ http-proxy-agent@^7.0.0, http-proxy-agent@^7.0.1: https-proxy-agent@^7.0.6: version "7.0.6" - resolved "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz#da8dfeac7da130b05c2ba4b59c9b6cd66611a6b9" integrity sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw== dependencies: agent-base "^7.1.2" @@ -4518,14 +4469,6 @@ image-size@~0.5.0: resolved "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz" integrity sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ== -import-fresh@^3.3.0: - version "3.3.1" - resolved "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz" - integrity sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ== - dependencies: - parent-module "^1.0.0" - resolve-from "^4.0.0" - imurmurhash@^0.1.4: version "0.1.4" resolved "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz" @@ -4568,11 +4511,16 @@ interpret@^1.0.0: resolved "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz" integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== -ip-address@10.0.1, ip-address@^10.0.1: +ip-address@10.0.1: version "10.0.1" resolved "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz" integrity sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA== +ip-address@^10.1.1: + version "10.2.0" + resolved "https://registry.yarnpkg.com/ip-address/-/ip-address-10.2.0.tgz#805fc178b20c518bd4c8548b24fe30892d7f3206" + integrity sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA== + ipaddr.js@1.9.1: version "1.9.1" resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" @@ -4605,11 +4553,6 @@ is-array-buffer@^3.0.4, is-array-buffer@^3.0.5: call-bound "^1.0.3" get-intrinsic "^1.2.6" -is-arrayish@^0.2.1: - version "0.2.1" - resolved "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz" - integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== - is-async-function@^2.0.0: version "2.1.1" resolved "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz" @@ -4643,11 +4586,6 @@ is-boolean-object@^1.2.1: call-bound "^1.0.3" has-tostringtag "^1.0.2" -is-buffer@^1.0.2, is-buffer@^1.1.5: - version "1.1.6" - resolved "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz" - integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== - is-callable@^1.2.7: version "1.2.7" resolved "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz" @@ -4682,11 +4620,6 @@ is-decimal@^2.0.0: resolved "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz" integrity sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A== -is-extendable@^0.1.1: - version "0.1.1" - resolved "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz" - integrity sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw== - is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz" @@ -4701,7 +4634,7 @@ is-finalizationregistry@^1.1.0: is-fullwidth-code-point@^3.0.0: version "3.0.0" - resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== is-fullwidth-code-point@^5.0.0: @@ -4761,13 +4694,6 @@ is-plain-obj@^4.0.0: resolved "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz" integrity sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg== -is-plain-object@^2.0.1: - version "2.0.4" - resolved "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz" - integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== - dependencies: - isobject "^3.0.1" - is-promise@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-4.0.0.tgz#42ff9f84206c1991d26debf520dd5c01042dd2f3" @@ -4854,11 +4780,6 @@ isexe@^2.0.0: resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz" integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== -isobject@^3.0.1: - version "3.0.1" - resolved "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz" - integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== - iterator.prototype@^1.1.4: version "1.1.5" resolved "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz" @@ -4881,13 +4802,6 @@ jose@^6.1.3: resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== -js-yaml@^4.1.0: - version "4.1.0" - resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz" - integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== - dependencies: - argparse "^2.0.1" - jsesc@^3.0.2, jsesc@~3.1.0: version "3.1.0" resolved "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz" @@ -4905,11 +4819,6 @@ json-buffer@3.0.1: resolved "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz" integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== -json-parse-even-better-errors@^2.3.0: - version "2.3.1" - resolved "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz" - integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== - json-schema-ref-resolver@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/json-schema-ref-resolver/-/json-schema-ref-resolver-3.0.0.tgz#28f6a410122cde9238762a5e9296faa38be28708" @@ -4952,15 +4861,6 @@ jsonc-parser@^3.3.1: resolved "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz" integrity sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ== -jsonfile@^6.0.1: - version "6.2.0" - resolved "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz" - integrity sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg== - dependencies: - universalify "^2.0.0" - optionalDependencies: - graceful-fs "^4.1.6" - "jsx-ast-utils@^2.4.1 || ^3.0.0": version "3.3.5" resolved "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz" @@ -4983,30 +4883,6 @@ keyv@^4.5.4: dependencies: json-buffer "3.0.1" -kind-of@^2.0.1: - version "2.0.1" - resolved "https://registry.npmjs.org/kind-of/-/kind-of-2.0.1.tgz" - integrity sha512-0u8i1NZ/mg0b+W3MGGw5I7+6Eib2nx72S/QvXa0hYjEkjTknYmEYQJwGu3mLC0BrhtJjtQafTkyRUQ75Kx0LVg== - dependencies: - is-buffer "^1.0.2" - -kind-of@^3.0.2: - version "3.2.2" - resolved "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz" - integrity sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ== - dependencies: - is-buffer "^1.1.5" - -lazy-cache@^0.2.3: - version "0.2.7" - resolved "https://registry.npmjs.org/lazy-cache/-/lazy-cache-0.2.7.tgz" - integrity sha512-gkX52wvU/R8DVMMt78ATVPFMJqfW8FPz1GZ1sVHBVQHmu/WvhIWE4cE1GBzhJNFicDeYhnwp6Rl35BcAIM3YOQ== - -lazy-cache@^1.0.3: - version "1.0.4" - resolved "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz" - integrity sha512-RE2g0b5VGZsOCFOCgP7omTRYFqydmZkBwl5oNnQ1lDYC57uyO9KqNnNVxT7COSHTxrRCWVcAVOcbjk+tvh/rgQ== - less@4.6.4: version "4.6.4" resolved "https://registry.yarnpkg.com/less/-/less-4.6.4.tgz#3ff8068e6c8a59f1ece8a6b9227bda28c1ed68a2" @@ -5114,11 +4990,6 @@ lightningcss@^1.32.0: lightningcss-win32-arm64-msvc "1.32.0" lightningcss-win32-x64-msvc "1.32.0" -lines-and-columns@^1.1.6: - version "1.2.4" - resolved "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz" - integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== - linkify-it@^5.0.0: version "5.0.0" resolved "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz" @@ -5214,7 +5085,7 @@ lru-cache@^5.1.1: lru-cache@^7.14.1: version "7.18.3" - resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.18.3.tgz#f793896e0fd0e954a59dfdd82f0773808df6aa89" integrity sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA== magic-string@^0.30.21: @@ -5490,15 +5361,6 @@ media-typer@^1.1.0: resolved "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz" integrity sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q== -merge-deep@^3.0.1: - version "3.0.3" - resolved "https://registry.npmjs.org/merge-deep/-/merge-deep-3.0.3.tgz" - integrity sha512-qtmzAS6t6grwEkNrunqTBdn0qKwFgNWvlxUbAV8es9M7Ot1EbyApytCnvE0jALPa46ZpKDUo527kKiaWplmlFA== - dependencies: - arr-union "^3.1.0" - clone-deep "^0.2.4" - kind-of "^3.0.2" - merge-descriptors@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-2.0.0.tgz#ea922f660635a2249ee565e0449f951e6b603808" @@ -5948,24 +5810,23 @@ minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5, minimist@^1.2.8: resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== -minipass@^7.1.2, minipass@^7.1.3: +minipass@^7.0.4, minipass@^7.1.2, minipass@^7.1.3: version "7.1.3" resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.3.tgz#79389b4eb1bb2d003a9bba87d492f2bd37bdc65b" integrity sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A== +minizlib@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-3.1.0.tgz#6ad76c3a8f10227c9b51d1c9ac8e30b27f5a251c" + integrity sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw== + dependencies: + minipass "^7.1.2" + mitt@^3.0.1: version "3.0.1" - resolved "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz" + resolved "https://registry.yarnpkg.com/mitt/-/mitt-3.0.1.tgz#ea36cf0cc30403601ae074c8f77b7092cdab36d1" integrity sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw== -mixin-object@^2.0.1: - version "2.0.1" - resolved "https://registry.npmjs.org/mixin-object/-/mixin-object-2.0.1.tgz" - integrity sha512-ALGF1Jt9ouehcaXaHhn6t1yGWRqGaHkPFndtFVHfZXOvkIZ/yoGaSi0AHVTafb3ZBGg4dr/bDwnaEKqCXzchMA== - dependencies: - for-in "^0.1.3" - is-extendable "^0.1.1" - mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3: version "0.5.3" resolved "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz" @@ -6025,9 +5886,9 @@ neo-async@^2.6.2: integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== netmask@^2.0.2: - version "2.0.2" - resolved "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz" - integrity sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg== + version "2.1.1" + resolved "https://registry.yarnpkg.com/netmask/-/netmask-2.1.1.tgz#80043d265b53aa521b3bd01e8fcdf353f9e1e81e" + integrity sha512-eonl3sLUha+S1GzTPxychyhnUzKyeQkZ7jLjKrBagJgPla13F+uQ71HgpFefyHgqrjEbCPkDArxYsjY8/+gLKA== node-abi@^3.3.0: version "3.75.0" @@ -6237,7 +6098,7 @@ p-throttle@^8.1.0: pac-proxy-agent@^7.1.0: version "7.2.0" - resolved "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz" + resolved "https://registry.yarnpkg.com/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz#9cfaf33ff25da36f6147a20844230ec92c06e5df" integrity sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA== dependencies: "@tootallnate/quickjs-emscripten" "^0.23.0" @@ -6251,7 +6112,7 @@ pac-proxy-agent@^7.1.0: pac-resolver@^7.0.1: version "7.0.1" - resolved "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz" + resolved "https://registry.yarnpkg.com/pac-resolver/-/pac-resolver-7.0.1.tgz#54675558ea368b64d210fd9c92a640b5f3b8abb6" integrity sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg== dependencies: degenerator "^5.0.0" @@ -6264,13 +6125,6 @@ package-up@^5.0.0: dependencies: find-up-simple "^1.0.0" -parent-module@^1.0.0: - version "1.0.1" - resolved "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz" - integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== - dependencies: - callsites "^3.0.0" - parse-entities@^4.0.0: version "4.0.2" resolved "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz" @@ -6284,16 +6138,6 @@ parse-entities@^4.0.0: is-decimal "^2.0.0" is-hexadecimal "^2.0.0" -parse-json@^5.2.0: - version "5.2.0" - resolved "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz" - integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== - dependencies: - "@babel/code-frame" "^7.0.0" - error-ex "^1.3.1" - json-parse-even-better-errors "^2.3.0" - lines-and-columns "^1.1.6" - parse-node-version@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz" @@ -6373,7 +6217,7 @@ pbf@^4.0.1: pend@~1.2.0: version "1.2.0" - resolved "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz" + resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" integrity sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg== picocolors@^1.1.1: @@ -6520,7 +6364,7 @@ process-warning@^5.0.0: progress@^2.0.3: version "2.0.3" - resolved "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== prop-types@15.x, prop-types@^15.7.2, prop-types@^15.8.1: @@ -6706,7 +6550,7 @@ proxy-addr@^2.0.7: proxy-agent@^6.5.0: version "6.5.0" - resolved "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz" + resolved "https://registry.yarnpkg.com/proxy-agent/-/proxy-agent-6.5.0.tgz#9e49acba8e4ee234aacb539f89ed9c23d02f232d" integrity sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A== dependencies: agent-base "^7.1.2" @@ -6751,7 +6595,7 @@ punycode@^2.1.0: resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz" integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== -puppeteer-core@24.43.0: +puppeteer-core@^24.43.0: version "24.43.0" resolved "https://registry.yarnpkg.com/puppeteer-core/-/puppeteer-core-24.43.0.tgz#8a2fe71c3a02ac9b43d055b884f0906581707431" integrity sha512-cCRNXsUlhyPoKDz6+TiSpfZpRS3mD6Y1YFKhkdr6ik6TMfuJb7fAtXq9ThUFc4sphxObDk3BuAvdxc1Y6YOnqQ== @@ -6764,65 +6608,6 @@ puppeteer-core@24.43.0: webdriver-bidi-protocol "0.4.1" ws "^8.20.0" -puppeteer-extra-plugin-stealth@^2.11.2: - version "2.11.2" - resolved "https://registry.npmjs.org/puppeteer-extra-plugin-stealth/-/puppeteer-extra-plugin-stealth-2.11.2.tgz" - integrity sha512-bUemM5XmTj9i2ZerBzsk2AN5is0wHMNE6K0hXBzBXOzP5m5G3Wl0RHhiqKeHToe/uIH8AoZiGhc1tCkLZQPKTQ== - dependencies: - debug "^4.1.1" - puppeteer-extra-plugin "^3.2.3" - puppeteer-extra-plugin-user-preferences "^2.4.1" - -puppeteer-extra-plugin-user-data-dir@^2.4.1: - version "2.4.1" - resolved "https://registry.npmjs.org/puppeteer-extra-plugin-user-data-dir/-/puppeteer-extra-plugin-user-data-dir-2.4.1.tgz" - integrity sha512-kH1GnCcqEDoBXO7epAse4TBPJh9tEpVEK/vkedKfjOVOhZAvLkHGc9swMs5ChrJbRnf8Hdpug6TJlEuimXNQ+g== - dependencies: - debug "^4.1.1" - fs-extra "^10.0.0" - puppeteer-extra-plugin "^3.2.3" - rimraf "^3.0.2" - -puppeteer-extra-plugin-user-preferences@^2.4.1: - version "2.4.1" - resolved "https://registry.npmjs.org/puppeteer-extra-plugin-user-preferences/-/puppeteer-extra-plugin-user-preferences-2.4.1.tgz" - integrity sha512-i1oAZxRbc1bk8MZufKCruCEC3CCafO9RKMkkodZltI4OqibLFXF3tj6HZ4LZ9C5vCXZjYcDWazgtY69mnmrQ9A== - dependencies: - debug "^4.1.1" - deepmerge "^4.2.2" - puppeteer-extra-plugin "^3.2.3" - puppeteer-extra-plugin-user-data-dir "^2.4.1" - -puppeteer-extra-plugin@^3.2.3: - version "3.2.3" - resolved "https://registry.npmjs.org/puppeteer-extra-plugin/-/puppeteer-extra-plugin-3.2.3.tgz" - integrity sha512-6RNy0e6pH8vaS3akPIKGg28xcryKscczt4wIl0ePciZENGE2yoaQJNd17UiEbdmh5/6WW6dPcfRWT9lxBwCi2Q== - dependencies: - "@types/debug" "^4.1.0" - debug "^4.1.1" - merge-deep "^3.0.1" - -puppeteer-extra@^3.3.6: - version "3.3.6" - resolved "https://registry.npmjs.org/puppeteer-extra/-/puppeteer-extra-3.3.6.tgz" - integrity sha512-rsLBE/6mMxAjlLd06LuGacrukP2bqbzKCLzV1vrhHFavqQE/taQ2UXv3H5P0Ls7nsrASa+6x3bDbXHpqMwq+7A== - dependencies: - "@types/debug" "^4.1.0" - debug "^4.1.1" - deepmerge "^4.2.2" - -puppeteer@^24.43.0: - version "24.43.0" - resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-24.43.0.tgz#98b4a0bc36bcc53c2c9d6afb80d0464b26d5cdf7" - integrity sha512-DRnMFz+J3s4lFUQcjqKl0/7h0jzlCZuUFU9lNjtKrnMl5WI1RwCaIItpHVu9empuPyUreYueN0sUW3/pnfdqsg== - dependencies: - "@puppeteer/browsers" "2.13.1" - chromium-bidi "14.0.0" - cosmiconfig "^9.0.0" - devtools-protocol "0.0.1608973" - puppeteer-core "24.43.0" - typed-query-selector "^2.12.2" - qs@^6.14.0: version "6.15.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.15.0.tgz#db8fd5d1b1d2d6b5b33adaf87429805f1909e7b3" @@ -7141,7 +6926,7 @@ remark-stringify@^11.0.0: require-directory@^2.1.1: version "2.1.1" - resolved "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== require-from-string@^2.0.2: @@ -7157,11 +6942,6 @@ resend@^6.12.3: postal-mime "2.7.4" svix "1.92.2" -resolve-from@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz" - integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== - resolve-protobuf-schema@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/resolve-protobuf-schema/-/resolve-protobuf-schema-2.1.0.tgz" @@ -7210,13 +6990,6 @@ rfdc@^1.2.0, rfdc@^1.3.1, rfdc@^1.4.1: resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.4.1.tgz#778f76c4fb731d93414e8f925fbecf64cce7f6ca" integrity sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA== -rimraf@^3.0.2: - version "3.0.2" - resolved "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz" - integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== - dependencies: - glob "^7.1.3" - robust-predicates@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/robust-predicates/-/robust-predicates-3.0.2.tgz#d5b28528c4824d20fc48df1928d41d9efa1ad771" @@ -7468,16 +7241,6 @@ setprototypeof@1.2.0, setprototypeof@~1.2.0: resolved "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz" integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== -shallow-clone@^0.1.2: - version "0.1.2" - resolved "https://registry.npmjs.org/shallow-clone/-/shallow-clone-0.1.2.tgz" - integrity sha512-J1zdXCky5GmNnuauESROVu31MQSnLoYvlyEn6j2Ztk6Q5EHFIhxkMhYcv6vuDzl2XEzoRr856QwzMgWM/TmZgw== - dependencies: - is-extendable "^0.1.1" - kind-of "^2.0.1" - lazy-cache "^0.2.3" - mixin-object "^2.0.1" - shebang-command@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz" @@ -7587,12 +7350,12 @@ slice-ansi@^7.1.0: smart-buffer@^4.2.0: version "4.2.0" - resolved "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz" + resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae" integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg== socks-proxy-agent@^8.0.5: version "8.0.5" - resolved "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz" + resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz#b9cdb4e7e998509d7659d689ce7697ac21645bee" integrity sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw== dependencies: agent-base "^7.1.2" @@ -7600,11 +7363,11 @@ socks-proxy-agent@^8.0.5: socks "^2.8.3" socks@^2.8.3: - version "2.8.7" - resolved "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz" - integrity sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A== + version "2.8.9" + resolved "https://registry.yarnpkg.com/socks/-/socks-2.8.9.tgz#aa5f130ca0f88a43fa44faf4869c50d22aa27752" + integrity sha512-LJhUYUvItdQ0LkJTmPeaEObWXAqFyfmP85x0tch/ez9cahmhlBBLbIqDFnvBnUJGagb0JbIQrkBs1wJ+yRYpEw== dependencies: - ip-address "^10.0.1" + ip-address "^10.1.1" smart-buffer "^4.2.0" sonic-boom@^4.0.1: @@ -7680,10 +7443,10 @@ stop-iteration-iterator@^1.1.0: es-errors "^1.3.0" internal-slot "^1.1.0" -streamx@^2.15.0, streamx@^2.21.0: - version "2.23.0" - resolved "https://registry.npmjs.org/streamx/-/streamx-2.23.0.tgz" - integrity sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg== +streamx@^2.12.5, streamx@^2.15.0, streamx@^2.25.0: + version "2.25.0" + resolved "https://registry.yarnpkg.com/streamx/-/streamx-2.25.0.tgz#cc967e99390fda8b918b1eeaf3bc437637c8c7af" + integrity sha512-0nQuG6jf1w+wddNEEXCF4nTg3LtufWINB5eFEN+5TNZW7KWJp6x87+JFL43vaAUPyCfH1wID+mNVyW6OHtFamg== dependencies: events-universal "^1.0.0" fast-fifo "^1.3.2" @@ -7696,7 +7459,7 @@ string-argv@^0.3.2: string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" - resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== dependencies: emoji-regex "^8.0.0" @@ -7796,7 +7559,7 @@ stringify-entities@^4.0.0: strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" - resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== dependencies: ansi-regex "^5.0.1" @@ -7864,9 +7627,9 @@ tar-fs@^2.0.0: tar-stream "^2.1.4" tar-fs@^3.1.1: - version "3.1.1" - resolved "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.1.tgz" - integrity sha512-LZA0oaPOc2fVo82Txf3gw+AkEd38szODlptMYejQUhndHMLQ9M059uXR+AfS7DNo0NpINvSqDsvyaCrBVkptWg== + version "3.1.2" + resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-3.1.2.tgz#114b012f54796f31e62f3e57792820a80b83ae6e" + integrity sha512-QGxxTxxyleAdyM3kpFs14ymbYmNFrfY+pHj7Z8FgtbZ7w2//VAgLMac7sT6nRpIHjppXO2AwwEOg0bPFVRcmXw== dependencies: pump "^3.0.0" tar-stream "^3.1.5" @@ -7886,18 +7649,37 @@ tar-stream@^2.1.4: readable-stream "^3.1.1" tar-stream@^3.1.5: - version "3.1.7" - resolved "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz" - integrity sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ== + version "3.2.0" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-3.2.0.tgz#0d0064d9b67ea3c9f5abde155e35faab0df37591" + integrity sha512-ojzvCvVaNp6aOTFmG7jaRD0meowIAuPc3cMMhSgKiVWws1GyHbGd/xvnyuRKcKlMpt3qvxx6r0hreCNITP9hIg== dependencies: b4a "^1.6.4" + bare-fs "^4.5.5" fast-fifo "^1.2.0" streamx "^2.15.0" +tar@^7.0.0: + version "7.5.15" + resolved "https://registry.yarnpkg.com/tar/-/tar-7.5.15.tgz#afe6d1316cddf614a566e3813e42fe01aed46fee" + integrity sha512-dzGK0boVlC4W5QFuQN1EFSl3bIDYsk7Tj40U6eIBnK2k/8ml7TZ5agbI5j5+qnoVcAA+rNtBml8SEiLxZpNqRQ== + dependencies: + "@isaacs/fs-minipass" "^4.0.0" + chownr "^3.0.0" + minipass "^7.1.2" + minizlib "^3.1.0" + yallist "^5.0.0" + +teex@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/teex/-/teex-1.0.1.tgz#b8fa7245ef8e8effa8078281946c85ab780a0b12" + integrity sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg== + dependencies: + streamx "^2.12.5" + text-decoder@^1.1.0: - version "1.2.3" - resolved "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz" - integrity sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA== + version "1.2.7" + resolved "https://registry.yarnpkg.com/text-decoder/-/text-decoder-1.2.7.tgz#5d073a9a74b9c0a9d28dfadcab96b604af57d8ba" + integrity sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ== dependencies: b4a "^1.6.4" @@ -8084,10 +7866,10 @@ undefsafe@^2.0.5: resolved "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz" integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA== -undici-types@~7.10.0: - version "7.10.0" - resolved "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz" - integrity sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag== +undici-types@~7.19.0: + version "7.19.2" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.19.2.tgz#1b67fc26d0f157a0cba3a58a5b5c1e2276b8ba2a" + integrity sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg== undici@^7.19.0: version "7.21.0" @@ -8175,11 +7957,6 @@ unist-util-visit@^5.0.0: unist-util-is "^6.0.0" unist-util-visit-parents "^6.0.0" -universalify@^2.0.0: - version "2.0.1" - resolved "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz" - integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== - unpipe@~1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz" @@ -8405,7 +8182,7 @@ wordwrap@^1.0.0: wrap-ansi@^7.0.0: version "7.0.0" - resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== dependencies: ansi-styles "^4.0.0" @@ -8441,7 +8218,7 @@ x-var@^3.0.1: y18n@^5.0.5: version "5.0.8" - resolved "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== yallist@^3.0.2: @@ -8449,6 +8226,11 @@ yallist@^3.0.2: resolved "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz" integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== +yallist@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-5.0.0.tgz#00e2de443639ed0d78fd87de0d27469fbcffb533" + integrity sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw== + yaml@^2.8.2: version "2.8.2" resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.8.2.tgz#5694f25eca0ce9c3e7a9d9e00ce0ddabbd9e35c5" @@ -8456,12 +8238,12 @@ yaml@^2.8.2: yargs-parser@^21.1.1: version "21.1.1" - resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== yargs@^17.7.2: version "17.7.2" - resolved "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== dependencies: cliui "^8.0.1" @@ -8474,7 +8256,7 @@ yargs@^17.7.2: yauzl@^2.10.0: version "2.10.0" - resolved "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz" + resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" integrity sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g== dependencies: buffer-crc32 "~0.2.3" @@ -8492,7 +8274,7 @@ zod-to-json-schema@^3.25.1: zod@^3.24.1: version "3.25.76" - resolved "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.25.76.tgz#26841c3f6fd22a6a2760e7ccb719179768471e34" integrity sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ== "zod@^3.25 || ^4.0":