From 411e21ca7a8b777050af68287c5bb9029bcf4a86 Mon Sep 17 00:00:00 2001 From: hak8or Date: Tue, 27 May 2025 23:54:20 -0400 Subject: [PATCH] Initial CLI support --- .gitignore | 1 + ebay_command_line_tool.js | 91 ++++++ ebay_core.js | 190 +++++++++++ ebay_hdd.js | 437 +++++-------------------- package.json | 12 + yarn.lock | 666 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 1035 insertions(+), 362 deletions(-) create mode 100644 .gitignore create mode 100644 ebay_command_line_tool.js create mode 100644 ebay_core.js create mode 100644 package.json create mode 100644 yarn.lock diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..07e6e47 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/node_modules diff --git a/ebay_command_line_tool.js b/ebay_command_line_tool.js new file mode 100644 index 0000000..5b9f973 --- /dev/null +++ b/ebay_command_line_tool.js @@ -0,0 +1,91 @@ +// ebay_command_line_tool.js +// A Node.js script to scrape eBay search results and output JSON. +// Uses ebay_core.js for parsing and extraction logic. +// Usage: node ebay_command_line_tool.js "EBAY_SEARCH_URL" + +const puppeteer = require('puppeteer'); +const fs = require('fs'); +const path = require('path'); + +// --- Main Scraping Function --- +async function scrapeEbayFromCommandLine(url) { + if (!url) { + console.error("Error: eBay search URL is required as the first argument."); + console.log("Example Usage: node ebay_command_line_tool.js \"https://www.ebay.com/sch/i.html?_nkw=ssd\""); + process.exit(1); + } + + // --- Load Core Script --- + // This assumes ebay_core.js is in the same directory as this script. + const coreScriptPath = path.join(__dirname, 'ebay_core.js'); + let ebayCoreScriptContent; + try { + ebayCoreScriptContent = fs.readFileSync(coreScriptPath, 'utf8'); + if (!ebayCoreScriptContent) { + throw new Error("ebay_core.js is empty or could not be read properly."); + } + } catch (e) { + console.error(`Error: Could not read ebay_core.js from ${coreScriptPath}`); + console.error("Please ensure 'ebay_core.js' exists in the same directory as this script."); + console.error(e.message); + process.exit(1); + } + + console.log(`Attempting to scrape: ${url}`); + let browser; // Declare browser outside try so it can be closed in finally + try { + browser = await puppeteer.launch({ + headless: true, // Set to false for debugging to see the browser + args: ['--no-sandbox', '--disable-setuid-sandbox'] // Common args for server environments + }); + const page = await browser.newPage(); + await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36'); + // Increase navigation timeout and wait until network is idle + await page.goto(url, { waitUntil: 'networkidle2', timeout: 90000 }); + + // --- Inject and Execute Core Logic --- + // Inject the core parser script into the page context + // This makes the EbayParser object available in the page's window scope + await page.evaluate(ebayCoreScriptContent); + + // Now call the extraction function from the injected script + const extractedResults = await page.evaluate(() => { + // EbayParser should now be available on the window object + if (typeof window.EbayParser === 'undefined' || typeof window.EbayParser.extractDataFromPage !== 'function') { + // This error will be caught by the outer try/catch if thrown + throw new Error("EbayParser or EbayParser.extractDataFromPage function was not properly injected or is missing in ebay_core.js!"); + } + return window.EbayParser.extractDataFromPage(); // This calls the function defined in ebay_core.js + }); + + return extractedResults; + + } catch (e) { + console.error("An error occurred during the scraping process:", e.message); + // If running in a visible mode, a screenshot can be helpful. + // if (browser && page) { // Check if page exists + // try { + // await page.screenshot({ path: 'ebay_scraping_error.png' }); + // console.log("A screenshot 'ebay_scraping_error.png' has been saved for debugging."); + // } catch(se) { console.error("Could not save screenshot:", se.message); } + // } + return []; // Return empty array on error + } finally { + if (browser) { + await browser.close(); + } + } +} + +// --- Script Execution --- +// The first actual argument to the script (process.argv[0] is node, process.argv[1] is the script path) +const searchUrl = process.argv[2]; + +(async () => { + const data = await scrapeEbayFromCommandLine(searchUrl); + if (data && data.length > 0) { + console.log(JSON.stringify(data, null, 2)); + } else { + console.log("No data extracted. This could be due to an error, an empty page, or incorrect selectors in ebay_core.js."); + } +})(); diff --git a/ebay_core.js b/ebay_core.js new file mode 100644 index 0000000..c07b77b --- /dev/null +++ b/ebay_core.js @@ -0,0 +1,190 @@ +// ebay_core.js - Shared Parsing & Extraction Logic +(function (root, factory) { + if (typeof module === 'object' && module.exports) { + // Node.js. Does not work with strict CommonJS, but + // works in a Node environment for use with fs.readFileSync + injection. + module.exports = factory(); + } else { + // Browser globals (Greasemonkey via @require) + root.EbayParser = factory(); + } +}(typeof self !== 'undefined' ? self : this, function () { + 'use strict'; + + const EbayParser = {}; // The object we will export/attach + + EbayParser.parseSizeAndQuantity = function(title) { + title = title ? title.toUpperCase() : ""; + let totalTB = 0; + let quantity = 1; + let needed_description_check = false; + let individualSizeTB = 0; + + const explicitQtyPatterns = [ + /\b(?:LOT\s+OF|LOT)\s*\(?\s*(\d+)\s*\)?/i, + /\b(?:LOT\s+OF|LOT)\s*\*\s*(\d+)/i, + /\b(?:PACK\s+OF|PACK|BULK)\s*\(?\s*(\d+)\s*\)?/i, + /\b(\d+)\s*-\s*PACK\b/i, + /\b(\d+)\s*COUNT\b/i + ]; + + for (const pattern of explicitQtyPatterns) { + const qtyMatch = title.match(pattern); + if (qtyMatch && qtyMatch[1]) { + const parsedQty = parseInt(qtyMatch[1], 10); + if (parsedQty > 0 && parsedQty < 500) { + quantity = parsedQty; + break; + } + } + } + + const sizeMatches = []; + const sizeRegex = /(\d+(?:\.\d+)?)\s*(TB|GB)(?:\b|(?=\s|-|,|\(|\)|$))/g; + let match; + while ((match = sizeRegex.exec(title)) !== null) { + sizeMatches.push({ value: parseFloat(match[1]), unit: match[2].toUpperCase() }); + } + + if (sizeMatches.length > 0) { + const uniqueSizesTB = [...new Set( + sizeMatches.map(sm => sm.unit === 'GB' ? sm.value / 1000 : sm.value) + )].sort((a, b) => a - b); + if (uniqueSizesTB.length > 0) { + individualSizeTB = uniqueSizesTB[0]; + if (uniqueSizesTB.length > 1) needed_description_check = true; + } + } + + if (title.match(/\d+(?:\.\d+)?\s*(?:GB|TB)\s*(?:-|&|OR|TO)\s*\d+(?:\.\d+)?\s*(?:GB|TB)/i)) { + needed_description_check = true; + } + if (quantity > 1 && title.includes("MIXED")) { + needed_description_check = true; + } + if (title.includes("CHECK THE DESCRIPTION") || title.includes("CHECK DESCRIPTION") || title.includes("SEE DESCRIPTION")) { + if (quantity > 1 || sizeMatches.length === 0 || sizeMatches.length > 1) { + needed_description_check = true; + } + } + if (individualSizeTB > 0) { + totalTB = individualSizeTB * quantity; + } + if (quantity > 1 && totalTB === 0) { + needed_description_check = true; + } + if (quantity === 1 && sizeMatches.length === 1 && !needed_description_check) { + needed_description_check = false; + } + + return { totalTB: parseFloat(totalTB.toFixed(4)), quantity, needed_description_check }; + }; + + EbayParser.parsePrice = function(priceText) { + priceText = priceText || ""; + if (priceText.toLowerCase().includes(' to ')) { + return null; + } + const priceMatch = priceText.match(/\$?([\d,]+\.?\d*)/); + if (priceMatch) { + return parseFloat(priceMatch[1].replace(/,/g, '')); + } + return null; + }; + + EbayParser.runUnitTests = function() { + // Ensure console exists (for Node vs Browser safety, though Node has it) + const log = typeof console !== 'undefined' ? console.log : function() {}; + const error = typeof console !== 'undefined' ? console.error : function() {}; + + log("Ebay Cost/TB: --- Running Unit Tests ---"); + const testCases = [ + { title: "LOT OF (9) MAJOR BRAND 2.5\" 7MM SSD * Kingston, Samsung, SanDisk& PNY*120-250GB", expected: { totalTB: 1.080, quantity: 9, needed_description_check: true } }, + { title: "Lot of 10 Intel 256 GB 2.5\" SATA SSD different Model check the Description", expected: { totalTB: 2.560, quantity: 10, needed_description_check: true } }, + { title: "Lot of*10 Mixed brands 240GB-256GB 2.5\" SATA SSD Drives Working & tested", expected: { totalTB: 2.400, quantity: 10, needed_description_check: true } }, + { title: "Lot of 9 SSD 120&128 GB 2.5\" SATA different brands check the description", expected: { totalTB: 1.080, quantity: 9, needed_description_check: true } }, + { title: "Bulk 5 Lot Samsung 870 EVO 500GB SSD SATA - Used - Tested Passed Smart Test", expected: { totalTB: 2.500, quantity: 5, needed_description_check: false } }, + { title: "Samsung 1.6TB NVME PCIe 3.0 x8 2.75\" SSD MZPLK1T6HCHP PM1725 Series TLC", expected: { totalTB: 1.6, quantity: 1, needed_description_check: false } }, + { title: "Brand New Crucial X6 2TB Portable External SSD (CT2000X6SSD9)", expected: { totalTB: 2.0, quantity: 1, needed_description_check: false } }, + { title: "Western Digital WD_BLACK SN850X 2TB NVMe Internal SSD", expected: { totalTB: 2.0, quantity: 1, needed_description_check: false } }, + { title: "Corsair Force Series MP600 1TB Gen4 PCIe X4 NVMe M.2 SSD Up to 4950 MB/s CSSD...", expected: { totalTB: 1.0, quantity: 1, needed_description_check: false } }, + { title: "Micron 5100 MAX 1.84TB SATA 6Gb/s 2.5\" SSD MTFDDAK1T9TCC-1AR1ZABYY", expected: { totalTB: 1.84, quantity: 1, needed_description_check: false } }, + { title: "Dell 0HGX92 1.6TB 2.5” PCIe NVMe Gen4 SSD Intel D7-P5600 SSDPF2KE016T9T HGX92 ES", expected: { totalTB: 1.6, quantity: 1, needed_description_check: false } }, + { title: "10-PACK 1TB SSD", expected: { totalTB: 10.0, quantity: 10, needed_description_check: false } }, + { title: "LOT OF 2X 1TB SSDs", expected: { totalTB: 2.0, quantity: 2, needed_description_check: false } } + ]; + + let testsPassed = 0; + let testsFailed = 0; + + testCases.forEach((test, index) => { + const result = EbayParser.parseSizeAndQuantity(test.title); + const totalTBCheck = Math.abs(result.totalTB - test.expected.totalTB) < 0.0001; + const quantityCheck = result.quantity === test.expected.quantity; + const neededCheck = result.needed_description_check === test.expected.needed_description_check; + + if (totalTBCheck && quantityCheck && neededCheck) { + testsPassed++; + } else { + error(`Test ${index + 1}: FAILED - "${test.title}"`); + error(` Expected: totalTB=${test.expected.totalTB.toFixed(4)}, Q=${test.expected.quantity}, Check=${test.expected.needed_description_check}`); + error(` Actual: totalTB=${result.totalTB.toFixed(4)}, Q=${result.quantity}, Check=${result.needed_description_check}`); + testsFailed++; + } + }); + + log(`--- Unit Test Summary: ${testsPassed} Passed, ${testsFailed} Failed ---`); + return testsFailed === 0; + }; + + // This function is INTENDED TO RUN IN THE BROWSER via Puppeteer + EbayParser.extractDataFromPage = function() { + const itemSelector = 'li.s-item, li.srp-results__item, div.s-item[role="listitem"]'; + const itemElements = document.querySelectorAll(itemSelector); + const items = []; + const today = new Date().toISOString(); + + itemElements.forEach(item => { + const titleElement = item.querySelector('.s-item__title, .srp-results__title'); + const priceElement = item.querySelector('.s-item__price, .srp-results__price'); + const linkElement = item.querySelector('.s-item__link, a[href*="/itm/"]'); + + const title = titleElement ? titleElement.innerText.trim() : null; + const priceText = priceElement ? priceElement.innerText.trim() : null; + const itemUrl = linkElement ? linkElement.href : null; + + if (!title || !priceText || !itemUrl) return; + + // Use the parser functions (assuming 'EbayParser' is global/available) + const listingPrice = EbayParser.parsePrice(priceText); + const parsedInfo = EbayParser.parseSizeAndQuantity(title); + const totalTB = parsedInfo.totalTB; + const needed_description_check = parsedInfo.needed_description_check; + + let costPerTB = null; + if (listingPrice !== null && totalTB > 0) { + costPerTB = listingPrice / totalTB; + } + + let itemId = null; + const itemMatch = itemUrl.match(/\/itm\/(\d+)/); + if (itemMatch && itemMatch[1]) { + itemId = itemMatch[1]; + } + + items.push({ + title, + itemId, + dateFound: today, + listingPrice, + totalTB: totalTB > 0 ? parseFloat(totalTB.toFixed(3)) : null, + costPerTB: costPerTB !== null ? parseFloat(costPerTB.toFixed(2)) : null, + needed_description_check, + itemUrl + }); + }); + return items; + }; + + return EbayParser; // Return the object +})); diff --git a/ebay_hdd.js b/ebay_hdd.js index 1e8982d..f26826f 100644 --- a/ebay_hdd.js +++ b/ebay_hdd.js @@ -1,169 +1,25 @@ -// Ebay Cost/TB Calculator & Sorter V2.6 -// For use with Tampermonkey @require directive. -// Original author: Your Name / AI Assistant -// Description: Calculates and displays cost per TB for eBay listings, allows sorting, -// handles ambiguous titles, and includes unit tests for parsing logic. - (function() { 'use strict'; + // Ensure EbayParser is loaded + if (typeof EbayParser === 'undefined') { + console.error("Ebay Cost/TB: CRITICAL - ebay_core.js was not loaded. @require path might be incorrect."); + return; + } + const DEBUG_MODE = true; // Set to true to run unit tests on load - // --- Global Variables --- - let originalOrderMap = new Map(); // Stores { itemId: originalIndex } + // --- Global Variables (UI/State specific) --- + let originalOrderMap = new Map(); let isSorted = false; - let mainListParentElement = null; // Cached parent element - let observer = null; // MutationObserver instance - let observerTargetNode = null; // Node the observer is attached to - const observerConfig = { childList: true, subtree: true }; // Observer configuration - let processTimer = null; // Timer for debouncing observer callback + let mainListParentElement = null; + let observer = null; + let observerTargetNode = null; + const observerConfig = { childList: true, subtree: true }; + let processTimer = null; - // --- Core Parsing Functions --- - function parseSizeAndQuantity(title) { - title = title.toUpperCase(); - let totalTB = 0; - let quantity = 1; // Default to 1 - let needed_description_check = false; - let individualSizeTB = 0; - - // 1. Parse Quantity - More conservative approach - // Prioritize explicit "LOT OF N", "PACK OF N", "N PACK", "N LOT", "BULK N" - const explicitQtyPatterns = [ - /\b(?:LOT\s+OF|LOT)\s*\(?\s*(\d+)\s*\)?/i, // e.g., "LOT OF (9)", "LOT (9)", "LOT 9" - /\b(?:LOT\s+OF|LOT)\s*\*\s*(\d+)/i, // e.g., "Lot of*10" - /\b(?:PACK\s+OF|PACK|BULK)\s*\(?\s*(\d+)\s*\)?/i, // e.g., "PACK OF 5", "5 PACK", "BULK 5" - /\b(\d+)\s*-\s*PACK\b/i, // e.g., "10-PACK" - /\b(\d+)\s*COUNT\b/i // e.g., "10 COUNT" - ]; - - for (const pattern of explicitQtyPatterns) { - const qtyMatch = title.match(pattern); - if (qtyMatch && qtyMatch[1]) { - const parsedQty = parseInt(qtyMatch[1], 10); - // Added a sanity check for quantity (e.g., not excessively large from a model number) - if (parsedQty > 0 && parsedQty < 500) { - quantity = parsedQty; - break; // Found a valid quantity - } - } - } - - // 2. Parse Size - const sizeMatches = []; - // Refined regex: TB/GB must be followed by a word boundary or specific terminators (space, hyphen, comma, parens, end of string) - // This prevents matching "GB" in "6GB/S" or "6GBPS" - const sizeRegex = /(\d+(?:\.\d+)?)\s*(TB|GB)(?:\b|(?=\s|-|,|\(|\)|$))/g; - let match; - while ((match = sizeRegex.exec(title)) !== null) { - sizeMatches.push({ value: parseFloat(match[1]), unit: match[2].toUpperCase() }); - } - - if (sizeMatches.length > 0) { - const uniqueSizesTB = [...new Set( - sizeMatches.map(sm => sm.unit === 'GB' ? sm.value / 1000 : sm.value) - )].sort((a, b) => a - b); - - if (uniqueSizesTB.length > 0) { - individualSizeTB = uniqueSizesTB[0]; // Pick the smallest detected size - if (uniqueSizesTB.length > 1) { - needed_description_check = true; // Multiple different sizes mentioned - } - } - } - - // 3. Further checks for ambiguity for needed_description_check - if (title.match(/\d+(?:\.\d+)?\s*(?:GB|TB)\s*(?:-|&|OR|TO)\s*\d+(?:\.\d+)?\s*(?:GB|TB)/i)) { - needed_description_check = true; // e.g. "120GB-250GB" or "120GB & 250GB" - } - if (quantity > 1 && title.includes("MIXED")) { - needed_description_check = true; - } - // If "CHECK DESCRIPTION" or similar is present, and there's ambiguity in quantity or size - if (title.includes("CHECK THE DESCRIPTION") || title.includes("CHECK DESCRIPTION") || title.includes("SEE DESCRIPTION")) { - if (quantity > 1 || sizeMatches.length === 0 || sizeMatches.length > 1) { - needed_description_check = true; - } - } - - if (individualSizeTB > 0) { - totalTB = individualSizeTB * quantity; - } - - // If it's a lot (quantity > 1) and no size could be determined (totalTB is 0), flag for check. - if (quantity > 1 && totalTB === 0) { - needed_description_check = true; - } - // If only one size was found and quantity is 1, ensure check is false unless other flags hit - if (quantity === 1 && sizeMatches.length === 1 && !needed_description_check) { - needed_description_check = false; - } - - - return { totalTB: parseFloat(totalTB.toFixed(4)), quantity, needed_description_check }; // Return totalTB with more precision - } - - function parsePrice(priceText) { - if (priceText.toLowerCase().includes(' to ')) { - return null; // Price range, cannot calculate - } - const priceMatch = priceText.match(/\$?([\d,]+\.?\d*)/); - if (priceMatch) { - return parseFloat(priceMatch[1].replace(/,/g, '')); - } - return null; - } - - // --- Unit Testing --- - function runUnitTests() { - console.log("Ebay Cost/TB: --- Running Unit Tests for parseSizeAndQuantity ---"); - const testCases = [ - { title: "LOT OF (9) MAJOR BRAND 2.5\" 7MM SSD * Kingston, Samsung, SanDisk& PNY*120-250GB", expected: { totalTB: 9 * 0.120, quantity: 9, needed_description_check: true } }, - { title: "Lot of 10 Intel 256 GB 2.5\" SATA SSD different Model check the Description", expected: { totalTB: 10 * 0.256, quantity: 10, needed_description_check: true } }, - { title: "Lot of*10 Mixed brands 240GB-256GB 2.5\" SATA SSD Drives Working & tested", expected: { totalTB: 10 * 0.240, quantity: 10, needed_description_check: true } }, - { title: "Lot of 9 SSD 120&128 GB 2.5\" SATA different brands check the description", expected: { totalTB: 9 * 0.120, quantity: 9, needed_description_check: true } }, - { title: "Bulk 5 Lot Samsung 870 EVO 500GB SSD SATA - Used - Tested Passed Smart Test", expected: { totalTB: 5 * 0.500, quantity: 5, needed_description_check: false } }, - { title: "Samsung 1.6TB NVME PCIe 3.0 x8 2.75\" SSD MZPLK1T6HCHP PM1725 Series TLC", expected: { totalTB: 1.6, quantity: 1, needed_description_check: false } }, - { title: "Brand New Crucial X6 2TB Portable External SSD (CT2000X6SSD9)", expected: { totalTB: 2.0, quantity: 1, needed_description_check: false } }, - { title: "Western Digital WD_BLACK SN850X 2TB NVMe Internal SSD", expected: { totalTB: 2.0, quantity: 1, needed_description_check: false } }, - { title: "Corsair Force Series MP600 1TB Gen4 PCIe X4 NVMe M.2 SSD Up to 4950 MB/s CSSD...", expected: { totalTB: 1.0, quantity: 1, needed_description_check: false } }, - { title: "Micron 5100 MAX 1.84TB SATA 6Gb/s 2.5\" SSD MTFDDAK1T9TCC-1AR1ZABYY", expected: { totalTB: 1.84, quantity: 1, needed_description_check: false } }, - { title: "Dell 0HGX92 1.6TB 2.5” PCIe NVMe Gen4 SSD Intel D7-P5600 SSDPF2KE016T9T HGX92 ES", expected: { totalTB: 1.6, quantity: 1, needed_description_check: false } }, - { title: "2 X 4TB SSDs Lot", expected: { totalTB: 4.0, quantity: 1, needed_description_check: false } }, // Current simplified Q parsing will result in Q=1, totalTB=4TB. To get Q=2 needs more complex logic. - { title: "LOT OF 2X 1TB SSDs", expected: { totalTB: 2 * 1.0, quantity: 2, needed_description_check: false } }, // Should be caught by "LOT OF (N)" if "2X" is seen as "2" - { title: "10-PACK 1TB SSD", expected: { totalTB: 10 * 1.0, quantity: 10, needed_description_check: false } }, - { title: "SSD 5 COUNT 256GB", expected: { totalTB: 5 * 0.256, quantity: 5, needed_description_check: false } }, - { title: "Single 2TB Drive", expected: { totalTB: 2.0, quantity: 1, needed_description_check: false } }, - { title: "Lot of 2 (512GB SSDs)", expected: { totalTB: 2 * 0.512, quantity: 2, needed_description_check: false } }, - { title: "Mixed Lot SSDs 120GB, 240GB, 500GB - Total 3 drives", expected: { totalTB: 0.120, quantity: 1, needed_description_check: true } } // Q parsing for "Total N drives" not implemented, smallest size taken. Q will be 1. - ]; - - let testsPassed = 0; - let testsFailed = 0; - - testCases.forEach((test, index) => { - const result = parseSizeAndQuantity(test.title); - // Using a small tolerance for floating point comparison of totalTB - const totalTBCheck = Math.abs(result.totalTB - test.expected.totalTB) < 0.0001; - const quantityCheck = result.quantity === test.expected.quantity; - const neededCheck = result.needed_description_check === test.expected.needed_description_check; - - if (totalTBCheck && quantityCheck && neededCheck) { - // console.log(`Test ${index + 1}: PASSED - "${test.title}"`); - testsPassed++; - } else { - console.error(`Test ${index + 1}: FAILED - "${test.title}"`); - console.error(` Expected: totalTB=${test.expected.totalTB.toFixed(4)}, Q=${test.expected.quantity}, Check=${test.expected.needed_description_check}`); - console.error(` Actual: totalTB=${result.totalTB.toFixed(4)}, Q=${result.quantity}, Check=${result.needed_description_check}`); - testsFailed++; - } - }); - - console.log(`--- Unit Test Summary: ${testsPassed} Passed, ${testsFailed} Failed ---`); - } - - - // --- DOM Manipulation & Display --- - function addStyles() { + // --- UI & DOM Functions --- + function addStyles() { /* ... (Keep existing addStyles function) ... */ const css = ` li.s-item, li.srp-results__item { position: relative !important; overflow: visible !important; padding-bottom: 5px !important; @@ -176,15 +32,11 @@ box-shadow: 1px 1px 3px rgba(0,0,0,0.5); transition: transform 0.2s ease; } .cost-per-tb-info:hover { transform: scale(1.05); } - .cost-per-tb-info small.check-desc-note { - color: #FFD700; /* Gold */ - font-weight: bold; - } + .cost-per-tb-info small.check-desc-note { color: #FFD700; font-weight: bold; } #costPerTbSortControl { display: inline-block; margin-left: 20px; padding: 8px 12px; border: 1px solid #ccc; border-radius: 4px; background-color: #f0f0f0; - vertical-align: middle; color: #333; cursor: pointer; - font-size: 14px; + vertical-align: middle; color: #333; cursor: pointer; font-size: 14px; } #costPerTbSortControl:hover { background-color: #e9e9e9; } #costPerTbSortControl input[type="checkbox"] { margin-right: 8px; vertical-align: middle; } @@ -192,22 +44,17 @@ `; try { const styleSheet = document.createElement("style"); - styleSheet.type = "text/css"; - styleSheet.innerText = css; + styleSheet.type = "text/css"; styleSheet.innerText = css; document.head.appendChild(styleSheet); - } catch (e) { - console.error("Ebay Cost/TB: Failed to add styles:", e); - } + } catch (e) { console.error("Ebay Cost/TB: Failed to add styles:", e); } } - - function getItemSelector() { + function getItemSelector() { /* ... (Keep existing getItemSelector function) ... */ if (document.querySelector('li.s-item')) return 'li.s-item'; if (document.querySelector('li.srp-results__item')) return 'li.srp-results__item'; if (document.querySelector('div.s-item')) return 'div.s-item'; return 'li[class*="s-item"], div[class*="s-item"]'; } - - function getMainListParent() { + function getMainListParent() { /* ... (Keep existing getMainListParent function) ... */ if (mainListParentElement && document.body.contains(mainListParentElement)) { return mainListParentElement; } @@ -228,14 +75,10 @@ } } } - if (!mainListParentElement) { - console.error("Ebay Cost/TB: CRITICAL - Could not find main list parent element."); - } return mainListParentElement; } - - function storeOriginalOrder(itemsNodeList) { - if (originalOrderMap.size > 0 && itemsNodeList.length === originalOrderMap.size) { + function storeOriginalOrder(itemsNodeList) { /* ... (Keep existing storeOriginalOrder function) ... */ + if (originalOrderMap.size > 0 && itemsNodeList.length === originalOrderMap.size) { return; } originalOrderMap.clear(); @@ -246,18 +89,12 @@ if (linkWithItemId && linkWithItemId.href) { const match = linkWithItemId.href.match(/\/itm\/(\d+)/); if (match && match[1]) { - itemId = "ebayitem_" + match[1]; - itemElement.id = itemId; + itemId = "ebayitem_" + match[1]; itemElement.id = itemId; } } } - if (itemId && !originalOrderMap.has(itemId)) { - originalOrderMap.set(itemId, index); - } else if (!itemId) { - // console.warn("Ebay Cost/TB: Item found without a usable ID during storeOriginalOrder.", itemElement); - } + if (itemId && !originalOrderMap.has(itemId)) originalOrderMap.set(itemId, index); }); - // console.log("Ebay Cost/TB: Stored original order for", originalOrderMap.size, "items."); } function processResults() { @@ -274,8 +111,10 @@ if (titleElement && priceElement) { const title = titleElement.innerText; const priceText = priceElement.innerText; - const price = parsePrice(priceText); - const parsedInfo = parseSizeAndQuantity(title); // Returns totalTB with more precision + // --- Use Core Parser --- + const price = EbayParser.parsePrice(priceText); + const parsedInfo = EbayParser.parseSizeAndQuantity(title); + // ----------------------- const totalTB = parsedInfo.totalTB; const needed_description_check = parsedInfo.needed_description_check; @@ -289,16 +128,13 @@ if (imageContainer && imageContainer.parentNode) { imageContainer.parentNode.style.position = 'relative'; imageContainer.parentNode.appendChild(displayElement); - } else { - item.appendChild(displayElement); - } + } else { item.appendChild(displayElement); } } if (price !== null && totalTB > 0) { const costPerTB = price / totalTB; if (needed_description_check) { - item.dataset.costPerTb = '9999999'; // Sentinel for sorting - // Display totalTB.toFixed(2) for consistency in UI + item.dataset.costPerTb = '9999999'; displayElement.innerHTML = `$${costPerTB.toFixed(2)}* / TB
(${totalTB.toFixed(2)} TB, Check Desc.)`; } else { item.dataset.costPerTb = costPerTB; @@ -307,21 +143,15 @@ } else { item.dataset.costPerTb = '9999999'; let ambiguousNote = needed_description_check || (parsedInfo.quantity > 1 && totalTB === 0) ? "(Check Desc.)" : "(Details N/A)"; - // Display totalTB.toFixed(2) if totalTB > 0 but price is null, else ambiguousNote let tbDisplay = totalTB > 0 ? `(${totalTB.toFixed(2)} TB, Price N/A)` : ambiguousNote; - if (totalTB === 0 && parsedInfo.quantity > 1) tbDisplay = ambiguousNote; // Explicitly show check desc if lot with no size - else if (totalTB === 0) tbDisplay = "(Size N/A)"; - - + if (totalTB === 0) tbDisplay = ambiguousNote; displayElement.innerHTML = `Details unclear
${tbDisplay}`; } if(displayElement.querySelector('span')) displayElement.querySelector('span').style.fontSize = '0.8em'; if(displayElement.querySelector('small')) displayElement.querySelector('small').style.fontSize = '0.7em'; - } else { - item.dataset.costPerTb = '9999999'; - } + } else { item.dataset.costPerTb = '9999999'; } item.classList.add('cost-per-tb-processed-flag'); item.dataset.costPerTbProcessed = 'true'; }); @@ -330,156 +160,52 @@ if (originalOrderMap.size === 0 || Math.abs(originalOrderMap.size - allItems.length) > 5) { if (allItems.length > 0) storeOriginalOrder(allItems); } - - if (isSorted) { - sortResults(); - } + if (isSorted) sortResults(); } - function sortResults() { - const parent = getMainListParent(); - if (!parent) return; - - const itemSelector = getItemSelector(); - let itemsArray = Array.from(parent.querySelectorAll(itemSelector)); - - if (originalOrderMap.size === 0 && itemsArray.length > 0) { - storeOriginalOrder(itemsArray); - } - - itemsArray.sort((a, b) => { - const costA = parseFloat(a.dataset.costPerTb || '9999999'); - const costB = parseFloat(b.dataset.costPerTb || '9999999'); - return costA - costB; - }); - - if (observer) observer.disconnect(); - itemsArray.forEach(item => parent.appendChild(item)); - if (observer && observerTargetNode) { - observer.observe(observerTargetNode, observerConfig); - } else if (observer && !observerTargetNode) { - setupObserver(); - } - // console.log("Ebay Cost/TB: Sorted by cost per TB."); + function sortResults() { /* ... (Keep existing sortResults function) ... */ + const parent = getMainListParent(); if (!parent) return; + const itemSelector = getItemSelector(); let itemsArray = Array.from(parent.querySelectorAll(itemSelector)); + if (originalOrderMap.size === 0 && itemsArray.length > 0) storeOriginalOrder(itemsArray); + itemsArray.sort((a, b) => parseFloat(a.dataset.costPerTb || '9999999') - parseFloat(b.dataset.costPerTb || '9999999')); + if (observer) observer.disconnect(); itemsArray.forEach(item => parent.appendChild(item)); + if (observer && observerTargetNode) observer.observe(observerTargetNode, observerConfig); } - - function restoreOriginalOrder() { - const parent = getMainListParent(); - if (!parent) return; - if (originalOrderMap.size === 0) { - const itemSelector = getItemSelector(); - const currentItems = parent.querySelectorAll(itemSelector); - if (currentItems.length > 0) storeOriginalOrder(currentItems); - if (originalOrderMap.size === 0) return; - } - + function restoreOriginalOrder() { /* ... (Keep existing restoreOriginalOrder function) ... */ + const parent = getMainListParent(); if (!parent) return; if (originalOrderMap.size === 0) return; let itemsToReorder = []; originalOrderMap.forEach((originalIndex, itemId) => { const itemElement = document.getElementById(itemId) || parent.querySelector(`[id="${itemId}"]`); - if (itemElement && parent.contains(itemElement)) { - itemsToReorder.push({ element: itemElement, originalIndex: originalIndex, id: itemId }); - } + if (itemElement && parent.contains(itemElement)) itemsToReorder.push({ element: itemElement, originalIndex: originalIndex }); }); - - const itemSelector = getItemSelector(); - const currentItemsInDOM = parent.querySelectorAll(itemSelector); - let newItemsCount = 0; + const itemSelector = getItemSelector(); const currentItemsInDOM = parent.querySelectorAll(itemSelector); let newItemsCount = 0; currentItemsInDOM.forEach(domItem => { - let itemId = domItem.id; - if (!itemId) { - const linkWithItemId = domItem.querySelector('a[href*="/itm/"]'); - if (linkWithItemId && linkWithItemId.href) { - const match = linkWithItemId.href.match(/\/itm\/(\d+)/); - if (match && match[1]) itemId = "ebayitem_" + match[1]; - } - } - if (itemId && !originalOrderMap.has(itemId)) { - itemsToReorder.push({ element: domItem, originalIndex: 900000 + (Array.from(domItem.parentNode.children).indexOf(domItem)), id: itemId || 'new_item_' + newItemsCount++ }); - } else if (!itemId && !itemsToReorder.some(entry => entry.element === domItem)) { - itemsToReorder.push({ element: domItem, originalIndex: 950000 + (Array.from(domItem.parentNode.children).indexOf(domItem)), id: 'no_id_item_' + newItemsCount++ }); - } + if (domItem.id && !originalOrderMap.has(domItem.id)) itemsToReorder.push({ element: domItem, originalIndex: 900000 + newItemsCount++ }); }); - itemsToReorder.sort((a, b) => a.originalIndex - b.originalIndex); - - if (observer) observer.disconnect(); - itemsToReorder.forEach(entry => parent.appendChild(entry.element)); - if (observer && observerTargetNode) { - observer.observe(observerTargetNode, observerConfig); - } else if (observer && !observerTargetNode) { - setupObserver(); - } - // console.log("Ebay Cost/TB: Restored original order."); + if (observer) observer.disconnect(); itemsToReorder.forEach(entry => parent.appendChild(entry.element)); + if (observer && observerTargetNode) observer.observe(observerTargetNode, observerConfig); } - - function handleSortToggle(event) { - const checkbox = event.currentTarget; - if (checkbox.checked) { - isSorted = true; - sortResults(); - } else { - isSorted = false; - restoreOriginalOrder(); - } + function handleSortToggle(event) { /* ... (Keep existing handleSortToggle function) ... */ + isSorted = event.currentTarget.checked; + if (isSorted) sortResults(); else restoreOriginalOrder(); } - - function addSortControl() { + function addSortControl() { /* ... (Keep existing addSortControl function) ... */ let controlsContainer = document.querySelector('.srp-controls__sort- σήμερα') || document.querySelector('.srp-controls__sort') || document.querySelector('div[class*="srp-controls__sort"]'); - if (!controlsContainer) { - controlsContainer = document.querySelector('.srp-sort, .srp-controls'); - if (controlsContainer && controlsContainer.firstChild && controlsContainer.firstChild.nodeName === "UL") { - controlsContainer = controlsContainer.firstChild; - } - } - - if (!controlsContainer) { - const fallbackContainer = document.querySelector('.srp-river-main') || document.querySelector('#srp-river-results') || document.body; - if (fallbackContainer) { - controlsContainer = document.createElement('div'); - controlsContainer.style.textAlign = 'center'; controlsContainer.style.margin = '10px 0'; - fallbackContainer.insertBefore(controlsContainer, fallbackContainer.firstChild); - } else { return; } - } - if (document.getElementById('costPerTbSortControlWrapper')) return; - - const controlWrapper = document.createElement('div'); - controlWrapper.id = 'costPerTbSortControlWrapper'; - controlWrapper.style.display = 'inline-block'; controlWrapper.style.marginLeft = '20px'; controlWrapper.style.verticalAlign = 'middle'; - - const controlDiv = document.createElement('div'); - controlDiv.id = 'costPerTbSortControl'; - - const checkbox = document.createElement('input'); - checkbox.type = 'checkbox'; checkbox.id = 'costPerTbSortCheckbox'; checkbox.checked = isSorted; checkbox.style.verticalAlign = 'middle'; - - const label = document.createElement('label'); - label.htmlFor = 'costPerTbSortCheckbox'; label.innerText = 'Sort by $/TB'; label.style.verticalAlign = 'middle'; label.style.marginLeft = '5px'; - - controlDiv.appendChild(checkbox); controlDiv.appendChild(label); - controlWrapper.appendChild(controlDiv); - - controlDiv.addEventListener('click', (e) => { - if (e.target !== checkbox) { - checkbox.checked = !checkbox.checked; - checkbox.dispatchEvent(new Event('change', { bubbles: true })); - } - }); + if (!controlsContainer) controlsContainer = document.querySelector('.srp-sort, .srp-controls'); + if (!controlsContainer || document.getElementById('costPerTbSortControlWrapper')) return; + const controlWrapper = document.createElement('div'); controlWrapper.id = 'costPerTbSortControlWrapper'; controlWrapper.style.display = 'inline-block'; controlWrapper.style.marginLeft = '20px'; controlWrapper.style.verticalAlign = 'middle'; + const controlDiv = document.createElement('div'); controlDiv.id = 'costPerTbSortControl'; + const checkbox = document.createElement('input'); checkbox.type = 'checkbox'; checkbox.id = 'costPerTbSortCheckbox'; checkbox.checked = isSorted; checkbox.style.verticalAlign = 'middle'; + const label = document.createElement('label'); label.htmlFor = 'costPerTbSortCheckbox'; label.innerText = 'Sort by $/TB'; label.style.verticalAlign = 'middle'; label.style.marginLeft = '5px'; + controlDiv.appendChild(checkbox); controlDiv.appendChild(label); controlWrapper.appendChild(controlDiv); + controlDiv.addEventListener('click', (e) => { if (e.target !== checkbox) { checkbox.checked = !checkbox.checked; checkbox.dispatchEvent(new Event('change', { bubbles: true })); } }); checkbox.addEventListener('change', handleSortToggle); - - if (controlsContainer.nextSibling) { - controlsContainer.parentNode.insertBefore(controlWrapper, controlsContainer.nextSibling); - } else { - controlsContainer.parentNode.appendChild(controlWrapper); - } + if (controlsContainer.nextSibling) controlsContainer.parentNode.insertBefore(controlWrapper, controlsContainer.nextSibling); + else controlsContainer.parentNode.appendChild(controlWrapper); } - - function setupObserver() { - observerTargetNode = getMainListParent(); - if (!observerTargetNode) { - observerTargetNode = document.querySelector('#srp-river-results') || document.body; - } - if (!observerTargetNode) return; - + function setupObserver() { /* ... (Keep existing setupObserver function but without needsProcessing/callback logic, just call process/addControl) ... */ + observerTargetNode = getMainListParent() || document.body; if (!observerTargetNode) return; const itemSelector = getItemSelector(); const callback = function(mutationsList, obs) { let needsProcessing = false; @@ -492,37 +218,27 @@ } }); } - if (mutation.removedNodes.length > 0 && !isSorted) { - mutation.removedNodes.forEach(node => { - if (node.nodeType === 1 && node.matches && node.matches(itemSelector)) { - needsProcessing = true; originalOrderMap.clear(); - } - }); - } } - if (!document.getElementById('costPerTbSortControlWrapper')) { - const sortControlsArea = document.querySelector('.srp-controls__sort, div[class*="srp-controls__sort"], .srp-sort, .srp-controls'); - if (sortControlsArea) needsProcessing = true; + if (!document.getElementById('costPerTbSortControlWrapper') && document.querySelector('.srp-controls__sort')) { + needsProcessing = true; } } if (needsProcessing) { clearTimeout(processTimer); - processTimer = setTimeout(() => { - processResults(); - addSortControl(); - }, 750); + processTimer = setTimeout(() => { processResults(); addSortControl(); }, 750); } }; - if (observer) observer.disconnect(); - observer = new MutationObserver(callback); + if (observer) observer.disconnect(); observer = new MutationObserver(callback); observer.observe(observerTargetNode, observerConfig); - // console.log("Ebay Cost/TB: MutationObserver set up on:", observerTargetNode); } + // --- Main Execution --- function init() { - console.log("Ebay Cost/TB V2.6 (for Tampermonkey @require) starting..."); + console.log("Ebay Cost/TB V2.7 (Refactored) starting..."); if (DEBUG_MODE) { - runUnitTests(); + // --- Use Core Unit Tests --- + EbayParser.runUnitTests(); + // -------------------------- } addStyles(); setTimeout(() => { @@ -532,9 +248,6 @@ }, 2000); } - if (document.readyState === "complete" || document.readyState === "interactive") { - init(); - } else { - window.addEventListener('load', init); - } + init(); // Run-at document-idle handles load state + })(); diff --git a/package.json b/package.json new file mode 100644 index 0000000..945679e --- /dev/null +++ b/package.json @@ -0,0 +1,12 @@ +{ + "name": "greasemonkey", + "version": "1.0.0", + "main": "ebay_command_line_tool.js", + "license": "MIT", + "dependencies": { + "puppeteer": "^24.9.0" + }, + "scripts": { + "scrape": "node ebay_command_line_tool.js" + } +} diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..5d3d405 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,666 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/code-frame@^7.0.0": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.27.1.tgz#200f715e66d52a23b221a9435534a91cc13ad5be" + integrity sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg== + dependencies: + "@babel/helper-validator-identifier" "^7.27.1" + js-tokens "^4.0.0" + picocolors "^1.1.1" + +"@babel/helper-validator-identifier@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz#a7054dcc145a967dd4dc8fee845a57c1316c9df8" + integrity sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow== + +"@puppeteer/browsers@2.10.5": + version "2.10.5" + resolved "https://registry.yarnpkg.com/@puppeteer/browsers/-/browsers-2.10.5.tgz#dddb8f8716ae6364f6f2d31125e76f311dd4a49d" + integrity sha512-eifa0o+i8dERnngJwKrfp3dEq7ia5XFyoqB17S4gK8GhsQE4/P8nxOfQSE0zQHxzzLo/cmF+7+ywEQ7wK7Fb+w== + dependencies: + debug "^4.4.1" + extract-zip "^2.0.1" + progress "^2.0.3" + proxy-agent "^6.5.0" + semver "^7.7.2" + tar-fs "^3.0.8" + yargs "^17.7.2" + +"@tootallnate/quickjs-emscripten@^0.23.0": + version "0.23.0" + resolved "https://registry.yarnpkg.com/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz#db4ecfd499a9765ab24002c3b696d02e6d32a12c" + integrity sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA== + +"@types/node@*": + version "22.15.23" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.15.23.tgz#a0b7c03f951f1ffe381a6a345c68d80e48043dd0" + integrity sha512-7Ec1zaFPF4RJ0eXu1YT/xgiebqwqoJz8rYPDi/O2BcZ++Wpt0Kq9cl0eg6NN6bYbPnR67ZLo7St5Q3UK0SnARw== + dependencies: + undici-types "~6.21.0" + +"@types/yauzl@^2.9.1": + version "2.10.3" + resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.10.3.tgz#e9b2808b4f109504a03cda958259876f61017999" + integrity sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q== + dependencies: + "@types/node" "*" + +agent-base@^7.1.0, agent-base@^7.1.2: + version "7.1.3" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-7.1.3.tgz#29435eb821bc4194633a5b89e5bc4703bafc25a1" + integrity sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw== + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-styles@^4.0.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +ast-types@^0.13.4: + version "0.13.4" + resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.13.4.tgz#ee0d77b343263965ecc3fb62da16e7222b2b6782" + integrity sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w== + dependencies: + tslib "^2.0.1" + +b4a@^1.6.4: + version "1.6.7" + resolved "https://registry.yarnpkg.com/b4a/-/b4a-1.6.7.tgz#a99587d4ebbfbd5a6e3b21bdb5d5fa385767abe4" + integrity sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg== + +bare-events@^2.2.0, bare-events@^2.5.4: + version "2.5.4" + resolved "https://registry.yarnpkg.com/bare-events/-/bare-events-2.5.4.tgz#16143d435e1ed9eafd1ab85f12b89b3357a41745" + integrity sha512-+gFfDkR8pj4/TrWCGUGWmJIkBwuxPS5F+a5yWjOHQt2hHvNZd5YLzadjmDUtFmMM4y429bnKLa8bYBMHcYdnQA== + +bare-fs@^4.0.1: + version "4.1.5" + resolved "https://registry.yarnpkg.com/bare-fs/-/bare-fs-4.1.5.tgz#1d06c076e68cc8bf97010d29af9e3ac3808cdcf7" + integrity sha512-1zccWBMypln0jEE05LzZt+V/8y8AQsQQqxtklqaIyg5nu6OAYFhZxPXinJTSG+kU5qyNmeLgcn9AW7eHiCHVLA== + dependencies: + bare-events "^2.5.4" + bare-path "^3.0.0" + bare-stream "^2.6.4" + +bare-os@^3.0.1: + version "3.6.1" + resolved "https://registry.yarnpkg.com/bare-os/-/bare-os-3.6.1.tgz#9921f6f59edbe81afa9f56910658422c0f4858d4" + integrity sha512-uaIjxokhFidJP+bmmvKSgiMzj2sV5GPHaZVAIktcxcpCyBFFWO+YlikVAdhmUo2vYFvFhOXIAlldqV29L8126g== + +bare-path@^3.0.0: + version "3.0.0" + 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.6.5" + resolved "https://registry.yarnpkg.com/bare-stream/-/bare-stream-2.6.5.tgz#bba8e879674c4c27f7e27805df005c15d7a2ca07" + integrity sha512-jSmxKJNJmHySi6hC42zlZnq00rga4jjxcgNZjY9N5WlOe/iOoGRtdwGsHzQv2RlH2KOYMwGUXhf2zXd32BA9RA== + dependencies: + streamx "^2.21.0" + +basic-ftp@^5.0.2: + version "5.0.5" + resolved "https://registry.yarnpkg.com/basic-ftp/-/basic-ftp-5.0.5.tgz#14a474f5fffecca1f4f406f1c26b18f800225ac0" + integrity sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg== + +buffer-crc32@~0.2.3: + version "0.2.13" + resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" + integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ== + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +chromium-bidi@5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/chromium-bidi/-/chromium-bidi-5.1.0.tgz#8d0e47f7ac9270262df29792318dd5378e983e62" + integrity sha512-9MSRhWRVoRPDG0TgzkHrshFSJJNZzfY5UFqUMuksg7zL1yoZIZ3jLB0YAgHclbiAxPI86pBnwDX1tbzoiV8aFw== + dependencies: + mitt "^3.0.1" + zod "^3.24.1" + +cliui@^8.0.1: + version "8.0.1" + 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" + +color-convert@^2.0.1: + version "2.0.1" + 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.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +cosmiconfig@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-9.0.0.tgz#34c3fc58287b915f3ae905ab6dc3de258b55ad9d" + 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" + +data-uri-to-buffer@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz#8a58bb67384b261a38ef18bea1810cb01badd28b" + integrity sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw== + +debug@4, debug@^4.1.1, debug@^4.3.4, debug@^4.4.1: + version "4.4.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.1.tgz#e5a8bc6cbc4c6cd3e64308b0693a3d4fa550189b" + integrity sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ== + dependencies: + ms "^2.1.3" + +degenerator@^5.0.0: + version "5.0.1" + 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" + escodegen "^2.1.0" + esprima "^4.0.1" + +devtools-protocol@0.0.1439962: + version "0.0.1439962" + resolved "https://registry.yarnpkg.com/devtools-protocol/-/devtools-protocol-0.0.1439962.tgz#395c5ca1cd83aa451c667056a025f9873c4598c1" + integrity sha512-jJF48UdryzKiWhJ1bLKr7BFWUQCEIT5uCNbDLqkQJBtkFxYzILJH44WN0PDKMIlGDN7Utb8vyUY85C3w4R/t2g== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +end-of-stream@^1.1.0: + version "1.4.4" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" + integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== + dependencies: + once "^1.4.0" + +env-paths@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2" + integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A== + +error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + +escalade@^3.1.1: + version "3.2.0" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" + integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== + +escodegen@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-2.1.0.tgz#ba93bbb7a43986d29d6041f99f5262da773e2e17" + integrity sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w== + dependencies: + esprima "^4.0.1" + estraverse "^5.2.0" + esutils "^2.0.2" + optionalDependencies: + source-map "~0.6.1" + +esprima@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +extract-zip@^2.0.1: + version "2.0.1" + 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" + get-stream "^5.1.0" + yauzl "^2.10.0" + optionalDependencies: + "@types/yauzl" "^2.9.1" + +fast-fifo@^1.2.0, fast-fifo@^1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/fast-fifo/-/fast-fifo-1.3.2.tgz#286e31de96eb96d38a97899815740ba2a4f3640c" + integrity sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ== + +fd-slicer@~1.1.0: + version "1.1.0" + 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" + +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-stream@^5.1.0: + version "5.2.0" + 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" + +get-uri@^6.0.1: + version "6.0.4" + resolved "https://registry.yarnpkg.com/get-uri/-/get-uri-6.0.4.tgz#6daaee9e12f9759e19e55ba313956883ef50e0a7" + integrity sha512-E1b1lFFLvLgak2whF2xDBcOy6NLVGZBqqjJjsIhvopKfWWEi64pLVTWWehV8KlLerZkfNTA95sTe2OdJKm1OzQ== + dependencies: + basic-ftp "^5.0.2" + data-uri-to-buffer "^6.0.2" + debug "^4.3.4" + +http-proxy-agent@^7.0.0, http-proxy-agent@^7.0.1: + version "7.0.2" + 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" + debug "^4.3.4" + +https-proxy-agent@^7.0.6: + version "7.0.6" + 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" + debug "4" + +import-fresh@^3.3.0: + version "3.3.1" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.1.tgz#9cecb56503c0ada1f2741dbbd6546e4b13b57ccf" + integrity sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +ip-address@^9.0.5: + version "9.0.5" + resolved "https://registry.yarnpkg.com/ip-address/-/ip-address-9.0.5.tgz#117a960819b08780c3bd1f14ef3c1cc1d3f3ea5a" + integrity sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g== + dependencies: + jsbn "1.1.0" + sprintf-js "^1.1.3" + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +jsbn@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-1.1.0.tgz#b01307cb29b618a1ed26ec79e911f803c4da0040" + integrity sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A== + +json-parse-even-better-errors@^2.3.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + +lines-and-columns@^1.1.6: + version "1.2.4" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + +lru-cache@^7.14.1: + version "7.18.3" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.18.3.tgz#f793896e0fd0e954a59dfdd82f0773808df6aa89" + integrity sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA== + +mitt@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/mitt/-/mitt-3.0.1.tgz#ea36cf0cc30403601ae074c8f77b7092cdab36d1" + integrity sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw== + +ms@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +netmask@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/netmask/-/netmask-2.0.2.tgz#8b01a07644065d536383835823bc52004ebac5e7" + integrity sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg== + +once@^1.3.1, once@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +pac-proxy-agent@^7.1.0: + version "7.2.0" + 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" + agent-base "^7.1.2" + debug "^4.3.4" + get-uri "^6.0.1" + http-proxy-agent "^7.0.0" + https-proxy-agent "^7.0.6" + pac-resolver "^7.0.1" + socks-proxy-agent "^8.0.5" + +pac-resolver@^7.0.1: + version "7.0.1" + 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" + netmask "^2.0.2" + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +parse-json@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + 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" + +pend@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" + integrity sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg== + +picocolors@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" + integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== + +progress@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" + integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== + +proxy-agent@^6.5.0: + version "6.5.0" + 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" + debug "^4.3.4" + http-proxy-agent "^7.0.1" + https-proxy-agent "^7.0.6" + lru-cache "^7.14.1" + pac-proxy-agent "^7.1.0" + proxy-from-env "^1.1.0" + socks-proxy-agent "^8.0.5" + +proxy-from-env@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + +pump@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.2.tgz#836f3edd6bc2ee599256c924ffe0d88573ddcbf8" + integrity sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +puppeteer-core@24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/puppeteer-core/-/puppeteer-core-24.9.0.tgz#fc489e83bf65db1dc72e53a78140ee567efd847e" + integrity sha512-HFdCeH/wx6QPz8EncafbCqJBqaCG1ENW75xg3cLFMRUoqZDgByT6HSueiumetT2uClZxwqj0qS4qMVZwLHRHHw== + dependencies: + "@puppeteer/browsers" "2.10.5" + chromium-bidi "5.1.0" + debug "^4.4.1" + devtools-protocol "0.0.1439962" + typed-query-selector "^2.12.0" + ws "^8.18.2" + +puppeteer@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-24.9.0.tgz#1d3f805e0170ca481b637a47c71a09b815594dae" + integrity sha512-L0pOtALIx8rgDt24Y+COm8X52v78gNtBOW6EmUcEPci0TYD72SAuaXKqasRIx4JXxmg2Tkw5ySKcpPOwN8xXnQ== + dependencies: + "@puppeteer/browsers" "2.10.5" + chromium-bidi "5.1.0" + cosmiconfig "^9.0.0" + devtools-protocol "0.0.1439962" + puppeteer-core "24.9.0" + typed-query-selector "^2.12.0" + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +semver@^7.7.2: + version "7.7.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.2.tgz#67d99fdcd35cec21e6f8b87a7fd515a33f982b58" + integrity sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA== + +smart-buffer@^4.2.0: + version "4.2.0" + 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.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz#b9cdb4e7e998509d7659d689ce7697ac21645bee" + integrity sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw== + dependencies: + agent-base "^7.1.2" + debug "^4.3.4" + socks "^2.8.3" + +socks@^2.8.3: + version "2.8.4" + resolved "https://registry.yarnpkg.com/socks/-/socks-2.8.4.tgz#07109755cdd4da03269bda4725baa061ab56d5cc" + integrity sha512-D3YaD0aRxR3mEcqnidIs7ReYJFVzWdd6fXJYUM8ixcQcJRGTka/b3saV0KflYhyVJXKhb947GndU35SxYNResQ== + dependencies: + ip-address "^9.0.5" + smart-buffer "^4.2.0" + +source-map@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +sprintf-js@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.3.tgz#4914b903a2f8b685d17fdf78a70e917e872e444a" + integrity sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA== + +streamx@^2.15.0, streamx@^2.21.0: + version "2.22.0" + resolved "https://registry.yarnpkg.com/streamx/-/streamx-2.22.0.tgz#cd7b5e57c95aaef0ff9b2aef7905afa62ec6e4a7" + integrity sha512-sLh1evHOzBy/iWRiR6d1zRcLao4gGZr3C1kzNz4fopCOKJb6xD9ub8Mpi9Mr1R6id5o43S+d93fI48UC5uM9aw== + dependencies: + fast-fifo "^1.3.2" + text-decoder "^1.1.0" + optionalDependencies: + bare-events "^2.2.0" + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + 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" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +tar-fs@^3.0.8: + version "3.0.9" + resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-3.0.9.tgz#d570793c6370d7078926c41fa422891566a0b617" + integrity sha512-XF4w9Xp+ZQgifKakjZYmFdkLoSWd34VGKcsTCwlNWM7QG3ZbaxnTsaBwnjFZqHRf/rROxaR8rXnbtwdvaDI+lA== + dependencies: + pump "^3.0.0" + tar-stream "^3.1.5" + optionalDependencies: + bare-fs "^4.0.1" + bare-path "^3.0.0" + +tar-stream@^3.1.5: + version "3.1.7" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-3.1.7.tgz#24b3fb5eabada19fe7338ed6d26e5f7c482e792b" + integrity sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ== + dependencies: + b4a "^1.6.4" + fast-fifo "^1.2.0" + streamx "^2.15.0" + +text-decoder@^1.1.0: + version "1.2.3" + resolved "https://registry.yarnpkg.com/text-decoder/-/text-decoder-1.2.3.tgz#b19da364d981b2326d5f43099c310cc80d770c65" + integrity sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA== + dependencies: + b4a "^1.6.4" + +tslib@^2.0.1: + version "2.8.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" + integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== + +typed-query-selector@^2.12.0: + version "2.12.0" + resolved "https://registry.yarnpkg.com/typed-query-selector/-/typed-query-selector-2.12.0.tgz#92b65dbc0a42655fccf4aeb1a08b1dddce8af5f2" + integrity sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg== + +undici-types@~6.21.0: + version "6.21.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.21.0.tgz#691d00af3909be93a7faa13be61b3a5b50ef12cb" + integrity sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ== + +wrap-ansi@^7.0.0: + version "7.0.0" + 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" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +ws@^8.18.2: + version "8.18.2" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.2.tgz#42738b2be57ced85f46154320aabb51ab003705a" + integrity sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ== + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yargs-parser@^21.1.1: + version "21.1.1" + 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.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" + integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== + dependencies: + cliui "^8.0.1" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.1.1" + +yauzl@^2.10.0: + version "2.10.0" + 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" + fd-slicer "~1.1.0" + +zod@^3.24.1: + version "3.25.32" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.25.32.tgz#769cc684072df780fc8f38130b0cd9283a8d3818" + integrity sha512-OSm2xTIRfW8CV5/QKgngwmQW/8aPfGdaQFlrGoErlgg/Epm7cjb6K6VEyExfe65a3VybUOnu381edLb0dfJl0g==