diff --git a/.gitignore b/.gitignore index 65c953ed..8c9f29f6 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,6 @@ node_modules browserstack.json specs tests.zip -package-lock.json .nyc_output/ .env.* log/*.log diff --git a/.npmrc b/.npmrc new file mode 100644 index 00000000..cafe685a --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +package-lock=true diff --git a/CODEOWNERS b/CODEOWNERS index b7b80e83..484b3336 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1 +1,18 @@ +# Default ownership for all other files * @browserstack/automate-dev + +# Ownership for the CODEOWNERS file +CODEOWNERS @browserstack/sdk-dev @browserstack/automate-dev + +# Ownership for SDK-related files +bin/testhub/ @browserstack/sdk-dev +bin/accessibility-automation/ @browserstack/sdk-dev +bin/testObservability/ @browserstack/sdk-dev + +# Shared ownership for certain files +/.gitignore @browserstack/sdk-dev @browserstack/automate-dev +/package.json @browserstack/sdk-dev @browserstack/automate-dev +bin/commands/runs.js @browserstack/sdk-dev @browserstack/automate-dev +bin/helpers/helper.js @browserstack/sdk-dev @browserstack/automate-dev +bin/helpers/logger.js @browserstack/sdk-dev @browserstack/automate-dev +bin/helpers/utils.js @browserstack/sdk-dev @browserstack/automate-dev diff --git a/bin/accessibility-automation/cypress/index.js b/bin/accessibility-automation/cypress/index.js index 6bc8aa3b..78e9c388 100644 --- a/bin/accessibility-automation/cypress/index.js +++ b/bin/accessibility-automation/cypress/index.js @@ -1,383 +1,430 @@ /* Event listeners + custom commands for Cypress */ const browserStackLog = (message) => { - if (!Cypress.env('BROWSERSTACK_LOGS')) return; - cy.task('browserstack_log', message); + if (!Cypress.env('BROWSERSTACK_LOGS')) return; + cy.task('browserstack_log', message); } - + const commandsToWrap = ['visit', 'click', 'type', 'request', 'dblclick', 'rightclick', 'clear', 'check', 'uncheck', 'select', 'trigger', 'selectFile', 'scrollIntoView', 'scroll', 'scrollTo', 'blur', 'focus', 'go', 'reload', 'submit', 'viewport', 'origin']; +// scroll is not a default function in cypress. +const commandToOverwrite = ['visit', 'click', 'type', 'request', 'dblclick', 'rightclick', 'clear', 'check', 'uncheck', 'select', 'trigger', 'selectFile', 'scrollIntoView', 'scrollTo', 'blur', 'focus', 'go', 'reload', 'submit', 'viewport', 'origin']; + +/* + Overrriding the cypress commands to perform Accessibility Scan before Each command + - runCutomizedCommand is handling both the cases of subject available in cypress original command + and chaning available from original cypress command. +*/ +const performModifiedScan = (originalFn, Subject, stateType, ...args) => { + let customChaining = cy.wrap(null).performScan(); + const changeSub = (args, stateType, newSubject) => { + if (stateType !== 'parent') { + return [newSubject, ...args.slice(1)]; + } + return args; + } + const runCustomizedCommand = () => { + if (!Subject) { + let orgS1, orgS2, cypressCommandSubject = null; + if((orgS2 = (orgS1 = cy).subject) !==null && orgS2 !== void 0){ + cypressCommandSubject = orgS2.call(orgS1); + } + customChaining.then(()=> cypressCommandSubject).then(() => {originalFn(...args)}); + } else { + let orgSC1, orgSC2, timeO1, cypressCommandChain = null, setTimeout = null; + if((timeO1 = args.find(arg => arg !== null && arg !== void 0 ? arg.timeout : null)) !== null && timeO1 !== void 0) { + setTimeout = timeO1.timeout; + } + if((orgSC1 = (orgSC2 = cy).subjectChain) !== null && orgSC1 !== void 0){ + cypressCommandChain = orgSC1.call(orgSC2); + } + customChaining.performScanSubjectQuery(cypressCommandChain, setTimeout).then({timeout: 30000}, (newSubject) => originalFn(...changeSub(args, stateType, newSubject))); + } + } + runCustomizedCommand(); +} const performScan = (win, payloadToSend) => - new Promise(async (resolve, reject) => { +new Promise(async (resolve, reject) => { const isHttpOrHttps = /^(http|https):$/.test(win.location.protocol); if (!isHttpOrHttps) { - resolve(); + return resolve(); } function findAccessibilityAutomationElement() { - return win.document.querySelector("#accessibility-automation-element"); + return win.document.querySelector("#accessibility-automation-element"); } - function waitForScannerReadiness(retryCount = 30, retryInterval = 100) { - return new Promise(async (resolve, reject) => { + function waitForScannerReadiness(retryCount = 100, retryInterval = 100) { + return new Promise(async (resolve, reject) => { let count = 0; const intervalID = setInterval(async () => { - if (count > retryCount) { - clearInterval(intervalID); - reject( - new Error( - "Accessibility Automation Scanner is not ready on the page." - ) - ); - } else if (findAccessibilityAutomationElement()) { - clearInterval(intervalID); - resolve("Scanner set"); - } else { - count += 1; - } + if (count > retryCount) { + clearInterval(intervalID); + return reject( + new Error( + "Accessibility Automation Scanner is not ready on the page." + ) + ); + } else if (findAccessibilityAutomationElement()) { + clearInterval(intervalID); + return resolve("Scanner set"); + } else { + count += 1; + } }, retryInterval); - }); + }); } function startScan() { - function onScanComplete() { - win.removeEventListener("A11Y_SCAN_FINISHED", onScanComplete); - resolve(); - } - - win.addEventListener("A11Y_SCAN_FINISHED", onScanComplete); - const e = new CustomEvent("A11Y_SCAN", { detail: payloadToSend }); - win.dispatchEvent(e); + function onScanComplete() { + win.removeEventListener("A11Y_SCAN_FINISHED", onScanComplete); + return resolve(); + } + + win.addEventListener("A11Y_SCAN_FINISHED", onScanComplete); + const e = new CustomEvent("A11Y_SCAN", { detail: payloadToSend }); + win.dispatchEvent(e); } if (findAccessibilityAutomationElement()) { - startScan(); + startScan(); } else { - waitForScannerReadiness() - .then(startScan) - .catch(async (err) => { - resolve("Scanner is not ready on the page after multiple retries. performscan"); + waitForScannerReadiness() + .then(startScan) + .catch(async (err) => { + return resolve("Scanner is not ready on the page after multiple retries. performscan"); }); } - }) +}) const getAccessibilityResultsSummary = (win) => - new Promise((resolve) => { +new Promise((resolve) => { const isHttpOrHttps = /^(http|https):$/.test(window.location.protocol); if (!isHttpOrHttps) { - resolve(); + return resolve(); } function findAccessibilityAutomationElement() { - return win.document.querySelector("#accessibility-automation-element"); + return win.document.querySelector("#accessibility-automation-element"); } function waitForScannerReadiness(retryCount = 30, retryInterval = 100) { - return new Promise((resolve, reject) => { - let count = 0; - const intervalID = setInterval(() => { - if (count > retryCount) { - clearInterval(intervalID); - reject( - new Error( - "Accessibility Automation Scanner is not ready on the page." - ) - ); - } else if (findAccessibilityAutomationElement()) { - clearInterval(intervalID); - resolve("Scanner set"); - } else { - count += 1; - } - }, retryInterval); - }); + return new Promise((resolve, reject) => { + let count = 0; + const intervalID = setInterval(() => { + if (count > retryCount) { + clearInterval(intervalID); + return reject( + new Error( + "Accessibility Automation Scanner is not ready on the page." + ) + ); + } else if (findAccessibilityAutomationElement()) { + clearInterval(intervalID); + return resolve("Scanner set"); + } else { + count += 1; + } + }, retryInterval); + }); } function getSummary() { - function onReceiveSummary(event) { - - win.removeEventListener("A11Y_RESULTS_SUMMARY", onReceiveSummary); - resolve(event.detail); - } + function onReceiveSummary(event) { + win.removeEventListener("A11Y_RESULTS_SUMMARY", onReceiveSummary); + return resolve(event.detail); + } - win.addEventListener("A11Y_RESULTS_SUMMARY", onReceiveSummary); - const e = new CustomEvent("A11Y_GET_RESULTS_SUMMARY"); - win.dispatchEvent(e); + win.addEventListener("A11Y_RESULTS_SUMMARY", onReceiveSummary); + const e = new CustomEvent("A11Y_GET_RESULTS_SUMMARY"); + win.dispatchEvent(e); } if (findAccessibilityAutomationElement()) { - getSummary(); + getSummary(); } else { - waitForScannerReadiness() - .then(getSummary) - .catch((err) => { - resolve(); + waitForScannerReadiness() + .then(getSummary) + .catch((err) => { + return resolve(); }); } - }) +}) const getAccessibilityResults = (win) => - new Promise((resolve) => { +new Promise((resolve) => { const isHttpOrHttps = /^(http|https):$/.test(window.location.protocol); if (!isHttpOrHttps) { - resolve(); + return resolve(); } function findAccessibilityAutomationElement() { - return win.document.querySelector("#accessibility-automation-element"); + return win.document.querySelector("#accessibility-automation-element"); } function waitForScannerReadiness(retryCount = 30, retryInterval = 100) { - return new Promise((resolve, reject) => { - let count = 0; - const intervalID = setInterval(() => { - if (count > retryCount) { - clearInterval(intervalID); - reject( - new Error( - "Accessibility Automation Scanner is not ready on the page." - ) - ); - } else if (findAccessibilityAutomationElement()) { - clearInterval(intervalID); - resolve("Scanner set"); - } else { - count += 1; - } - }, retryInterval); - }); + return new Promise((resolve, reject) => { + let count = 0; + const intervalID = setInterval(() => { + if (count > retryCount) { + clearInterval(intervalID); + return reject( + new Error( + "Accessibility Automation Scanner is not ready on the page." + ) + ); + } else if (findAccessibilityAutomationElement()) { + clearInterval(intervalID); + return resolve("Scanner set"); + } else { + count += 1; + } + }, retryInterval); + }); } function getResults() { - function onReceivedResult(event) { - win.removeEventListener("A11Y_RESULTS_RESPONSE", onReceivedResult); - resolve(event.detail); - } - - win.addEventListener("A11Y_RESULTS_RESPONSE", onReceivedResult); - const e = new CustomEvent("A11Y_GET_RESULTS"); - win.dispatchEvent(e); + function onReceivedResult(event) { + win.removeEventListener("A11Y_RESULTS_RESPONSE", onReceivedResult); + return resolve(event.detail); + } + + win.addEventListener("A11Y_RESULTS_RESPONSE", onReceivedResult); + const e = new CustomEvent("A11Y_GET_RESULTS"); + win.dispatchEvent(e); } if (findAccessibilityAutomationElement()) { - getResults(); + getResults(); } else { - waitForScannerReadiness() - .then(getResults) - .catch((err) => { - resolve(); + waitForScannerReadiness() + .then(getResults) + .catch((err) => { + return resolve(); }); } - }); +}); const saveTestResults = (win, payloadToSend) => - new Promise( (resolve, reject) => { +new Promise( (resolve, reject) => { try { - const isHttpOrHttps = /^(http|https):$/.test(win.location.protocol); - if (!isHttpOrHttps) { - resolve("Unable to save accessibility results, Invalid URL."); - } + const isHttpOrHttps = /^(http|https):$/.test(win.location.protocol); + if (!isHttpOrHttps) { + resolve("Unable to save accessibility results, Invalid URL."); + return; + } - function findAccessibilityAutomationElement() { - return win.document.querySelector("#accessibility-automation-element"); - } + function findAccessibilityAutomationElement() { + return win.document.querySelector("#accessibility-automation-element"); + } - function waitForScannerReadiness(retryCount = 30, retryInterval = 100) { - return new Promise((resolve, reject) => { - let count = 0; - const intervalID = setInterval(async () => { - if (count > retryCount) { - clearInterval(intervalID); - reject( - new Error( - "Accessibility Automation Scanner is not ready on the page." - ) - ); - } else if (findAccessibilityAutomationElement()) { - clearInterval(intervalID); - resolve("Scanner set"); - } else { - count += 1; - } - }, retryInterval); - }); - } + function waitForScannerReadiness(retryCount = 30, retryInterval = 100) { + return new Promise((resolve, reject) => { + let count = 0; + const intervalID = setInterval(async () => { + if (count > retryCount) { + clearInterval(intervalID); + return reject( + new Error( + "Accessibility Automation Scanner is not ready on the page." + ) + ); + } else if (findAccessibilityAutomationElement()) { + clearInterval(intervalID); + return resolve("Scanner set"); + } else { + count += 1; + } + }, retryInterval); + }); + } - function saveResults() { - function onResultsSaved(event) { - resolve(); + function saveResults() { + function onResultsSaved(event) { + return resolve(); + } + win.addEventListener("A11Y_RESULTS_SAVED", onResultsSaved); + const e = new CustomEvent("A11Y_SAVE_RESULTS", { + detail: payloadToSend, + }); + win.dispatchEvent(e); } - win.addEventListener("A11Y_RESULTS_SAVED", onResultsSaved); - const e = new CustomEvent("A11Y_SAVE_RESULTS", { - detail: payloadToSend, - }); - win.dispatchEvent(e); - } - if (findAccessibilityAutomationElement()) { - saveResults(); - } else { - waitForScannerReadiness() - .then(saveResults) - .catch(async (err) => { - resolve("Scanner is not ready on the page after multiple retries. after run"); - }); - } - - } catch(er) { - resolve() + if (findAccessibilityAutomationElement()) { + saveResults(); + } else { + waitForScannerReadiness() + .then(saveResults) + .catch(async (err) => { + return resolve("Scanner is not ready on the page after multiple retries. after run"); + }); + } + } catch(error) { + browserStackLog(`Error in saving results with error: ${error.message}`); + return resolve(); } - }) +}) const shouldScanForAccessibility = (attributes) => { - if (Cypress.env("IS_ACCESSIBILITY_EXTENSION_LOADED") !== "true") return false; + if (Cypress.env("IS_ACCESSIBILITY_EXTENSION_LOADED") !== "true") return false; - const extensionPath = Cypress.env("ACCESSIBILITY_EXTENSION_PATH"); - const isHeaded = Cypress.browser.isHeaded; + const extensionPath = Cypress.env("ACCESSIBILITY_EXTENSION_PATH"); + const isHeaded = Cypress.browser.isHeaded; - if (!isHeaded || (extensionPath === undefined)) return false; + if (!isHeaded || (extensionPath === undefined)) return false; - let shouldScanTestForAccessibility = true; + let shouldScanTestForAccessibility = true; - if (Cypress.env("INCLUDE_TAGS_FOR_ACCESSIBILITY") || Cypress.env("EXCLUDE_TAGS_FOR_ACCESSIBILITY")) { - try { - let includeTagArray = []; - let excludeTagArray = []; - if (Cypress.env("INCLUDE_TAGS_FOR_ACCESSIBILITY")) { - includeTagArray = Cypress.env("INCLUDE_TAGS_FOR_ACCESSIBILITY").split(";") - } - if (Cypress.env("EXCLUDE_TAGS_FOR_ACCESSIBILITY")) { - excludeTagArray = Cypress.env("EXCLUDE_TAGS_FOR_ACCESSIBILITY").split(";") - } - - const fullTestName = attributes.title; - const excluded = excludeTagArray.some((exclude) => fullTestName.includes(exclude)); - const included = includeTagArray.length === 0 || includeTags.some((include) => fullTestName.includes(include)); - shouldScanTestForAccessibility = !excluded && included; - } catch (error) { - browserStackLog("Error while validating test case for accessibility before scanning. Error : ", error); + if (Cypress.env("INCLUDE_TAGS_FOR_ACCESSIBILITY") || Cypress.env("EXCLUDE_TAGS_FOR_ACCESSIBILITY")) { + try { + let includeTagArray = []; + let excludeTagArray = []; + if (Cypress.env("INCLUDE_TAGS_FOR_ACCESSIBILITY")) { + includeTagArray = Cypress.env("INCLUDE_TAGS_FOR_ACCESSIBILITY").split(";") + } + if (Cypress.env("EXCLUDE_TAGS_FOR_ACCESSIBILITY")) { + excludeTagArray = Cypress.env("EXCLUDE_TAGS_FOR_ACCESSIBILITY").split(";") + } + + const fullTestName = attributes.title; + const excluded = excludeTagArray.some((exclude) => fullTestName.includes(exclude)); + const included = includeTagArray.length === 0 || includeTags.some((include) => fullTestName.includes(include)); + shouldScanTestForAccessibility = !excluded && included; + } catch (error) { + browserStackLog(`Error while validating test case for accessibility before scanning. Error : ${error.message}`); + } } - } - return shouldScanTestForAccessibility; + return shouldScanTestForAccessibility; } -Cypress.on('command:start', async (command) => { - if(!command || !command.attributes) return; - if(command.attributes.name == 'window' || command.attributes.name == 'then' || command.attributes.name == 'wrap') { - return; - } - - if (!commandsToWrap.includes(command.attributes.name)) return; - - const attributes = Cypress.mocha.getRunner().suite.ctx.currentTest || Cypress.mocha.getRunner().suite.ctx._runnable; - - let shouldScanTestForAccessibility = shouldScanForAccessibility(attributes); - if (!shouldScanTestForAccessibility) return; - - cy.window().then((win) => { - browserStackLog('Performing scan form command ' + command.attributes.name); - cy.wrap(performScan(win, {method: command.attributes.name}), {timeout: 30000}); - }) -}) +commandToOverwrite.forEach((command) => { + Cypress.Commands.overwrite(command, (originalFn, ...args) => { + const attributes = Cypress.mocha.getRunner().suite.ctx.currentTest || Cypress.mocha.getRunner().suite.ctx._runnable; + const shouldScanTestForAccessibility = shouldScanForAccessibility(attributes); + const state = cy.state('current'), Subject = 'getSubjectFromChain' in cy; + const stateName = state === null || state === void 0 ? void 0 : state.get('name'); + let stateType = null; + if (!shouldScanTestForAccessibility || (stateName && stateName !== command)) { + return originalFn(...args); + } + if(state !== null && state !== void 0){ + stateType = state.get('type'); + } + performModifiedScan(originalFn, Subject, stateType, ...args); + }); +}); afterEach(() => { - const attributes = Cypress.mocha.getRunner().suite.ctx.currentTest; - cy.window().then(async (win) => { - let shouldScanTestForAccessibility = shouldScanForAccessibility(attributes); - if (!shouldScanTestForAccessibility) return cy.wrap({}); - - cy.wrap(performScan(win), {timeout: 30000}).then(() => { - try { - let os_data; - if (Cypress.env("OS")) { - os_data = Cypress.env("OS"); - } else { - os_data = Cypress.platform === 'linux' ? 'mac' : "win" - } - let filePath = ''; - if (attributes.invocationDetails !== undefined && attributes.invocationDetails.relativeFile !== undefined) { - filePath = attributes.invocationDetails.relativeFile; + const attributes = Cypress.mocha.getRunner().suite.ctx.currentTest; + cy.window().then(async (win) => { + let shouldScanTestForAccessibility = shouldScanForAccessibility(attributes); + if (!shouldScanTestForAccessibility) return cy.wrap({}); + + cy.wrap(performScan(win), {timeout: 30000}).then(() => { + try { + let os_data; + if (Cypress.env("OS")) { + os_data = Cypress.env("OS"); + } else { + os_data = Cypress.platform === 'linux' ? 'mac' : "win" + } + let filePath = ''; + if (attributes.invocationDetails !== undefined && attributes.invocationDetails.relativeFile !== undefined) { + filePath = attributes.invocationDetails.relativeFile; + } else if (attributes.prevAttempts && attributes.prevAttempts.length > 0) { + filePath = (attributes.prevAttempts[0].invocationDetails && attributes.prevAttempts[0].invocationDetails.relativeFile) || ''; + } + + let testRunUuid = null; + cy.task('get_test_run_uuid', { testIdentifier: attributes.title }) + .then((response) => { + if (response && response.testRunUuid) { + testRunUuid = response.testRunUuid; + } + + const payloadToSend = { + "thTestRunUuid": testRunUuid, + "thBuildUuid": Cypress.env("BROWSERSTACK_TESTHUB_UUID"), + "thJwtToken": Cypress.env("BROWSERSTACK_TESTHUB_JWT") + }; + browserStackLog(`Payload to send: ${JSON.stringify(payloadToSend)}`); + + return cy.wrap(saveTestResults(win, payloadToSend), {timeout: 30000}); + }).then(() => { + browserStackLog(`Saved accessibility test results`); + }) + + } catch (er) { + browserStackLog(`Error in saving results with error: ${er.message}`); } - const payloadToSend = { - "saveResults": shouldScanTestForAccessibility, - "testDetails": { - "name": attributes.title, - "testRunId": '5058', // variable not consumed, shouldn't matter what we send - "filePath": filePath, - "scopeList": [ - filePath, - attributes.title - ] - }, - "platform": { - "os_name": os_data, - "os_version": Cypress.env("OS_VERSION"), - "browser_name": Cypress.browser.name, - "browser_version": Cypress.browser.version - } - }; - browserStackLog(`Saving accessibility test results`); - cy.wrap(saveTestResults(win, payloadToSend), {timeout: 30000}).then(() => { - browserStackLog(`Saved accessibility test results`); }) - - } catch (er) { - } - }) - }); + }); }) Cypress.Commands.add('performScan', () => { - try { - const attributes = Cypress.mocha.getRunner().suite.ctx.currentTest || Cypress.mocha.getRunner().suite.ctx._runnable; - const shouldScanTestForAccessibility = shouldScanForAccessibility(attributes); - if (!shouldScanTestForAccessibility) { - browserStackLog(`Not a Accessibility Automation session, cannot perform scan.`); - return cy.wrap({}); - } - cy.window().then(async (win) => { - browserStackLog(`Performing accessibility scan`); - await performScan(win); - }); - } catch {} + try { + const attributes = Cypress.mocha.getRunner().suite.ctx.currentTest || Cypress.mocha.getRunner().suite.ctx._runnable; + const shouldScanTestForAccessibility = shouldScanForAccessibility(attributes); + if (!shouldScanTestForAccessibility) { + browserStackLog(`Not a Accessibility Automation session, cannot perform scan.`); + return cy.wrap({}); + } + cy.window().then(async (win) => { + browserStackLog(`Performing accessibility scan`); + cy.wrap(performScan(win), {timeout:30000}); + }); + } catch(error) { + browserStackLog(`Error in performing scan with error: ${error.message}`); + } }) Cypress.Commands.add('getAccessibilityResultsSummary', () => { - try { - const attributes = Cypress.mocha.getRunner().suite.ctx.currentTest || Cypress.mocha.getRunner().suite.ctx._runnable; - const shouldScanTestForAccessibility = shouldScanForAccessibility(attributes); - if (!shouldScanTestForAccessibility) { - browserStackLog(`Not a Accessibility Automation session, cannot retrieve Accessibility results summary.`); - return cy.wrap({}); - } - cy.window().then(async (win) => { - await performScan(win); - browserStackLog('Getting accessibility results summary'); - return await getAccessibilityResultsSummary(win); - }); - } catch {} - + try { + const attributes = Cypress.mocha.getRunner().suite.ctx.currentTest || Cypress.mocha.getRunner().suite.ctx._runnable; + const shouldScanTestForAccessibility = shouldScanForAccessibility(attributes); + if (!shouldScanTestForAccessibility) { + browserStackLog(`Not a Accessibility Automation session, cannot retrieve Accessibility results summary.`); + return cy.wrap({}); + } + cy.window().then(async (win) => { + await performScan(win); + browserStackLog('Getting accessibility results summary'); + return await getAccessibilityResultsSummary(win); + }); + } catch(error) { + browserStackLog(`Error in getting accessibilty results summary with error: ${error.message}`); + } + }); Cypress.Commands.add('getAccessibilityResults', () => { - try { - const attributes = Cypress.mocha.getRunner().suite.ctx.currentTest || Cypress.mocha.getRunner().suite.ctx._runnable; - const shouldScanTestForAccessibility = shouldScanForAccessibility(attributes); - if (!shouldScanTestForAccessibility) { - browserStackLog(`Not a Accessibility Automation session, cannot retrieve Accessibility results.`); - return cy.wrap({}); - } + try { + const attributes = Cypress.mocha.getRunner().suite.ctx.currentTest || Cypress.mocha.getRunner().suite.ctx._runnable; + const shouldScanTestForAccessibility = shouldScanForAccessibility(attributes); + if (!shouldScanTestForAccessibility) { + browserStackLog(`Not a Accessibility Automation session, cannot retrieve Accessibility results.`); + return cy.wrap({}); + } -/* browserstack_accessibility_automation_script */ + /* browserstack_accessibility_automation_script */ - cy.window().then(async (win) => { - await performScan(win); - browserStackLog('Getting accessibility results'); - return await getAccessibilityResults(win); - }); + cy.window().then(async (win) => { + await performScan(win); + browserStackLog('Getting accessibility results'); + return await getAccessibilityResults(win); + }); - } catch {} - + } catch(error) { + browserStackLog(`Error in getting accessibilty results with error: ${error.message}`); + } }); + +if (!Cypress.Commands.hasOwnProperty('_browserstackSDKQueryAdded')) { + Cypress.Commands.addQuery('performScanSubjectQuery', function (chaining, setTimeout) { + this.set('timeout', setTimeout); + return () => cy.getSubjectFromChain(chaining); + }); + Cypress.Commands._browserstackSDKQueryAdded = true; +} diff --git a/bin/accessibility-automation/helper.js b/bin/accessibility-automation/helper.js index 8a08b674..e0ec145f 100644 --- a/bin/accessibility-automation/helper.js +++ b/bin/accessibility-automation/helper.js @@ -3,12 +3,14 @@ const { API_URL } = require('./constants'); const utils = require('../helpers/utils'); const fs = require('fs'); const path = require('path'); -const request = require('request'); +const axios = require('axios'); const os = require('os'); const glob = require('glob'); const helper = require('../helpers/helper'); const { CYPRESS_V10_AND_ABOVE_CONFIG_FILE_EXTENSIONS } = require('../helpers/constants'); +const { consoleHolder } = require("../testObservability/helper/constants"); const supportFileContentMap = {} +const { HttpsProxyAgent = require('https-proxy-agent') } = require('https-proxy-agent'); exports.checkAccessibilityPlatform = (user_config) => { let accessibility = false; @@ -19,7 +21,7 @@ exports.checkAccessibilityPlatform = (user_config) => { } }) } catch {} - + return accessibility; } @@ -86,8 +88,8 @@ exports.createAccessibilityTestRun = async (user_config, framework) => { const config = { auth: { - user: userName, - pass: accessKey + username: userName, + password: accessKey }, headers: { 'Content-Type': 'application/json' @@ -105,71 +107,81 @@ exports.createAccessibilityTestRun = async (user_config, framework) => { process.env.BROWSERSTACK_TEST_ACCESSIBILITY = 'true'; } logger.debug(`BrowserStack Accessibility Automation Test Run ID: ${response.data.data.id}`); - + this.setAccessibilityCypressCapabilities(user_config, response.data); - helper.setBrowserstackCypressCliDependency(user_config); + if(user_config.run_settings.auto_import_dev_dependencies != true) helper.setBrowserstackCypressCliDependency(user_config); } catch (error) { if (error.response) { + logger.error("Incorrect Cred"); logger.error( `Exception while creating test run for BrowserStack Accessibility Automation: ${ error.response.status - } ${error.response.statusText} ${JSON.stringify(error.response.data)}` + } ${error.response.statusText} ${JSON.stringify(error.response.data)} + ` ); - } else { - if(error.message === 'Invalid configuration passed.') { - logger.error( - `Exception while creating test run for BrowserStack Accessibility Automation: ${ - error.message || error.stack - }` - ); - for(const errorkey of error.errors){ - logger.error(errorkey.message); - } - - } else { - logger.error( - `Exception while creating test run for BrowserStack Accessibility Automation: ${ - error.message || error.stack - }` - ); + } else if (error.message === 'Invalid configuration passed.') { + logger.error("Invalid configuration passed."); + logger.error( + `Exception while creating test run for BrowserStack Accessibility Automation: ${ + error.message || error.stack + }` + ); + for (const errorkey of error.errors) { + logger.error(errorkey.message); } - // since create accessibility session failed - process.env.BROWSERSTACK_TEST_ACCESSIBILITY = 'false'; - user_config.run_settings.accessibility = false; + } else { + logger.error( + `Exception while creating test run for BrowserStack Accessibility Automation: ${ + error.message || error.stack + }` + ); } + // since create accessibility session failed + process.env.BROWSERSTACK_TEST_ACCESSIBILITY = 'false'; + user_config.run_settings.accessibility = false; } } const nodeRequest = (type, url, data, config) => { return new Promise(async (resolve, reject) => { - const options = {...config,...{ + const options = { + ...config, method: type, url: `${API_URL}/${url}`, - body: data, - json: config.headers['Content-Type'] === 'application/json', - }}; + data: data + }; - request(options, function callback(error, response, body) { - if(error) { - logger.info("error in nodeRequest", error); - reject(error); - } else if(!(response.statusCode == 201 || response.statusCode == 200)) { - logger.info("response.statusCode in nodeRequest", response.statusCode); - reject(response && response.body ? response.body : `Received response from BrowserStack Server with status : ${response.statusCode}`); + if(process.env.HTTP_PROXY){ + options.proxy = false + options.httpsAgent = new HttpsProxyAgent(process.env.HTTP_PROXY); + + } else if (process.env.HTTPS_PROXY){ + options.proxy = false + options.httpsAgent = new HttpsProxyAgent(process.env.HTTPS_PROXY); + } + + axios(options).then(response => { + if(!(response.status == 201 || response.status == 200)) { + logger.info("response.status in nodeRequest", response.status); + reject(response && response.data ? response.data : `Received response from BrowserStack Server with status : ${response.status}`); } else { - try { - if(typeof(body) !== 'object') body = JSON.parse(body); - } catch(e) { - if(!url.includes('/stop')) { - reject('Not a JSON response from BrowserStack Server'); + try { + if(typeof(response.data) !== 'object') body = JSON.parse(response.data); + } catch(e) { + if(!url.includes('/stop')) { + reject('Not a JSON response from BrowserStack Server'); + } } - } - resolve({ - data: body - }); + resolve({ + data: response.data + }); } - }); + }).catch(error => { + + logger.info("error in nodeRequest", error); + reject(error); + }) }); } @@ -202,22 +214,52 @@ const getAccessibilityCypressCommandEventListener = (extName) => { exports.setAccessibilityEventListeners = (bsConfig) => { try { - // Searching form command.js recursively + const supportFilesData = helper.getSupportFiles(bsConfig, true); if(!supportFilesData.supportFile) return; - glob(process.cwd() + supportFilesData.supportFile, {}, (err, files) => { - if(err) return logger.debug('EXCEPTION IN BUILD START EVENT : Unable to parse cypress support files'); + + const isPattern = glob.hasMagic(supportFilesData.supportFile); + if(!isPattern) { + logger.debug(`Using user defined support file: ${supportFilesData.supportFile}`); + let file; + try { + file = process.cwd() + supportFilesData.supportFile; + const defaultFileContent = fs.readFileSync(file, {encoding: 'utf-8'}); + let cypressCommandEventListener = getAccessibilityCypressCommandEventListener(path.extname(file)); + const alreadyIncludes = defaultFileContent.includes(cypressCommandEventListener); + if(!alreadyIncludes) { + let newFileContent = defaultFileContent + + '\n' + + cypressCommandEventListener + + '\n'; + fs.writeFileSync(file, newFileContent, {encoding: 'utf-8'}); + supportFileContentMap[file] = supportFilesData.cleanupParams ? supportFilesData.cleanupParams : defaultFileContent; + } + } catch(e) { + logger.debug(`Unable to modify file contents for ${file} to set event listeners with error ${e}`, true, e); + } + return; + } + + const globPattern = process.cwd() + supportFilesData.supportFile; + glob(globPattern, {}, (err, files) => { + if(err) { + logger.debug('EXCEPTION IN BUILD START EVENT : Unable to parse cypress support files'); + return; + } + files.forEach(file => { try { - if(!file.includes('commands.js') && !file.includes('commands.ts')) { + const fileName = path.basename(file); + if(['e2e.js', 'e2e.ts', 'component.ts', 'component.js'].includes(fileName) && !file.includes('node_modules')) { + const defaultFileContent = fs.readFileSync(file, {encoding: 'utf-8'}); - let cypressCommandEventListener = getAccessibilityCypressCommandEventListener(path.extname(file)); if(!defaultFileContent.includes(cypressCommandEventListener)) { - let newFileContent = defaultFileContent + + let newFileContent = defaultFileContent + '\n' + cypressCommandEventListener + - '\n' + '\n'; fs.writeFileSync(file, newFileContent, {encoding: 'utf-8'}); supportFileContentMap[file] = supportFilesData.cleanupParams ? supportFilesData.cleanupParams : defaultFileContent; } diff --git a/bin/accessibility-automation/plugin/index.js b/bin/accessibility-automation/plugin/index.js index dd47f208..5ea42676 100644 --- a/bin/accessibility-automation/plugin/index.js +++ b/bin/accessibility-automation/plugin/index.js @@ -1,5 +1,7 @@ - const path = require("node:path"); +const { decodeJWTToken } = require("../../helpers/utils"); +const utils = require('../../helpers/utils'); +const http = require('http'); const browserstackAccessibility = (on, config) => { let browser_validation = true; @@ -10,9 +12,54 @@ const browserstackAccessibility = (on, config) => { on('task', { browserstack_log(message) { console.log(message) - + return null }, + get_test_run_uuid({ testIdentifier, retries = 15, interval = 300 } = {}) { + return new Promise((resolve) => { + if(!testIdentifier) return resolve(null); + const port = process.env.REPORTER_API_PORT_NO; + let attempt = 0; + const fetchUuid = () => { + const options = { + hostname: '127.0.0.1', + port, + path: `/test-uuid?testIdentifier=${encodeURIComponent(testIdentifier)}`, + method: 'GET', + timeout: 2000 + }; + const httpModule = http; + const req = httpModule.request(options, (res) => { + let data = ''; + res.on('data', (chunk) => data += chunk); + res.on('end', () => { + if(res.statusCode === 200) { + try { + const json = JSON.parse(data || '{}'); + return resolve({ testRunUuid: json.testRunUuid || null }); + } catch(e) { + return resolve(null); + } + } else if (res.statusCode === 404) { + // Server up but endpoint not responding as expected – stop retrying. + return resolve(null); + } else { + retryOrResolve(); + } + }); + }); + req.on('error', () => retryOrResolve()); + req.on('timeout', () => { req.destroy(); retryOrResolve(); }); + req.end(); + }; + const retryOrResolve = () => { + attempt += 1; + if(attempt >= retries) return resolve(null); + setTimeout(fetchUuid, interval); + }; + fetchUuid(); + }); + } }) on('before:browser:launch', (browser = {}, launchOptions) => { try { @@ -31,16 +78,29 @@ const browserstackAccessibility = (on, config) => { } if (browser_validation) { const ally_path = path.dirname(process.env.ACCESSIBILITY_EXTENSION_PATH) + const payload = decodeJWTToken(process.env.ACCESSIBILITY_AUTH); launchOptions.extensions.push(ally_path); + if(!utils.isUndefined(payload) && !utils.isUndefined(payload.a11y_core_config) && payload.a11y_core_config.domForge === true) { + launchOptions.args.push("--auto-open-devtools-for-tabs"); + launchOptions.preferences.default["devtools"] = launchOptions.preferences.default["devtools"] || {}; + launchOptions.preferences.default["devtools"]["preferences"] = launchOptions.preferences.default["devtools"]["preferences"] || {}; + launchOptions.preferences.default["devtools"]["preferences"][ + "currentDockState" + ] = '"undocked"'; + } return launchOptions } } } catch(err) {} - + }) config.env.ACCESSIBILITY_EXTENSION_PATH = process.env.ACCESSIBILITY_EXTENSION_PATH config.env.OS_VERSION = process.env.OS_VERSION config.env.OS = process.env.OS + config.env.BROWSERSTACK_TESTHUB_UUID = process.env.BROWSERSTACK_TESTHUB_UUID + config.env.BROWSERSTACK_TESTHUB_JWT = process.env.BROWSERSTACK_TESTHUB_JWT + config.env.BROWSERSTACK_TESTHUB_API_PORT = process.env.BROWSERSTACK_TESTHUB_API_PORT + config.env.REPORTER_API_PORT_NO = process.env.REPORTER_API_PORT_NO config.env.IS_ACCESSIBILITY_EXTENSION_LOADED = browser_validation.toString() diff --git a/bin/commands/generateReport.js b/bin/commands/generateReport.js index 81c6fab8..603ed0a7 100644 --- a/bin/commands/generateReport.js +++ b/bin/commands/generateReport.js @@ -5,13 +5,13 @@ const logger = require("../helpers/logger").winstonLogger, utils = require("../helpers/utils"), reporterHTML = require('../helpers/reporterHTML'), getInitialDetails = require('../helpers/getInitialDetails').getInitialDetails; - +const { isTurboScaleSession } = require('../helpers/atsHelper'); module.exports = function generateReport(args, rawArgs) { let bsConfigPath = utils.getConfigPath(args.cf); let reportGenerator = reporterHTML.reportGenerator; - return utils.validateBstackJson(bsConfigPath).then(function (bsConfig) { + return utils.validateBstackJson(bsConfigPath).then(async function (bsConfig) { // setting setDefaults to {} if not present and set via env variables or via args. utils.setDefaults(bsConfig, args); @@ -21,9 +21,9 @@ module.exports = function generateReport(args, rawArgs) { // accept the access key from command line if provided utils.setAccessKey(bsConfig, args); - getInitialDetails(bsConfig, args, rawArgs).then((buildReportData) => { - - utils.setUsageReportingFlag(bsConfig, args.disableUsageReporting); + try { + let buildReportData = isTurboScaleSession(bsConfig) ? null : await getInitialDetails(bsConfig, args, rawArgs); + utils.setUsageReportingFlag(bsConfig, args.disableUsageReporting); // set cypress config filename utils.setCypressConfigFilename(bsConfig, args); @@ -31,12 +31,11 @@ module.exports = function generateReport(args, rawArgs) { let messageType = Constants.messageTypes.INFO; let errorCode = null; let buildId = args._[1]; - reportGenerator(bsConfig, buildId, args, rawArgs, buildReportData); utils.sendUsageReport(bsConfig, args, 'generate-report called', messageType, errorCode, buildReportData, rawArgs); - }).catch((err) => { + } catch(err) { logger.warn(err); - }); + }; }).catch(function (err) { logger.error(err); utils.setUsageReportingFlag(null, args.disableUsageReporting); diff --git a/bin/commands/info.js b/bin/commands/info.js index 715b9647..e8e9344a 100644 --- a/bin/commands/info.js +++ b/bin/commands/info.js @@ -1,12 +1,13 @@ 'use strict'; -const request = require('request'); - +const axios = require('axios').default; const config = require("../helpers/config"), logger = require("../helpers/logger").winstonLogger, Constants = require("../helpers/constants"), utils = require("../helpers/utils"), getInitialDetails = require('../helpers/getInitialDetails').getInitialDetails; +const { setAxiosProxy } = require('../helpers/helper'); + module.exports = function info(args, rawArgs) { let bsConfigPath = utils.getConfigPath(args.cf); @@ -19,7 +20,7 @@ module.exports = function info(args, rawArgs) { // accept the access key from command line if provided utils.setAccessKey(bsConfig, args); - getInitialDetails(bsConfig, args, rawArgs).then((buildReportData) => { + getInitialDetails(bsConfig, args, rawArgs).then(async (buildReportData) => { utils.setUsageReportingFlag(bsConfig, args.disableUsageReporting); @@ -43,53 +44,46 @@ module.exports = function info(args, rawArgs) { options.url = `${config.turboScaleBuildsUrl}/${buildId}`; } - request.get(options, function (err, resp, body) { - let message = null; - let messageType = null; - let errorCode = null; - - if (err) { - message = Constants.userMessages.BUILD_INFO_FAILED; - messageType = Constants.messageTypes.ERROR; - errorCode = 'api_failed_build_info'; - - logger.info(message); - logger.error(utils.formatRequest(err, resp, body)); - } else { - let build = null; + let message = null; + let messageType = null; + let errorCode = null; + + options.config = { + auth: { + username: bsConfig.auth.username, + password: bsConfig.auth.access_key + }, + headers: options.headers + }; + setAxiosProxy(options.config); + + try { + const response = await axios.get(options.url, options.config); + let build = null; try { - build = JSON.parse(body); + build = response.data; } catch (error) { build = null; } - - if (resp.statusCode == 299) { + if (response.status == 299) { messageType = Constants.messageTypes.INFO; errorCode = 'api_deprecated'; - - if (build) { - message = build.message; - logger.info(message); - } else { - message = Constants.userMessages.API_DEPRECATED; - logger.info(message); - } - logger.info(utils.formatRequest(err, resp, body)); - } else if (resp.statusCode != 200) { + message = build ? build.message : Constants.userMessages.API_DEPRECATED; + logger.info(utils.formatRequest(response.statusText, response, response.data)); + } else if (response.status != 200) { + message = Constants.userMessages.BUILD_INFO_FAILED; messageType = Constants.messageTypes.ERROR; errorCode = 'api_failed_build_info'; - if (build) { message = `${ Constants.userMessages.BUILD_INFO_FAILED } with error: \n${JSON.stringify(build, null, 2)}`; - logger.error(message); if (build.message === 'Unauthorized') errorCode = 'api_auth_failed'; } else { message = Constants.userMessages.BUILD_INFO_FAILED; - logger.error(message); } - logger.error(utils.formatRequest(err, resp, body)); + logger.error(message); + logger.error(utils.formatRequest(response.statusText, response, response.data)); } else { messageType = Constants.messageTypes.SUCCESS; message = `Build info for build id: \n ${JSON.stringify( @@ -97,11 +91,18 @@ module.exports = function info(args, rawArgs) { null, 2 )}`; - logger.info(message); } - } - utils.sendUsageReport(bsConfig, args, message, messageType, errorCode, buildReportData, rawArgs); - }); + logger.info(message); + } catch (error) { + message = `${ + Constants.userMessages.BUILD_INFO_FAILED + } with error: \n${error.response.data.message}`; + messageType = Constants.messageTypes.ERROR; + errorCode = 'api_failed_build_info'; + logger.info(message); + logger.error(utils.formatRequest(error.response.statusText, error.response, error.response.data)); + } + utils.sendUsageReport(bsConfig, args, message, messageType, errorCode, buildReportData, rawArgs); }).catch((err) => { logger.warn(err); }); diff --git a/bin/commands/runs.js b/bin/commands/runs.js index 57a6d0f9..a4e21b5a 100644 --- a/bin/commands/runs.js +++ b/bin/commands/runs.js @@ -30,7 +30,6 @@ const { printBuildLink } = require('../testObservability/helper/helper'); - const { createAccessibilityTestRun, setAccessibilityEventListeners, @@ -38,9 +37,11 @@ const { supportFileCleanup } = require('../accessibility-automation/helper'); const { isTurboScaleSession, getTurboScaleGridDetails, patchCypressConfigFileContent, atsFileCleanup } = require('../helpers/atsHelper'); +const { shouldProcessEventForTesthub, checkAndSetAccessibility, findAvailablePort } = require('../testhub/utils'); +const TestHubHandler = require('../testhub/testhubHandler'); module.exports = function run(args, rawArgs) { - + utils.normalizeTestReportingEnvVars(); markBlockStart('preBuild'); // set debug mode (--cli-debug) utils.setDebugMode(args); @@ -64,15 +65,14 @@ module.exports = function run(args, rawArgs) { // set cypress config filename utils.setCypressConfigFilename(bsConfig, args); - - /* - Set testObservability & browserstackAutomation flags - */ + + /* Set testObservability & browserstackAutomation flags */ const [isTestObservabilitySession, isBrowserstackInfra] = setTestObservabilityFlags(bsConfig); const checkAccessibility = checkAccessibilityPlatform(bsConfig); const isAccessibilitySession = bsConfig.run_settings.accessibility || checkAccessibility; const turboScaleSession = isTurboScaleSession(bsConfig); Constants.turboScaleObj.enabled = turboScaleSession; + utils.setUsageReportingFlag(bsConfig, args.disableUsageReporting); @@ -102,6 +102,7 @@ module.exports = function run(args, rawArgs) { // set spec timeout utils.setSpecTimeout(bsConfig, args); } + // accept the specs list from command line if provided utils.setUserSpecs(bsConfig, args); @@ -112,11 +113,15 @@ module.exports = function run(args, rawArgs) { // set build tag caps utils.setBuildTags(bsConfig, args); - /* - Send build start to Observability - */ - if(isTestObservabilitySession) { - await launchTestSession(bsConfig, bsConfigPath); + checkAndSetAccessibility(bsConfig, isAccessibilitySession); + + const preferredPort = 5348; + const port = await findAvailablePort(preferredPort); + process.env.REPORTER_API_PORT_NO = port + + // Send build start to TEST REPORTING AND ANALYTICS + if(shouldProcessEventForTesthub()) { + await TestHubHandler.launchBuild(bsConfig, bsConfigPath); utils.setO11yProcessHooks(null, bsConfig, args, null, buildReportData); } @@ -145,19 +150,13 @@ module.exports = function run(args, rawArgs) { // set the no-wrap utils.setNoWrap(bsConfig, args); + // process auto-import dev dependencies + utils.processAutoImportDependencies(bsConfig.run_settings); + // add cypress dependency if missing utils.setCypressNpmDependency(bsConfig); - if (isAccessibilitySession && isBrowserstackInfra) { - await createAccessibilityTestRun(bsConfig); - } - if (turboScaleSession) { - // Local is only required in case user is running on trial grid and wants to access private website. - // Even then, it will be spawned separately via browserstack-cli ats connect-grid command and not via browserstack-cypress-cli - // Hence whenever running on ATS, need to make local as false - bsConfig.connection_settings.local = false; - const gridDetails = await getTurboScaleGridDetails(bsConfig, args, rawArgs); if (gridDetails && Object.keys(gridDetails).length > 0) { @@ -199,6 +198,10 @@ module.exports = function run(args, rawArgs) { logger.debug("Completed setting the configs"); if(!isBrowserstackInfra) { + if(process.env.BS_TESTOPS_BUILD_COMPLETED) { + setEventListeners(bsConfig); + } + return runCypressTestsLocally(bsConfig, args, rawArgs); } @@ -206,11 +209,11 @@ module.exports = function run(args, rawArgs) { markBlockStart('validateConfig'); logger.debug("Started configs validation"); return capabilityHelper.validate(bsConfig, args).then(function (cypressConfigFile) { - if(process.env.BROWSERSTACK_TEST_ACCESSIBILITY) { + if(process.env.BROWSERSTACK_TEST_ACCESSIBILITY === 'true') { setAccessibilityEventListeners(bsConfig); } if(process.env.BS_TESTOPS_BUILD_COMPLETED) { - // setEventListeners(bsConfig); + setEventListeners(bsConfig); } markBlockEnd('validateConfig'); logger.debug("Completed configs validation"); @@ -365,21 +368,21 @@ module.exports = function run(args, rawArgs) { await new Promise(resolve => setTimeout(resolve, 5000)); // download build artifacts - if (exitCode != Constants.BUILD_FAILED_EXIT_CODE && !turboScaleSession) { + if (exitCode != Constants.BUILD_FAILED_EXIT_CODE) { if (utils.nonEmptyArray(bsConfig.run_settings.downloads)) { logger.debug("Downloading build artifacts"); - await downloadBuildArtifacts(bsConfig, data.build_id, args, rawArgs, buildReportData); + await downloadBuildArtifacts(bsConfig, data.build_id, args, rawArgs, buildReportData, turboScaleSession); } // Generate custom report! - reportGenerator(bsConfig, data.build_id, args, rawArgs, buildReportData, function(){ + reportGenerator(bsConfig, data.build_id, args, rawArgs, buildReportData, function(modifiedExitCode=exitCode){ utils.sendUsageReport(bsConfig, args, `${message}\n${dashboardLink}`, Constants.messageTypes.SUCCESS, null, buildReportData, rawArgs); markBlockEnd('postBuild'); - utils.handleSyncExit(exitCode, data.dashboard_url); + utils.handleSyncExit(modifiedExitCode, data.dashboard_url); }); } else if(!turboScaleSession){ let stacktraceUrl = getStackTraceUrl(); - downloadBuildStacktrace(stacktraceUrl).then((message) => { + downloadBuildStacktrace(stacktraceUrl, bsConfig).then((message) => { utils.sendUsageReport(bsConfig, args, message, Constants.messageTypes.SUCCESS, null, buildReportData, rawArgs); }).catch((err) => { let message = `Downloading build stacktrace failed with statuscode: ${err}. Please visit ${data.dashboard_url} for additional details.`; @@ -468,8 +471,12 @@ module.exports = function run(args, rawArgs) { utils.sendUsageReport(bsConfig, args, `${err}\n${Constants.userMessages.LOCAL_START_FAILED}`, Constants.messageTypes.ERROR, 'local_start_failed', buildReportData, rawArgs); } else { logger.error(Constants.userMessages.ZIP_UPLOAD_FAILED); - fileHelpers.deleteZip(); utils.sendUsageReport(bsConfig, args, `${err}\n${Constants.userMessages.ZIP_UPLOAD_FAILED}`, Constants.messageTypes.ERROR, 'zip_upload_failed', buildReportData, rawArgs); + try { + fileHelpers.deleteZip(); + } catch (err) { + utils.sendUsageReport(bsConfig, args, Constants.userMessages.ZIP_DELETE_FAILED, Constants.messageTypes.ERROR, 'zip_deletion_failed', buildReportData, rawArgs); + } try { fileHelpers.deletePackageArchieve(); } catch (err) { diff --git a/bin/commands/stop.js b/bin/commands/stop.js index e62b516e..c37f7443 100644 --- a/bin/commands/stop.js +++ b/bin/commands/stop.js @@ -1,5 +1,4 @@ 'use strict'; -const request = require('request'); const config = require("../helpers/config"), logger = require("../helpers/logger").winstonLogger, diff --git a/bin/helpers/atsHelper.js b/bin/helpers/atsHelper.js index 01e874ef..fe3cefa8 100644 --- a/bin/helpers/atsHelper.js +++ b/bin/helpers/atsHelper.js @@ -1,7 +1,9 @@ const path = require('path'); -const fs = require('fs') - -const request = require('request'), +const fs = require('fs'); +const { consoleHolder } = require('../testObservability/helper/constants'); +const { HttpsProxyAgent = require('https-proxy-agent') } = require('https-proxy-agent'); +const { v4: uuidv4 } = require('uuid'); +const axios = require('axios'), logger = require('./logger').winstonLogger, utils = require('./utils'), config = require('./config'); @@ -42,44 +44,53 @@ exports.getTurboScaleGridName = (bsConfig) => { }; exports.getTurboScaleGridDetails = async (bsConfig, args, rawArgs) => { - try { - const gridName = this.getTurboScaleGridName(bsConfig); - - return new Promise((resolve, reject) => { - let options = { - url: `${config.turboScaleAPIUrl}/grids/${gridName}`, - auth: { - username: bsConfig.auth.username, - password: bsConfig.auth.access_key, - }, - headers: { - 'User-Agent': utils.getUserAgent(), - } - }; - let responseData = {}; - request.get(options, function (err, resp, data) { - if(err) { - logger.warn(utils.formatRequest(err, resp, data)); - utils.sendUsageReport(bsConfig, args, err, Constants.messageTypes.ERROR, 'get_ats_details_failed', null, rawArgs); - resolve({}); - } else { - try { - responseData = JSON.parse(data); - } catch (e) { - responseData = {}; - } - if(resp.statusCode != 200) { - logger.warn(`Warn: Get Automate TurboScale Details Request failed with status code ${resp.statusCode}`); - utils.sendUsageReport(bsConfig, args, responseData["error"], Constants.messageTypes.ERROR, 'get_ats_details_failed', null, rawArgs); + try { + const gridName = this.getTurboScaleGridName(bsConfig); + + return new Promise((resolve, reject) => { + let options = { + url: `${config.turboScaleAPIUrl}/grids/${gridName}`, + auth: { + username: bsConfig.auth.username, + password: bsConfig.auth.access_key, + }, + headers: { + 'User-Agent': utils.getUserAgent(), + } + }; + + if (process.env.HTTP_PROXY) { + options.proxy = false; + options.httpsAgent = new HttpsProxyAgent(process.env.HTTP_PROXY); + } else if (process.env.HTTPS_PROXY) { + options.proxy = false; + options.httpsAgent = new HttpsProxyAgent(process.env.HTTPS_PROXY); + } + + let responseData = {}; + + axios(options).then(response => { + try { + responseData = response.data; + } catch (e) { + responseData = {}; + } + if(response.status != 200) { + logger.warn(`Warn: Get Automate TurboScale Details Request failed with status code ${response.status}`); + utils.sendUsageReport(bsConfig, args, responseData["error"], Constants.messageTypes.ERROR, 'get_ats_details_failed', null, rawArgs); + resolve({}); + } + resolve(responseData); + }).catch(error => { + logger.warn(`Grid with name - ${gridName} not found`); + logger.warn(utils.formatRequest(error, null, null)); + utils.sendUsageReport(bsConfig, args, error, Constants.messageTypes.ERROR, 'get_ats_details_failed', null, rawArgs); resolve({}); - } - resolve(responseData); - } + }); }); - }); - } catch (err) { - logger.error(`Failed to find TurboScale Grid: ${err}: ${err.stack}`); - } + } catch (err) { + logger.error(`Failed to find TurboScale Grid: ${err}: ${err.stack}`); + } }; exports.patchCypressConfigFileContent = (bsConfig) => { @@ -87,7 +98,6 @@ exports.patchCypressConfigFileContent = (bsConfig) => { let cypressConfigFileData = fs.readFileSync(path.resolve(bsConfig.run_settings.cypress_config_file)).toString(); const patchedConfigFileData = cypressConfigFileData + '\n\n' + ` let originalFunction = module.exports.e2e.setupNodeEvents; - module.exports.e2e.setupNodeEvents = (on, config) => { const bstackOn = require("./cypressPatch.js")(on); if (originalFunction !== null && originalFunction !== undefined) { @@ -99,7 +109,9 @@ exports.patchCypressConfigFileContent = (bsConfig) => { let confPath = bsConfig.run_settings.cypress_config_file; let patchedConfPathList = confPath.split(path.sep); - patchedConfPathList[patchedConfPathList.length - 1] = 'patched_ats_config_file.js' + const uniqueNamePatchFileName = `patched_ats_config_file_${uuidv4()}.js`; + patchedConfPathList[patchedConfPathList.length - 1] = uniqueNamePatchFileName; + logger.debug("Patch file name is " + uniqueNamePatchFileName); const patchedConfPath = patchedConfPathList.join(path.sep); bsConfig.run_settings.patched_cypress_config_file = patchedConfPath; diff --git a/bin/helpers/build.js b/bin/helpers/build.js index 9d97a57a..fc1bb4bf 100644 --- a/bin/helpers/build.js +++ b/bin/helpers/build.js @@ -1,5 +1,5 @@ 'use strict'; -const request = require('request'); +const axios = require('axios').default; const config = require('./config'), capabilityHelper = require("../helpers/capabilityHelper"), @@ -7,9 +7,11 @@ const config = require('./config'), utils = require('../helpers/utils'), logger = require('../helpers/logger').winstonLogger; +const { setAxiosProxy } = require('./helper'); + const createBuild = (bsConfig, zip) => { return new Promise(function (resolve, reject) { - capabilityHelper.caps(bsConfig, zip).then(function(data){ + capabilityHelper.caps(bsConfig, zip).then(async function(data){ let options = { url: config.buildUrl, auth: { @@ -22,43 +24,51 @@ const createBuild = (bsConfig, zip) => { }, body: data } - if (Constants.turboScaleObj.enabled) { options.url = Constants.turboScaleObj.buildUrl; } - request.post(options, function (err, resp, body) { - if (err) { - logger.error(utils.formatRequest(err, resp, body)); - reject(err); - } else { - let build = null; - try { - build = JSON.parse(body); - } catch (error) { - build = null; - } + const axiosConfig = { + auth: { + username: options.auth.user, + password: options.auth.password + }, + headers: options.headers + } + setAxiosProxy(axiosConfig); - if (resp.statusCode == 299) { - if (build) { - resolve(build.message); - } else { - logger.error(utils.formatRequest(err, resp, body)); - reject(Constants.userMessages.API_DEPRECATED); - } - } else if (resp.statusCode != 201) { - logger.error(utils.formatRequest(err, resp, body)); - if (build) { - reject(`${Constants.userMessages.BUILD_FAILED} Error: ${build.message}`); - } else { - reject(Constants.userMessages.BUILD_FAILED); - } + try { + const response = await axios.post(options.url, data, axiosConfig); + let build = null; + try { + build = response.data; + } catch (error) { + build = null; + } + if (response.status == 299) { + if (build) { + resolve(build.message); + } else { + logger.error(utils.formatRequest(response.statusText, response, response.data)); + reject(Constants.userMessages.API_DEPRECATED); + } + } else if (response.status != 201) { + logger.error(utils.formatRequest(response.statusText, response, response.data)); + if (build) { + reject(`${Constants.userMessages.BUILD_FAILED} Error: ${build.message}`); } else { - resolve(build); + reject(Constants.userMessages.BUILD_FAILED); } - resolve(build); } - }) + resolve(build); + } catch (error) { + if(error.response) { + logger.error(utils.formatRequest(error.response.statusText, error.response, error.response.data)); + reject(`${Constants.userMessages.BUILD_FAILED} Error: ${error.response.data.message}`); + } else { + reject(error); + } + } }).catch(function(err){ reject(err); }); diff --git a/bin/helpers/buildArtifacts.js b/bin/helpers/buildArtifacts.js index 6f361af9..4070ef8b 100644 --- a/bin/helpers/buildArtifacts.js +++ b/bin/helpers/buildArtifacts.js @@ -8,9 +8,12 @@ const logger = require('./logger').winstonLogger, Constants = require("./constants"), config = require("./config"); -const request = require('request'); +const { default: axios } = require('axios'); +const { HttpsProxyAgent = require('https-proxy-agent') } = require('https-proxy-agent'); +const FormData = require('form-data'); const decompress = require('decompress'); const unzipper = require("unzipper"); +const { setAxiosProxy } = require('./helper'); let BUILD_ARTIFACTS_TOTAL_COUNT = 0; let BUILD_ARTIFACTS_FAIL_COUNT = 0; @@ -35,6 +38,9 @@ const parseAndDownloadArtifacts = async (buildId, data, bsConfig, args, rawArgs, utils.sendUsageReport(bsConfig, args, warningMessage, Constants.messageTypes.ERROR, 'build_artifacts_not_found', buildReportData, rawArgs); } else { BUILD_ARTIFACTS_FAIL_COUNT += 1; + const errorMsg = `Error downloading build artifacts for ${sessionId} with error: ${error}`; + logger.debug(errorMsg); + utils.sendUsageReport(bsConfig, args, errorMsg, Constants.messageTypes.ERROR, 'build_artifacts_parse_error', buildReportData, rawArgs); } // delete malformed zip if present let tmpFilePath = path.join(filePath, fileName); @@ -102,18 +108,25 @@ const downloadAndUnzip = async (filePath, fileName, url) => { let tmpFilePath = path.join(filePath, fileName); const writer = fs.createWriteStream(tmpFilePath); + logger.debug(`Downloading build artifact for: ${filePath}`) return new Promise(async (resolve, reject) => { - request.get(url).on('response', function(response) { - - if(response.statusCode != 200) { - if (response.statusCode === 404) { + try { + const axiosConfig = { + responseType: 'stream', + validateStatus: status => (status >= 200 && status < 300) || status === 404 + }; + setAxiosProxy(axiosConfig); + const response = await axios.get(url, axiosConfig); + if(response.status != 200) { + if (response.status === 404) { reject(Constants.userMessages.DOWNLOAD_BUILD_ARTIFACTS_NOT_FOUND); } - reject(); + const errorMsg = `Non 200 status code, got status code: ${response.status}`; + reject(errorMsg); } else { //ensure that the user can call `then()` only when the file has //been downloaded entirely. - response.pipe(writer); + response.data.pipe(writer); let error = null; writer.on('error', err => { error = err; @@ -132,7 +145,9 @@ const downloadAndUnzip = async (filePath, fileName, url) => { resolve(true); }); } - }); + } catch (error) { + reject(error); + } }); } @@ -183,38 +198,50 @@ const sendUpdatesToBstack = async (bsConfig, buildId, args, options, rawArgs, bu } options.formData = data.toString(); + const axiosConfig = { + auth: { + username: options.auth.username, + password: options.auth.password + }, + headers: options.headers + }; + setAxiosProxy(axiosConfig); + let responseData = null; return new Promise (async (resolve, reject) => { - request.post(options, function (err, resp, data) { - if(err) { - utils.sendUsageReport(bsConfig, args, err, Constants.messageTypes.ERROR, 'api_failed_build_artifacts_status_update', buildReportData, rawArgs); - logger.error(utils.formatRequest(err, resp, data)); - reject(err); - } else { - try { - responseData = JSON.parse(data); - } catch(e) { - responseData = {}; - } - if (resp.statusCode != 200) { - if (responseData && responseData["error"]) { - utils.sendUsageReport(bsConfig, args, responseData["error"], Constants.messageTypes.ERROR, 'api_failed_build_artifacts_status_update', buildReportData, rawArgs); - reject(responseData["error"]) - } + try { + const response = await axios.post(options.url, data, axiosConfig); + try { + responseData = response.data; + } catch(e) { + responseData = {}; + } + if (response.status != 200) { + if (responseData && responseData["error"]) { + utils.sendUsageReport(bsConfig, args, responseData["error"], Constants.messageTypes.ERROR, 'api_failed_build_artifacts_status_update', buildReportData, rawArgs); + reject(responseData["error"]) } } - resolve() - }); + resolve(); + } catch (error) { + if(error.response) { + utils.sendUsageReport(bsConfig, args, error.response, Constants.messageTypes.ERROR, 'api_failed_build_artifacts_status_update', buildReportData, rawArgs); + logger.error(utils.formatRequest(error.response.statusText, error.response, error.response.data)); + reject(errror.response.data.message); + } + } }); } -exports.downloadBuildArtifacts = async (bsConfig, buildId, args, rawArgs, buildReportData = null) => { +exports.downloadBuildArtifacts = async (bsConfig, buildId, args, rawArgs, buildReportData = null, isTurboScaleSession = false) => { return new Promise ( async (resolve, reject) => { BUILD_ARTIFACTS_FAIL_COUNT = 0; BUILD_ARTIFACTS_TOTAL_COUNT = 0; let options = { - url: `${config.buildUrl}${buildId}/build_artifacts`, + url: isTurboScaleSession + ? `${config.turboScaleBuildsUrl}/${buildId}/build_artifacts` + : `${config.buildUrl}${buildId}/build_artifacts`, auth: { username: bsConfig.auth.username, password: bsConfig.auth.access_key, @@ -228,53 +255,51 @@ exports.downloadBuildArtifacts = async (bsConfig, buildId, args, rawArgs, buildR let messageType = null; let errorCode = null; let buildDetails = null; - request.get(options, async function (err, resp, body) { - if(err) { - logger.error(utils.formatRequest(err, resp, body)); - utils.sendUsageReport(bsConfig, args, err, Constants.messageTypes.ERROR, 'api_failed_build_artifacts', buildReportData, rawArgs); + options.config = { + auth: options.auth, + headers: options.headers + } + setAxiosProxy(options.config); + let response; + try { + response = await axios.get(options.url, options.config); + buildDetails = response.data; + await createDirectories(buildId, buildDetails); + await parseAndDownloadArtifacts(buildId, buildDetails, bsConfig, args, rawArgs, buildReportData); + if (BUILD_ARTIFACTS_FAIL_COUNT > 0) { + messageType = Constants.messageTypes.ERROR; + message = Constants.userMessages.DOWNLOAD_BUILD_ARTIFACTS_FAILED.replace('', buildId).replace('', BUILD_ARTIFACTS_FAIL_COUNT); + logger.error(message); process.exitCode = Constants.ERROR_EXIT_CODE; } else { - try { - buildDetails = JSON.parse(body); - if(resp.statusCode != 200) { - logger.error('Downloading the build artifacts failed.'); - logger.error(`Error: Request failed with status code ${resp.statusCode}`) - logger.error(utils.formatRequest(err, resp, body)); - utils.sendUsageReport(bsConfig, args, buildDetails, Constants.messageTypes.ERROR, 'api_failed_build_artifacts', buildReportData, rawArgs); - process.exitCode = Constants.ERROR_EXIT_CODE; - } else { - await createDirectories(buildId, buildDetails); - await parseAndDownloadArtifacts(buildId, buildDetails, bsConfig, args, rawArgs, buildReportData); - if (BUILD_ARTIFACTS_FAIL_COUNT > 0) { - messageType = Constants.messageTypes.ERROR; - message = Constants.userMessages.DOWNLOAD_BUILD_ARTIFACTS_FAILED.replace('', buildId).replace('', BUILD_ARTIFACTS_FAIL_COUNT); - logger.error(message); - process.exitCode = Constants.ERROR_EXIT_CODE; - } else { - messageType = Constants.messageTypes.SUCCESS; - message = Constants.userMessages.DOWNLOAD_BUILD_ARTIFACTS_SUCCESS.replace('', buildId).replace('', process.cwd()); - logger.info(message); - } - await sendUpdatesToBstack(bsConfig, buildId, args, options, rawArgs, buildReportData) - utils.sendUsageReport(bsConfig, args, message, messageType, null, buildReportData, rawArgs); - } - } catch (err) { + messageType = Constants.messageTypes.SUCCESS; + message = Constants.userMessages.DOWNLOAD_BUILD_ARTIFACTS_SUCCESS.replace('', buildId).replace('', process.cwd()); + logger.info(message); + } + await sendUpdatesToBstack(bsConfig, buildId, args, options, rawArgs, buildReportData) + utils.sendUsageReport(bsConfig, args, message, messageType, null, buildReportData, rawArgs); + } catch (err) { + messageType = Constants.messageTypes.ERROR; + errorCode = 'api_failed_build_artifacts'; + if(err.response && err.response.status !== 200) { + logger.error('Downloading the build artifacts failed.'); + logger.error(`Error: Request failed with status code ${err.response.status}`) + logger.error(utils.formatRequest(err.response.statusText, err.response, err.response.data)); + utils.sendUsageReport(bsConfig, args, JSON.stringify(buildDetails), Constants.messageTypes.ERROR, 'api_failed_build_artifacts', buildReportData, rawArgs); + } else { + if (BUILD_ARTIFACTS_FAIL_COUNT > 0) { messageType = Constants.messageTypes.ERROR; - errorCode = 'api_failed_build_artifacts'; - if (BUILD_ARTIFACTS_FAIL_COUNT > 0) { - messageType = Constants.messageTypes.ERROR; - message = Constants.userMessages.DOWNLOAD_BUILD_ARTIFACTS_FAILED.replace('', buildId).replace('', BUILD_ARTIFACTS_FAIL_COUNT); - logger.error(message); - } else { - logger.error('Downloading the build artifacts failed.'); - } - utils.sendUsageReport(bsConfig, args, err, messageType, errorCode, buildReportData, rawArgs); - logger.error(`Error: Request failed with status code ${resp.statusCode}`) - logger.error(utils.formatRequest(err, resp, body)); - process.exitCode = Constants.ERROR_EXIT_CODE; + message = Constants.userMessages.DOWNLOAD_BUILD_ARTIFACTS_FAILED.replace('', buildId).replace('', BUILD_ARTIFACTS_FAIL_COUNT); + logger.error(message); + } else { + logger.error('Downloading the build artifacts failed.'); } + utils.sendUsageReport(bsConfig, args, err, messageType, errorCode, buildReportData, rawArgs); + logger.error(`Error: Request failed with status code ${resp.status}`) + logger.error(utils.formatRequest(err, resp, body)); } - resolve(); - }); + process.exitCode = Constants.ERROR_EXIT_CODE; + } + resolve(); }); }; diff --git a/bin/helpers/capabilityHelper.js b/bin/helpers/capabilityHelper.js index ae09e260..f380b981 100644 --- a/bin/helpers/capabilityHelper.js +++ b/bin/helpers/capabilityHelper.js @@ -131,6 +131,10 @@ const caps = (bsConfig, zip) => { obj.run_settings = JSON.stringify(bsConfig.run_settings); } + obj.cypress_cli_user_agent = Utils.getUserAgent(); + + logger.info(`Cypress CLI User Agent: ${obj.cypress_cli_user_agent}`); + if(obj.parallels === Constants.cliMessages.RUN.DEFAULT_PARALLEL_MESSAGE) obj.parallels = undefined if (obj.project) logger.info(`Project name is: ${obj.project}`); @@ -179,11 +183,17 @@ const getAccessibilityPlatforms = (bsConfig) => { const addCypressZipStartLocation = (runSettings) => { let resolvedHomeDirectoryPath = path.resolve(runSettings.home_directory); let resolvedCypressConfigFilePath = path.resolve(runSettings.cypressConfigFilePath); - runSettings.cypressZipStartLocation = path.dirname(resolvedCypressConfigFilePath.split(resolvedHomeDirectoryPath)[1]); + + // Convert to POSIX style paths for consistent behavior + let posixHomePath = resolvedHomeDirectoryPath.split(path.sep).join(path.posix.sep); + let posixConfigPath = resolvedCypressConfigFilePath.split(path.sep).join(path.posix.sep); + + runSettings.cypressZipStartLocation = path.posix.dirname(posixConfigPath.split(posixHomePath)[1]); runSettings.cypressZipStartLocation = runSettings.cypressZipStartLocation.substring(1); logger.debug(`Setting cypress zip start location = ${runSettings.cypressZipStartLocation}`); } + const validate = (bsConfig, args) => { return new Promise(function (resolve, reject) { logger.info(Constants.userMessages.VALIDATING_CONFIG); diff --git a/bin/helpers/checkUploaded.js b/bin/helpers/checkUploaded.js index 2acb5cba..dfc29241 100644 --- a/bin/helpers/checkUploaded.js +++ b/bin/helpers/checkUploaded.js @@ -1,5 +1,5 @@ 'use strict'; -const request = require('request'); +const { default: axios } = require('axios'); const { combineMacWinNpmDependencies } = require('./helper'); const crypto = require('crypto'), @@ -11,6 +11,7 @@ const crypto = require('crypto'), utils = require('./utils'), logger = require('./logger').winstonLogger; +const { setAxiosProxy } = require('./helper'); const checkSpecsMd5 = (runSettings, args, instrumentBlocks) => { return new Promise(function (resolve, reject) { @@ -91,7 +92,7 @@ const checkUploadedMd5 = (bsConfig, args, instrumentBlocks) => { } instrumentBlocks.markBlockStart("checkAlreadyUploaded.md5Total"); - checkSpecsMd5(bsConfig.run_settings, args, instrumentBlocks).then(function (zip_md5sum) { + checkSpecsMd5(bsConfig.run_settings, args, instrumentBlocks).then(async function (zip_md5sum) { instrumentBlocks.markBlockStart("checkAlreadyUploaded.md5Package"); let npm_package_md5sum = checkPackageMd5(bsConfig.run_settings); instrumentBlocks.markBlockEnd("checkAlreadyUploaded.md5Package"); @@ -116,7 +117,7 @@ const checkUploadedMd5 = (bsConfig, args, instrumentBlocks) => { 'Content-Type': 'application/json', "User-Agent": utils.getUserAgent(), }, - body: JSON.stringify(data) + body: data }; if (Constants.turboScaleObj.enabled) { @@ -124,39 +125,48 @@ const checkUploadedMd5 = (bsConfig, args, instrumentBlocks) => { } instrumentBlocks.markBlockStart("checkAlreadyUploaded.railsCheck"); - request.post(options, function (err, resp, body) { - if (err) { - instrumentBlocks.markBlockEnd("checkAlreadyUploaded.railsCheck"); - resolve(obj); - } else { - let zipData = null; - try { - zipData = JSON.parse(body); - } catch (error) { - zipData = {}; - } - if (resp.statusCode === 200) { - if (!utils.isUndefined(zipData.zipUrl)) { - Object.assign(obj, zipData, {zipUrlPresent: true}); - } - if (!utils.isUndefined(zipData.npmPackageUrl)) { - Object.assign(obj, zipData, {packageUrlPresent: true}); - } - } - if (utils.isTrueString(zipData.disableNpmSuiteCache)) { - bsConfig.run_settings.cache_dependencies = false; - Object.assign(obj, {packageUrlPresent: false}); - delete obj.npm_package_md5sum; + + const axiosConfig = { + auth: { + username: options.auth.user, + password: options.auth.password + }, + headers: options.headers + }; + setAxiosProxy(axiosConfig); + + try { + const response = await axios.post(options.url, options.body, axiosConfig); + let zipData = null; + try { + zipData = response.data; + } catch (error) { + zipData = {}; + } + if (response.status === 200) { + if (!utils.isUndefined(zipData.zipUrl)) { + Object.assign(obj, zipData, {zipUrlPresent: true}); } - if (utils.isTrueString(zipData.disableTestSuiteCache)) { - args["force-upload"] = true; - Object.assign(obj, {zipUrlPresent: false}); - delete obj.zip_md5sum; + if (!utils.isUndefined(zipData.npmPackageUrl)) { + Object.assign(obj, zipData, {packageUrlPresent: true}); } - instrumentBlocks.markBlockEnd("checkAlreadyUploaded.railsCheck"); - resolve(obj); } - }); + if (utils.isTrueString(zipData.disableNpmSuiteCache)) { + bsConfig.run_settings.cache_dependencies = false; + Object.assign(obj, {packageUrlPresent: false}); + delete obj.npm_package_md5sum; + } + if (utils.isTrueString(zipData.disableTestSuiteCache)) { + args["force-upload"] = true; + Object.assign(obj, {zipUrlPresent: false}); + delete obj.zip_md5sum; + } + instrumentBlocks.markBlockEnd("checkAlreadyUploaded.railsCheck"); + resolve(obj); + } catch (error) { + instrumentBlocks.markBlockEnd("checkAlreadyUploaded.railsCheck"); + resolve(obj); + } }).catch((err) => { let errString = err.stack ? err.stack.toString().substring(0,100) : err.toString().substring(0,100); resolve({zipUrlPresent: false, packageUrlPresent: false, error: errString}); diff --git a/bin/helpers/constants.js b/bin/helpers/constants.js index 2b0e83bf..d55dc988 100644 --- a/bin/helpers/constants.js +++ b/bin/helpers/constants.js @@ -196,6 +196,28 @@ const validationMessages = { "You have specified '--record' flag but you've not provided the '--record-key' and we could not find any value in 'CYPRESS_RECORD_KEY' environment variable. Your record functionality on cypress.io dashboard might not work as it needs the key and projectId", NODE_VERSION_PARSING_ERROR: "We weren't able to successfully parse the specified nodeVersion. We will be using the default nodeVersion to run your tests.", + AUTO_IMPORT_CONFLICT_ERROR: + "Cannot use both 'auto_import_dev_dependencies' and manual npm dependency configuration. Please either set 'auto_import_dev_dependencies' to false or remove manual 'npm_dependencies', 'win_npm_dependencies', and 'mac_npm_dependencies' configurations.", + AUTO_IMPORT_INVALID_TYPE: + "'auto_import_dev_dependencies' must be a boolean value (true or false).", + PACKAGE_JSON_NOT_FOUND: + "package.json not found in project directory. Cannot auto-import devDependencies.", + PACKAGE_JSON_PERMISSION_DENIED: + "Cannot read package.json due to permission issues. Please check file permissions.", + PACKAGE_JSON_MALFORMED: + "package.json contains invalid JSON syntax. Please fix the JSON format.", + PACKAGE_JSON_NOT_OBJECT: + "package.json must contain a JSON object, not an array or other type.", + DEVDEPS_INVALID_FORMAT: + "devDependencies field in package.json must be an object, not an array or other type.", + EXCLUDE_DEPS_INVALID_TYPE: + "'exclude_dependencies' must be an array of strings.", + EXCLUDE_DEPS_INVALID_PATTERNS: + "'exclude_dependencies' must contain only string values representing regex patterns.", + INVALID_REGEX_PATTERN: + "Invalid regex pattern found in 'exclude_dependencies': {pattern}. Please provide valid regex patterns.", + DEPENDENCIES_PARAM_INVALID: + "Dependencies parameter must be an object.", }; const cliMessages = { diff --git a/bin/helpers/downloadBuildStacktrace.js b/bin/helpers/downloadBuildStacktrace.js index a7471038..50d55f7c 100644 --- a/bin/helpers/downloadBuildStacktrace.js +++ b/bin/helpers/downloadBuildStacktrace.js @@ -1,28 +1,37 @@ -'use strict' -const request = require('request'); +'use strict'; +const { default: axios } = require('axios'); +const { setAxiosProxy } = require('./helper'); + +const downloadBuildStacktrace = async (url, bsConfig) => { + const axiosConfig = { + auth: { + username: bsConfig.auth.username, + password: bsConfig.auth.access_key + }, + responseType: 'stream', + }; + setAxiosProxy(axiosConfig); -const downloadBuildStacktrace = async (url) => { return new Promise(async (resolve, reject) => { - request.get(url).on('response', function (response) { - if(response.statusCode == 200) { - response.pipe(process.stdout); + try { + const response = await axios.get(url, axiosConfig); + if (response.status === 200) { + response.data.pipe(process.stdout); let error = null; process.stdout.on('error', (err) => { error = err; process.stdout.close(); - reject(response.statusCode); + reject(response.status); }); process.stdout.on('close', async () => { - if(!error) { - resolve("Build stacktrace downloaded successfully"); + if (!error) { + resolve('Build stacktrace downloaded successfully'); } }); - } else { - reject(response.statusCode); } - }).on('end', () => { - resolve("Build stacktrace downloaded successfully"); - }); + } catch (error) { + reject(error.response.status); + } }); }; diff --git a/bin/helpers/fileHelpers.js b/bin/helpers/fileHelpers.js index cdd73932..e993c667 100644 --- a/bin/helpers/fileHelpers.js +++ b/bin/helpers/fileHelpers.js @@ -27,12 +27,15 @@ exports.fileExists = function (filePath, cb) { exports.deleteZip = () => { try { + if (!fs.existsSync(config.fileName)) { + return 0; + } fs.unlinkSync(config.fileName); logger.info(Constants.userMessages.ZIP_DELETED); return 0; } catch (err) { logger.info(Constants.userMessages.ZIP_DELETE_FAILED); - return 1; + throw err; } }; diff --git a/bin/helpers/getInitialDetails.js b/bin/helpers/getInitialDetails.js index 30d3114b..61b1539d 100644 --- a/bin/helpers/getInitialDetails.js +++ b/bin/helpers/getInitialDetails.js @@ -1,11 +1,14 @@ -const request = require('request'), - logger = require('./logger').winstonLogger, +const { default: axios } = require('axios'); + +const logger = require('./logger').winstonLogger, utils = require('./utils'), config = require("./config"), Constants = require('./constants'); +const { setAxiosProxy } = require('./helper'); + exports.getInitialDetails = (bsConfig, args, rawArgs) => { - return new Promise((resolve, reject) => { + return new Promise(async (resolve, reject) => { let options = { url: config.getInitialDetails, auth: { @@ -17,28 +20,36 @@ exports.getInitialDetails = (bsConfig, args, rawArgs) => { } }; let responseData = {}; - request.get(options, function (err, resp, data) { - if(err) { - logger.warn(utils.formatRequest(err, resp, data)); - utils.sendUsageReport(bsConfig, args, err, Constants.messageTypes.ERROR, 'get_initial_details_failed', null, rawArgs); + + const axiosConfig = { + auth: options.auth, + headers: options.headers, + } + setAxiosProxy(axiosConfig); + + try { + const response = await axios.get(options.url, axiosConfig); + try { + responseData = response.data; + } catch (e) { + responseData = {}; + } + if(response.status != 200) { + logger.warn(`Warn: Get Initial Details Request failed with status code ${response.status}`); + utils.sendUsageReport(bsConfig, args, responseData["error"], Constants.messageTypes.ERROR, 'get_initial_details_failed', null, rawArgs); resolve({}); } else { - try { - responseData = JSON.parse(data); - } catch (e) { - responseData = {}; - } - if(resp.statusCode != 200) { - logger.warn(`Warn: Get Initial Details Request failed with status code ${resp.statusCode}`); - utils.sendUsageReport(bsConfig, args, responseData["error"], Constants.messageTypes.ERROR, 'get_initial_details_failed', null, rawArgs); - resolve({}); - } else { - if (!utils.isUndefined(responseData.grr) && responseData.grr.enabled && !utils.isUndefined(responseData.grr.urls)) { - config.uploadUrl = responseData.grr.urls.upload_url; - } - resolve(responseData); + if (!utils.isUndefined(responseData.grr) && responseData.grr.enabled && !utils.isUndefined(responseData.grr.urls)) { + config.uploadUrl = responseData.grr.urls.upload_url; } + resolve(responseData); + } + } catch (error) { + if(error.response && error.response.status !== 200) { + logger.warn(`Warn: Get Initial Details Request failed with status code ${error.response.status}`); + utils.sendUsageReport(bsConfig, args, error.response.data["error"], Constants.messageTypes.ERROR, 'get_initial_details_failed', null, rawArgs); } - }); + resolve({}); + } }); }; diff --git a/bin/helpers/helper.js b/bin/helpers/helper.js index 3e89e971..6d01cfd5 100644 --- a/bin/helpers/helper.js +++ b/bin/helpers/helper.js @@ -6,7 +6,6 @@ const fs = require('fs'); const path = require('path'); const http = require('http'); const https = require('https'); -const request = require('request'); const gitLastCommit = require('git-last-commit'); const { v4: uuidv4 } = require('uuid'); const os = require('os'); @@ -17,12 +16,14 @@ const { spawn, execSync } = require('child_process'); const glob = require('glob'); const pGitconfig = promisify(gitconfig); const { readCypressConfigFile } = require('./readCypressConfigUtil'); -const CrashReporter = require('../testObservability/crashReporter'); const { MAX_GIT_META_DATA_SIZE_IN_BYTES, GIT_META_DATA_TRUNCATED } = require('./constants') +const CrashReporter = require('../testObservability/crashReporter'); +const { HttpsProxyAgent = require('https-proxy-agent') } = require('https-proxy-agent'); +const { TEST_REPORTING_ANALYTICS } = require("../testObservability/helper/constants"); exports.debug = (text, shouldReport = false, throwable = null) => { if (process.env.BROWSERSTACK_OBSERVABILITY_DEBUG === "true" || process.env.BROWSERSTACK_OBSERVABILITY_DEBUG === "1") { - logger.info(`[ OBSERVABILITY ] ${text}`); + logger.info(`[ ${TEST_REPORTING_ANALYTICS} ] ${text}`); } if(shouldReport) { CrashReporter.getInstance().uploadCrashReport(text, throwable ? throwable && throwable.stack : null); @@ -181,8 +182,20 @@ exports.getGitMetaData = () => { } }) } + +exports.getHostInfo = () => { + return { + hostname: os.hostname(), + platform: os.platform(), + type: os.type(), + version: os.version(), + arch: os.arch() + } +} + exports.getCiInfo = () => { var env = process.env; + // Jenkins if ((typeof env.JENKINS_URL === "string" && env.JENKINS_URL.length > 0) || (typeof env.JENKINS_HOME === "string" && env.JENKINS_HOME.length > 0)) { return { @@ -190,7 +203,7 @@ exports.getCiInfo = () => { build_url: env.BUILD_URL, job_name: env.JOB_NAME, build_number: env.BUILD_NUMBER - } + }; } // CircleCI if (env.CI === "true" && env.CIRCLECI === "true") { @@ -199,7 +212,7 @@ exports.getCiInfo = () => { build_url: env.CIRCLE_BUILD_URL, job_name: env.CIRCLE_JOB, build_number: env.CIRCLE_BUILD_NUM - } + }; } // Travis CI if (env.CI === "true" && env.TRAVIS === "true") { @@ -208,7 +221,7 @@ exports.getCiInfo = () => { build_url: env.TRAVIS_BUILD_WEB_URL, job_name: env.TRAVIS_JOB_NAME, build_number: env.TRAVIS_BUILD_NUMBER - } + }; } // Codeship if (env.CI === "true" && env.CI_NAME === "codeship") { @@ -217,7 +230,7 @@ exports.getCiInfo = () => { build_url: null, job_name: null, build_number: null - } + }; } // Bitbucket if (env.BITBUCKET_BRANCH && env.BITBUCKET_COMMIT) { @@ -226,7 +239,7 @@ exports.getCiInfo = () => { build_url: env.BITBUCKET_GIT_HTTP_ORIGIN, job_name: null, build_number: env.BITBUCKET_BUILD_NUMBER - } + }; } // Drone if (env.CI === "true" && env.DRONE === "true") { @@ -235,7 +248,7 @@ exports.getCiInfo = () => { build_url: env.DRONE_BUILD_LINK, job_name: null, build_number: env.DRONE_BUILD_NUMBER - } + }; } // Semaphore if (env.CI === "true" && env.SEMAPHORE === "true") { @@ -244,7 +257,7 @@ exports.getCiInfo = () => { build_url: env.SEMAPHORE_ORGANIZATION_URL, job_name: env.SEMAPHORE_JOB_NAME, build_number: env.SEMAPHORE_JOB_ID - } + }; } // GitLab if (env.CI === "true" && env.GITLAB_CI === "true") { @@ -253,7 +266,16 @@ exports.getCiInfo = () => { build_url: env.CI_JOB_URL, job_name: env.CI_JOB_NAME, build_number: env.CI_JOB_ID - } + }; + } + // GitHub Actions + if (env.GITHUB_ACTIONS === "true" || env.CI === "true" && env.GITHUB_RUN_ID) { + return { + name: "GitHub Actions", + build_url: `${env.GITHUB_SERVER_URL || 'https://github.com'}/${env.GITHUB_REPOSITORY}/actions/runs/${env.GITHUB_RUN_ID}`, + job_name: env.GITHUB_WORKFLOW || env.GITHUB_JOB, + build_number: env.GITHUB_RUN_NUMBER + }; } // Buildkite if (env.CI === "true" && env.BUILDKITE === "true") { @@ -262,7 +284,7 @@ exports.getCiInfo = () => { build_url: env.BUILDKITE_BUILD_URL, job_name: env.BUILDKITE_LABEL || env.BUILDKITE_PIPELINE_NAME, build_number: env.BUILDKITE_BUILD_NUMBER - } + }; } // Visual Studio Team Services if (env.TF_BUILD === "True") { @@ -271,7 +293,7 @@ exports.getCiInfo = () => { build_url: `${env.SYSTEM_TEAMFOUNDATIONSERVERURI}${env.SYSTEM_TEAMPROJECTID}`, job_name: env.SYSTEM_DEFINITIONID, build_number: env.BUILD_BUILDID - } + }; } // if no matches, return null return null; @@ -317,7 +339,7 @@ exports.setBrowserstackCypressCliDependency = (bsConfig) => { if (runSettings.npm_dependencies !== undefined && typeof runSettings.npm_dependencies === 'object') { if (!("browserstack-cypress-cli" in runSettings.npm_dependencies)) { - logger.warn("Missing browserstack-cypress-cli not found in npm_dependencies"); + logger.warn("Missing browserstack-cypress-cli not found in npm_dependencies"); runSettings.npm_dependencies['browserstack-cypress-cli'] = this.getAgentVersion() || "latest"; logger.warn(`Adding browserstack-cypress-cli version ${runSettings.npm_dependencies['browserstack-cypress-cli']} in npm_dependencies`); } @@ -442,6 +464,14 @@ exports.truncateString = (field, truncateSizeInBytes) => { return field; }; +exports.setAxiosProxy = (axiosConfig) => { + if (process.env.HTTP_PROXY || process.env.HTTPS_PROXY) { + const httpProxy = process.env.HTTP_PROXY || process.env.HTTPS_PROXY + axiosConfig.proxy = false; + axiosConfig.httpsAgent = new HttpsProxyAgent(httpProxy); + }; +}; + exports.combineMacWinNpmDependencies = (runSettings) => { return Object.assign({}, runSettings.npm_dependencies, runSettings.win_npm_dependencies || {}, runSettings.mac_npm_dependencies || {}) }; diff --git a/bin/helpers/packageInstaller.js b/bin/helpers/packageInstaller.js index 18c4a30f..20823626 100644 --- a/bin/helpers/packageInstaller.js +++ b/bin/helpers/packageInstaller.js @@ -32,9 +32,10 @@ const setupPackageFolder = (runSettings, directoryPath) => { } // Combine win and mac specific dependencies if present - if (typeof runSettings.npm_dependencies === 'object') { + const combinedDependencies = combineMacWinNpmDependencies(runSettings); + if (combinedDependencies && Object.keys(combinedDependencies).length > 0) { Object.assign(packageJSON, { - devDependencies: combineMacWinNpmDependencies(runSettings), + devDependencies: combinedDependencies, }); } diff --git a/bin/helpers/readCypressConfigUtil.js b/bin/helpers/readCypressConfigUtil.js index bdf556bc..45e29578 100644 --- a/bin/helpers/readCypressConfigUtil.js +++ b/bin/helpers/readCypressConfigUtil.js @@ -13,6 +13,120 @@ exports.detectLanguage = (cypress_config_filename) => { return constants.CYPRESS_V10_AND_ABOVE_CONFIG_FILE_EXTENSIONS.includes(extension) ? extension : 'js' } +function resolveTsConfigPath(bsConfig, cypress_config_filepath) { + const working_dir = path.dirname(cypress_config_filepath); + + // Priority order for finding tsconfig + const candidates = [ + bsConfig.run_settings && bsConfig.run_settings.ts_config_file_path, // User specified + path.join(working_dir, 'tsconfig.json'), // Same directory as cypress config + path.join(working_dir, '..', 'tsconfig.json'), // Parent directory + path.join(process.cwd(), 'tsconfig.json') // Project root + ].filter(Boolean).map(p => path.resolve(p)); + + for (const candidate of candidates) { + if (fs.existsSync(candidate)) { + logger.debug(`Found tsconfig at: ${candidate}`); + return candidate; + } + } + + return null; +} + +function generateTscCommandAndTempTsConfig(bsConfig, bstack_node_modules_path, complied_js_dir, cypress_config_filepath) { + const working_dir = path.dirname(cypress_config_filepath); + const typescript_path = path.join(bstack_node_modules_path, 'typescript', 'bin', 'tsc'); + const tsc_alias_path = require.resolve('tsc-alias/dist/bin/index.js'); + + // Smart tsconfig detection and validation + const resolvedTsConfigPath = resolveTsConfigPath(bsConfig, cypress_config_filepath); + let hasValidTsConfig = false; + + if (resolvedTsConfigPath) { + try { + // Validate the tsconfig is readable and valid JSON + const tsConfigContent = fs.readFileSync(resolvedTsConfigPath, 'utf8'); + JSON.parse(tsConfigContent); + hasValidTsConfig = true; + logger.info(`Using existing tsconfig: ${resolvedTsConfigPath}`); + } catch (error) { + logger.warn(`Invalid tsconfig file: ${resolvedTsConfigPath}, falling back to default configuration. Error: ${error.message}`); + hasValidTsConfig = false; + } + } else { + logger.info('No tsconfig found, using default TypeScript configuration'); + } + + let tempTsConfig; + + if (hasValidTsConfig) { + // Scenario 1: User has valid tsconfig - use extends approach + tempTsConfig = { + extends: resolvedTsConfigPath, + compilerOptions: { + // Force override critical parameters for BrowserStack compatibility + "outDir": path.basename(complied_js_dir), + "listEmittedFiles": true, + // Ensure these are always set regardless of base tsconfig + "allowSyntheticDefaultImports": true, + "esModuleInterop": true + }, + include: [cypress_config_filepath] + }; + } else { + // Scenario 2: No tsconfig or invalid tsconfig - create standalone with all basic parameters + tempTsConfig = { + compilerOptions: { + // Preserve old command-line parameters for backwards compatibility + "outDir": path.basename(complied_js_dir), + "listEmittedFiles": true, + "allowSyntheticDefaultImports": true, + "module": "commonjs", + "declaration": false, + + // Add essential missing parameters for robust compilation + "target": "es2017", + "moduleResolution": "node", + "esModuleInterop": true, + "allowJs": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "strict": false, // Avoid breaking existing code + "noEmitOnError": false // Continue compilation even with errors + }, + include: [cypress_config_filepath], + exclude: ["node_modules", "dist", "build"] + }; + } + + // Write the temporary tsconfig + const tempTsConfigPath = path.join(working_dir, 'tsconfig.singlefile.tmp.json'); + fs.writeFileSync(tempTsConfigPath, JSON.stringify(tempTsConfig, null, 2)); + logger.info(`Temporary tsconfig created at: ${tempTsConfigPath}`); + + // Platform-specific command generation + const isWindows = /^win/.test(process.platform); + + if (isWindows) { + // Windows: Use && to chain commands, no space after SET + const setNodePath = isWindows + ? `set NODE_PATH=${bstack_node_modules_path}` + : `NODE_PATH="${bstack_node_modules_path}"`; + + const tscCommand = `${setNodePath} && node "${typescript_path}" --project "${tempTsConfigPath}" && ${setNodePath} && node "${tsc_alias_path}" --project "${tempTsConfigPath}" --verbose`; + logger.info(`TypeScript compilation command: ${tscCommand}`); + return { tscCommand, tempTsConfigPath }; + } else { + // Unix/Linux/macOS: Use ; to separate commands or && to chain + const nodePathPrefix = `NODE_PATH=${bstack_node_modules_path}`; + const tscCommand = `${nodePathPrefix} node "${typescript_path}" --project "${tempTsConfigPath}" && ${nodePathPrefix} node "${tsc_alias_path}" --project "${tempTsConfigPath}" --verbose`; + logger.info(`TypeScript compilation command: ${tscCommand}`); + return { tscCommand, tempTsConfigPath }; + } +} + exports.convertTsConfig = (bsConfig, cypress_config_filepath, bstack_node_modules_path) => { const cypress_config_filename = bsConfig.run_settings.cypress_config_filename const working_dir = path.dirname(cypress_config_filepath); @@ -22,19 +136,12 @@ exports.convertTsConfig = (bsConfig, cypress_config_filepath, bstack_node_module } fs.mkdirSync(complied_js_dir, { recursive: true }) - const typescript_path = path.join(bstack_node_modules_path, 'typescript', 'bin', 'tsc') - - let tsc_command = `NODE_PATH=${bstack_node_modules_path} node "${typescript_path}" --outDir "${complied_js_dir}" --listEmittedFiles true --allowSyntheticDefaultImports --module commonjs --declaration false "${cypress_config_filepath}"` + const { tscCommand, tempTsConfigPath } = generateTscCommandAndTempTsConfig(bsConfig, bstack_node_modules_path, complied_js_dir, cypress_config_filepath); - if (/^win/.test(process.platform)) { - tsc_command = `set NODE_PATH=${bstack_node_modules_path}&& node "${typescript_path}" --outDir "${complied_js_dir}" --listEmittedFiles true --allowSyntheticDefaultImports --module commonjs --declaration false "${cypress_config_filepath}"` - } - - let tsc_output try { - logger.debug(`Running: ${tsc_command}`) - tsc_output = cp.execSync(tsc_command, { cwd: working_dir }) + logger.debug(`Running: ${tscCommand}`) + tsc_output = cp.execSync(tscCommand, { cwd: working_dir }) } catch (err) { // error while compiling ts files logger.debug(err.message); @@ -44,6 +151,21 @@ exports.convertTsConfig = (bsConfig, cypress_config_filepath, bstack_node_module logger.debug(`Saved compiled js output at: ${complied_js_dir}`); logger.debug(`Finding compiled cypress config file in: ${complied_js_dir}`); + // Clean up the temporary tsconfig file + if (fs.existsSync(tempTsConfigPath)) { + fs.unlinkSync(tempTsConfigPath); + logger.debug(`Temporary tsconfig file removed: ${tempTsConfigPath}`); + } + + if (tsc_output) { + logger.debug(tsc_output.toString()); + } + + if (!tsc_output) { + logger.error('No TypeScript compilation output available'); + return null; + } + const lines = tsc_output.toString().split('\n'); let foundLine = null; for (let i = 0; i < lines.length; i++) { @@ -53,7 +175,7 @@ exports.convertTsConfig = (bsConfig, cypress_config_filepath, bstack_node_module } } if (foundLine === null) { - logger.error(`No compiled cypress config found. There might some error running ${tsc_command} command`) + logger.error(`No compiled cypress config found. There might some error running ${tscCommand} command`) return null } else { const compiled_cypress_config_filepath = foundLine.split('TSFILE: ').pop() diff --git a/bin/helpers/reporterHTML.js b/bin/helpers/reporterHTML.js index 5859b103..c12da320 100644 --- a/bin/helpers/reporterHTML.js +++ b/bin/helpers/reporterHTML.js @@ -1,13 +1,17 @@ +const axios = require('axios').default; + const fs = require('fs'), path = require('path'), - request = require('request'), logger = require('./logger').winstonLogger, utils = require("./utils"), Constants = require('./constants'), config = require("./config"), decompress = require('decompress'); +const { isTurboScaleSession } = require('../helpers/atsHelper'); + +const { setAxiosProxy } = require('./helper'); -let reportGenerator = (bsConfig, buildId, args, rawArgs, buildReportData, cb) => { +let reportGenerator = async (bsConfig, buildId, args, rawArgs, buildReportData, cb) => { let options = { url: `${config.buildUrl}${buildId}/custom_report`, auth: { @@ -19,34 +23,35 @@ let reportGenerator = (bsConfig, buildId, args, rawArgs, buildReportData, cb) => }, }; + if (isTurboScaleSession(bsConfig)) { + options.url = `${config.turboScaleBuildsUrl}/${buildId}/custom_report`; + } + logger.debug('Started fetching the build json and html reports.'); - return request.get(options, async function (err, resp, body) { - let message = null; - let messageType = null; - let errorCode = null; - let build; - - if (err) { - message = err; - messageType = Constants.messageTypes.ERROR; - errorCode = 'api_failed_build_report'; + let message = null; + let messageType = null; + let errorCode = null; + let build; - logger.error('Generating the build report failed.'); - logger.error(utils.formatRequest(err, resp, body)); + const axiosConfig = { + auth: { + username: options.auth.user, + password: options.auth.password + }, + headers: options.headers + } + setAxiosProxy(axiosConfig); - utils.sendUsageReport(bsConfig, args, message, messageType, errorCode, buildReportData, rawArgs); - return; - } else { - logger.debug('Received reports data from upstream.'); - try { - build = JSON.parse(body); - } catch (error) { - build = null; - } + try { + const response = await axios.get(options.url, axiosConfig); + logger.debug('Received reports data from upstream.'); + try { + build = response.data; + } catch (error) { + build = null; } - - if (resp.statusCode == 299) { + if (response.status == 299) { messageType = Constants.messageTypes.INFO; errorCode = 'api_deprecated'; if (build) { @@ -56,18 +61,18 @@ let reportGenerator = (bsConfig, buildId, args, rawArgs, buildReportData, cb) => message = Constants.userMessages.API_DEPRECATED; logger.info(message); } - } else if (resp.statusCode === 422) { + } else if (response.status === 422) { messageType = Constants.messageTypes.ERROR; errorCode = 'api_failed_build_generate_report'; try { - response = JSON.parse(body); + response = error.response.data; message = response.message; } catch (error) { logger.error(`Error generating the report: ${error}`); response = {message: message}; } - logger.error(utils.formatRequest(err, resp, body)); - } else if (resp.statusCode != 200) { + logger.error(utils.formatRequest(response.statusText, response, response.data)); + } else if (response.status != 200) { messageType = Constants.messageTypes.ERROR; errorCode = 'api_failed_build_generate_report'; @@ -79,7 +84,7 @@ let reportGenerator = (bsConfig, buildId, args, rawArgs, buildReportData, cb) => if (build.message === 'Unauthorized') errorCode = 'api_auth_failed'; } else { message = Constants.userMessages.BUILD_GENERATE_REPORT_FAILED.replace('', buildId); - logger.error(utils.formatRequest(err, resp, body)); + logger.error(utils.formatRequest(error.response.statusText, error.response, error.response.data)); } } else { messageType = Constants.messageTypes.SUCCESS; @@ -92,7 +97,19 @@ let reportGenerator = (bsConfig, buildId, args, rawArgs, buildReportData, cb) => if (cb){ cb(); } - }); + } catch (error) { + message = error.response.statusText; + messageType = Constants.messageTypes.ERROR; + errorCode = 'api_failed_build_report'; + + logger.error('Generating the build report failed.'); + logger.error(utils.formatRequest(error.response.statusText, error.response, error.response.data)); + utils.sendUsageReport(bsConfig, args, message, messageType, errorCode, buildReportData, rawArgs); + if (cb){ + cb(Constants.ERROR_EXIT_CODE); + } + return; + } } async function generateCypressBuildReport(report_data) { @@ -111,15 +128,16 @@ function getReportResponse(filePath, fileName, reportJsonUrl) { const writer = fs.createWriteStream(tmpFilePath); logger.debug(`Fetching build reports zip.`) return new Promise(async (resolve, reject) => { - request.get(reportJsonUrl).on('response', function(response) { - - if(response.statusCode != 200) { - let message = `Received non 200 response while fetching reports, code: ${response.statusCode}`; - reject(message); - } else { + try { + const axiosConfig = { + responseType: 'stream', + }; + setAxiosProxy(axiosConfig); + const response = await axios.get(reportJsonUrl, axiosConfig); + if(response.status === 200) { //ensure that the user can call `then()` only when the file has //been downloaded entirely. - response.pipe(writer); + response.data.pipe(writer); let error = null; writer.on('error', err => { error = err; @@ -144,7 +162,10 @@ function getReportResponse(filePath, fileName, reportJsonUrl) { //'error' stream; }); } - }); + } catch (error) { + let message = `Received non 200 response while fetching reports, code: ${error.response.status}`; + reject(message); + } }); } diff --git a/bin/helpers/sync/syncSpecsLogs.js b/bin/helpers/sync/syncSpecsLogs.js index 8933868c..0f6141e8 100644 --- a/bin/helpers/sync/syncSpecsLogs.js +++ b/bin/helpers/sync/syncSpecsLogs.js @@ -1,6 +1,8 @@ "use strict"; -const request = require("request"), - config = require("../config"), + +const { default: axios } = require("axios"); + +const config = require("../config"), utils = require("../utils"), logger = require("../logger").syncCliLogger, winstonLogger = require("../logger").winstonLogger, @@ -9,6 +11,8 @@ const request = require("request"), tableStream = require('table').createStream, chalk = require('chalk'); +const { setAxiosProxy } = require('../helper'); + let whileLoop = true, whileTries = config.retries, options, timeout = 3000, n = 2, tableConfig, stream, endTime, startTime = Date.now(), buildStarted = false; let specSummary = { "buildError": null, @@ -135,27 +139,23 @@ let printSpecsStatus = (bsConfig, buildDetails, rawArgs, buildReportData) => { }); }; -let whileProcess = (whilstCallback) => { - request.post(options, function(error, response, body) { - if (error) { - whileTries -= 1; - if (whileTries === 0) { - whileLoop = false; - endTime = Date.now(); - specSummary.exitCode = config.networkErrorExitCode; - return whilstCallback({ status: 504, message: "Tries limit reached" }); //Gateway Timeout - } else { - n = 2 - return setTimeout(whilstCallback, timeout * n, null); - } - } +let whileProcess = async (whilstCallback) => { + try { + const axiosConfig = { + auth: { + username: options.auth.user, + password: options.auth.password + }, + headers: options.headers + }; + setAxiosProxy(axiosConfig); + const response = await axios.post(options.url, {}, axiosConfig); whileTries = config.retries; // reset to default after every successful request - - switch (response.statusCode) { + switch (response.status) { case 202: // get data here and print it n = 2 - showSpecsStatus(body, 202); + showSpecsStatus(response.data, 202); return setTimeout(whilstCallback, timeout * n, null); case 204: // No data available, wait for some time and ask again n = 1 @@ -163,14 +163,27 @@ let whileProcess = (whilstCallback) => { case 200: // Build is completed. whileLoop = false; endTime = Date.now(); - showSpecsStatus(body, 200); + showSpecsStatus(response.data, 200); return specSummary.exitCode == Constants.BUILD_FAILED_EXIT_CODE ? whilstCallback({ status: 204, message: "No specs ran in the build"} ) : whilstCallback(null, body); default: whileLoop = false; - return whilstCallback({ status: response.statusCode, message: body }); + return whilstCallback({ status: response.status, message: response.data }); } - }); + } catch (error) { + if (error) { + whileTries -= 1; + if (whileTries === 0) { + whileLoop = false; + endTime = Date.now(); + specSummary.exitCode = config.networkErrorExitCode; + return whilstCallback({ status: 504, message: "Tries limit reached" }); //Gateway Timeout + } else { + n = 2 + return setTimeout(whilstCallback, timeout * n, null); + } + } + } } let getStackTraceUrl = () => { @@ -178,7 +191,7 @@ let getStackTraceUrl = () => { } let showSpecsStatus = (data, statusCode) => { - let specData = JSON.parse(data); + let specData = data; specData["specData"].forEach(specDetails => { if (specDetails.type === Constants.CYPRESS_CUSTOM_ERRORS_TO_PRINT_KEY) { addCustomErrorToPrint(specDetails); diff --git a/bin/helpers/usageReporting.js b/bin/helpers/usageReporting.js index 5a7a250e..f8a10933 100644 --- a/bin/helpers/usageReporting.js +++ b/bin/helpers/usageReporting.js @@ -1,7 +1,6 @@ 'use strict'; const cp = require("child_process"), os = require("os"), - request = require("requestretry"), fs = require('fs'), path = require('path'); @@ -10,7 +9,11 @@ const config = require('./config'), utils = require('./utils'); const { AUTH_REGEX, REDACTED_AUTH, REDACTED, CLI_ARGS_REGEX, RAW_ARGS_REGEX } = require("./constants"); -const { isTurboScaleSession } = require('../helpers/atsHelper'); +const { default: axios } = require("axios"); +const axiosRetry = require("axios-retry"); +const { isTurboScaleSession } = require("./atsHelper"); + +const { setAxiosProxy } = require('./helper'); function get_version(package_name) { try { @@ -157,6 +160,10 @@ function ci_environment() { if (env.CI === "true" && env.GITLAB_CI === "true") { return "GitLab"; } + // GitHub Actions + if (env.GITHUB_ACTIONS === "true" || (env.CI === "true" && env.GITHUB_RUN_ID)) { + return "GitHub Actions"; + } // Buildkite if (env.CI === "true" && env.BUILDKITE === "true") { return "Buildkite"; @@ -217,31 +224,36 @@ function sendTurboscaleErrorLogs(args) { password: bsConfig.auth.access_key, }, url: `${config.turboScaleAPIUrl}/send-instrumentation`, - body: turboscaleErrorPayload, - json: true, - maxAttempts: 10, // (default) try 3 times + data: turboscaleErrorPayload, + maxAttempts: 10, retryDelay: 2000, // (default) wait for 2s before trying again - retrySrategy: request.RetryStrategies.HTTPOrNetworkError, // (default) retry on 5xx or network errors }; - fileLogger.info(`Sending ${JSON.stringify(turboscaleErrorPayload)} to ${config.turboScaleAPIUrl}/send-instrumentation`); - request(options, function (error, res, body) { - if (error) { - //write err response to file - fileLogger.error(JSON.stringify(error)); - return; + axiosRetry(axios, { + retries: options.maxAttempts, + retryDelay: (retryCount) => options.retryDelay, + retryCondition: (error) => { + return axiosRetry.isRetryableError(error) // (default) retry on 5xx or network errors } - // write response file + }); + + fileLogger.info(`Sending ${JSON.stringify(turboscaleErrorPayload)} to ${config.turboScaleAPIUrl}/send-instrumentation`); + + axios(options) + .then((res) => { let response = { - attempts: res.attempts, - statusCode: res.statusCode, - body: body + attempts: res.config['axios-retry'].retryCount + 1, + statusCode: res.status, + body: res.data }; fileLogger.info(`${JSON.stringify(response)}`); + }) + .catch((error) => { + fileLogger.error(JSON.stringify(error)); }); } -function send(args) { +async function send(args) { let bsConfig = JSON.parse(JSON.stringify(args.bstack_config)); if (isTurboScaleSession(bsConfig) && args.message_type === 'error') { @@ -307,10 +319,6 @@ function send(args) { }, }; - if (isTurboScaleSession(bsConfig)) { - payload.event_type = 'hst_cypress_cli_stats'; - } - const options = { headers: { "Content-Type": "text/json", @@ -321,24 +329,38 @@ function send(args) { json: true, maxAttempts: 10, // (default) try 3 times retryDelay: 2000, // (default) wait for 2s before trying again - retrySrategy: request.RetryStrategies.HTTPOrNetworkError, // (default) retry on 5xx or network errors }; + const axiosConfig = { + headers: options.headers, + }; + setAxiosProxy(axiosConfig); + fileLogger.info(`Sending ${JSON.stringify(payload)} to ${config.usageReportingUrl}`); - request(options, function (error, res, body) { - if (error) { - //write err response to file - fileLogger.error(JSON.stringify(error)); - return; + axiosRetry(axios, + { + retries: 3, + retryDelay: 2000, + retryCondition: (error) => { + return utils.isNotUndefined(error.response) && (error.response.status === 503 || error.response.status === 500) } - // write response file - let response = { - attempts: res.attempts, - statusCode: res.statusCode, - body: body - }; - fileLogger.info(`${JSON.stringify(response)}`); }); + try { + const response = await axios.post(options.url, options.body, axiosConfig); + let result = { + statusText: response.statusText, + statusCode: response.status, + body: response.data + }; + fileLogger.info(`${JSON.stringify(result)}`); + } catch (error) { + if (error.response) { + fileLogger.error(JSON.stringify(error.response.data)); + } else { + fileLogger.error(`Error sending usage data: ${error.message}`); + } + return; + } } module.exports = { diff --git a/bin/helpers/utils.js b/bin/helpers/utils.js index fc5fd614..b15414fb 100644 --- a/bin/helpers/utils.js +++ b/bin/helpers/utils.js @@ -12,6 +12,7 @@ const { promisify } = require('util'); const readdir = promisify(fs.readdir); const stat = promisify(fs.stat); const TIMEZONE = require("../helpers/timezone.json"); +const { setAxiosProxy } = require('./helper'); const usageReporting = require("./usageReporting"), logger = require("./logger").winstonLogger, @@ -25,13 +26,15 @@ const usageReporting = require("./usageReporting"), o11yHelpers = require('../testObservability/helper/helper'), { OBSERVABILITY_ENV_VARS, TEST_OBSERVABILITY_REPORTER } = require('../testObservability/helper/constants'); -const request = require('request'); +const { default: axios } = require("axios"); +const { shouldProcessEventForTesthub } = require("../testhub/utils"); exports.validateBstackJson = (bsConfigPath) => { return new Promise(function (resolve, reject) { try { logger.info(`Reading config from ${bsConfigPath}`); let bsConfig = require(bsConfigPath); + bsConfig = exports.normalizeTestReportingConfig(bsConfig); resolve(bsConfig); } catch (e) { reject( @@ -329,8 +332,8 @@ exports.setCypressConfigFilename = (bsConfig, args) => { exports.setCypressTestSuiteType = (bsConfig) => { for (const possibleCypressFileName of Constants.CYPRESS_CONFIG_FILE_NAMES) { - if (bsConfig.run_settings.cypressConfigFilePath && - typeof(bsConfig.run_settings.cypressConfigFilePath) === 'string' && + if (bsConfig.run_settings.cypressConfigFilePath && + typeof(bsConfig.run_settings.cypressConfigFilePath) === 'string' && (path.extname(bsConfig.run_settings.cypressConfigFilePath) == path.extname(possibleCypressFileName) || bsConfig.run_settings.cypressConfigFilePath.endsWith(possibleCypressFileName))) { bsConfig.run_settings.cypressTestSuiteType = Constants.CYPRESS_CONFIG_FILE_MAPPING[possibleCypressFileName].type; break; @@ -346,11 +349,11 @@ exports.setCypressTestSuiteType = (bsConfig) => { exports.setCypressNpmDependency = (bsConfig) => { const runSettings = bsConfig.run_settings; - if (runSettings.npm_dependencies !== undefined && + if (runSettings.npm_dependencies !== undefined && Object.keys(runSettings.npm_dependencies).length !== 0 && typeof runSettings.npm_dependencies === 'object') { if (!("cypress" in runSettings.npm_dependencies) && runSettings.cypressTestSuiteType === Constants.CYPRESS_V10_AND_ABOVE_TYPE) { - logger.warn("Missing cypress not found in npm_dependencies"); + logger.warn("Missing cypress not found in npm_dependencies"); if("cypress_version" in runSettings){ runSettings.npm_dependencies.cypress = `^${runSettings.cypress_version.toString().split(".")[0]}`; } else if (runSettings.cypressTestSuiteType === Constants.CYPRESS_V10_AND_ABOVE_TYPE) { @@ -385,13 +388,13 @@ exports.setGeolocation = (bsConfig, args) => { } exports.isSpecTimeoutArgPassed = () => { - return this.searchForOption('--spec-timeout') || this.searchForOption('-t'); + return this.searchForOption('--spec-timeout') || this.searchForOption('-t'); } exports.setSpecTimeout = (bsConfig, args) => { let specTimeout = null; if(this.isSpecTimeoutArgPassed()) { - if(!this.isUndefined(args.specTimeout)) { - specTimeout = args.specTimeout; + if(!this.isUndefined(args.specTimeout)) { + specTimeout = args.specTimeout; } else { specTimeout = 'undefined' } @@ -409,7 +412,7 @@ exports.setTimezone = (bsConfig, args) => { let newTimezone; if(this.isNotUndefined(timezone)) { if(this.isValidTimezone(timezone)){ - newTimezone = timezone; + newTimezone = timezone; } else { logger.error(`Invalid timezone = ${timezone}`); syncCliLogger.info(chalk.red(Constants.userMessages.INVALID_TIMEZONE)); @@ -442,11 +445,11 @@ exports.setProjectId = (bsConfig, args, cypressConfigFile) => { } else if(!this.isUndefined(process.env.CYPRESS_PROJECT_ID)) { return process.env.CYPRESS_PROJECT_ID; } else if(!this.isUndefined(bsConfig.run_settings["projectId"])) { - return bsConfig.run_settings["projectId"]; + return bsConfig.run_settings["projectId"]; } else { // ignore reading cypressconfig if enforce_settings is passed if (this.isUndefinedOrFalse(bsConfig.run_settings.enforce_settings) && !this.isUndefined(cypressConfigFile) && !this.isUndefined(cypressConfigFile["projectId"])) { - return cypressConfigFile["projectId"]; + return cypressConfigFile["projectId"]; } } } @@ -495,7 +498,7 @@ exports.setUserSpecs = (bsConfig, args) => { bsConfig.run_settings.specs = process.env.BROWSERSTACK_RERUN_TESTS; return; } - + let bsConfigSpecs = bsConfig.run_settings.specs; if (!this.isUndefined(args.specs)) { @@ -591,7 +594,7 @@ exports.setSystemEnvs = (bsConfig) => { OBSERVABILITY_ENV_VARS.forEach(key => { envKeys[key] = process.env[key]; }); - + let gitConfigPath = o11yHelpers.findGitConfig(process.cwd()); if(!o11yHelpers.isBrowserstackInfra()) process.env.OBSERVABILITY_GIT_CONFIG_PATH_LOCAL = gitConfigPath; if(gitConfigPath) { @@ -1018,14 +1021,27 @@ exports.checkLocalBinaryRunning = (bsConfig, localIdentifier) => { }, body: JSON.stringify({ localIdentifier: localIdentifier}), }; - return new Promise ( function(resolve, reject) { - request.post(options, function (err, resp, body) { - if(err){ - reject(err); + + const axiosConfig = { + auth: { + username: options.auth.user, + password: options.auth.password + }, + headers: options.headers + }; + setAxiosProxy(axiosConfig); + + return new Promise (async function(resolve, reject) { + try { + const response = await axios.post(options.url, { + localIdentifier: localIdentifier + }, axiosConfig); + resolve(response.data) + } catch (error) { + if(error.response) { + reject(error.response.data.message); } - let response = JSON.parse(body); - resolve(response); - }); + } }); }; @@ -1152,7 +1168,7 @@ exports.getNumberOfSpecFiles = (bsConfig, args, cypressConfig, turboScaleSession let files if (globSearchPattern) { let fileMatchedWithBstackSpecPattern = glob.sync(globSearchPattern, { - cwd: bsConfig.run_settings.cypressProjectDir, matchBase: true, ignore: ignoreFiles + cwd: bsConfig.run_settings.cypressProjectDir, matchBase: true, ignore: ignoreFiles }); fileMatchedWithBstackSpecPattern = fileMatchedWithBstackSpecPattern.map((file) => path.resolve(bsConfig.run_settings.cypressProjectDir, file)) @@ -1164,8 +1180,17 @@ exports.getNumberOfSpecFiles = (bsConfig, args, cypressConfig, turboScaleSession logger.debug(`${files ? files.length : 0} spec files found`); if (turboScaleSession) { - // remove unwanted path prefix for turboscale - files = files.map((x) => { return path.join(testFolderPath, x.split(testFolderPath)[1]) }) + if (bsConfig.run_settings.cypress_config_file && bsConfig.run_settings.cypress_config_filename !== 'false') { + const configFilePath = path.resolve(bsConfig.run_settings.cypressConfigFilePath); + const directory = path.join(path.dirname(configFilePath), '/'); + // remove unwanted path prefix for turboscale + files = files.map((x) => { return path.join('', x.split(directory)[1]) }) + } else { + files = files.map((x) => { return path.join(testFolderPath, x.split(testFolderPath)[1]) }) + } + + // sanitize spec list as CLI might be running on windows + files = files.map((x) => { return x.replaceAll("\\", "/") }) // setting specs for turboScale as we don't have patched API for turboscale so we will rely on info from CLI bsConfig.run_settings.specs = files; } @@ -1431,12 +1456,12 @@ exports.setEnforceSettingsConfig = (bsConfig, args) => { /** * Splits a string by a specified splitChar. * If leftLimiter and rightLimiter are specified then string won't be splitted if the splitChar is within the range - * + * * @param {String} str - the string that needs to be splitted * @param {String} splitChar - the split string/char from which the string will be splited * @param {String} [leftLimiter] - the starting string/char of the range * @param {String} [rightLimiter] - the ending string/char of the range - * + * * @example Example usage of splitStringByCharButIgnoreIfWithinARange. * // returns ["folder/A/B", "folder/{C,D}/E"] * utils.splitStringByCharButIgnoreIfWithinARange("folder/A/B,folder/{C,D}/E", ",", "{", "}"); @@ -1475,7 +1500,7 @@ exports.splitStringByCharButIgnoreIfWithinARange = (str, splitChar, leftLimiter, // blindly send other passed configs with run_settings and handle at backend exports.setOtherConfigs = (bsConfig, args) => { - if(o11yHelpers.isTestObservabilitySession() && process.env.BS_TESTOPS_JWT) { + if(shouldProcessEventForTesthub()) { bsConfig["run_settings"]["reporter"] = TEST_OBSERVABILITY_REPORTER; return; } @@ -1494,7 +1519,15 @@ exports.setOtherConfigs = (bsConfig, args) => { exports.readBsConfigJSON = (bsConfigPath) => { try { fs.accessSync(bsConfigPath, fs.constants.R_OK); - return fs.readFileSync(bsConfigPath, 'utf-8'); + const configContent = fs.readFileSync(bsConfigPath, 'utf-8'); + try { + const bsConfig = JSON.parse(configContent); + const normalizedBsConfig = exports.normalizeTestReportingConfig(bsConfig); + return JSON.stringify(normalizedBsConfig); + } catch (err) { + logger.error(`Error parsing JSON from ${bsConfigPath}:`, err); + return null; + } } catch (err) { return null; } @@ -1537,14 +1570,14 @@ exports.setCLIMode = (bsConfig, args) => { exports.formatRequest = (err, resp, body) => { return { err, - status: resp ? resp.statusCode : null, + status: resp ? resp.status : null, body: body ? util.format('%j', body) : null } } exports.setDebugMode = (args) => { if(args.cliDebug || String(process.env.DEBUG).toLowerCase() === 'true'){ - args.cliDebug ? + args.cliDebug ? logger.info("CLI is running with the --cli-debug argument. Running CLI in the debug mode...") : logger.info("DEBUG environment variable set to 'true'. Running CLI in the debug mode...") ; transports.loggerConsole.level = 'debug'; @@ -1557,78 +1590,80 @@ exports.setDebugMode = (args) => { exports.stopBrowserStackBuild = async (bsConfig, args, buildId, rawArgs, buildReportData = null) => { let that = this; - return new Promise(function (resolve, reject) { - let url = config.buildStopUrl + buildId; - let options = { - url: url, - auth: { - username: bsConfig["auth"]["username"], - password: bsConfig["auth"]["access_key"], - }, - headers: { - 'User-Agent': that.getUserAgent(), - }, - }; - if (Constants.turboScaleObj.enabled) { - options.url = `${config.turboScaleBuildsUrl}/${buildId}/stop`; - } + let url = config.buildStopUrl + buildId; + let options = { + url: url, + auth: { + username: bsConfig["auth"]["username"], + password: bsConfig["auth"]["access_key"], + }, + headers: { + 'User-Agent': that.getUserAgent(), + }, + }; - let message = null; - let messageType = null; - let errorCode = null; - let build = null; - request.post(options, function(err, resp, data) { - if(err) { - message = Constants.userMessages.BUILD_STOP_FAILED; - messageType = Constants.messageTypes.ERROR; - errorCode = 'api_failed_build_stop'; + const axiosConfig = { + auth: options.auth, + headers: options.headers + }; + setAxiosProxy(axiosConfig); + + if (Constants.turboScaleObj.enabled) { + options.url = `${config.turboScaleBuildsUrl}/${buildId}/stop`; + } + + let message = null; + let messageType = null; + let errorCode = null; + let build = null; + + try { + const response = await axios.post(options.url, {}, axiosConfig); + + build = response.data; + if (response.status == 299) { + messageType = Constants.messageTypes.INFO; + errorCode = 'api_deprecated'; + + if (build) { + message = build.message; logger.info(message); } else { - try { - build = JSON.parse(data); - if (resp.statusCode == 299) { - messageType = Constants.messageTypes.INFO; - errorCode = 'api_deprecated'; - - if (build) { - message = build.message; - logger.info(message); - } else { - message = Constants.userMessages.API_DEPRECATED; - logger.info(message); - } - } else if (resp.statusCode != 200) { - messageType = Constants.messageTypes.ERROR; - errorCode = 'api_failed_build_stop'; - - if (build) { - message = `${ - Constants.userMessages.BUILD_STOP_FAILED - } with error: \n${JSON.stringify(build, null, 2)}`; - logger.error(message); - if (build.message === 'Unauthorized') errorCode = 'api_auth_failed'; - } else { - message = Constants.userMessages.BUILD_STOP_FAILED; - logger.error(message); - } - } else { - messageType = Constants.messageTypes.SUCCESS; - message = `${JSON.stringify(build, null, 2)}`; - logger.info(message); - } - } catch(err) { - message = Constants.userMessages.BUILD_STOP_FAILED; - messageType = Constants.messageTypes.ERROR; - errorCode = 'api_failed_build_stop'; - logger.info(message); - } finally { - that.sendUsageReport(bsConfig, args, message, messageType, errorCode, buildReportData, rawArgs); - } + message = Constants.userMessages.API_DEPRECATED; + logger.info(message); } - resolve(); - }); - }); + } else if (response.status !== 200) { + messageType = Constants.messageTypes.ERROR; + errorCode = 'api_failed_build_stop'; + + if (build) { + message = `${ + Constants.userMessages.BUILD_STOP_FAILED + } with error: \n${JSON.stringify(build, null, 2)}`; + logger.error(message); + if (build.message === 'Unauthorized') errorCode = 'api_auth_failed'; + } else { + message = Constants.userMessages.BUILD_STOP_FAILED; + logger.error(message); + } + } else { + messageType = Constants.messageTypes.SUCCESS; + message = `${JSON.stringify(build, null, 2)}`; + logger.info(message); + } + } catch(err) { + if(err.response) { + message = `${ + Constants.userMessages.BUILD_STOP_FAILED + } with error: ${err.response.data.message}`; + messageType = Constants.messageTypes.ERROR; + errorCode = 'api_failed_build_stop'; + logger.error(message); + } + } finally { + that.sendUsageReport(bsConfig, args, message, messageType, errorCode, buildReportData, rawArgs); + } } exports.setProcessHooks = (buildId, bsConfig, bsLocal, args, buildReportData) => { @@ -1645,6 +1680,7 @@ exports.setProcessHooks = (buildId, bsConfig, bsLocal, args, buildReportData) => process.on('uncaughtException', processExitHandler.bind(this, bindData)); } + exports.setO11yProcessHooks = (() => { let bindData = {}; let handlerAdded = false; @@ -1719,15 +1755,14 @@ exports.getVideoConfig = (cypressConfig, bsConfig = {}) => { video: true, videoUploadOnPasses: true } - // Reading bsconfig in case of enforce_settings - if ( this.isUndefined(bsConfig.run_settings) || this.isUndefinedOrFalse(bsConfig.run_settings.enforce_settings) ) { - if (!this.isUndefined(cypressConfig.video)) conf.video = cypressConfig.video; - if (!this.isUndefined(cypressConfig.videoUploadOnPasses)) conf.videoUploadOnPasses = cypressConfig.videoUploadOnPasses; - } - else { - if (!this.isUndefined(bsConfig.run_settings) && !this.isUndefined(bsConfig.run_settings.video)) conf.video = bsConfig.run_settings.video; - if (!this.isUndefined(bsConfig.run_settings) && !this.isUndefined(bsConfig.run_settings.videoUploadOnPasses)) conf.videoUploadOnPasses = bsConfig.run_settings.videoUploadOnPasses; - } + + // here if we have enforce_setting: true then cypressConfig will be {} as we are not reading cypress.config.js file in that case + if (!this.isUndefined(bsConfig.run_settings) && !this.isUndefined(bsConfig.run_settings.video)) conf.video = bsConfig.run_settings.video; + if (!this.isUndefined(bsConfig.run_settings) && !this.isUndefined(bsConfig.run_settings.videoUploadOnPasses)) conf.videoUploadOnPasses = bsConfig.run_settings.videoUploadOnPasses; + if (!this.isUndefined(cypressConfig.video)) { conf.video = cypressConfig.video;} + if (!this.isUndefined(cypressConfig.videoUploadOnPasses)){ conf.videoUploadOnPasses = cypressConfig.videoUploadOnPasses;} + if (!this.isUndefined(cypressConfig.e2e) && !this.isUndefined(cypressConfig.e2e.video)) { conf.video = cypressConfig.e2e.video;} + if (!this.isUndefined(cypressConfig.e2e) && !this.isUndefined(cypressConfig.e2e.videoUploadOnPasses)){ conf.videoUploadOnPasses = cypressConfig.e2e.videoUploadOnPasses;} // set video in cli config in case of cypress 13 or above as default value is false there. this.setVideoCliConfig(bsConfig,conf); @@ -1754,5 +1789,256 @@ exports.getMajorVersion = (version) => { } catch(error) { logger.debug(`Some Error occurred while fetching major version of ${version}. Returning null. Error Details: ${error}`) return null; - } + } +} + +const base64UrlDecode = (str) => { + const base64 = str.replace(/-/g, '+').replace(/_/g, '/'); + const buffer = Buffer.from(base64, 'base64'); + return buffer.toString('utf-8'); +}; + +exports.decodeJWTToken = (token) => { + try{ + const parts = token.split('.'); + if (parts.length < 2) { + throw new Error('Invalid JWT token'); + } + const payload = JSON.parse(base64UrlDecode(parts[1])); + return payload + } catch (error) { + logger.err("Error in token decoding with error:", error.message); + return undefined; + } +} + +exports.validateAutoImportConflict = (runSettings) => { + const constants = require('./constants'); + + // Validate auto_import_dev_dependencies type + if (runSettings.auto_import_dev_dependencies !== undefined && + typeof runSettings.auto_import_dev_dependencies !== 'boolean') { + throw new Error(constants.validationMessages.AUTO_IMPORT_INVALID_TYPE); + } + + // Skip validation if auto_import_dev_dependencies is not enabled + if (!runSettings.auto_import_dev_dependencies) { + return; + } + + // Check if any manual npm dependency configurations have values + const hasNpmDeps = runSettings.npm_dependencies && + typeof runSettings.npm_dependencies === 'object' && + Object.keys(runSettings.npm_dependencies).length > 0; + + const hasWinDeps = runSettings.win_npm_dependencies && + typeof runSettings.win_npm_dependencies === 'object' && + Object.keys(runSettings.win_npm_dependencies).length > 0; + + const hasMacDeps = runSettings.mac_npm_dependencies && + typeof runSettings.mac_npm_dependencies === 'object' && + Object.keys(runSettings.mac_npm_dependencies).length > 0; + + if (hasNpmDeps || hasWinDeps || hasMacDeps) { + throw new Error(constants.validationMessages.AUTO_IMPORT_CONFLICT_ERROR); + } +}; + +exports.readPackageJsonDevDependencies = (projectDir) => { + const fs = require('fs'); + const path = require('path'); + const constants = require('./constants'); + + const packageJsonPath = path.join(projectDir, 'package.json'); + + try { + const packageJsonContent = fs.readFileSync(packageJsonPath, 'utf8'); + + // Remove BOM if present + const cleanedContent = packageJsonContent.replace(/^\ufeff/, ''); + + if (!cleanedContent.trim()) { + throw new Error(constants.validationMessages.PACKAGE_JSON_MALFORMED); + } + + let packageJson; + try { + packageJson = JSON.parse(cleanedContent); + } catch (parseError) { + throw new Error(constants.validationMessages.PACKAGE_JSON_MALFORMED); + } + + if (typeof packageJson !== 'object' || packageJson === null || Array.isArray(packageJson)) { + throw new Error(constants.validationMessages.PACKAGE_JSON_NOT_OBJECT); + } + + // Handle missing devDependencies field + if (packageJson.devDependencies === undefined) { + return {}; + } + + // Validate devDependencies format + if (typeof packageJson.devDependencies !== 'object' || + packageJson.devDependencies === null || + Array.isArray(packageJson.devDependencies)) { + throw new Error(constants.validationMessages.DEVDEPS_INVALID_FORMAT); + } + + return packageJson.devDependencies; + + } catch (error) { + if (error.code === 'ENOENT') { + throw new Error(constants.validationMessages.PACKAGE_JSON_NOT_FOUND); + } else if (error.code === 'EACCES') { + throw new Error(constants.validationMessages.PACKAGE_JSON_PERMISSION_DENIED); + } else if (error.message.includes(constants.validationMessages.PACKAGE_JSON_MALFORMED) || + error.message.includes(constants.validationMessages.PACKAGE_JSON_NOT_OBJECT) || + error.message.includes(constants.validationMessages.DEVDEPS_INVALID_FORMAT)) { + throw error; + } else { + throw new Error(`Cannot read package.json: ${error.message}`); + } + } +}; + +exports.filterDependenciesWithRegex = (dependencies, excludePatterns) => { + const constants = require('./constants'); + + // Validate dependencies parameter + if (!dependencies || typeof dependencies !== 'object' || Array.isArray(dependencies)) { + throw new Error(constants.validationMessages.DEPENDENCIES_PARAM_INVALID); + } + + // Return all dependencies if no exclusion patterns + if (!excludePatterns) { + return dependencies; + } + + // Validate excludePatterns parameter + if (!Array.isArray(excludePatterns)) { + throw new Error(constants.validationMessages.EXCLUDE_DEPS_INVALID_TYPE); + } + + // Validate all patterns are strings + for (const pattern of excludePatterns) { + if (typeof pattern !== 'string') { + throw new Error(constants.validationMessages.EXCLUDE_DEPS_INVALID_PATTERNS); + } + } + + // If no patterns, return all dependencies + if (excludePatterns.length === 0) { + return dependencies; + } + + const filteredDependencies = {}; + + for (const [packageName, version] of Object.entries(dependencies)) { + let shouldExclude = false; + + for (const pattern of excludePatterns) { + // Skip empty patterns + if (!pattern) { + continue; + } + + try { + const regex = new RegExp(pattern); + if (regex.test(packageName)) { + shouldExclude = true; + break; + } + } catch (regexError) { + const errorMsg = constants.validationMessages.INVALID_REGEX_PATTERN.replace('{pattern}', pattern); + throw new Error(errorMsg); + } + } + + if (!shouldExclude) { + filteredDependencies[packageName] = version; + } + } + + return filteredDependencies; +}; + +exports.ensureBrowserstackCypressCliDependency = (npmDependencies) => { + // Validate npmDependencies parameter + if (npmDependencies === undefined || npmDependencies === null || + typeof npmDependencies !== 'object' || Array.isArray(npmDependencies)) { + return; + } + + // Check if browserstack-cypress-cli already exists + if ("browserstack-cypress-cli" in npmDependencies) { + return; + } + + logger.warn("Missing browserstack-cypress-cli not found in npm_dependencies"); + + // Get version from package.json (similar to getAgentVersion) + let version = "latest"; + try { + const packageJsonPath = path.join(__dirname, '../../package.json'); + if (fs.existsSync(packageJsonPath)) { + version = require(packageJsonPath).version; + } + } catch (err) { + logger.debug("Could not read package.json version, using 'latest'"); + } + + npmDependencies['browserstack-cypress-cli'] = version; + logger.warn(`Adding browserstack-cypress-cli version ${version} in npm_dependencies`); +}; + +exports.processAutoImportDependencies = (runSettings) => { + // Always run validation first + exports.validateAutoImportConflict(runSettings); + + // Skip processing if auto_import_dev_dependencies is not enabled + if (runSettings.auto_import_dev_dependencies != true) { + return; + } + + // Determine project directory using battle-tested logic + let projectDir; + if (runSettings.home_directory) { + projectDir = runSettings.home_directory; + } else { + const path = require('path'); + projectDir = path.dirname(runSettings.cypressConfigFilePath); + } + + // Read devDependencies from package.json + const devDependencies = exports.readPackageJsonDevDependencies(projectDir); + + // Apply exclusion filters + const filteredDependencies = exports.filterDependenciesWithRegex(devDependencies, runSettings.exclude_dependencies); + + // Set the npm_dependencies in runSettings + runSettings.npm_dependencies = filteredDependencies; + + // Ensure browserstack-cypress-cli dependency is present when auto import is enabled + exports.ensureBrowserstackCypressCliDependency(runSettings.npm_dependencies); +}; +exports.normalizeTestReportingEnvVars = () => { + if (!this.isUndefined(process.env.BROWSERSTACK_TEST_REPORTING)){ + process.env.BROWSERSTACK_TEST_OBSERVABILITY = process.env.BROWSERSTACK_TEST_REPORTING; + } + + if (!this.isUndefined(process.env.BROWSERSTACK_TEST_REPORTING_DEBUG)){ + process.env.BROWSERSTACK_OBSERVABILITY_DEBUG = process.env.BROWSERSTACK_TEST_REPORTING_DEBUG; + } +} + +exports.normalizeTestReportingConfig = (bsConfig) => { + if (!this.isUndefined(bsConfig["testReporting"])) { + bsConfig["testObservability"] = bsConfig["testReporting"]; + } + + if (!this.isUndefined(bsConfig["testReportingOptions"])) { + bsConfig["testObservabilityOptions"] = bsConfig["testReportingOptions"]; + } + + return bsConfig; } diff --git a/bin/helpers/zipUpload.js b/bin/helpers/zipUpload.js index 441f9ec0..8b59611c 100644 --- a/bin/helpers/zipUpload.js +++ b/bin/helpers/zipUpload.js @@ -1,26 +1,27 @@ 'use strict'; -const request = require("request"), - fs = require("fs"); +const fs = require("fs"); const cliProgress = require('cli-progress'); +const { default: axios } = require("axios"); +const FormData = require("form-data"); const config = require("./config"), logger = require("./logger").winstonLogger, Constants = require("./constants"), utils = require("./utils"); +const { setAxiosProxy } = require('./helper'); const purgeUploadBar = (obj) => { obj.bar1.update(100, { speed: ((obj.size / (Date.now() - obj.startTime)) / 125).toFixed(2) //kbits per sec }); obj.bar1.stop(); - clearInterval(obj.zipInterval); } const uploadSuits = (bsConfig, filePath, opts, obj) => { - return new Promise(function (resolve, reject) { + return new Promise(async function (resolve, reject) { let uploadProgressBarErrorFlags = { noConnectionReportSent: false, unknownErrorReportSent: false @@ -54,94 +55,69 @@ const uploadSuits = (bsConfig, filePath, opts, obj) => { let options = utils.generateUploadParams(bsConfig, filePath, opts.md5Data, opts.fileDetails) let responseData = null; - var r = request.post(options, function (err, resp, body) { - - if (err) { - reject({message: err, stacktrace: utils.formatRequest(err, resp, body)}); - } else { - try { - responseData = JSON.parse(body); - } catch (e) { - responseData = {}; - } - if (resp.statusCode != 200) { - if (resp.statusCode == 401) { - if (responseData && responseData["error"]) { - responseData["time"] = Date.now() - obj.startTime; - return reject({message: responseData["error"], stacktrace: utils.formatRequest(err, resp, body)}); - } else { - return reject({message: Constants.validationMessages.INVALID_DEFAULT_AUTH_PARAMS, stacktrace: utils.formatRequest(err, resp, body)}); - } - } - if (!opts.propogateError){ - purgeUploadBar(obj); - if (resp.statusCode == 413) { - return resolve({warn: Constants.userMessages.NODE_MODULES_LIMIT_EXCEEDED.replace("%SIZE%", (size / 1000000).toFixed(2))}); - } - return resolve({}) - } - if(responseData && responseData["error"]){ - responseData["time"] = Date.now() - obj.startTime; - reject({message: responseData["error"], stacktrace: utils.formatRequest(err, resp, body)}); + try { + const formData = new FormData(); + formData.append("file", fs.createReadStream(filePath)); + formData.append("filetype", opts.fileDetails.filetype); + formData.append("filename", opts.fileDetails.filename); + formData.append("zipMd5sum", opts.md5Data ? opts.md5Data : ''); + + const axiosConfig = { + auth: { + username: options.auth.user, + password: options.auth.password + }, + headers: options.headers, + onUploadProgress: (progressEvent) => { + let percent = parseInt(Math.floor((progressEvent.loaded * 100) / progressEvent.total)); + obj.bar1.update(percent, { + speed: ((progressEvent.bytes / (Date.now() - obj.startTime)) / 125).toFixed(2) //kbits per sec + }); + }, + }; + setAxiosProxy(axiosConfig); + + const response = await axios.post(options.url, formData, axiosConfig); + responseData = response.data; + purgeUploadBar(obj) + logger.info(`${opts.messages.uploadingSuccess} (${responseData[opts.md5ReturnKey]})`); + opts.cleanupMethod(); + responseData["time"] = Date.now() - obj.startTime; + resolve(responseData); + } catch (error) { + let responseData = null; + if(error.response){ + responseData = error.response.data; + if (error.response.status === 401) { + if (responseData && responseData.error) { + responseData.time = Date.now() - obj.startTime; + return reject({message: responseData.error, stacktrace: utils.formatRequest(responseData.error, error.response, responseData)}); } else { - if (resp.statusCode == 413) { - reject({message: Constants.userMessages.ZIP_UPLOAD_LIMIT_EXCEEDED, stacktrace: utils.formatRequest(err, resp, body)}); - } else { - reject({message: Constants.userMessages.ZIP_UPLOADER_NOT_REACHABLE, stacktrace: utils.formatRequest(err, resp, body)}); - } - } - } else { + return reject({message: Constants.validationMessages.INVALID_DEFAULT_AUTH_PARAMS, stacktrace: utils.formatRequest(error.response.statusText, error.response, responseData)}); + } + } + if (!opts.propogateError){ purgeUploadBar(obj); - logger.info(`${opts.messages.uploadingSuccess} (${responseData[opts.md5ReturnKey]})`); - opts.cleanupMethod(); - responseData["time"] = Date.now() - obj.startTime; - resolve(responseData); + if (error.response.status === 413) { + return resolve({warn: Constants.userMessages.NODE_MODULES_LIMIT_EXCEEDED.replace("%SIZE%", (size / 1000000).toFixed(2))}); + } + return resolve({}) } - } - }); - - obj.zipInterval = setInterval(function () { - const errorCode = 'update_upload_progress_bar_failed'; - try { - if (r && r.req && r.req.connection) { - let dispatched = r.req.connection._bytesDispatched; - let percent = dispatched * 100.0 / size; - obj.bar1.update(percent, { - speed: ((dispatched / (Date.now() - obj.startTime)) / 125).toFixed(2) //kbits per sec - }); + if(responseData && responseData["error"]){ + responseData["time"] = Date.now() - obj.startTime; + reject({message: responseData["error"], stacktrace: utils.formatRequest(error.response.statusText, error.response, responseData)}); } else { - if (!uploadProgressBarErrorFlags.noConnectionReportSent) { - logger.debug(Constants.userMessages.NO_CONNECTION_WHILE_UPDATING_UPLOAD_PROGRESS_BAR); - utils.sendUsageReport( - bsConfig, - null, - Constants.userMessages.NO_CONNECTION_WHILE_UPDATING_UPLOAD_PROGRESS_BAR, - Constants.messageTypes.WARNING, - errorCode, - null, - null - ); - uploadProgressBarErrorFlags.noConnectionReportSent = true; + if (error.response.status === 413) { + reject({message: Constants.userMessages.ZIP_UPLOAD_LIMIT_EXCEEDED, stacktrace: utils.formatRequest(error.response.statusText, error.response, responseData)}); + } else { + reject({message: Constants.userMessages.ZIP_UPLOADER_NOT_REACHABLE, stacktrace: utils.formatRequest(error.response.statusText, error.response, responseData)}); } } - } catch (error) { - if (!uploadProgressBarErrorFlags.unknownErrorReportSent) { - logger.debug('Unable to determine progress.'); - logger.debug(error); - utils.sendUsageReport( - bsConfig, - null, - error.stack, - Constants.messageTypes.WARNING, - errorCode, - null, - null - ); - uploadProgressBarErrorFlags.unknownErrorReportSent = true; - } + reject({message: error.response, stacktrace: utils.formatRequest(error.response.statusText, error.response, error.response.data)}); + } else { + reject({}) } - }, 150); - + } }); } diff --git a/bin/templates/configTemplate.js b/bin/templates/configTemplate.js index 297a2c23..5008d830 100644 --- a/bin/templates/configTemplate.js +++ b/bin/templates/configTemplate.js @@ -59,6 +59,8 @@ module.exports = function () { "parallels": "Number of parallels you want to run", "npm_dependencies": { }, + "auto_import_dev_dependencies": false, + "exclude_dependencies": [], "package_config_options": { }, "headless": true diff --git a/bin/testObservability/crashReporter/index.js b/bin/testObservability/crashReporter/index.js index 7433c164..1839c168 100644 --- a/bin/testObservability/crashReporter/index.js +++ b/bin/testObservability/crashReporter/index.js @@ -1,13 +1,13 @@ const fs = require('fs'); const path = require('path'); -const request = require('request'); -const http = require('http'); +const axios = require('axios'); const https = require('https'); +const { HttpsProxyAgent = require('https-proxy-agent') } = require('https-proxy-agent'); const logger = require("../../helpers/logger").winstonLogger; const utils = require('../../helpers/utils'); -const { API_URL, consoleHolder } = require('../helper/constants'); +const { API_URL, consoleHolder, TEST_REPORTING_ANALYTICS } = require('../helper/constants'); /* Below global methods are added here to remove cyclic dependency with helper.js, refactor later */ const httpsKeepAliveAgent = new https.Agent({ @@ -19,7 +19,7 @@ const httpsKeepAliveAgent = new https.Agent({ const debug = (text) => { if (process.env.BROWSERSTACK_OBSERVABILITY_DEBUG === "true" || process.env.BROWSERSTACK_OBSERVABILITY_DEBUG === "1") { - logger.info(`[ OBSERVABILITY ] ${text}`); + logger.info(`[ ${TEST_REPORTING_ANALYTICS} ] ${text}`); } } @@ -126,7 +126,7 @@ class CrashReporter { if (!this.credentialsForCrashReportUpload.username || !this.credentialsForCrashReportUpload.password) { return debug('[Crash_Report_Upload] Failed to parse user credentials while reporting crash') } - + const data = { hashed_id: process.env.BS_TESTOPS_BUILD_HASHED_ID, observability_version: { @@ -140,7 +140,7 @@ class CrashReporter { }, config: this.userConfigForReporting } - + const options = { auth: { ...this.credentialsForCrashReportUpload @@ -151,20 +151,29 @@ class CrashReporter { }, method: 'POST', url: `${API_URL}/api/v1/analytics`, - body: data, + data: data, json: true, agent: httpsKeepAliveAgent }; + + if(process.env.HTTP_PROXY){ + options.proxy = false + options.httpsAgent = new HttpsProxyAgent(process.env.HTTP_PROXY); + } else if (process.env.HTTPS_PROXY){ + options.proxy = false + options.httpsAgent = new HttpsProxyAgent(process.env.HTTPS_PROXY); + } - request(options, function callback(error, response, body) { - if(error) { - debug(`[Crash_Report_Upload] Failed due to ${error}`); - } else if(response.statusCode != 200) { - debug(`[Crash_Report_Upload] Failed due to ${response && response.body ? response.body : `Received response from BrowserStack Server with status : ${response.statusCode}`}`); - } else { - debug(`[Crash_Report_Upload] Success response: ${JSON.stringify({status: response.status, body: response.body})}`) - } - }); + axios(options) + .then(response => { + + if(response.status != 200) { + debug(`[Crash_Report_Upload] Failed due to ${response && response.data ? response.data : `Received response from BrowserStack Server with status : ${response.status}`}`); + } else { + debug(`[Crash_Report_Upload] Success response: ${JSON.stringify({status: response.status, body: response.data})}`) + } + }) + .catch(error => debug(`[Crash_Report_Upload] Failed due to ${error}`)); } catch(e) { debug(`[Crash_Report_Upload] Processing failed due to ${e && e.stack}`); } diff --git a/bin/testObservability/cypress/index.js b/bin/testObservability/cypress/index.js index 31c8d08e..bb898811 100644 --- a/bin/testObservability/cypress/index.js +++ b/bin/testObservability/cypress/index.js @@ -1,158 +1,333 @@ /* Event listeners + custom commands for Cypress */ /* Used to detect Gherkin steps */ -Cypress.on('log:added', (log) => { - return () => { - return cy.now('task', 'test_observability_step', { - log - }, {log: false}) +const STEP_KEYWORDS = ['given', 'when', 'then', 'and', 'but', '*']; + +let eventsQueue = []; +let testRunStarted = false; + +const browserStackLog = (message) => { + + if (!Cypress.env('BROWSERSTACK_LOGS')) return; + cy.task('browserstack_log', message); +} + +const shouldSkipCommand = (command) => { + if (!Cypress.env('BROWSERSTACK_O11Y_LOGS')) { + return true; + } + return command.attributes.name == 'log' || (command.attributes.name == 'task' && (['test_observability_platform_details', 'test_observability_step', 'test_observability_command', 'browserstack_log', 'test_observability_log'].some(event => command.attributes.args.includes(event)))); +} + +Cypress.on('log:changed', (attrs) => { + if (!Cypress.env('BROWSERSTACK_O11Y_LOGS')) return; + if (!attrs) return; + if (!attrs.createdAtTimestamp || !attrs.updatedAtTimestamp) return; + if (attrs.state !== 'passed' && attrs.state !== 'failed') return; + + if (attrs.name === 'assert') { + const assertMessage = (attrs.message || '') + const actualLocation = (attrs.testId === attrs.hookId) ? 'test' : 'hook'; + + eventsQueue.push({ + task: 'test_observability_command', + data: { + type: 'COMMAND_START', + command: { + attributes: { + id: attrs.id, + name: 'assert', + testId: attrs.testId, + hookId: attrs.hookId, + args: [assertMessage] + }, + state: 'pending', + started_at: new Date(attrs.createdAtTimestamp).toISOString(), + location: actualLocation + } + }, + options: { log: false } + }); + + eventsQueue.push({ + task: 'test_observability_command', + data: { + type: 'COMMAND_END', + command: { + attributes: { + id: attrs.id, + name: 'assert', + testId: attrs.testId, + hookId: attrs.hookId, + args: [assertMessage] + }, + state: attrs.state, + finished_at: new Date(attrs.updatedAtTimestamp).toISOString(), + location: actualLocation + } + }, + options: { log: false } + }); + } + + const keyword = (attrs.displayName || attrs.name || '').trim(); + + if (STEP_KEYWORDS.includes(keyword.toLowerCase())) { + const text = (attrs.message || '') + + eventsQueue.push({ + task: 'test_observability_step', + data: { + log: { + name: 'step', + chainerId: attrs.chainerId, + consoleProps: { step: { keyword, text } } + }, + started_at: new Date(attrs.createdAtTimestamp).toISOString(), + finished_at: new Date(attrs.updatedAtTimestamp).toISOString() + }, + options: { log: false } + }); + + if (attrs.state === 'failed') { + eventsQueue.push({ + task: 'test_observability_step', + data: { + log: { + name: 'then', + type: 'child', + chainerId: attrs.chainerId, + state: attrs.state, + err: attrs.err + }, + finished_at: new Date(attrs.updatedAtTimestamp).toISOString() + }, + options: { log: false } + }); + } } }); Cypress.on('command:start', (command) => { - if(!command || !command.attributes) return; - if(command.attributes.name == 'log' || (command.attributes.name == 'task' && (command.attributes.args.includes('test_observability_command') || command.attributes.args.includes('test_observability_log')))) { + + if (!command || !command.attributes) return; + if (shouldSkipCommand(command)) { return; } + /* Send command details */ - cy.now('task', 'test_observability_command', { - type: 'COMMAND_START', - command: { - attributes: { - id: command.attributes.id, - name: command.attributes.name, - args: command.attributes.args - }, - state: 'pending' - } - }, {log: false}).then((res) => { - }).catch((err) => { + eventsQueue.push({ + task: 'test_observability_command', + data: { + type: 'COMMAND_START', + command: { + attributes: { + id: command.attributes.id, + name: command.attributes.name, + args: command.attributes.args + }, + state: 'pending', + started_at: new Date().toISOString(), + location: testRunStarted ? 'test' : 'hook' + } + }, + options: { log: false } }); - /* Send platform details */ - cy.now('task', 'test_observability_platform_details', { - testTitle: Cypress.currentTest.title, - browser: Cypress.browser, - platform: Cypress.platform, - cypressVersion: Cypress.version - }, {log: false}).then((res) => { - }).catch((err) => { + let testTitle = ''; + try { + const runner = Cypress.mocha.getRunner(); + const ctx = runner.suite.ctx; + testTitle = ctx.currentTest.title || ctx._runnable.title; + } catch (error) { + // Silently handle if any property is undefined + } + + eventsQueue.push({ + task: 'test_observability_platform_details', + data: { + testTitle, + browser: Cypress.browser, + platform: Cypress.platform, + cypressVersion: Cypress.version + }, + options: { log: false } }); }); Cypress.on('command:retry', (command) => { - if(!command || !command.attributes) return; - if(command.attributes.name == 'log' || (command.attributes.name == 'task' && (command.attributes.args.includes('test_observability_command') || command.attributes.args.includes('test_observability_log')))) { + if (!command || !command.attributes) return; + if (shouldSkipCommand(command)) { return; } - cy.now('task', 'test_observability_command', { - type: 'COMMAND_RETRY', - command: { - _log: command._log, - error: { - message: command && command.error ? command.error.message : null, - isDefaultAssertionErr: command && command.error ? command.error.isDefaultAssertionErr : null + eventsQueue.push({ + task: 'test_observability_command', + data: { + type: 'COMMAND_RETRY', + command: { + _log: command._log, + error: { + message: command && command.error ? command.error.message : null, + isDefaultAssertionErr: command && command.error ? command.error.isDefaultAssertionErr : null + }, + location: testRunStarted ? 'test' : 'hook' } - } - }, {log: false}).then((res) => { - }).catch((err) => { + }, + options: { log: false } }); }); Cypress.on('command:end', (command) => { - if(!command || !command.attributes) return; - if(command.attributes.name == 'log' || (command.attributes.name == 'task' && (command.attributes.args.includes('test_observability_command') || command.attributes.args.includes('test_observability_log')))) { + if (!command || !command.attributes) return; + if (shouldSkipCommand(command)) { return; } - cy.now('task', 'test_observability_command', { - 'type': 'COMMAND_END', - 'command': { - 'attributes': { - 'id': command.attributes.id, - 'name': command.attributes.name, - 'args': command.attributes.args - }, - 'state': command.state - } - }, {log: false}).then((res) => { - }).catch((err) => { + eventsQueue.push({ + task: 'test_observability_command', + data: { + 'type': 'COMMAND_END', + 'command': { + 'attributes': { + 'id': command.attributes.id, + 'name': command.attributes.name, + 'args': command.attributes.args + }, + 'state': command.state, + finished_at: new Date().toISOString(), + location: testRunStarted ? 'test' : 'hook' + } + }, + options: { log: false } }); }); -Cypress.Commands.overwrite('log', (originalFn, ...args) => { - if(args.includes('test_observability_log') || args.includes('test_observability_command')) return; +Cypress.on('command:enqueued', (attrs) => { + if (!Cypress.env('BROWSERSTACK_O11Y_LOGS')) return; + if (!attrs || attrs.name !== 'log') return; + const args = attrs.args || []; + if (args.includes('test_observability_log') || args.includes('test_observability_command')) return; const message = args.reduce((result, logItem) => { if (typeof logItem === 'object') { return [result, JSON.stringify(logItem)].join(' '); } - return [result, logItem ? logItem.toString() : ''].join(' '); }, ''); - cy.now('task', 'test_observability_log', { - 'level': 'info', - message, - }, {log: false}).then((res) => { - }).catch((err) => { + eventsQueue.push({ + task: 'test_observability_log', + data: { + level: 'info', + message, + timestamp: new Date().toISOString() + }, + options: { log: false } }); +}); + +Cypress.Commands.overwrite('log', (originalFn, ...args) => { + if (args.includes('test_observability_log') || args.includes('test_observability_command')) return; originalFn(...args); }); Cypress.Commands.add('trace', (message, file) => { - cy.now('task', 'test_observability_log', { - level: 'trace', - message, - file, - }).then((res) => { - }).catch((err) => { + eventsQueue.push({ + task: 'test_observability_log', + data: { + level: 'trace', + message, + file, + }, + options: { log: false } }); }); Cypress.Commands.add('logDebug', (message, file) => { - cy.now('task', 'test_observability_log', { - level: 'debug', - message, - file, - }).then((res) => { - }).catch((err) => { + eventsQueue.push({ + task: 'test_observability_log', + data: { + level: 'debug', + message, + file, + }, + options: { log: false } }); }); Cypress.Commands.add('info', (message, file) => { - cy.now('task', 'test_observability_log', { - level: 'info', - message, - file, - }).then((res) => { - }).catch((err) => { + eventsQueue.push({ + task: 'test_observability_log', + data: { + level: 'info', + message, + file, + }, + options: { log: false } }); }); Cypress.Commands.add('warn', (message, file) => { - cy.now('task', 'test_observability_log', { - level: 'warn', - message, - file, - }).then((res) => { - }).catch((err) => { + eventsQueue.push({ + task: 'test_observability_log', + data: { + level: 'warn', + message, + file, + }, + options: { log: false } }); }); Cypress.Commands.add('error', (message, file) => { - cy.now('task', 'test_observability_log', { - level: 'error', - message, - file, - }).then((res) => { - }).catch((err) => { + eventsQueue.push({ + task: 'test_observability_log', + data: { + level: 'error', + message, + file, + }, + options: { log: false } }); }); Cypress.Commands.add('fatal', (message, file) => { - cy.now('task', 'test_observability_log', { - level: 'fatal', - message, - file, - }).then((res) => { - }).catch((err) => { + eventsQueue.push({ + task: 'test_observability_log', + data: { + level: 'fatal', + message, + file, + }, + options: { log: false } }); }); + +beforeEach(() => { + /* browserstack internal helper hook */ + + if (!Cypress.env('BROWSERSTACK_O11Y_LOGS')) { + return; + } + + if (eventsQueue.length > 0) { + eventsQueue.forEach(event => { + cy.task(event.task, event.data, event.options); + }); + } + eventsQueue = []; + testRunStarted = true; +}); + +afterEach(function() { + /* browserstack internal helper hook */ + if (!Cypress.env('BROWSERSTACK_O11Y_LOGS')) { + return; + } + + if (eventsQueue.length > 0) { + eventsQueue.forEach(event => { + cy.task(event.task, event.data, event.options); + }); + } + + eventsQueue = []; + testRunStarted = false; +}); diff --git a/bin/testObservability/helper/cleanupQueueSync.js b/bin/testObservability/helper/cleanupQueueSync.js index aa2603f4..3cf76c10 100644 --- a/bin/testObservability/helper/cleanupQueueSync.js +++ b/bin/testObservability/helper/cleanupQueueSync.js @@ -2,10 +2,9 @@ * Sending all the remaining queues for synchronous manner */ -const RequestQueueHandler = require('./requestQueueHandler'); +const requestHandler = require('./requestQueueHandler'); const shutdown = async () => { - const requestHandler = new RequestQueueHandler(); requestHandler.queue = require(process.argv[2].trim()); await requestHandler.shutdown(); } diff --git a/bin/testObservability/helper/constants.js b/bin/testObservability/helper/constants.js index dbf5e053..d3013f40 100644 --- a/bin/testObservability/helper/constants.js +++ b/bin/testObservability/helper/constants.js @@ -16,6 +16,7 @@ exports.IPC_EVENTS = { exports.OBSERVABILITY_ENV_VARS = [ "BROWSERSTACK_TEST_OBSERVABILITY", + "BROWSERSTACK_TEST_REPORTING", "BROWSERSTACK_AUTOMATION", "BS_TESTOPS_BUILD_COMPLETED", "BS_TESTOPS_JWT", @@ -23,6 +24,7 @@ exports.OBSERVABILITY_ENV_VARS = [ "BS_TESTOPS_ALLOW_SCREENSHOTS", "OBSERVABILITY_LAUNCH_SDK_VERSION", "BROWSERSTACK_OBSERVABILITY_DEBUG", + "BROWSERSTACK_TEST_REPORTING_DEBUG", "OBS_CRASH_REPORTING_USERNAME", "OBS_CRASH_REPORTING_ACCESS_KEY", "OBS_CRASH_REPORTING_BS_CONFIG_PATH", @@ -34,3 +36,5 @@ exports.TEST_OBSERVABILITY_REPORTER = 'browserstack-cypress-cli/bin/testObservab exports.TEST_OBSERVABILITY_REPORTER_LOCAL = path.join(__dirname, '..', 'reporter'); exports.PENDING_QUEUES_FILE = `pending_queues_${process.pid}.json`; + +exports.TEST_REPORTING_ANALYTICS = 'TEST REPORTING AND ANALYTICS'; diff --git a/bin/testObservability/helper/helper.js b/bin/testObservability/helper/helper.js index 1b7da41f..264639b5 100644 --- a/bin/testObservability/helper/helper.js +++ b/bin/testObservability/helper/helper.js @@ -1,8 +1,6 @@ const fs = require('fs'); const path = require('path'); -const http = require('http'); const https = require('https'); -const request = require('requestretry'); const { v4: uuidv4 } = require('uuid'); const os = require('os'); const { promisify } = require('util'); @@ -10,6 +8,8 @@ const gitconfig = require('gitconfiglocal'); const { spawn, execSync } = require('child_process'); const glob = require('glob'); const util = require('util'); +const axios = require('axios'); +const { HttpsProxyAgent = require('https-proxy-agent') } = require('https-proxy-agent'); const { runOptions } = require('../../helpers/runnerArgs') @@ -27,7 +27,7 @@ const GLOBAL_MODULE_PATH = execSync('npm root -g').toString().trim(); const { name, version } = require('../../../package.json'); const { CYPRESS_V10_AND_ABOVE_CONFIG_FILE_EXTENSIONS } = require('../../helpers/constants'); -const { consoleHolder, API_URL, TEST_OBSERVABILITY_REPORTER, TEST_OBSERVABILITY_REPORTER_LOCAL } = require('./constants'); +const { consoleHolder, API_URL, TEST_OBSERVABILITY_REPORTER, TEST_OBSERVABILITY_REPORTER_LOCAL, TEST_REPORTING_ANALYTICS } = require('./constants'); const ALLOWED_MODULES = [ 'cypress/package.json', @@ -43,13 +43,13 @@ exports.pending_test_uploads = { exports.debugOnConsole = (text) => { if ((process.env.BROWSERSTACK_OBSERVABILITY_DEBUG + '') === "true" || (process.env.BROWSERSTACK_OBSERVABILITY_DEBUG + '') === "1") { - consoleHolder.log(`[ OBSERVABILITY ] ${text}`); + consoleHolder.log(`[ ${TEST_REPORTING_ANALYTICS} ] ${text}`); } } exports.debug = (text, shouldReport = false, throwable = null) => { if (process.env.BROWSERSTACK_OBSERVABILITY_DEBUG === "true" || process.env.BROWSERSTACK_OBSERVABILITY_DEBUG === "1") { - logger.info(`[ OBSERVABILITY ] ${text}`); + logger.info(`[ ${TEST_REPORTING_ANALYTICS} ] ${text}`); } if(shouldReport) { CrashReporter.getInstance().uploadCrashReport(text, throwable ? throwable && throwable.stack : null); @@ -105,7 +105,7 @@ exports.printBuildLink = async (shouldStopSession, exitCode = null) => { && process.env.BS_TESTOPS_BUILD_HASHED_ID != "null" && process.env.BS_TESTOPS_BUILD_HASHED_ID != "undefined") { console.log(); - logger.info(`Visit https://observability.browserstack.com/builds/${process.env.BS_TESTOPS_BUILD_HASHED_ID} to view build report, insights, and many more debugging information all at one place!\n`); + logger.info(`Visit https://automation.browserstack.com/builds/${process.env.BS_TESTOPS_BUILD_HASHED_ID} to view build report, insights, and many more debugging information all at one place!\n`); } } catch(err) { exports.debug('Build Not Found'); @@ -116,40 +116,57 @@ exports.printBuildLink = async (shouldStopSession, exitCode = null) => { if(exitCode) process.exit(exitCode); } -const nodeRequest = (type, url, data, config) => { - return new Promise(async (resolve, reject) => { - const options = {...config,...{ - method: type, - url: `${API_URL}/${url}`, - body: data, - json: config.headers['Content-Type'] === 'application/json', - agent: this.httpsKeepAliveAgent, - maxAttempts: 2 - }}; +exports.nodeRequest = (type, url, data, config) => { + const requestQueueHandler = require('./requestQueueHandler'); + return new Promise(async (resolve, reject) => { + const options = { + ...config, + method: type, + url: `${API_URL}/${url}`, + data: data, + httpsAgent: this.httpsKeepAliveAgent, + maxAttempts: 2, + headers: { + ...config.headers, + 'Content-Type': 'application/json;charset=utf-8', + "X-Forwarded-For": "127.0.0.1" + }, + clientIp: "127.0.0.1" + }; - if(url === exports.requestQueueHandler.screenshotEventUrl) { - options.agent = httpsScreenshotsKeepAliveAgent; - } + if(process.env.HTTP_PROXY){ + options.proxy = false + options.httpsAgent = new HttpsProxyAgent(process.env.HTTP_PROXY); + } else if (process.env.HTTPS_PROXY){ + options.proxy = false + options.httpsAgent = new HttpsProxyAgent(process.env.HTTPS_PROXY); + } - request(options, function callback(error, response, body) { - if(error) { - reject(error); - } else if(response.statusCode != 200) { - reject(response && response.body ? response.body : `Received response from BrowserStack Server with status : ${response.statusCode}`); - } else { - try { - if(typeof(body) !== 'object') body = JSON.parse(body); - } catch(e) { - if(!url.includes('/stop')) { - reject('Not a JSON response from BrowserStack Server'); + if(url === requestQueueHandler.screenshotEventUrl) { + options.agent = httpsScreenshotsKeepAliveAgent; + } + axios(options) + .then(response => { + + if(response.status != 200) { + reject(response && response.data ? response.data : `Received response from BrowserStack Server with status : ${response.status}`); + } else { + try { + const responseBody = typeof response.data === 'object' ? response.data : JSON.parse(response.data); + resolve({ data: responseBody }); + } catch (error) { + if (!url.includes('/stop')) { + reject('Not a JSON response from BrowserStack Server'); + } else { + resolve({ data: response.data }); + } + } } - } - resolve({ - data: body + }) + .catch(error => { + reject(error) }); - } }); - }); } exports.failureData = (errors,tag) => { @@ -252,7 +269,7 @@ exports.getPackageVersion = (package_, bsConfig = null) => { return packageVersion; } -const setEnvironmentVariablesForRemoteReporter = (BS_TESTOPS_JWT, BS_TESTOPS_BUILD_HASHED_ID, BS_TESTOPS_ALLOW_SCREENSHOTS, OBSERVABILITY_LAUNCH_SDK_VERSION) => { +exports.setEnvironmentVariablesForRemoteReporter = (BS_TESTOPS_JWT, BS_TESTOPS_BUILD_HASHED_ID, BS_TESTOPS_ALLOW_SCREENSHOTS, OBSERVABILITY_LAUNCH_SDK_VERSION) => { process.env.BS_TESTOPS_JWT = BS_TESTOPS_JWT; process.env.BS_TESTOPS_BUILD_HASHED_ID = BS_TESTOPS_BUILD_HASHED_ID; process.env.BS_TESTOPS_ALLOW_SCREENSHOTS = BS_TESTOPS_ALLOW_SCREENSHOTS; @@ -267,6 +284,10 @@ const getCypressCommandEventListener = (isJS) => { ) } +const isE2ESupportFile = (file) => { + return file.includes('e2e.js') || file.includes('e2e.ts'); +} + exports.setEventListeners = (bsConfig) => { try { const supportFilesData = helper.getSupportFiles(bsConfig, false); @@ -275,9 +296,9 @@ exports.setEventListeners = (bsConfig) => { if(err) return exports.debug('EXCEPTION IN BUILD START EVENT : Unable to parse cypress support files'); files.forEach(file => { try { - if(!file.includes('commands.js')) { + if (isE2ESupportFile(file) || !files.some(f => isE2ESupportFile(f))) { const defaultFileContent = fs.readFileSync(file, {encoding: 'utf-8'}); - + let cypressCommandEventListener = getCypressCommandEventListener(file.includes('js')); if(!defaultFileContent.includes(cypressCommandEventListener)) { let newFileContent = defaultFileContent + @@ -326,7 +347,7 @@ exports.setCrashReportingConfigFromReporter = (credentialsStr, bsConfigPath, cyp } } -const setCrashReportingConfig = (bsConfig, bsConfigPath) => { +exports.setCrashReportingConfig = (bsConfig, bsConfigPath) => { try { const browserstackConfigFile = utils.readBsConfigJSON(bsConfigPath); const cypressConfigFile = getCypressConfigFileContent(bsConfig, null); @@ -345,10 +366,10 @@ const setCrashReportingConfig = (bsConfig, bsConfigPath) => { exports.launchTestSession = async (user_config, bsConfigPath) => { setCrashReportingConfig(user_config, bsConfigPath); - + const obsUserName = user_config["auth"]["username"]; const obsAccessKey = user_config["auth"]["access_key"]; - + const BSTestOpsToken = `${obsUserName || ''}:${obsAccessKey || ''}`; if(BSTestOpsToken === '') { exports.debug('EXCEPTION IN BUILD START EVENT : Missing authentication token', true, null); @@ -386,6 +407,7 @@ exports.launchTestSession = async (user_config, bsConfigPath) => { sdkVersion: helper.getAgentVersion() } }; + const config = { auth: { username: obsUserName, @@ -397,11 +419,11 @@ exports.launchTestSession = async (user_config, bsConfigPath) => { } }; - const response = await nodeRequest('POST','api/v1/builds',data,config); + const response = await exports.nodeRequest('POST','api/v1/builds',data,config); exports.debug('Build creation successfull!'); process.env.BS_TESTOPS_BUILD_COMPLETED = true; - setEnvironmentVariablesForRemoteReporter(response.data.jwt, response.data.build_hashed_id, response.data.allow_screenshots, data.observability_version.sdkVersion); - if(this.isBrowserstackInfra()) helper.setBrowserstackCypressCliDependency(user_config); + exports.setEnvironmentVariablesForRemoteReporter(response.data.jwt, response.data.build_hashed_id, response.data.allow_screenshots, data.observability_version.sdkVersion); + if(this.isBrowserstackInfra() && (user_config.run_settings.auto_import_dev_dependencies != true)) helper.setBrowserstackCypressCliDependency(user_config); } catch(error) { if(!error.errorType) { if (error.response) { @@ -427,7 +449,7 @@ exports.launchTestSession = async (user_config, bsConfigPath) => { } process.env.BS_TESTOPS_BUILD_COMPLETED = false; - setEnvironmentVariablesForRemoteReporter(null, null, null); + exports.setEnvironmentVariablesForRemoteReporter(null, null, null); } } } @@ -480,13 +502,13 @@ exports.batchAndPostEvents = async (eventUrl, kind, data) => { 'Authorization': `Bearer ${process.env.BS_TESTOPS_JWT}`, 'Content-Type': 'application/json', 'X-BSTACK-TESTOPS': 'true' - } + } }; try { const eventsUuids = data.map(eventData => `${eventData.event_type}:${eventData.test_run ? eventData.test_run.uuid : (eventData.hook_run ? eventData.hook_run.uuid : null)}`).join(', '); - exports.debugOnConsole(`[Request Batch Send] for events:uuids ${eventsUuids}`); - const response = await nodeRequest('POST',eventUrl,data,config); + exports.debugOnConsole(`[Request Batch Send] for events:uuids ${eventsUuids}`); + const response = await exports.nodeRequest('POST',eventUrl,data,config); exports.debugOnConsole(`[Request Batch Response] for events:uuids ${eventsUuids}`); if(response.data.error) { throw({message: response.data.error}); @@ -497,18 +519,16 @@ exports.batchAndPostEvents = async (eventUrl, kind, data) => { } catch(error) { exports.debugOnConsole(`[Request Error] Error in sending request ${util.format(error)}`); if (error.response) { - exports.debug(`EXCEPTION IN ${kind} REQUEST TO TEST OBSERVABILITY : ${error.response.status} ${error.response.statusText} ${JSON.stringify(error.response.data)}`, true, error); + exports.debug(`EXCEPTION IN ${kind} REQUEST TO ${TEST_REPORTING_ANALYTICS} : ${error.response.status} ${error.response.statusText} ${JSON.stringify(error.response.data)}`, true, error); } else { - exports.debug(`EXCEPTION IN ${kind} REQUEST TO TEST OBSERVABILITY : ${error.message || error}`, true, error); + exports.debug(`EXCEPTION IN ${kind} REQUEST TO ${TEST_REPORTING_ANALYTICS} : ${error.message || error}`, true, error); } exports.pending_test_uploads.count = Math.max(0,exports.pending_test_uploads.count - data.length); } } -const RequestQueueHandler = require('./requestQueueHandler'); -exports.requestQueueHandler = new RequestQueueHandler(); - exports.uploadEventData = async (eventData, run=0) => { + const requestQueueHandler = require('./requestQueueHandler'); exports.debugOnConsole(`[uploadEventData] ${eventData.event_type}`); const log_tag = { ['TestRunStarted']: 'Test_Start_Upload', @@ -522,10 +542,10 @@ exports.uploadEventData = async (eventData, run=0) => { }[eventData.event_type]; if(run === 0 && process.env.BS_TESTOPS_JWT != "null") exports.pending_test_uploads.count += 1; - + if (process.env.BS_TESTOPS_BUILD_COMPLETED === "true") { if(process.env.BS_TESTOPS_JWT == "null") { - exports.debug(`EXCEPTION IN ${log_tag} REQUEST TO TEST OBSERVABILITY : missing authentication token`); + exports.debug(`EXCEPTION IN ${log_tag} REQUEST TO ${TEST_REPORTING_ANALYTICS}: missing authentication token`); exports.pending_test_uploads.count = Math.max(0,exports.pending_test_uploads.count-1); return { status: 'error', @@ -533,9 +553,9 @@ exports.uploadEventData = async (eventData, run=0) => { }; } else { let data = eventData, event_api_url = 'api/v1/event'; - - exports.requestQueueHandler.start(); - const { shouldProceed, proceedWithData, proceedWithUrl } = exports.requestQueueHandler.add(eventData); + + requestQueueHandler.start(); + const { shouldProceed, proceedWithData, proceedWithUrl } = requestQueueHandler.add(eventData); exports.debugOnConsole(`[Request Queue] ${eventData.event_type} with uuid ${eventData.test_run ? eventData.test_run.uuid : (eventData.hook_run ? eventData.hook_run.uuid : null)} is added`) if(!shouldProceed) { return; @@ -547,20 +567,20 @@ exports.uploadEventData = async (eventData, run=0) => { const config = { headers: { 'Authorization': `Bearer ${process.env.BS_TESTOPS_JWT}`, - 'Content-Type': 'application/json', + 'Content-Type': 'application/json;charset=utf-8', 'X-BSTACK-TESTOPS': 'true' } }; - + try { const eventsUuids = data.map(eventData => `${eventData.event_type}:${eventData.test_run ? eventData.test_run.uuid : (eventData.hook_run ? eventData.hook_run.uuid : null)}`).join(', '); exports.debugOnConsole(`[Request Send] for events:uuids ${eventsUuids}`); - const response = await nodeRequest('POST',event_api_url,data,config); + const response = await exports.nodeRequest('POST',event_api_url,data,config); exports.debugOnConsole(`[Request Repsonse] ${util.format(response.data)} for events:uuids ${eventsUuids}`) if(response.data.error) { throw({message: response.data.error}); } else { - exports.debug(`${event_api_url !== exports.requestQueueHandler.eventUrl ? log_tag : 'Batch-Queue'}[${run}] event successfull!`) + exports.debug(`${event_api_url !== requestQueueHandler.eventUrl ? log_tag : 'Batch-Queue'}[${run}] event successfull!`) exports.pending_test_uploads.count = Math.max(0,exports.pending_test_uploads.count - (event_api_url === 'api/v1/event' ? 1 : data.length)); return { status: 'success', @@ -570,9 +590,9 @@ exports.uploadEventData = async (eventData, run=0) => { } catch(error) { exports.debugOnConsole(`[Request Error] Error in sending request ${util.format(error)}`); if (error.response) { - exports.debug(`EXCEPTION IN ${event_api_url !== exports.requestQueueHandler.eventUrl ? log_tag : 'Batch-Queue'} REQUEST TO TEST OBSERVABILITY : ${error.response.status} ${error.response.statusText} ${JSON.stringify(error.response.data)}`, true, error); + exports.debug(`EXCEPTION IN ${event_api_url !== requestQueueHandler.eventUrl ? log_tag : 'Batch-Queue'} REQUEST TO ${TEST_REPORTING_ANALYTICS} : ${error.response.status} ${error.response.statusText} ${JSON.stringify(error.response.data)}`, true, error); } else { - exports.debug(`EXCEPTION IN ${event_api_url !== exports.requestQueueHandler.eventUrl ? log_tag : 'Batch-Queue'} REQUEST TO TEST OBSERVABILITY : ${error.message || error}`, true, error); + exports.debug(`EXCEPTION IN ${event_api_url !== requestQueueHandler.eventUrl ? log_tag : 'Batch-Queue'} REQUEST TO ${TEST_REPORTING_ANALYTICS} : ${error.message || error}`, true, error); } exports.pending_test_uploads.count = Math.max(0,exports.pending_test_uploads.count - (event_api_url === 'api/v1/event' ? 1 : data.length)); return { @@ -582,7 +602,7 @@ exports.uploadEventData = async (eventData, run=0) => { } } } else if (run >= 5) { - exports.debug(`EXCEPTION IN ${log_tag} REQUEST TO TEST OBSERVABILITY : Build Start is not completed and ${log_tag} retry runs exceeded`); + exports.debug(`EXCEPTION IN ${log_tag} REQUEST TO ${TEST_REPORTING_ANALYTICS} : Build Start is not completed and ${log_tag} retry runs exceeded`); if(process.env.BS_TESTOPS_JWT != "null") exports.pending_test_uploads.count = Math.max(0,exports.pending_test_uploads.count-1); return { status: 'error', @@ -613,7 +633,7 @@ exports.setTestObservabilityFlags = (bsConfig) => { isTestObservabilitySession = false; exports.debug(`EXCEPTION while parsing testObservability capability with error ${e}`, true, e); } - + /* browserstackAutomation */ let isBrowserstackInfra = true; try { @@ -624,7 +644,7 @@ exports.setTestObservabilityFlags = (bsConfig) => { isBrowserstackInfra = true; exports.debug(`EXCEPTION while parsing browserstackAutomation capability with error ${e}`, true, e); } - + if(isTestObservabilitySession) logger.warn("testObservability is set to true. Other test reporters you are using will be automatically disabled. Learn more at browserstack.com/docs/test-observability/overview/what-is-test-observability"); process.env.BROWSERSTACK_TEST_OBSERVABILITY = isTestObservabilitySession; @@ -648,7 +668,7 @@ exports.shouldReRunObservabilityTests = () => { exports.stopBuildUpstream = async () => { if (process.env.BS_TESTOPS_BUILD_COMPLETED === "true") { if(process.env.BS_TESTOPS_JWT == "null" || process.env.BS_TESTOPS_BUILD_HASHED_ID == "null") { - exports.debug('EXCEPTION IN stopBuildUpstream REQUEST TO TEST OBSERVABILITY : Missing authentication token'); + exports.debug(`EXCEPTION IN stopBuildUpstream REQUEST TO ${TEST_REPORTING_ANALYTICS} : Missing authentication token`); return { status: 'error', message: 'Token/buildID is undefined, build creation might have failed' @@ -664,9 +684,9 @@ exports.stopBuildUpstream = async () => { 'X-BSTACK-TESTOPS': 'true' } }; - + try { - const response = await nodeRequest('PUT',`api/v1/builds/${process.env.BS_TESTOPS_BUILD_HASHED_ID}/stop`,data,config); + const response = await exports.nodeRequest('PUT',`api/v1/builds/${process.env.BS_TESTOPS_BUILD_HASHED_ID}/stop`,data,config); if(response.data && response.data.error) { throw({message: response.data.error}); } else { @@ -678,9 +698,9 @@ exports.stopBuildUpstream = async () => { } } catch(error) { if (error.response) { - exports.debug(`EXCEPTION IN stopBuildUpstream REQUEST TO TEST OBSERVABILITY : ${error.response.status} ${error.response.statusText} ${JSON.stringify(error.response.data)}`, true, error); + exports.debug(`EXCEPTION IN stopBuildUpstream REQUEST TO ${TEST_REPORTING_ANALYTICS} : ${error.response.status} ${error.response.statusText} ${JSON.stringify(error.response.data)}`, true, error); } else { - exports.debug(`EXCEPTION IN stopBuildUpstream REQUEST TO TEST OBSERVABILITY : ${error.message || error}`, true, error); + exports.debug(`EXCEPTION IN stopBuildUpstream REQUEST TO ${TEST_REPORTING_ANALYTICS} : ${error.message || error}`, true, error); } return { status: 'error', diff --git a/bin/testObservability/helper/requestQueueHandler.js b/bin/testObservability/helper/requestQueueHandler.js index 172384f2..245a4bd8 100644 --- a/bin/testObservability/helper/requestQueueHandler.js +++ b/bin/testObservability/helper/requestQueueHandler.js @@ -39,7 +39,7 @@ class RequestQueueHandler { this.queue.splice(0,BATCH_SIZE); this.resetEventBatchPolling(); } - + return { shouldProceed: shouldProceed, proceedWithData: data, @@ -54,7 +54,7 @@ class RequestQueueHandler { shutdownSync = () => { this.removeEventBatchPolling('REMOVING'); - + fs.writeFileSync(path.join(__dirname, PENDING_QUEUES_FILE), JSON.stringify(this.queue)); this.queue = []; cp.spawnSync('node', [path.join(__dirname, 'cleanupQueueSync.js'), path.join(__dirname, PENDING_QUEUES_FILE)], {stdio: 'inherit'}); @@ -98,4 +98,4 @@ class RequestQueueHandler { } } -module.exports = RequestQueueHandler; +module.exports = new RequestQueueHandler(); diff --git a/bin/testObservability/plugin/index.js b/bin/testObservability/plugin/index.js index 6880eb75..d32ade0d 100644 --- a/bin/testObservability/plugin/index.js +++ b/bin/testObservability/plugin/index.js @@ -3,6 +3,12 @@ const { connectIPCClient } = require('./ipcClient'); const { IPC_EVENTS } = require('../helper/constants'); const browserstackTestObservabilityPlugin = (on, config, callbacks) => { + + try { + config.env.BROWSERSTACK_O11Y_LOGS = 'true'; + process.env.BROWSERSTACK_O11Y_LOGS = 'true'; + } catch (err) {} + connectIPCClient(config); on('task', { diff --git a/bin/testObservability/plugin/ipcServer.js b/bin/testObservability/plugin/ipcServer.js index 16fc7236..9ac1b398 100644 --- a/bin/testObservability/plugin/ipcServer.js +++ b/bin/testObservability/plugin/ipcServer.js @@ -1,6 +1,6 @@ const ipc = require('node-ipc'); const { consoleHolder } = require('../helper/constants'); -const { requestQueueHandler } = require('../helper/helper'); +const requestQueueHandler = require('../helper/requestQueueHandler'); exports.startIPCServer = (subscribeServerEvents, unsubscribeServerEvents) => { if (ipc.server) { @@ -13,17 +13,17 @@ exports.startIPCServer = (subscribeServerEvents, unsubscribeServerEvents) => { ipc.config.silent = true; ipc.serve(() => { - + ipc.server.on('socket.disconnected', (socket, destroyedSocketID) => { ipc.log(`client ${destroyedSocketID} has disconnected!`); }); - + ipc.server.on('destroy', () => { ipc.log('server destroyed'); }); - + subscribeServerEvents(ipc.server); - + process.on('exit', () => { unsubscribeServerEvents(ipc.server); ipc.server.stop(); @@ -31,8 +31,8 @@ exports.startIPCServer = (subscribeServerEvents, unsubscribeServerEvents) => { // on exit handler will block the process requestQueueHandler.shutdownSync(); }); - + }); - + ipc.server.start(); }; diff --git a/bin/testObservability/reporter/index.js b/bin/testObservability/reporter/index.js index d8c4cb9c..219a593c 100644 --- a/bin/testObservability/reporter/index.js +++ b/bin/testObservability/reporter/index.js @@ -11,8 +11,9 @@ const Mocha = requireModule('mocha'); // const Runnable = requireModule('mocha/lib/runnable'); const Runnable = require('mocha/lib/runnable'); // need to handle as this isn't present in older mocha versions const { v4: uuidv4 } = require('uuid'); +const http = require('http'); -const { IPC_EVENTS } = require('../helper/constants'); +const { IPC_EVENTS, TEST_REPORTING_ANALYTICS } = require('../helper/constants'); const { startIPCServer } = require('../plugin/ipcServer'); const HOOK_TYPES_MAP = { @@ -52,7 +53,6 @@ const { mapTestHooks, debug, isBrowserstackInfra, - requestQueueHandler, getHookSkippedTests, getOSDetailsFromSystem, findGitConfig, @@ -62,6 +62,7 @@ const { } = require('../helper/helper'); const { consoleHolder } = require('../helper/constants'); +const { shouldProcessEventForTesthub } = require('../../testhub/utils'); // this reporter outputs test results, indenting two spaces per suite class MyReporter { @@ -71,12 +72,16 @@ class MyReporter { this._testEnv = getTestEnv(); this._paths = new PathHelper({ cwd: process.cwd() }, this._testEnv.location_prefix); this.currentTestSteps = []; + this.httpServer = null; this.currentTestCucumberSteps = []; this.hooksStarted = {}; this.beforeHooks = []; + this.testIdMap = {}; + this.internalIdMap = {}; this.platformDetailsMap = {}; this.runStatusMarkedHash = {}; this.haveSentBuildUpdate = false; + this.startHttpServer(); this.registerListeners(); setCrashReportingConfigFromReporter(null, process.env.OBS_CRASH_REPORTING_BS_CONFIG_PATH, process.env.OBS_CRASH_REPORTING_CYPRESS_CONFIG_PATH); @@ -88,14 +93,17 @@ class MyReporter { }) .on(EVENT_HOOK_BEGIN, async (hook) => { + if (this.isInternalHook(hook)) return; debugOnConsole(`[MOCHA EVENT] EVENT_HOOK_BEGIN`); - if(this.testObservability == true) { + if(shouldProcessEventForTesthub()) { if(!hook.hookAnalyticsId) { hook.hookAnalyticsId = uuidv4(); } else if(this.runStatusMarkedHash[hook.hookAnalyticsId]) { delete this.runStatusMarkedHash[hook.hookAnalyticsId]; hook.hookAnalyticsId = uuidv4(); } + + if (hook.id) this.internalIdMap[hook.id] = hook.hookAnalyticsId; debugOnConsole(`[MOCHA EVENT] EVENT_HOOK_BEGIN for uuid: ${hook.hookAnalyticsId}`); hook.hook_started_at = (new Date()).toISOString(); hook.started_at = (new Date()).toISOString(); @@ -105,8 +113,9 @@ class MyReporter { }) .on(EVENT_HOOK_END, async (hook) => { + if (this.isInternalHook(hook)) return; debugOnConsole(`[MOCHA EVENT] EVENT_HOOK_END`); - if(this.testObservability == true) { + if(shouldProcessEventForTesthub()) { if(!this.runStatusMarkedHash[hook.hookAnalyticsId]) { if(!hook.hookAnalyticsId) { /* Hook objects don't maintain uuids in Cypress-Mocha */ @@ -126,12 +135,16 @@ class MyReporter { } }) - .on(EVENT_SUITE_END, (suite) => { + .on(EVENT_SUITE_END, (suite) => { + if (suite.root) { + debugOnConsole(`[MOCHA EVENT] EVENT_SUITE_END for root suite`); + this.internalIdMap = {}; + } }) .on(EVENT_TEST_PASS, async (test) => { debugOnConsole(`[MOCHA EVENT] EVENT_TEST_PASS`); - if(this.testObservability == true) { + if(shouldProcessEventForTesthub()) { debugOnConsole(`[MOCHA EVENT] EVENT_TEST_PASS for uuid: ${test.testAnalyticsId}`); if(!this.runStatusMarkedHash[test.testAnalyticsId]) { if(test.testAnalyticsId) this.runStatusMarkedHash[test.testAnalyticsId] = true; @@ -142,7 +155,7 @@ class MyReporter { .on(EVENT_TEST_FAIL, async (test, err) => { debugOnConsole(`[MOCHA EVENT] EVENT_TEST_FAIL`); - if(this.testObservability == true) { + if(shouldProcessEventForTesthub()) { debugOnConsole(`[MOCHA EVENT] EVENT_TEST_FAIL for uuid: ${test.testAnalyticsId}`); if((test.testAnalyticsId && !this.runStatusMarkedHash[test.testAnalyticsId]) || (test.hookAnalyticsId && !this.runStatusMarkedHash[test.hookAnalyticsId])) { if(test.testAnalyticsId) { @@ -158,7 +171,7 @@ class MyReporter { .on(EVENT_TEST_PENDING, async (test) => { debugOnConsole(`[MOCHA EVENT] EVENT_TEST_PENDING`); - if(this.testObservability == true) { + if(shouldProcessEventForTesthub()) { if(!test.testAnalyticsId) test.testAnalyticsId = uuidv4(); debugOnConsole(`[MOCHA EVENT] EVENT_TEST_PENDING for uuid: ${test.testAnalyticsId}`); if(!this.runStatusMarkedHash[test.testAnalyticsId]) { @@ -169,30 +182,28 @@ class MyReporter { }) .on(EVENT_TEST_BEGIN, async (test) => { - debugOnConsole(`[MOCHA EVENT] EVENT_TEST_BEGIN`); debugOnConsole(`[MOCHA EVENT] EVENT_TEST_BEGIN for uuid: ${test.testAnalyticsId}`); if (this.runStatusMarkedHash[test.testAnalyticsId]) return; - if(this.testObservability == true) { + if(shouldProcessEventForTesthub()) { await this.testStarted(test); } }) .on(EVENT_TEST_END, async (test) => { - debugOnConsole(`[MOCHA EVENT] EVENT_TEST_END`); debugOnConsole(`[MOCHA EVENT] EVENT_TEST_BEGIN for uuid: ${test.testAnalyticsId}`); if (this.runStatusMarkedHash[test.testAnalyticsId]) return; - if(this.testObservability == true) { + if(shouldProcessEventForTesthub()) { if(!this.runStatusMarkedHash[test.testAnalyticsId]) { if(test.testAnalyticsId) this.runStatusMarkedHash[test.testAnalyticsId] = true; await this.sendTestRunEvent(test); } } }) - + .once(EVENT_RUN_END, async () => { try { debugOnConsole(`[MOCHA EVENT] EVENT_RUN_END`); - if(this.testObservability == true) { + if(shouldProcessEventForTesthub()) { const hookSkippedTests = getHookSkippedTests(this.runner.suite); for(const test of hookSkippedTests) { if(!test.testAnalyticsId) test.testAnalyticsId = uuidv4(); @@ -208,6 +219,100 @@ class MyReporter { }); } + isInternalHook(hook) { + if (!hook || !hook.body) return false; + + const body = hook.body.toString(); + + // Keep your existing check for BrowserStack internal helpers + if (body.includes('/* browserstack internal helper hook */')) { + return true; + } + + // Filter duplicate hooks caused by @badeball + const isBadeballInternal = + body.includes('beforeEachHandler.call(this, context)') || + body.includes('afterEachHandler.call(this, context)') || + body.includes('beforeHandler.call(this, context)') || + body.includes('afterHandler.call(this, context)'); + + if (isBadeballInternal) { + return true; + } + + return false; +} + + async startHttpServer() { + if(this.httpServer !== null) return; + + try { + const httpModule = http; + this.httpServer = httpModule.createServer(async(req, res) => { + try { + // Set CORS headers + res.setHeader('Access-Control-Allow-Origin', '*'); + res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); + res.setHeader('Access-Control-Allow-Headers', 'Content-Type'); + + if (req.method === 'OPTIONS') { + res.writeHead(200); + res.end(); + return; + } + const parsedUrl = new URL(req.url, `http://${req.headers.host}`); + const pathname = parsedUrl.pathname; + const query = parsedUrl.searchParams; + + if (pathname === '/test-uuid' && req.method === 'GET') { + const testIdentifier = query.get('testIdentifier'); + + if (!testIdentifier) { + res.writeHead(400, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ + error: 'testIdentifier parameter is required', + testRunUuid: null + })); + return; + } + const testRunUuid = this.getTestId(testIdentifier); + + + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ testRunUuid: testRunUuid })); + } else { + res.writeHead(404, { 'Content-Type': 'text/plain' }); + res.end('Not Found'); + } + } catch (error) { + debugOnConsole(`Exception in handling HTTP request : ${error}`); + debug(`Exception in handling HTTP request : ${error}`, true, error); + res.writeHead(500, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ testRunUuid: null })); + } + }); + + const port = process.env.REPORTER_API_PORT_NO; + + this.httpServer.on('error', (error) => { + if (error.code === 'EADDRINUSE') { + debugOnConsole(`Port ${port} is already in use. HTTP server could not start.`); + debug(`Port ${port} is already in use. HTTP server could not start.`, true, error); + } else { + debugOnConsole(`Exception in starting reporter server : ${error}`); + debug(`Exception in starting reporter server : ${error}`, true, error); + } + }); + + this.httpServer.listen(port, '127.0.0.1', async () => { + console.log(`Reporter HTTP server listening on port ${port}`); + }); + } catch (error) { + debugOnConsole(`Exception in starting reporter server : ${error}`); + debug(`Exception in starting reporter server : ${error}`, true, error); + } + } + registerListeners() { startIPCServer( (server) => { @@ -232,6 +337,8 @@ class MyReporter { this.current_test = test; test.retryOf = null; test.testAnalyticsId = uuidv4(); + if (test.id) this.internalIdMap[test.id] = test.testAnalyticsId; + debugOnConsole(`[MOCHA EVENT] EVENT_TEST_BEGIN for uuid: ${test.testAnalyticsId}`); test.started_at = (new Date()).toISOString(); test.test_started_at = test.started_at; if(test._currentRetry > 0 && lastTest && lastTest.title == test.title) { @@ -249,8 +356,16 @@ class MyReporter { } } - uploadTestSteps = async (shouldClearCurrentSteps = true, cypressSteps = null) => { + uploadTestSteps = async (shouldClearCurrentSteps = true, cypressSteps = null) => { const currentTestSteps = cypressSteps ? cypressSteps : JSON.parse(JSON.stringify(this.currentTestSteps)); + + if(shouldClearCurrentSteps && !cypressSteps) { + this.currentTestSteps = []; + } + + if (!currentTestSteps || currentTestSteps.length === 0) { + return; + } /* TODO - Send as test logs */ const allStepsAsLogs = []; currentTestSteps.forEach(step => { @@ -273,7 +388,6 @@ class MyReporter { event_type: 'LogCreated', logs: allStepsAsLogs }); - if(shouldClearCurrentSteps) this.currentTestSteps = []; } sendTestRunEvent = async (test, err = undefined, customFinished=false, eventType = "TestRunFinished") => { @@ -307,9 +421,9 @@ class MyReporter { let gitConfigPath = process.env.OBSERVABILITY_GIT_CONFIG_PATH ? process.env.OBSERVABILITY_GIT_CONFIG_PATH.toString() : (rootParentFile ? findGitConfig(rootParentFile) : null); if(!isBrowserstackInfra()) gitConfigPath = process.env.OBSERVABILITY_GIT_CONFIG_PATH_LOCAL ? process.env.OBSERVABILITY_GIT_CONFIG_PATH_LOCAL.toString() : null; const prefixedTestPath = rootParentFile ? this._paths.prefixTestPath(rootParentFile) : 'File path could not be found'; - + const fileSeparator = getFileSeparatorData(); - + let testData = { 'framework': 'Cypress', 'uuid': (eventType.includes("Test") ? test.testAnalyticsId : test.hookAnalyticsId) || uuidv4(), @@ -339,6 +453,8 @@ class MyReporter { debugOnConsole(`${eventType} for uuid: ${testData.uuid}`); + this.mapTestId(testData, eventType); + if(eventType.match(/TestRunFinished/) || eventType.match(/TestRunSkipped/)) { testData['meta'].steps = JSON.parse(JSON.stringify(this.currentTestCucumberSteps)); this.currentTestCucumberSteps = []; @@ -417,14 +533,14 @@ class MyReporter { } if(eventType == "HookRunFinished") delete testData.started_at; - + if(eventType.match(/HookRun/)) { testData['hook_type'] = HOOK_TYPES_MAP[testData['hook_type']]; uploadData['hook_run'] = testData; } else { uploadData['test_run'] = testData; } - + if(eventType == 'HookRunFinished' && testData['hook_type'] == 'BEFORE_ALL') { uploadData.cypressSteps = JSON.parse(JSON.stringify(this.currentTestSteps)); this.beforeHooks.push(uploadData); @@ -500,12 +616,24 @@ class MyReporter { } } + mapTestId = (testData, eventType) => { + if (!eventType.match(/TestRun/)) {return} + + this.testIdMap[testData.name] = testData.uuid; + } + + getTestId = (testIdentifier) => { + return this.testIdMap[testIdentifier] || null; + } + appendTestItemLog = async (log) => { try { if(this.current_hook && ( this.current_hook.hookAnalyticsId && !this.runStatusMarkedHash[this.current_hook.hookAnalyticsId] )) { log.hook_run_uuid = this.current_hook.hookAnalyticsId; } - if(!log.hook_run_uuid && this.current_test && ( this.current_test.testAnalyticsId && !this.runStatusMarkedHash[this.current_test.testAnalyticsId] )) log.test_run_uuid = this.current_test.testAnalyticsId; + if(!log.hook_run_uuid && this.current_test && this.current_test.testAnalyticsId) { + log.test_run_uuid = this.current_test.testAnalyticsId; + } if(log.hook_run_uuid || log.test_run_uuid) { await uploadEventData({ event_type: 'LogCreated', @@ -513,14 +641,14 @@ class MyReporter { }); } } catch(error) { - debug(`Exception in uploading log data to Observability with error : ${error}`, true, error); + debug(`Exception in uploading log data to ${TEST_REPORTING_ANALYTICS} with error : ${error}`, true, error); } } cypressConfigListener = async (config) => { } - cypressCucumberStepListener = async ({log}) => { + cypressCucumberStepListener = async ({log, started_at, finished_at}) => { if(log.name == 'step' && log.consoleProps && log.consoleProps.step && log.consoleProps.step.keyword) { this.currentTestCucumberSteps = [ ...this.currentTestCucumberSteps, @@ -528,8 +656,8 @@ class MyReporter { id: log.chainerId, keyword: log.consoleProps.step.keyword, text: log.consoleProps.step.text, - started_at: new Date().toISOString(), - finished_at: new Date().toISOString(), + started_at: started_at || new Date().toISOString(), + finished_at: finished_at || new Date().toISOString(), duration: 0, result: 'passed' } @@ -539,8 +667,8 @@ class MyReporter { if(gherkinStep.id == log.chainerId) { this.currentTestCucumberSteps[idx] = { ...gherkinStep, - finished_at: new Date().toISOString(), - duration: Date.now() - (new Date(gherkinStep.started_at)).getTime(), + finished_at: finished_at || new Date().toISOString(), + duration: (finished_at ? new Date(finished_at).getTime() : Date.now()) - (new Date(gherkinStep.started_at)).getTime(), result: log.state, failure: log.err?.stack || log.err?.message, failure_reason: log.err?.stack || log.err?.message, @@ -551,9 +679,9 @@ class MyReporter { } } - cypressLogListener = async ({level, message, file}) => { + cypressLogListener = async ({timestamp, level, message}) => { this.appendTestItemLog({ - timestamp: new Date().toISOString(), + timestamp: timestamp || new Date().toISOString(), level: level.toUpperCase(), message, kind: 'TEST_LOG', @@ -610,16 +738,22 @@ class MyReporter { if(isCommandPresent) { return; } - + + const cypressTestId = command.testId || command.attributes?.testId; + const cypressHookId = command.hookId || command.attributes?.hookId; + const currentStepObj = { id: command.attributes.id, text: 'cy.' + command.attributes.name + '(' + this.getFormattedArgs(command.attributes.args) + ')', - started_at: new Date().toISOString(), + started_at: command.started_at || new Date().toISOString(), finished_at: null, duration: null, result: 'pending', - test_run_uuid: this.current_test?.testAnalyticsId && !this.runStatusMarkedHash[this.current_test.testAnalyticsId] ? this.current_test.testAnalyticsId : null, - hook_run_uuid : this.current_hook?.hookAnalyticsId && !this.runStatusMarkedHash[this.current_hook.hookAnalyticsId] ? this.current_hook.hookAnalyticsId : null + test_run_uuid: command.location === 'test' + ? (this.internalIdMap[cypressTestId] || this.current_test?.testAnalyticsId) : null, + + hook_run_uuid : command.location === 'hook' + ? (this.internalIdMap[cypressHookId] || this.current_hook?.hookAnalyticsId) : null }; if(currentStepObj.hook_run_uuid && currentStepObj.test_run_uuid) delete currentStepObj.test_run_uuid; this.currentTestSteps = [ @@ -632,8 +766,8 @@ class MyReporter { if(val.id == command.attributes.id) { this.currentTestSteps[idx] = { ...val, - finished_at: new Date().toISOString(), - duration: Date.now() - (new Date(val.started_at)).getTime(), + finished_at: command.finished_at || new Date().toISOString(), + duration: (command.finished_at ? new Date(command.finished_at).getTime() : Date.now()) - (new Date(val.started_at)).getTime(), result: command.state }; stepUpdated = true; @@ -642,6 +776,10 @@ class MyReporter { if(!stepUpdated) { /* COMMAND_END reported before COMMAND_START */ + + const cypressTestId = command.testId || command.attributes?.testId; + const cypressHookId = command.hookId || command.attributes?.hookId; + const currentStepObj = { id: command.attributes.id, text: 'cy.' + command.attributes.name + '(' + this.getFormattedArgs(command.attributes.args) + ')', @@ -649,8 +787,11 @@ class MyReporter { finished_at: new Date().toISOString(), duration: 0, result: command.state, - test_run_uuid: this.current_test?.testAnalyticsId && !this.runStatusMarkedHash[this.current_test.testAnalyticsId] ? this.current_test.testAnalyticsId : null, - hook_run_uuid : this.current_hook?.hookAnalyticsId && !this.runStatusMarkedHash[this.current_hook.hookAnalyticsId] ? this.current_hook.hookAnalyticsId : null + test_run_uuid: command.location === 'test' + ? (this.internalIdMap[cypressTestId] || this.current_test?.testAnalyticsId) : null, + + hook_run_uuid : command.location === 'hook' + ? (this.internalIdMap[cypressHookId] || this.current_hook?.hookAnalyticsId) : null }; if(currentStepObj.hook_run_uuid && currentStepObj.test_run_uuid) delete currentStepObj.test_run_uuid; this.currentTestSteps = [ @@ -660,7 +801,7 @@ class MyReporter { } } else if(type == 'COMMAND_RETRY') { if(!command.id) return; - + let isRetryStepFound = false; /* Parse steps array in reverse and update the last step with common chainerId */ for(let idx=this.currentTestSteps.length-1; idx>=0; idx--) { @@ -754,7 +895,7 @@ class MyReporter { }); } } - + return null } } diff --git a/bin/testhub/constants.js b/bin/testhub/constants.js new file mode 100644 index 00000000..6be9945f --- /dev/null +++ b/bin/testhub/constants.js @@ -0,0 +1,10 @@ +module.exports = { + 'TESTHUB_BUILD_API': 'api/v2/builds', + 'ACCESSIBILITY': 'accessibility', + 'OBSERVABILITY': 'observability', + 'ERROR': { + 'INVALID_CREDENTIALS': 'ERROR_INVALID_CREDENTIALS', + 'DEPRECATED': 'ERROR_SDK_DEPRECATED', + 'ACCESS_DENIED': 'ERROR_ACCESS_DENIED' + }, +}; diff --git a/bin/testhub/testhubHandler.js b/bin/testhub/testhubHandler.js new file mode 100644 index 00000000..a6b4724a --- /dev/null +++ b/bin/testhub/testhubHandler.js @@ -0,0 +1,135 @@ +const { + setCrashReportingConfig, + isTestObservabilitySession, + nodeRequest, +} = require("../testObservability/helper/helper"); +const helper = require("../helpers/helper"); +const testhubUtils = require("../testhub/utils"); +const TESTHUB_CONSTANTS = require("./constants"); +const logger = require('../../bin/helpers/logger').winstonLogger; + +class TestHubHandler { + static async launchBuild(user_config, bsConfigPath) { + setCrashReportingConfig(user_config, bsConfigPath); + + const obsUserName = user_config["auth"]["username"]; + const obsAccessKey = user_config["auth"]["access_key"]; + const BSTestOpsToken = `${obsUserName || ""}:${obsAccessKey || ""}`; + + if (BSTestOpsToken === "") { + // if olly true + if (isTestObservabilitySession()) { + logger.debug( + "EXCEPTION IN BUILD START EVENT : Missing authentication token" + ); + process.env.BS_TESTOPS_BUILD_COMPLETED = false; + } + + if (testhubUtils.isAccessibilityEnabled()) { + logger.debug( + "Exception while creating test run for BrowserStack Accessibility Automation: Missing authentication token" + ); + process.env.BROWSERSTACK_TEST_ACCESSIBILITY = "false"; + } + + return [null, null]; + } + + try { + const data = await this.generateBuildUpstreamData(user_config); + const config = this.getConfig(obsUserName, obsAccessKey); + const response = await nodeRequest( "POST", TESTHUB_CONSTANTS.TESTHUB_BUILD_API, data, config); + const launchData = this.extractDataFromResponse(user_config, data, response, config); + } catch (error) { + console.log(error); + if (error.success === false) { // non 200 response + testhubUtils.logBuildError(error); + return; + } + + } + } + + static async generateBuildUpstreamData(user_config) { + const { buildName, projectName, buildDescription, buildTags } = helper.getBuildDetails(user_config, true); + const productMap = testhubUtils.getProductMap(user_config); + const data = { + project_name: projectName, + name: buildName, + build_identifier: "", // no build identifier in cypress + description: buildDescription || "", + started_at: new Date().toISOString(), + tags: buildTags, + host_info: helper.getHostInfo(), + ci_info: helper.getCiInfo(), + build_run_identifier: process.env.BROWSERSTACK_BUILD_RUN_IDENTIFIER, + failed_tests_rerun: process.env.BROWSERSTACK_RERUN || false, + version_control: await helper.getGitMetaData(), + accessibility: testhubUtils.getAccessibilityOptions(user_config), + framework_details: testhubUtils.getFrameworkDetails(), + product_map: productMap, + browserstackAutomation: productMap["automate"], + }; + + return data; + } + + static async extractDataFromResponse( + user_config, + requestData, + response, + config + ) { + const launchData = {}; + + if (isTestObservabilitySession()) { + const [jwt, buildHashedId, allowScreenshot] = + testhubUtils.setTestObservabilityVariables( + user_config, + requestData, + response.data + ); + if (jwt && buildHashedId) { + launchData[TESTHUB_CONSTANTS.OBSERVABILITY] = { + jwt, + buildHashedId, + allowScreenshot, + }; + process.env.BROWSERSTACK_TEST_OBSERVABILITY = "true"; + } else { + launchData[TESTHUB_CONSTANTS.OBSERVABILITY] = {}; + process.env.BROWSERSTACK_TEST_OBSERVABILITY = "false"; + } + } else { + process.env.BROWSERSTACK_TEST_OBSERVABILITY = "false"; + } + + if(testhubUtils.isAccessibilityEnabled()) { + testhubUtils.setAccessibilityVariables(user_config, response.data); + } else { + process.env.BROWSERSTACK_ACCESSIBILITY = 'false'; + testhubUtils.checkAndSetAccessibility(user_config, false) + } + + if (testhubUtils.shouldProcessEventForTesthub()) { + testhubUtils.setTestHubCommonMetaInfo(user_config, response.data); + } + + return launchData; + } + + static getConfig(obsUserName, obsAccessKey) { + return { + auth: { + username: obsUserName, + password: obsAccessKey, + }, + headers: { + "Content-Type": "application/json", + "X-BSTACK-TESTOPS": "true", + }, + }; + } +} + +module.exports = TestHubHandler; diff --git a/bin/testhub/utils.js b/bin/testhub/utils.js new file mode 100644 index 00000000..718bb595 --- /dev/null +++ b/bin/testhub/utils.js @@ -0,0 +1,287 @@ +const os = require("os"); + +const logger = require("../../bin/helpers/logger").winstonLogger; +const TESTHUB_CONSTANTS = require("./constants"); +const testObservabilityHelper = require("../../bin/testObservability/helper/helper"); +const helper = require("../helpers/helper"); +const accessibilityHelper = require("../accessibility-automation/helper"); +const detectPort = require('detect-port'); + + +const isUndefined = (value) => value === undefined || value === null; + +exports.getFrameworkDetails = (user_config) => { + return { + frameworkName: "Cypress", + frameworkVersion: testObservabilityHelper.getPackageVersion( + "cypress", + user_config + ), + sdkVersion: helper.getAgentVersion(), + language: "javascript", + testFramework: { + name: "cypress", + version: helper.getPackageVersion("cypress", user_config), + }, + }; +}; + +exports.isAccessibilityEnabled = () => { + if (process.env.BROWSERSTACK_TEST_ACCESSIBILITY !== undefined) { + return process.env.BROWSERSTACK_TEST_ACCESSIBILITY === "true"; + } + logger.debug('Accessibility is disabled'); + return false; +}; + +// app-automate and percy support is not present for cypress +exports.getProductMap = (user_config) => { + return { + observability: testObservabilityHelper.isTestObservabilitySession(), + accessibility: exports.isAccessibilityEnabled(user_config), + percy: false, + automate: testObservabilityHelper.isBrowserstackInfra(), + app_automate: false, + }; +}; + +exports.shouldProcessEventForTesthub = () => { + return ( + testObservabilityHelper.isTestObservabilitySession() || + exports.isAccessibilityEnabled() + ); +}; + +exports.setTestObservabilityVariables = ( + user_config, + requestData, + responseData +) => { + if (!responseData.observability) { + exports.handleErrorForObservability(); + + return [null, null, null]; + } + + if (!responseData.observability.success) { + exports.handleErrorForObservability(responseData.observability); + + return [null, null, null]; + } + + if (responseData.observability && responseData.observability.success) { + process.env.BS_TESTOPS_BUILD_COMPLETED = true; + testObservabilityHelper.setEnvironmentVariablesForRemoteReporter( + responseData.jwt, + responseData.build_hashed_id, + responseData.observability.options.allow_screenshots.toString(), + requestData.framework_details.sdkVersion + ); + if(user_config.run_settings.auto_import_dev_dependencies != true) helper.setBrowserstackCypressCliDependency(user_config); + return [ + responseData.jwt, + responseData.build_hashed_id, + process.env.BS_TESTOPS_ALLOW_SCREENSHOTS, + ]; + } + return [null, null, null]; +}; + +exports.handleErrorForObservability = (error = null) => { + process.env.BROWSERSTACK_TESTHUB_UUID = "null"; + process.env.BROWSERSTACK_TESTHUB_JWT = "null"; + process.env.BS_TESTOPS_BUILD_COMPLETED = "false"; + process.env.BS_TESTOPS_JWT = "null"; + process.env.BS_TESTOPS_BUILD_HASHED_ID = "null"; + process.env.BS_TESTOPS_ALLOW_SCREENSHOTS = "null"; + exports.logBuildError(error, TESTHUB_CONSTANTS.OBSERVABILITY); +}; + +exports.setAccessibilityVariables = (user_config, responseData) => { + if (!responseData.accessibility) { + exports.handleErrorForAccessibility(user_config); + + return [null, null]; + } + + if (!responseData.accessibility.success) { + exports.handleErrorForAccessibility( + user_config, + responseData.accessibility + ); + + return [null, null]; + } + + if (responseData.accessibility.options) { + logger.debug( + `BrowserStack Accessibility Automation Build Hashed ID: ${responseData.build_hashed_id}` + ); + setAccessibilityCypressCapabilities(user_config, responseData); + helper.setBrowserstackCypressCliDependency(user_config); + } +}; + +const setAccessibilityCypressCapabilities = (user_config, responseData) => { + if (isUndefined(user_config.run_settings.accessibilityOptions)) { + user_config.run_settings.accessibilityOptions = {}; + } + const { accessibilityToken, scannerVersion } = jsonifyAccessibilityArray( + responseData.accessibility.options.capabilities, + "name", + "value" + ); + process.env.ACCESSIBILITY_AUTH = accessibilityToken; + process.env.BS_A11Y_JWT = accessibilityToken; + process.env.ACCESSIBILITY_SCANNERVERSION = scannerVersion; + + if (accessibilityToken && responseData.build_hashed_id) { + this.checkAndSetAccessibility(user_config, true); + } + + user_config.run_settings.accessibilityOptions["authToken"] = accessibilityToken; + user_config.run_settings.accessibilityOptions["auth"] = accessibilityToken; + user_config.run_settings.accessibilityOptions["scannerVersion"] = scannerVersion; + user_config.run_settings.system_env_vars.push(`ACCESSIBILITY_AUTH=${accessibilityToken}`) + user_config.run_settings.system_env_vars.push(`ACCESSIBILITY_SCANNERVERSION=${scannerVersion}`) +}; + +// To handle array of json, eg: [{keyName : '', valueName : ''}] +const jsonifyAccessibilityArray = (dataArray, keyName, valueName) => { + const result = {}; + dataArray.forEach((element) => { + result[element[keyName]] = element[valueName]; + }); + + return result; +}; + +exports.handleErrorForAccessibility = (user_config, error = null) => { + exports.checkAndSetAccessibility(user_config, false); + process.env.BROWSERSTACK_TESTHUB_UUID = "null"; + process.env.BROWSERSTACK_TESTHUB_JWT = "null"; + exports.logBuildError(error, TESTHUB_CONSTANTS.ACCESSIBILITY); +}; + +exports.logBuildError = (error, product = "") => { + if (error === undefined) { + logger.error(`${product.toUpperCase()} Build creation failed`); + + return; + } + + try { + for (const errorJson of error.errors) { + const errorType = errorJson.key; + const errorMessage = errorJson.message; + if (errorMessage) { + switch (errorType) { + case TESTHUB_CONSTANTS.ERROR.INVALID_CREDENTIALS: + logger.error(errorMessage); + break; + case TESTHUB_CONSTANTS.ERROR.ACCESS_DENIED: + logger.info(errorMessage); + break; + case TESTHUB_CONSTANTS.ERROR.DEPRECATED: + logger.error(errorMessage); + break; + default: + logger.error(errorMessage); + } + } + } + } catch (e) { + logger.error(error); + } +}; + +exports.findAvailablePort = async (preferredPort, maxAttempts = 10) => { + const findPort = detectPort.detect || detectPort; + let port = preferredPort; + for (let attempts = 0; attempts < maxAttempts; attempts++) { + try { + const availablePort = await findPort(port); + + if (availablePort === port) { + return port; + } else { + // Double-check suggested port + const verify = await findPort(availablePort); + if (verify === availablePort) { + return availablePort; + } + } + + // Try next port + port++; + } catch (err) { + logger.warn(`Error checking port ${port}:`, err.message); + + // If permission denied, jump to dynamic range + if (err.code === "EACCES") { + port = 49152; + } else { + port++; + } + } + } + + const fallbackPort = Math.floor(Math.random() * (65535 - 49152)) + 49152; + logger.warn(`Could not find available port. Using fallback port: ${fallbackPort}`); + return fallbackPort; +} + +exports.setTestHubCommonMetaInfo = (user_config, responseData) => { + process.env.BROWSERSTACK_TESTHUB_JWT = responseData.jwt; + process.env.BROWSERSTACK_TESTHUB_UUID = responseData.build_hashed_id; + user_config.run_settings.system_env_vars.push(`BROWSERSTACK_TESTHUB_JWT`); + user_config.run_settings.system_env_vars.push(`BROWSERSTACK_TESTHUB_UUID`); + user_config.run_settings.system_env_vars.push(`REPORTER_API_PORT_NO`); +}; + +exports.checkAndSetAccessibility = (user_config, accessibilityFlag) => { + if (!accessibilityHelper.isAccessibilitySupportedCypressVersion(user_config.run_settings.cypress_config_file)) + { + logger.warn(`Accessibility Testing is not supported on Cypress version 9 and below.`); + process.env.BROWSERSTACK_TEST_ACCESSIBILITY = 'false'; + user_config.run_settings.accessibility = false; + return; + } + + if (!user_config.run_settings.system_env_vars) { + user_config.run_settings.system_env_vars = []; + } + + if (!isUndefined(accessibilityFlag)) { + process.env.BROWSERSTACK_TEST_ACCESSIBILITY = accessibilityFlag.toString(); + user_config.run_settings.accessibility = accessibilityFlag; + if ( + !user_config.run_settings.system_env_vars.includes("BROWSERSTACK_TEST_ACCESSIBILITY") + ) { + user_config.run_settings.system_env_vars.push(`BROWSERSTACK_TEST_ACCESSIBILITY=${accessibilityFlag}`); + } + return; + } + return; +}; + +exports.getAccessibilityOptions = (user_config) => { + const settings = isUndefined(user_config.run_settings.accessibilityOptions) + ? {} + : user_config.run_settings.accessibilityOptions; + return { settings: settings }; +}; + +exports.appendTestHubParams = (testData, eventType, accessibilityScanInfo) => { + if ( + exports.isAccessibilityEnabled() && + !["HookRunStarted", "HookRunFinished", "TestRunStarted"].includes( + eventType + ) && + !isUndefined(accessibilityScanInfo[testData.name]) + ) { + testData["product_map"] = { + accessibility: accessibilityScanInfo[testData.name], + }; + } +}; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..81508046 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6424 @@ +{ + "name": "browserstack-cypress-cli", + "version": "1.36.5", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "browserstack-cypress-cli", + "version": "1.36.5", + "license": "MIT", + "dependencies": { + "archiver": "5.3.0", + "async": "3.2.3", + "axios": "^1.15.0", + "axios-retry": "^3.5.0", + "browserstack-local": "^1.5.12", + "chalk": "4.1.2", + "cli-progress": "^3.10.0", + "decompress": "4.2.1", + "detect-port": "1.5.1", + "form-data": "^4.0.0", + "fs-extra": "8.1.0", + "getmac": "5.20.0", + "git-last-commit": "^1.0.1", + "git-repo-info": "^2.1.1", + "gitconfiglocal": "^2.1.0", + "glob": "^7.2.0", + "https-proxy-agent": "^5.0.1", + "mkdirp": "1.0.4", + "mocha": "^10.2.0", + "node-ipc": "9.1.1", + "table": "5.4.6", + "tsc-alias": "^1.8.16", + "unzipper": "^0.12.3", + "update-notifier": "7.0.0", + "uuid": "8.3.2", + "windows-release": "^5.1.0", + "winston": "2.4.4", + "yargs": "14.2.3" + }, + "bin": { + "browserstack-cypress": "bin/runner.js" + }, + "devDependencies": { + "chai": "4.3.6", + "chai-as-promised": "7.1.1", + "custom-env": "2.0.1", + "mocha": "^10.2.0", + "nyc": "15.1.0", + "proxyquire": "2.1.3", + "rewire": "^9.0.1", + "sinon": "9.2.4" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.3", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.3.tgz", + "integrity": "sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "dev": true, + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz", + "integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", + "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", + "dev": true, + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.5" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", + "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", + "dev": true, + "dependencies": { + "ajv": "^6.14.0", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.5", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", + "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "dev": true, + "dependencies": { + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "dev": true, + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.6.tgz", + "integrity": "sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@pnpm/config.env-replace": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz", + "integrity": "sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==", + "engines": { + "node": ">=12.22.0" + } + }, + "node_modules/@pnpm/network.ca-file": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@pnpm/network.ca-file/-/network.ca-file-1.0.2.tgz", + "integrity": "sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==", + "dependencies": { + "graceful-fs": "4.2.10" + }, + "engines": { + "node": ">=12.22.0" + } + }, + "node_modules/@pnpm/network.ca-file/node_modules/graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" + }, + "node_modules/@pnpm/npm-conf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@pnpm/npm-conf/-/npm-conf-3.0.2.tgz", + "integrity": "sha512-h104Kh26rR8tm+a3Qkc5S4VLYint3FE48as7+/5oCEcKR2idC/pF1G6AhIXKI+eHPJa/3J9i5z0Al47IeGHPkA==", + "dependencies": { + "@pnpm/config.env-replace": "^1.1.0", + "@pnpm/network.ca-file": "^1.0.1", + "config-chain": "^1.1.11" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@sindresorhus/is": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.6.0.tgz", + "integrity": "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@sinonjs/commons": { + "version": "1.8.6", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz", + "integrity": "sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/commons/node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz", + "integrity": "sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^1.7.0" + } + }, + "node_modules/@sinonjs/samsam": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-5.3.1.tgz", + "integrity": "sha512-1Hc0b1TtyfBu8ixF/tpfSHTVWKwCBLY4QJbkgnE7HcwyvT2xArDxb4K7dMgqRm3szI+LJbzmW/s4xxEhv6hwDg==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^1.6.0", + "lodash.get": "^4.4.2", + "type-detect": "^4.0.8" + } + }, + "node_modules/@sinonjs/text-encoding": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.3.tgz", + "integrity": "sha512-DE427ROAphMQzU4ENbliGYrBSYPXF+TtLg9S8vzeA+OF4ZKzoDdzfL8sxuMUGS/lgRhM6j1URSk9ghf7Xo1tyA==", + "deprecated": "Deprecated: no longer maintained and no longer used by Sinon packages. See\n https://github.com/sinonjs/nise/issues/243 for replacement details.", + "dev": true + }, + "node_modules/@szmarczak/http-timer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", + "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==", + "dependencies": { + "defer-to-connect": "^2.0.1" + }, + "engines": { + "node": ">=14.16" + } + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true + }, + "node_modules/@types/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q==" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "node_modules/@types/node": { + "version": "16.18.126", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.126.tgz", + "integrity": "sha512-OTcgaiwfGFBKacvfwuHzzn1KLxH/er8mluiy8/uM3sGXHaRe73RrSIj01jow9t4kJEW633Ov+cOexXeiApTyAw==" + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/address": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/address/-/address-1.2.2.tgz", + "integrity": "sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-align": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", + "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", + "dependencies": { + "string-width": "^4.1.0" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/append-transform": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", + "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", + "dev": true, + "dependencies": { + "default-require-extensions": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/archiver": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.0.tgz", + "integrity": "sha512-iUw+oDwK0fgNpvveEsdQ0Ase6IIKztBJU2U0E9MzszMfmVVUyv1QJhS2ITW9ZCqx8dktAxVAjWWkKehuZE8OPg==", + "dependencies": { + "archiver-utils": "^2.1.0", + "async": "^3.2.0", + "buffer-crc32": "^0.2.1", + "readable-stream": "^3.6.0", + "readdir-glob": "^1.0.0", + "tar-stream": "^2.2.0", + "zip-stream": "^4.1.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/archiver-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", + "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", + "dependencies": { + "glob": "^7.1.4", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^2.0.0" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/archiver-utils/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==", + "dev": true + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "engines": { + "node": ">=4" + } + }, + "node_modules/async": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.3.tgz", + "integrity": "sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axios": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.16.0.tgz", + "integrity": "sha512-6hp5CwvTPlN2A31g5dxnwAX0orzM7pmCRDLnZSX772mv8WDqICwFjowHuPs04Mc8deIld1+ejhtaMn5vp6b+1w==", + "dependencies": { + "follow-redirects": "^1.16.0", + "form-data": "^4.0.5", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/axios-retry": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/axios-retry/-/axios-retry-3.9.1.tgz", + "integrity": "sha512-8PJDLJv7qTTMMwdnbMvrLYuvB47M81wRtxQmEdV5w4rgbTXTt+vtPkXwajOfOdSyv/wZICJOC+/UhXH4aQ/R+w==", + "dependencies": { + "@babel/runtime": "^7.15.4", + "is-retry-allowed": "^2.2.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.27", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.27.tgz", + "integrity": "sha512-zEs/ufmZoUd7WftKpKyXaT6RFxpQ5Qm9xytKRHvJfxFV9DFJkZph9RvJ1LcOUi0Z1ZVijMte65JbILeV+8QQEA==", + "dev": true, + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" + }, + "node_modules/boxen": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-7.1.1.tgz", + "integrity": "sha512-2hCgjEmP8YLWQ130n2FerGv7rYpfBmnmp9Uy2Le1vge6X3gZIfSmEzP5QTDElFxcvVcXlEn8Aq6MU/PZygIOog==", + "dependencies": { + "ansi-align": "^3.0.1", + "camelcase": "^7.0.1", + "chalk": "^5.2.0", + "cli-boxes": "^3.0.0", + "string-width": "^5.1.2", + "type-fest": "^2.13.0", + "widest-line": "^4.0.1", + "wrap-ansi": "^8.1.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/boxen/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/boxen/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/boxen/node_modules/camelcase": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-7.0.1.tgz", + "integrity": "sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/boxen/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/boxen/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" + }, + "node_modules/boxen/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/boxen/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/boxen/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/browserstack-local": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/browserstack-local/-/browserstack-local-1.5.13.tgz", + "integrity": "sha512-7helY+Ms3ss4BtIQZTIyshdAFZSvS9A7ZpEB9stRaobeZ9BM1BkJFTuMakQNTOj78llv0+/qDI5Ak+bkGWV1xg==", + "dependencies": { + "agent-base": "^6.0.2", + "https-proxy-agent": "^5.0.1", + "is-running": "^2.1.0", + "tree-kill": "^1.2.2" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-alloc": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", + "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", + "dependencies": { + "buffer-alloc-unsafe": "^1.1.0", + "buffer-fill": "^1.0.0" + } + }, + "node_modules/buffer-alloc-unsafe": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", + "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==" + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "engines": { + "node": "*" + } + }, + "node_modules/buffer-fill": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", + "integrity": "sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ==" + }, + "node_modules/cacheable-lookup": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", + "integrity": "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==", + "engines": { + "node": ">=14.16" + } + }, + "node_modules/cacheable-request": { + "version": "10.2.14", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.14.tgz", + "integrity": "sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==", + "dependencies": { + "@types/http-cache-semantics": "^4.0.2", + "get-stream": "^6.0.1", + "http-cache-semantics": "^4.1.1", + "keyv": "^4.5.3", + "mimic-response": "^4.0.0", + "normalize-url": "^8.0.0", + "responselike": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + } + }, + "node_modules/cacheable-request/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/caching-transform": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", + "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", + "dev": true, + "dependencies": { + "hasha": "^5.0.0", + "make-dir": "^3.0.0", + "package-hash": "^4.0.0", + "write-file-atomic": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/caching-transform/node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/caching-transform/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/call-bind": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", + "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "get-intrinsic": "^1.3.0", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001792", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001792.tgz", + "integrity": "sha512-hVLMUZFgR4JJ6ACt1uEESvQN1/dBVqPAKY0hgrV70eN3391K6juAfTjKZLKvOMsx8PxA7gsY1/tLMMTcfFLLpw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/chai": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.6.tgz", + "integrity": "sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q==", + "dev": true, + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", + "get-func-name": "^2.0.0", + "loupe": "^2.3.1", + "pathval": "^1.1.1", + "type-detect": "^4.0.5" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chai-as-promised": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-7.1.1.tgz", + "integrity": "sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA==", + "dev": true, + "dependencies": { + "check-error": "^1.0.2" + }, + "peerDependencies": { + "chai": ">= 2.1.2 < 5" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/check-error": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", + "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.2" + }, + "engines": { + "node": "*" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/cli-boxes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", + "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-progress": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/cli-progress/-/cli-progress-3.12.0.tgz", + "integrity": "sha512-tRkV3HJ1ASwm19THiiLIXLO7Im7wlTuKnvkYaTkyoAPefqjNg7W7DHKUlGRxy9vxDvbyCYQkQozvptuMkGCg8A==", + "dependencies": { + "string-width": "^4.2.3" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/colors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", + "integrity": "sha512-pFGrxThWcWQ2MsAz6RtgeWe4NK2kUE1WfsrvvlctdII745EW9I0yflqhe7++M5LEc7bV2c/9/5zc8sFcpL0Drw==", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true + }, + "node_modules/compress-commons": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.2.tgz", + "integrity": "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==", + "dependencies": { + "buffer-crc32": "^0.2.13", + "crc32-stream": "^4.0.2", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "dependencies": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, + "node_modules/configstore": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-6.0.0.tgz", + "integrity": "sha512-cD31W1v3GqUlQvbBCGcXmd2Nj9SvLDOP1oQ0YFuLETufzSPaKp11rYBsSOm7rCsW3OnIRAFM3OxRhceaXNYHkA==", + "dependencies": { + "dot-prop": "^6.0.1", + "graceful-fs": "^4.2.6", + "unique-string": "^3.0.0", + "write-file-atomic": "^3.0.3", + "xdg-basedir": "^5.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/yeoman/configstore?sponsor=1" + } + }, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/crc32-stream": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.3.tgz", + "integrity": "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==", + "dependencies": { + "crc-32": "^1.2.0", + "readable-stream": "^3.4.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/crypto-random-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-4.0.0.tgz", + "integrity": "sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==", + "dependencies": { + "type-fest": "^1.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/crypto-random-string/node_modules/type-fest": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", + "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/custom-env": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/custom-env/-/custom-env-2.0.1.tgz", + "integrity": "sha512-gVv19DBDuTQ8MM/WPlf8025Aa46Aqmfrr/kDxyDfXWe5t7C3+OgBsuhrsXTKQ4hGo+z1OuuaOQnhgTWIpvrz+w==", + "dev": true, + "dependencies": { + "dotenv": "*", + "dotenv-expand": "^5.0.0" + } + }, + "node_modules/cycle": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/cycle/-/cycle-1.0.3.tgz", + "integrity": "sha512-TVF6svNzeQCOpjCqsy0/CSy8VgObG3wXusJ73xW2GbG5rGx7lC8zxDSURicsXI2UsGdi2L0QNRCi745/wUDvsA==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/decompress/-/decompress-4.2.1.tgz", + "integrity": "sha512-e48kc2IjU+2Zw8cTb6VZcJQ3lgVbS4uuB1TfCHbiZIP/haNXm+SVyhu+87jts5/3ROpd82GSVCoNs/z8l4ZOaQ==", + "dependencies": { + "decompress-tar": "^4.0.0", + "decompress-tarbz2": "^4.0.0", + "decompress-targz": "^4.0.0", + "decompress-unzip": "^4.0.1", + "graceful-fs": "^4.1.10", + "make-dir": "^1.0.0", + "pify": "^2.3.0", + "strip-dirs": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-tar": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/decompress-tar/-/decompress-tar-4.1.1.tgz", + "integrity": "sha512-JdJMaCrGpB5fESVyxwpCx4Jdj2AagLmv3y58Qy4GE6HMVjWz1FeVQk1Ct4Kye7PftcdOo/7U7UKzYBJgqnGeUQ==", + "dependencies": { + "file-type": "^5.2.0", + "is-stream": "^1.1.0", + "tar-stream": "^1.5.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-tar/node_modules/bl": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.3.tgz", + "integrity": "sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==", + "dependencies": { + "readable-stream": "^2.3.5", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/decompress-tar/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/decompress-tar/node_modules/tar-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz", + "integrity": "sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==", + "dependencies": { + "bl": "^1.0.0", + "buffer-alloc": "^1.2.0", + "end-of-stream": "^1.0.0", + "fs-constants": "^1.0.0", + "readable-stream": "^2.3.0", + "to-buffer": "^1.1.1", + "xtend": "^4.0.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/decompress-tarbz2": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/decompress-tarbz2/-/decompress-tarbz2-4.1.1.tgz", + "integrity": "sha512-s88xLzf1r81ICXLAVQVzaN6ZmX4A6U4z2nMbOwobxkLoIIfjVMBg7TeguTUXkKeXni795B6y5rnvDw7rxhAq9A==", + "dependencies": { + "decompress-tar": "^4.1.0", + "file-type": "^6.1.0", + "is-stream": "^1.1.0", + "seek-bzip": "^1.0.5", + "unbzip2-stream": "^1.0.9" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-tarbz2/node_modules/file-type": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-6.2.0.tgz", + "integrity": "sha512-YPcTBDV+2Tm0VqjybVd32MHdlEGAtuxS3VAYsumFokDSMG+ROT5wawGlnHDoz7bfMcMDt9hxuXvXwoKUx2fkOg==", + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-targz": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/decompress-targz/-/decompress-targz-4.1.1.tgz", + "integrity": "sha512-4z81Znfr6chWnRDNfFNqLwPvm4db3WuZkqV+UgXQzSngG3CEKdBkw5jrv3axjjL96glyiiKjsxJG3X6WBZwX3w==", + "dependencies": { + "decompress-tar": "^4.1.1", + "file-type": "^5.2.0", + "is-stream": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-unzip": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/decompress-unzip/-/decompress-unzip-4.0.1.tgz", + "integrity": "sha512-1fqeluvxgnn86MOh66u8FjbtJpAFv5wgCT9Iw8rcBqQcCo5tO8eiJw7NNTrvt9n4CRBVq7CstiS922oPgyGLrw==", + "dependencies": { + "file-type": "^3.8.0", + "get-stream": "^2.2.0", + "pify": "^2.3.0", + "yauzl": "^2.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-unzip/node_modules/file-type": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz", + "integrity": "sha512-RLoqTXE8/vPmMuTI88DAzhMYC99I8BWv7zYP4A1puo5HIjEJ5EX48ighy4ZyKMG9EDXxBgW6e++cn7d1xuFghA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/deep-eql": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "dev": true, + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/default-require-extensions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.1.tgz", + "integrity": "sha512-eXTJmRbm2TIt9MgWTsOH1wEuhew6XGZcMeGKCtLedIg/NCsg1iBePXkceTdK4Fii7pzmN9tGsZhKzZ4h7O/fxw==", + "dev": true, + "dependencies": { + "strip-bom": "^4.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "engines": { + "node": ">=10" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-port": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/detect-port/-/detect-port-1.5.1.tgz", + "integrity": "sha512-aBzdj76lueB6uUst5iAs7+0H/oOjqI5D16XUWxlWMIMROhcM0rfsNVk93zTngq1dDNpoXRr++Sus7ETAExppAQ==", + "dependencies": { + "address": "^1.0.1", + "debug": "4" + }, + "bin": { + "detect": "bin/detect-port.js", + "detect-port": "bin/detect-port.js" + } + }, + "node_modules/diff": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.2.tgz", + "integrity": "sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dot-prop": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz", + "integrity": "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==", + "dependencies": { + "is-obj": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/dotenv": { + "version": "17.4.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", + "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dotenv-expand": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz", + "integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==", + "dev": true + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", + "dependencies": { + "readable-stream": "^2.0.2" + } + }, + "node_modules/duplexer2/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" + }, + "node_modules/easy-stack": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/easy-stack/-/easy-stack-1.0.1.tgz", + "integrity": "sha512-wK2sCs4feiiJeFXn3zvY0p41mdU5VUgbgs1rNsc/y5ngFUijdWd+iIN8eoyuZHKB8xN6BL4PdWmzqFmxNg6V2w==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.352", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.352.tgz", + "integrity": "sha512-9wHk8x6dyuimoe18EdiDPWKExNdxYqo4fn4FwOVVper6RxT3cmpBwBkWWfSOCYJjQdIco/nPhJhNLmn4Ufg1Yg==", + "dev": true + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-goat": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-4.0.0.tgz", + "integrity": "sha512-2Sd4ShcWxbx6OY1IHyla/CVNwvg7XwZVoXZHcSu9w9SReNP1EzzD5T8NWKIR38fIqEns9kDWKUQTXXAmlDrdPg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", + "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.2", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.5", + "@eslint/js": "9.39.4", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.5", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/event-pubsub": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/event-pubsub/-/event-pubsub-4.3.0.tgz", + "integrity": "sha512-z7IyloorXvKbFx9Bpie2+vMJKKx1fH1EN5yiTfp8CiLOTptSYy1g8H4yDpGlEdshL1PBiFtBHepF2cNsqeEeFQ==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/execa/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/execa/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eyes": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", + "integrity": "sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ==", + "engines": { + "node": "> 0.1.90" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/file-type": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-5.2.0.tgz", + "integrity": "sha512-Iq1nJ6D2+yIO4c8HHg4fyVb8mAJieo1Oloy1mLLaB2PvezNedhBVm+QU7g0qM42aiMbRXTxKKwGD17rjKNJYVQ==", + "engines": { + "node": ">=4" + } + }, + "node_modules/fill-keys": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/fill-keys/-/fill-keys-1.0.2.tgz", + "integrity": "sha512-tcgI872xXjwFF4xgQmLxi76GnwJG3g/3isB1l4/G5Z4zrbddGpBjqZCO9oEAcB5wX0Hj/5iQB3toxfO7in1hHA==", + "dev": true, + "dependencies": { + "is-object": "~1.0.1", + "merge-descriptors": "~1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/avajs/find-cache-dir?sponsor=1" + } + }, + "node_modules/find-cache-dir/node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/find-cache-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "bin": { + "flat": "cli.js" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true + }, + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/foreground-child": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", + "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/form-data-encoder": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-2.1.4.tgz", + "integrity": "sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==", + "engines": { + "node": ">= 14.17" + } + }, + "node_modules/fromentries": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", + "integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/fs-constants": { + "version": "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==" + }, + "node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-func-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-2.3.1.tgz", + "integrity": "sha512-AUGhbbemXxrZJRD5cDvKtQxLuYaIbNtDTK8YqupCI393Q2KSTreEsLUN3ZxAWFGiKTzL6nKuzfcIvieflUX9qA==", + "dependencies": { + "object-assign": "^4.0.1", + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/get-tsconfig": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz", + "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/getmac": { + "version": "5.20.0", + "resolved": "https://registry.npmjs.org/getmac/-/getmac-5.20.0.tgz", + "integrity": "sha512-O9T855fb+Hx9dsTJHNv72ZUuA6Y18+BO/0ypPXf6s/tunzXqhc3kbQkNAl+9HVKVlwkWmglHS4LMoJ9YbymKYQ==", + "dependencies": { + "@types/node": "^16.4.7" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/git-last-commit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/git-last-commit/-/git-last-commit-1.0.1.tgz", + "integrity": "sha512-FDSgeMqa7GnJDxt/q0AbrxbfeTyxp4ImxEw1e4nw6NUHA5FMhFUq33dTXI4Xdgcj1VQ1q5QLWF6WxFrJ8KCBOg==" + }, + "node_modules/git-repo-info": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/git-repo-info/-/git-repo-info-2.1.1.tgz", + "integrity": "sha512-8aCohiDo4jwjOwma4FmYFd3i97urZulL8XL24nIPxuE+GZnfsAyy/g2Shqx6OjUiFKUXZM+Yy+KHnOmmA3FVcg==", + "engines": { + "node": ">= 4.0" + } + }, + "node_modules/gitconfiglocal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/gitconfiglocal/-/gitconfiglocal-2.1.0.tgz", + "integrity": "sha512-qoerOEliJn3z+Zyn1HW2F6eoYJqKwS6MgC9cztTLUB/xLWX8gD/6T60pKn4+t/d6tP7JlybI7Z3z+I572CR/Vg==", + "dependencies": { + "ini": "^1.3.2" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/global-dirs": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.1.tgz", + "integrity": "sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==", + "dependencies": { + "ini": "2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/global-dirs/node_modules/ini": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", + "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/got": { + "version": "12.6.1", + "resolved": "https://registry.npmjs.org/got/-/got-12.6.1.tgz", + "integrity": "sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ==", + "dependencies": { + "@sindresorhus/is": "^5.2.0", + "@szmarczak/http-timer": "^5.0.1", + "cacheable-lookup": "^7.0.0", + "cacheable-request": "^10.2.8", + "decompress-response": "^6.0.0", + "form-data-encoder": "^2.1.2", + "get-stream": "^6.0.1", + "http2-wrapper": "^2.1.10", + "lowercase-keys": "^3.0.0", + "p-cancelable": "^3.0.0", + "responselike": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/got/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graceful-fs": { + "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==" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasha": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", + "integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==", + "dev": true, + "dependencies": { + "is-stream": "^2.0.0", + "type-fest": "^0.8.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/hasha/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/hasha/node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/hasown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==" + }, + "node_modules/http2-wrapper": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.1.tgz", + "integrity": "sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==", + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.2.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/import-lazy": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-4.0.0.tgz", + "integrity": "sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", + "dev": true, + "dependencies": { + "hasown": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-in-ci": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-in-ci/-/is-in-ci-0.1.0.tgz", + "integrity": "sha512-d9PXLEY0v1iJ64xLiQMJ51J128EYHAaOR4yZqQi8aHGfw6KgifM3/Viw1oZZ1GCVmb3gBuyhLyHj0HgR2DhSXQ==", + "bin": { + "is-in-ci": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-installed-globally": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", + "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", + "dependencies": { + "global-dirs": "^3.0.0", + "is-path-inside": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-natural-number": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-natural-number/-/is-natural-number-4.0.1.tgz", + "integrity": "sha512-Y4LTamMe0DDQIIAlaer9eKebAlDSV6huy+TWhJVPlzZh2o4tRP5SQWFlLn5N0To4mDD22/qdOq+veo1cSISLgQ==" + }, + "node_modules/is-npm": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-6.1.0.tgz", + "integrity": "sha512-O2z4/kNgyjhQwVR1Wpkbfc19JIhggF97NZNCpWTnjH7kVcZMUrnut9XSN7txI7VdyIYk5ZatOq3zvSuWpU8hoA==", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.2.tgz", + "integrity": "sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-retry-allowed": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-2.2.0.tgz", + "integrity": "sha512-XVm7LOeLpTW4jV19QSH38vkswxoLud8sQ57YwJVTPWdiaI9I8keEhGFpBlslyVsgdQy4Opg8QOLb8YRgsyZiQg==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-running": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-running/-/is-running-2.1.0.tgz", + "integrity": "sha512-mjJd3PujZMl7j+D395WTIO5tU5RIDBfVSRtRR4VOJou3H66E38UjbjvDGh3slJzPuolsb+yQFqwHNNdyp5jg3w==" + }, + "node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-hook": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz", + "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", + "dev": true, + "dependencies": { + "append-transform": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", + "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", + "dev": true, + "dependencies": { + "@babel/core": "^7.7.5", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.0.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/istanbul-lib-processinfo": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.3.tgz", + "integrity": "sha512-NkwHbo3E00oybX6NGJi6ar0B29vxyvNwoC7eJ4G4Yq28UfY758Hgn/heV8VRFhevPED4LXfFz0DQ8z/0kw9zMg==", + "dev": true, + "dependencies": { + "archy": "^1.0.0", + "cross-spawn": "^7.0.3", + "istanbul-lib-coverage": "^3.2.0", + "p-map": "^3.0.0", + "rimraf": "^3.0.0", + "uuid": "^8.3.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report/node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/js-message": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/js-message/-/js-message-1.0.5.tgz", + "integrity": "sha512-hTqHqrm7jrZ+iN93QsKcNOTSgX3F+2NSgdnF+xvf8FfhC2MPqYRzzgXQ1LlhfyIzPTS6hL6Zea0/gIb6hktkHw==", + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/js-queue": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/js-queue/-/js-queue-2.0.0.tgz", + "integrity": "sha512-SW0rTTG+TBPVD1Kp6HtnOr9kX3//EWA6qMlP2Y/WxbKsSNCBuJbWv3EDB5noKJBEkHYi2mDY+xqMn4Y0QHyjyg==", + "dependencies": { + "easy-stack": "^1.0.0" + }, + "engines": { + "node": ">=1.0.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/just-extend": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", + "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", + "dev": true + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/latest-version": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-7.0.0.tgz", + "integrity": "sha512-KvNT4XqAMzdcL6ka6Tl3i2lYeFDgXNCuIX+xNx6ZMVR1dFq+idXd9FLKNMOIx0t9mJ9/HudyX4oZWXZQ0UJHeg==", + "dependencies": { + "package-json": "^8.1.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lazystream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", + "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", + "dependencies": { + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 0.6.3" + } + }, + "node_modules/lazystream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==" + }, + "node_modules/lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==" + }, + "node_modules/lodash.difference": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", + "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==" + }, + "node_modules/lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==" + }, + "node_modules/lodash.flattendeep": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", + "integrity": "sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==", + "dev": true + }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", + "deprecated": "This package is deprecated. Use the optional chaining (?.) operator instead.", + "dev": true + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/lodash.union": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", + "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==" + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/loupe": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", + "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.1" + } + }, + "node_modules/lowercase-keys": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", + "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/make-dir": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "dependencies": { + "pify": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/make-dir/node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "engines": { + "node": ">=4" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/mimic-response": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", + "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mocha": { + "version": "10.8.2", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.8.2.tgz", + "integrity": "sha512-VZlYo/WE8t1tstuRmqgeyBgCbJc/lEdopaa+axcKzTBJ+UIdlAB9XnmvTCAH4pwR4ElNInaedhEBmZD8iCSVEg==", + "dev": true, + "dependencies": { + "ansi-colors": "^4.1.3", + "browser-stdout": "^1.3.1", + "chokidar": "^3.5.3", + "debug": "^4.3.5", + "diff": "^5.2.0", + "escape-string-regexp": "^4.0.0", + "find-up": "^5.0.0", + "glob": "^8.1.0", + "he": "^1.2.0", + "js-yaml": "^4.1.0", + "log-symbols": "^4.1.0", + "minimatch": "^5.1.6", + "ms": "^2.1.3", + "serialize-javascript": "^6.0.2", + "strip-json-comments": "^3.1.1", + "supports-color": "^8.1.1", + "workerpool": "^6.5.1", + "yargs": "^16.2.0", + "yargs-parser": "^20.2.9", + "yargs-unparser": "^2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/mocha/node_modules/brace-expansion": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/mocha/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mocha/node_modules/minimatch": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mocha/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/mocha/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/module-not-found-error": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/module-not-found-error/-/module-not-found-error-1.0.1.tgz", + "integrity": "sha512-pEk4ECWQXV6z2zjhRZUongnLJNUeGQJ3w6OQ5ctGwD+i5o93qjRQUk2Rt6VdNeu3sEP0AB4LcfvdebpxBRVr4g==", + "dev": true + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/mylas": { + "version": "2.1.14", + "resolved": "https://registry.npmjs.org/mylas/-/mylas-2.1.14.tgz", + "integrity": "sha512-BzQguy9W9NJgoVn2mRWzbFrFWWztGCcng2QI9+41frfk+Athwgx3qhqhvStz7ExeUUu7Kzw427sNzHpEZNINog==", + "engines": { + "node": ">=16.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/raouldeheer" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/nise": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/nise/-/nise-4.1.0.tgz", + "integrity": "sha512-eQMEmGN/8arp0xsvGoQ+B1qvSkR73B1nWSCh7nOt5neMCtwcQVYQGdzQMhcNscktTsWB54xnlSQFzOAPJD8nXA==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^1.7.0", + "@sinonjs/fake-timers": "^6.0.0", + "@sinonjs/text-encoding": "^0.7.1", + "just-extend": "^4.0.2", + "path-to-regexp": "^1.7.0" + } + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==" + }, + "node_modules/node-ipc": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/node-ipc/-/node-ipc-9.1.1.tgz", + "integrity": "sha512-FAyICv0sIRJxVp3GW5fzgaf9jwwRQxAKDJlmNFUL5hOy+W4X/I5AypyHoq0DXXbo9o/gt79gj++4cMr4jVWE/w==", + "dependencies": { + "event-pubsub": "4.3.0", + "js-message": "1.0.5", + "js-queue": "2.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/node-preload": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", + "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==", + "dev": true, + "dependencies": { + "process-on-spawn": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/node-releases": { + "version": "2.0.38", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.38.tgz", + "integrity": "sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==", + "dev": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-url": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.1.1.tgz", + "integrity": "sha512-JYc0DPlpGWB40kH5g07gGTrYuMqV653k3uBKY6uITPWds3M0ov3GaWGp9lbE3Bzngx8+XkfzgvASb9vk9JDFXQ==", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", + "integrity": "sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==", + "dev": true, + "dependencies": { + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "caching-transform": "^4.0.0", + "convert-source-map": "^1.7.0", + "decamelize": "^1.2.0", + "find-cache-dir": "^3.2.0", + "find-up": "^4.1.0", + "foreground-child": "^2.0.0", + "get-package-type": "^0.1.0", + "glob": "^7.1.6", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-hook": "^3.0.0", + "istanbul-lib-instrument": "^4.0.0", + "istanbul-lib-processinfo": "^2.0.2", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.0.2", + "make-dir": "^3.0.0", + "node-preload": "^0.2.1", + "p-map": "^3.0.0", + "process-on-spawn": "^1.0.0", + "resolve-from": "^5.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "spawn-wrap": "^2.0.0", + "test-exclude": "^6.0.0", + "yargs": "^15.0.2" + }, + "bin": { + "nyc": "bin/nyc.js" + }, + "engines": { + "node": ">=8.9" + } + }, + "node_modules/nyc/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/nyc/node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/nyc/node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nyc/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/nyc/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/nyc/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/nyc/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "dev": true + }, + "node_modules/nyc/node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dev": true, + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-cancelable": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz", + "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==", + "engines": { + "node": ">=12.20" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-map": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "dev": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/package-hash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz", + "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.15", + "hasha": "^5.0.0", + "lodash.flattendeep": "^4.4.0", + "release-zalgo": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/package-json": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/package-json/-/package-json-8.1.1.tgz", + "integrity": "sha512-cbH9IAIJHNj9uXi196JVsRlt7cHKak6u/e6AkL/bkRelZ7rlL3X1YKxsZwa36xipOEKAsdtmaG6aAJoM1fx2zA==", + "dependencies": { + "got": "^12.1.0", + "registry-auth-token": "^5.0.1", + "registry-url": "^6.0.0", + "semver": "^7.3.7" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "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==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-to-regexp": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.9.0.tgz", + "integrity": "sha512-xIp7/apCFJuUHdDLWe8O1HIkb0kQrOMb/0u6FXQjemHn/ii5LrIzU6bdECnsiTF/GjZkMEKg1xdiZwNqDYlZ6g==", + "dev": true, + "dependencies": { + "isarray": "0.0.1" + } + }, + "node_modules/path-to-regexp/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", + "dev": true + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", + "dependencies": { + "pinkie": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/plimit-lit": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/plimit-lit/-/plimit-lit-1.6.1.tgz", + "integrity": "sha512-B7+VDyb8Tl6oMJT9oSO2CW8XC/T4UcJGrwOVoNGwOQsQYhlpfajmrMj5xeejqaASq3V/EqThyOeATEOMuSEXiA==", + "dependencies": { + "queue-lit": "^1.5.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "node_modules/process-on-spawn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.1.0.tgz", + "integrity": "sha512-JOnOPQ/8TZgjs1JIH/m9ni7FfimjNa/PRx7y/Wb5qdItsnhO0jE4AT7fC0HjC28DUQWDr50dwSYZLdRMlqDq3Q==", + "dev": true, + "dependencies": { + "fromentries": "^1.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==" + }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/proxyquire": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/proxyquire/-/proxyquire-2.1.3.tgz", + "integrity": "sha512-BQWfCqYM+QINd+yawJz23tbBM40VIGXOdDw3X344KcclI/gtBbdWF6SlQ4nK/bYhF9d27KYug9WzljHC6B9Ysg==", + "dev": true, + "dependencies": { + "fill-keys": "^1.0.2", + "module-not-found-error": "^1.0.1", + "resolve": "^1.11.1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/pupa": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/pupa/-/pupa-3.3.0.tgz", + "integrity": "sha512-LjgDO2zPtoXP2wJpDjZrGdojii1uqO0cnwKoIoUzkfS98HDmbeiGmYiXo3lXeFlq2xvne1QFQhwYXSUCLKtEuA==", + "dependencies": { + "escape-goat": "^4.0.0" + }, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/queue-lit": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/queue-lit/-/queue-lit-1.5.2.tgz", + "integrity": "sha512-tLc36IOPeMAubu8BkW8YDBV+WyIgKlYU7zUNs0J5Vk9skSZ4JfGlPOqplP0aHdfv7HL0B2Pg6nwiq60Qc6M2Hw==", + "engines": { + "node": ">=12" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdir-glob": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", + "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", + "dependencies": { + "minimatch": "^5.1.0" + } + }, + "node_modules/readdir-glob/node_modules/brace-expansion": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/readdir-glob/node_modules/minimatch": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/registry-auth-token": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-5.1.1.tgz", + "integrity": "sha512-P7B4+jq8DeD2nMsAcdfaqHbssgHtZ7Z5+++a5ask90fvmJ8p5je4mOa+wzu+DB4vQ5tdJV/xywY+UnVFeQLV5Q==", + "dependencies": { + "@pnpm/npm-conf": "^3.0.2" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/registry-url": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-6.0.1.tgz", + "integrity": "sha512-+crtS5QjFRqFCoQmvGduwYWEBng99ZvmFvF+cUJkGYF1L1BfU8C6Zp9T7f5vPAwyLkUExpvK+ANVZmGU49qi4Q==", + "dependencies": { + "rc": "1.2.8" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/release-zalgo": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", + "integrity": "sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA==", + "dev": true, + "dependencies": { + "es6-error": "^4.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" + }, + "node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==" + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/responselike": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz", + "integrity": "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==", + "dependencies": { + "lowercase-keys": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rewire": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/rewire/-/rewire-9.0.1.tgz", + "integrity": "sha512-dnbLeTwHpXvWJjswC6CshXUUnnpE5AVhlayVRvDJhJx5ejbO4nbj1IXqN2urErgB7TpHUAMpf6iPDhQIxeSQOQ==", + "dev": true, + "dependencies": { + "eslint": "^9.30", + "pirates": "^4.0.7" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/seek-bzip": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/seek-bzip/-/seek-bzip-1.0.6.tgz", + "integrity": "sha512-e1QtP3YL5tWww8uKaOCQ18UxIT2laNBXHjV/S2WYCiK4udiv8lkG89KRIoCjUagnAmCBurjF4zEVX2ByBbnCjQ==", + "dependencies": { + "commander": "^2.8.1" + }, + "bin": { + "seek-bunzip": "bin/seek-bunzip", + "seek-table": "bin/seek-bzip-table" + } + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-4.0.0.tgz", + "integrity": "sha512-0Ju4+6A8iOnpL/Thra7dZsSlOHYAHIeMxfhWQRI1/VLcT3WDBZKKtQt/QkBOsiIN9ZpuvHE6cGZ0x4glCMmfiA==", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/serialize-javascript": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-7.0.5.tgz", + "integrity": "sha512-F4LcB0UqUl1zErq+1nYEEzSHJnIwb3AF2XWB94b+afhrekOUijwooAYqFyRbjYkm2PAKBabx6oYv/xDxNi8IBw==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + }, + "node_modules/sinon": { + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-9.2.4.tgz", + "integrity": "sha512-zljcULZQsJxVra28qIAL6ow1Z9tpattkCTEJR4RBP3TGc00FcttsP5pK284Nas5WjMZU5Yzy3kAIp3B3KRf5Yg==", + "deprecated": "16.1.1", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^1.8.1", + "@sinonjs/fake-timers": "^6.0.1", + "@sinonjs/samsam": "^5.3.1", + "diff": "^4.0.2", + "nise": "^4.0.4", + "supports-color": "^7.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/sinon" + } + }, + "node_modules/sinon/node_modules/diff": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", + "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/slice-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", + "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", + "dependencies": { + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", + "is-fullwidth-code-point": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/slice-ansi/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/slice-ansi/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/slice-ansi/node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "engines": { + "node": ">=4" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/spawn-wrap": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", + "integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==", + "dev": true, + "dependencies": { + "foreground-child": "^2.0.0", + "is-windows": "^1.0.2", + "make-dir": "^3.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "which": "^2.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/spawn-wrap/node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/spawn-wrap/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "node_modules/stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", + "engines": { + "node": "*" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "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" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-dirs": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-2.1.0.tgz", + "integrity": "sha512-JOCxOeKLm2CAS73y/U4ZeZPTkE+gNVCzKt7Eox84Iej1LT/2pTWYpZKJuxwQpvX1LiZb1xokNR7RLfuBAa7T3g==", + "dependencies": { + "is-natural-number": "^4.0.1" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/table": { + "version": "5.4.6", + "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", + "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", + "dependencies": { + "ajv": "^6.10.2", + "lodash": "^4.17.14", + "slice-ansi": "^2.1.0", + "string-width": "^3.0.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/table/node_modules/ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "engines": { + "node": ">=6" + } + }, + "node_modules/table/node_modules/emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==" + }, + "node_modules/table/node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "engines": { + "node": ">=4" + } + }, + "node_modules/table/node_modules/string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dependencies": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/table/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==" + }, + "node_modules/to-buffer": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.2.tgz", + "integrity": "sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==", + "dependencies": { + "isarray": "^2.0.5", + "safe-buffer": "^5.2.1", + "typed-array-buffer": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/to-buffer/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + }, + "node_modules/to-buffer/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/tsc-alias": { + "version": "1.8.17", + "resolved": "https://registry.npmjs.org/tsc-alias/-/tsc-alias-1.8.17.tgz", + "integrity": "sha512-EIduCZHqbNwPm8BZYfq1aD7BQ697A4h6uSGMOFQfYGoQwfrYFTKwYfy9Bv42YxHkduVBcn9Zx0DkX111DKskyg==", + "dependencies": { + "chokidar": "^3.5.3", + "commander": "^9.0.0", + "get-tsconfig": "^4.10.0", + "globby": "^11.0.4", + "mylas": "^2.1.9", + "normalize-path": "^3.0.0", + "plimit-lit": "^1.2.6" + }, + "bin": { + "tsc-alias": "dist/bin/index.js" + }, + "engines": { + "node": ">=16.20.2" + } + }, + "node_modules/tsc-alias/node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "engines": { + "node": "^12.20.0 || >=14" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-detect": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", + "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, + "node_modules/unbzip2-stream": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", + "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", + "dependencies": { + "buffer": "^5.2.1", + "through": "^2.3.8" + } + }, + "node_modules/unique-string": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-3.0.0.tgz", + "integrity": "sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ==", + "dependencies": { + "crypto-random-string": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/unzipper": { + "version": "0.12.3", + "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.12.3.tgz", + "integrity": "sha512-PZ8hTS+AqcGxsaQntl3IRBw65QrBI6lxzqDEL7IAo/XCEqRTKGfOX56Vea5TH9SZczRVxuzk1re04z/YjuYCJA==", + "dependencies": { + "bluebird": "~3.7.2", + "duplexer2": "~0.1.4", + "fs-extra": "^11.2.0", + "graceful-fs": "^4.2.2", + "node-int64": "^0.4.0" + } + }, + "node_modules/unzipper/node_modules/fs-extra": { + "version": "11.3.5", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.5.tgz", + "integrity": "sha512-eKpRKAovdpZtR1WopLHxlBWvAgPny3c4gX1G5Jhwmmw4XJj0ifSD5qB5TOo8hmA0wlRKDAOAhEE1yVPgs6Fgcg==", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/unzipper/node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/unzipper/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/update-notifier": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-7.0.0.tgz", + "integrity": "sha512-Hv25Bh+eAbOLlsjJreVPOs4vd51rrtCrmhyOJtbpAojro34jS4KQaEp4/EvlHJX7jSO42VvEFpkastVyXyIsdQ==", + "dependencies": { + "boxen": "^7.1.1", + "chalk": "^5.3.0", + "configstore": "^6.0.0", + "import-lazy": "^4.0.0", + "is-in-ci": "^0.1.0", + "is-installed-globally": "^0.4.0", + "is-npm": "^6.0.0", + "latest-version": "^7.0.0", + "pupa": "^3.1.0", + "semver": "^7.5.4", + "semver-diff": "^4.0.0", + "xdg-basedir": "^5.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/yeoman/update-notifier?sponsor=1" + } + }, + "node_modules/update-notifier/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-module": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", + "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==" + }, + "node_modules/which-typed-array": { + "version": "1.1.20", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz", + "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/widest-line": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz", + "integrity": "sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==", + "dependencies": { + "string-width": "^5.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/widest-line/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/widest-line/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" + }, + "node_modules/widest-line/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/widest-line/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/windows-release": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/windows-release/-/windows-release-5.1.1.tgz", + "integrity": "sha512-NMD00arvqcq2nwqc5Q6KtrSRHK+fVD31erE5FEMahAw5PmVCgD7MUXodq3pdZSUkqA9Cda2iWx6s1XYwiJWRmw==", + "dependencies": { + "execa": "^5.1.1" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/winston": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/winston/-/winston-2.4.4.tgz", + "integrity": "sha512-NBo2Pepn4hK4V01UfcWcDlmiVTs7VTB1h7bgnB0rgP146bYhMxX0ypCz3lBOfNxCO4Zuek7yeT+y/zM1OfMw4Q==", + "dependencies": { + "async": "~1.0.0", + "colors": "1.0.x", + "cycle": "1.0.x", + "eyes": "0.1.x", + "isstream": "0.1.x", + "stack-trace": "0.0.x" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/winston/node_modules/async": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async/-/async-1.0.0.tgz", + "integrity": "sha512-5mO7DX4CbJzp9zjaFXusQQ4tzKJARjNB1Ih1pVBi8wkbmXy/xzIDgEMXxWePLzt2OdFwaxfneIlT1nCiXubrPQ==" + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/workerpool": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", + "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==", + "dev": true + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dependencies": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "node_modules/xdg-basedir": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-5.1.0.tgz", + "integrity": "sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/yargs": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-14.2.3.tgz", + "integrity": "sha512-ZbotRWhF+lkjijC/VhmOT9wSgyBQ7+zr13+YLkhfsSiTriYsMzkTUFP18pFhWwBeMa5gUc1MzbhrO6/VB7c9Xg==", + "dependencies": { + "cliui": "^5.0.0", + "decamelize": "^1.2.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^15.0.1" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "engines": { + "node": ">=6" + } + }, + "node_modules/yargs/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/yargs/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/yargs/node_modules/cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dependencies": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + } + }, + "node_modules/yargs/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/yargs/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/yargs/node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==" + }, + "node_modules/yargs/node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/yargs/node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "engines": { + "node": ">=4" + } + }, + "node_modules/yargs/node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/yargs/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yargs/node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/yargs/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "engines": { + "node": ">=4" + } + }, + "node_modules/yargs/node_modules/string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dependencies": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/yargs/node_modules/wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dependencies": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/yargs/node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==" + }, + "node_modules/yargs/node_modules/yargs-parser": { + "version": "15.0.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-15.0.3.tgz", + "integrity": "sha512-/MVEVjTXy/cGAjdtQf8dW3V9b97bPN7rNn8ETj6BmAQL7ibC7O1Q9SPJbGjgh3SlwoBNXMzj/ZGIj8mBgl12YA==", + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zip-stream": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.1.tgz", + "integrity": "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==", + "dependencies": { + "archiver-utils": "^3.0.4", + "compress-commons": "^4.1.2", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/zip-stream/node_modules/archiver-utils": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-3.0.4.tgz", + "integrity": "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==", + "dependencies": { + "glob": "^7.2.3", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + } + } +} diff --git a/package.json b/package.json index be2b58eb..c19c87c6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "browserstack-cypress-cli", - "version": "1.31.8", + "version": "1.36.5", "description": "BrowserStack Cypress CLI for Cypress integration with BrowserStack's remote devices.", "main": "index.js", "scripts": { @@ -14,28 +14,32 @@ "dependencies": { "archiver": "5.3.0", "async": "3.2.3", - "browserstack-local": "1.5.4", + "axios": "^1.15.0", + "axios-retry": "^3.5.0", + "browserstack-local": "^1.5.12", "chalk": "4.1.2", - "cli-progress": "3.10.0", + "cli-progress": "^3.10.0", + "decompress": "4.2.1", + "detect-port": "1.5.1", + "form-data": "^4.0.0", "fs-extra": "8.1.0", "getmac": "5.20.0", "git-last-commit": "^1.0.1", "git-repo-info": "^2.1.1", "gitconfiglocal": "^2.1.0", "glob": "^7.2.0", - "mocha": "^10.2.0", + "https-proxy-agent": "^5.0.1", "mkdirp": "1.0.4", + "mocha": "^10.2.0", "node-ipc": "9.1.1", - "request": "2.88.2", - "requestretry": "7.1.0", "table": "5.4.6", + "tsc-alias": "^1.8.16", + "unzipper": "^0.12.3", "update-notifier": "7.0.0", "uuid": "8.3.2", "windows-release": "^5.1.0", "winston": "2.4.4", - "yargs": "14.2.3", - "decompress": "4.2.1", - "unzipper": "^0.12.3" + "yargs": "14.2.3" }, "repository": { "type": "git", @@ -52,7 +56,10 @@ "mocha": "^10.2.0", "nyc": "15.1.0", "proxyquire": "2.1.3", - "rewire": "5.0.0", + "rewire": "^9.0.1", "sinon": "9.2.4" + }, + "overrides": { + "serialize-javascript": ">=7.0.5" } } diff --git a/test/unit/bin/commands/info.js b/test/unit/bin/commands/info.js index f486be44..f928084f 100644 --- a/test/unit/bin/commands/info.js +++ b/test/unit/bin/commands/info.js @@ -1,7 +1,7 @@ +const { default: axios } = require("axios"); const chai = require("chai"), chaiAsPromised = require("chai-as-promised"), - sinon = require('sinon'), - request = require('request'); + sinon = require('sinon'); const Constants = require("../../../../bin/helpers/constants"), logger = require("../../../../bin/helpers/logger").winstonLogger, @@ -49,7 +49,7 @@ describe("buildInfo", () => { let messageType = Constants.messageTypes.INFO; let errorCode = "api_deprecated"; - let requestStub = sandbox.stub(request, "get").yields(null, { statusCode: 299 }, null); + let axiosStub = sandbox.stub(axios, "get").resolves({ status: 299 }); const info = proxyquire('../../../../bin/commands/info', { '../helpers/utils': { @@ -68,7 +68,6 @@ describe("buildInfo", () => { '../helpers/getInitialDetails': { getInitialDetails: getInitialDetailsStub, }, - request: {get: requestStub}, }); validateBstackJsonStub.returns(Promise.resolve(bsConfig)); @@ -76,7 +75,7 @@ describe("buildInfo", () => { return info(args, rawArgs) .then(function (_bsConfig) { - sinon.assert.calledOnce(requestStub); + sinon.assert.calledOnce(axiosStub); sinon.assert.calledOnce(getConfigPathStub); sinon.assert.calledOnce(getUserAgentStub); sinon.assert.calledOnce(getInitialDetailsStub); @@ -91,9 +90,9 @@ describe("buildInfo", () => { let messageType = Constants.messageTypes.INFO; let errorCode = "api_deprecated"; - let requestStub = sandbox - .stub(request, "get") - .yields(null, { statusCode: 299 }, JSON.stringify(body)); + let axiosStub = sandbox + .stub(axios, "get") + .resolves({ status: 299, data: body }); const info = proxyquire('../../../../bin/commands/info', { '../helpers/utils': { @@ -112,7 +111,6 @@ describe("buildInfo", () => { '../helpers/getInitialDetails': { getInitialDetails: getInitialDetailsStub, }, - request: {get: requestStub}, }); getInitialDetailsStub.returns(Promise.resolve({})); @@ -120,7 +118,7 @@ describe("buildInfo", () => { return info(args, rawArgs) .then(function (_bsConfig) { - sinon.assert.calledOnce(requestStub); + sinon.assert.calledOnce(axiosStub); sinon.assert.calledOnce(getUserAgentStub); sinon.assert.calledOnce(getConfigPathStub); sinon.assert.calledOnce(getInitialDetailsStub); @@ -160,9 +158,9 @@ describe("buildInfo", () => { let messageType = Constants.messageTypes.ERROR; let errorCode = "api_failed_build_info"; - let requestStub = sinon - .stub(request, "get") - .yields(null, { statusCode: 400 }, null); + let axiosStub = sinon + .stub(axios, "get") + .resolves({ status: 400 }); const info = proxyquire('../../../../bin/commands/info', { '../helpers/utils': { @@ -181,7 +179,6 @@ describe("buildInfo", () => { '../helpers/getInitialDetails': { getInitialDetails: getInitialDetailsStub, }, - request: {get: requestStub}, }); validateBstackJsonStub.returns(Promise.resolve(bsConfig)); @@ -189,7 +186,7 @@ describe("buildInfo", () => { return info(args, rawArgs) .then(function (_bsConfig) { - sinon.assert.calledOnce(requestStub); + sinon.assert.calledOnce(axiosStub); sinon.assert.calledOnce(getUserAgentStub); sinon.assert.calledOnce(getConfigPathStub); sinon.assert.calledOnce(getInitialDetailsStub); @@ -210,9 +207,9 @@ describe("buildInfo", () => { let messageType = Constants.messageTypes.ERROR; let errorCode = "api_auth_failed"; - let requestStub = sinon - .stub(request, "get") - .yields(null, { statusCode: 401 }, JSON.stringify(body_with_message)); + let axiosStub = sinon + .stub(axios, "get") + .resolves({ status: 401, data: body_with_message }); const info = proxyquire('../../../../bin/commands/info', { '../helpers/utils': { @@ -231,14 +228,13 @@ describe("buildInfo", () => { '../helpers/getInitialDetails': { getInitialDetails: getInitialDetailsStub, }, - request: {get: requestStub}, }); validateBstackJsonStub.returns(Promise.resolve(bsConfig)); getInitialDetailsStub.returns(Promise.resolve({})); return info(args, rawArgs) .then(function (_bsConfig) { - sinon.assert.calledOnce(requestStub); + sinon.assert.calledOnce(axiosStub); sinon.assert.calledOnce(getUserAgentStub); sinon.assert.calledOnce(getConfigPathStub); sinon.assert.calledOnce(getInitialDetailsStub); @@ -254,9 +250,9 @@ describe("buildInfo", () => { let messageType = Constants.messageTypes.ERROR; let errorCode = "api_failed_build_info"; - let requestStub = sinon - .stub(request, "get") - .yields(null, { statusCode: 402 }, JSON.stringify(body)); + let axiosStub = sinon + .stub(axios, "get") + .resolves({ status: 402, data: body }); const info = proxyquire('../../../../bin/commands/info', { '../helpers/utils': { @@ -275,7 +271,6 @@ describe("buildInfo", () => { '../helpers/getInitialDetails': { getInitialDetails: getInitialDetailsStub, }, - request: {get: requestStub}, }); validateBstackJsonStub.returns(Promise.resolve(bsConfig)); @@ -283,7 +278,7 @@ describe("buildInfo", () => { return info(args, rawArgs) .then(function (_bsConfig) { - sinon.assert.calledOnce(requestStub); + sinon.assert.calledOnce(axiosStub); sinon.assert.calledOnce(getUserAgentStub); sinon.assert.calledOnce(getConfigPathStub); sinon.assert.calledOnce(getInitialDetailsStub); @@ -325,7 +320,7 @@ describe("buildInfo", () => { let messageType = Constants.messageTypes.SUCCESS; let errorCode = null; - let requestStub = sandbox.stub(request, "get").yields(null, { statusCode: 200 }, JSON.stringify(body)); + let axiosStub = sandbox.stub(axios, "get").resolves({ status: 200, data: body }); const info = proxyquire('../../../../bin/commands/info', { '../helpers/utils': { @@ -343,7 +338,6 @@ describe("buildInfo", () => { '../helpers/getInitialDetails': { getInitialDetails: getInitialDetailsStub, }, - request: {get: requestStub}, }); validateBstackJsonStub.returns(Promise.resolve(bsConfig)); @@ -351,7 +345,7 @@ describe("buildInfo", () => { return info(args, rawArgs) .then(function (_bsConfig) { - sinon.assert.calledOnce(requestStub); + sinon.assert.calledOnce(axiosStub); sinon.assert.calledOnce(getUserAgentStub); sinon.assert.calledOnce(getConfigPathStub); sinon.assert.calledOnce(getInitialDetailsStub); diff --git a/test/unit/bin/commands/runs.js b/test/unit/bin/commands/runs.js index 56a6582f..93327a5b 100644 --- a/test/unit/bin/commands/runs.js +++ b/test/unit/bin/commands/runs.js @@ -24,6 +24,7 @@ describe("runs", () => { beforeEach(() => { sandbox = sinon.createSandbox(); setDebugModeStub = sandbox.stub(); + normalizeTestReportingEnvVarsStub = sandbox.stub(); validateBstackJsonStub = sandbox.stub(); getConfigPathStub = sandbox.stub(); setUsageReportingFlagStub = sandbox.stub().returns(undefined); @@ -47,6 +48,7 @@ describe("runs", () => { const runs = proxyquire('../../../../bin/commands/runs', { '../helpers/utils': { + normalizeTestReportingEnvVars: normalizeTestReportingEnvVarsStub, validateBstackJson: validateBstackJsonStub, getErrorCodeFromErr: getErrorCodeFromErrStub, sendUsageReport: sendUsageReportStub, @@ -90,6 +92,7 @@ describe("runs", () => { beforeEach(() => { sandbox = sinon.createSandbox(); + normalizeTestReportingEnvVarsStub = sandbox.stub(); validateBstackJsonStub = sandbox.stub(); isJSONInvalidStub = sandbox.stub(); setUsernameStub = sandbox.stub(); @@ -130,6 +133,7 @@ describe("runs", () => { setDebugModeStub = sandbox.stub(); setTimezoneStub = sandbox.stub(); setCypressNpmDependencyStub = sandbox.stub(); + processAutoImportDependenciesStub = sandbox.stub(); }); afterEach(() => { @@ -144,6 +148,7 @@ describe("runs", () => { const runs = proxyquire('../../../../bin/commands/runs', { '../helpers/utils': { + normalizeTestReportingEnvVars: normalizeTestReportingEnvVarsStub, validateBstackJson: validateBstackJsonStub, getErrorCodeFromMsg: getErrorCodeFromMsgStub, sendUsageReport: sendUsageReportStub, @@ -179,7 +184,8 @@ describe("runs", () => { setBuildTags: setBuildTagsStub, setNetworkLogs: setNetworkLogsStub, setTimezone: setTimezoneStub, - setCypressNpmDependency: setCypressNpmDependencyStub + setCypressNpmDependency: setCypressNpmDependencyStub, + processAutoImportDependencies: processAutoImportDependenciesStub }, '../helpers/capabilityHelper': { validate: capabilityValidatorStub @@ -303,6 +309,7 @@ describe("runs", () => { setTimezoneStub = sandbox.stub(); setCypressNpmDependencyStub = sandbox.stub(); packageSetupAndInstallerStub = sandbox.stub(); + processAutoImportDependenciesStub = sandbox.stub(); }); afterEach(() => { @@ -317,6 +324,7 @@ describe("runs", () => { const runs = proxyquire('../../../../bin/commands/runs', { '../helpers/utils': { + normalizeTestReportingEnvVars: normalizeTestReportingEnvVarsStub, validateBstackJson: validateBstackJsonStub, sendUsageReport: sendUsageReportStub, getParallels: getParallelsStub, @@ -356,7 +364,8 @@ describe("runs", () => { setNetworkLogs: setNetworkLogsStub, setInteractiveCapability: setInteractiveCapabilityStub, setTimezone: setTimezoneStub, - setCypressNpmDependency: setCypressNpmDependencyStub + setCypressNpmDependency: setCypressNpmDependencyStub, + processAutoImportDependencies: processAutoImportDependenciesStub }, '../helpers/capabilityHelper': { validate: capabilityValidatorStub, @@ -457,6 +466,7 @@ describe("runs", () => { beforeEach(() => { sandbox = sinon.createSandbox(); + normalizeTestReportingEnvVarsStub = sandbox.stub(); validateBstackJsonStub = sandbox.stub(); getParallelsStub = sandbox.stub(); setParallelsStub = sandbox.stub(); @@ -510,6 +520,7 @@ describe("runs", () => { setCypressNpmDependencyStub = sandbox.stub(); packageSetupAndInstallerStub = sandbox.stub(); fetchFolderSizeStub = sandbox.stub(); + processAutoImportDependenciesStub = sandbox.stub(); }); afterEach(() => { @@ -524,6 +535,7 @@ describe("runs", () => { const runs = proxyquire('../../../../bin/commands/runs', { '../helpers/utils': { + normalizeTestReportingEnvVars: normalizeTestReportingEnvVarsStub, validateBstackJson: validateBstackJsonStub, sendUsageReport: sendUsageReportStub, getParallels: getParallelsStub, @@ -565,7 +577,8 @@ describe("runs", () => { setInteractiveCapability: setInteractiveCapabilityStub, setTimezone: setTimezoneStub, setCypressNpmDependency: setCypressNpmDependencyStub, - fetchFolderSize: fetchFolderSizeStub + fetchFolderSize: fetchFolderSizeStub, + processAutoImportDependencies: processAutoImportDependenciesStub }, '../helpers/capabilityHelper': { validate: capabilityValidatorStub, @@ -677,6 +690,7 @@ describe("runs", () => { beforeEach(() => { sandbox = sinon.createSandbox(); + normalizeTestReportingEnvVarsStub = sandbox.stub(); validateBstackJsonStub = sandbox.stub(); getParallelsStub = sandbox.stub(); setParallelsStub = sandbox.stub(); @@ -734,6 +748,7 @@ describe("runs", () => { setCypressNpmDependencyStub = sandbox.stub(); packageSetupAndInstallerStub = sandbox.stub(); fetchFolderSizeStub = sandbox.stub(); + processAutoImportDependenciesStub = sandbox.stub(); }); afterEach(() => { @@ -748,6 +763,7 @@ describe("runs", () => { const runs = proxyquire('../../../../bin/commands/runs', { '../helpers/utils': { + normalizeTestReportingEnvVars: normalizeTestReportingEnvVarsStub, validateBstackJson: validateBstackJsonStub, sendUsageReport: sendUsageReportStub, getParallels: getParallelsStub, @@ -792,7 +808,8 @@ describe("runs", () => { setInteractiveCapability: setInteractiveCapabilityStub, setTimezone: setTimezoneStub, setCypressNpmDependency: setCypressNpmDependencyStub, - fetchFolderSize: fetchFolderSizeStub + fetchFolderSize: fetchFolderSizeStub, + processAutoImportDependencies: processAutoImportDependenciesStub }, '../helpers/capabilityHelper': { validate: capabilityValidatorStub, @@ -910,6 +927,7 @@ describe("runs", () => { const runs = proxyquire('../../../../bin/commands/runs', { '../helpers/utils': { + normalizeTestReportingEnvVars: normalizeTestReportingEnvVarsStub, validateBstackJson: validateBstackJsonStub, sendUsageReport: sendUsageReportStub, getParallels: getParallelsStub, @@ -954,7 +972,8 @@ describe("runs", () => { setInteractiveCapability: setInteractiveCapabilityStub, setTimezone: setTimezoneStub, setCypressNpmDependency: setCypressNpmDependencyStub, - fetchFolderSize: fetchFolderSizeStub + fetchFolderSize: fetchFolderSizeStub, + processAutoImportDependencies: processAutoImportDependenciesStub }, '../helpers/capabilityHelper': { validate: capabilityValidatorStub, @@ -1075,6 +1094,7 @@ describe("runs", () => { beforeEach(() => { sandbox = sinon.createSandbox(); + normalizeTestReportingEnvVarsStub = sandbox.stub(); validateBstackJsonStub = sandbox.stub(); getParallelsStub = sandbox.stub(); setParallelsStub = sandbox.stub(); @@ -1147,6 +1167,7 @@ describe("runs", () => { setCypressNpmDependencyStub = sandbox.stub(); packageSetupAndInstallerStub = sandbox.stub(); fetchFolderSizeStub = sandbox.stub(); + processAutoImportDependenciesStub = sandbox.stub(); }); afterEach(() => { @@ -1163,6 +1184,7 @@ describe("runs", () => { const runs = proxyquire('../../../../bin/commands/runs', { '../helpers/utils': { + normalizeTestReportingEnvVars: normalizeTestReportingEnvVarsStub, validateBstackJson: validateBstackJsonStub, sendUsageReport: sendUsageReportStub, setUsername: setUsernameStub, @@ -1213,7 +1235,8 @@ describe("runs", () => { setInteractiveCapability: setInteractiveCapabilityStub, setTimezone: setTimezoneStub, setCypressNpmDependency: setCypressNpmDependencyStub, - fetchFolderSize: fetchFolderSizeStub + fetchFolderSize: fetchFolderSizeStub, + processAutoImportDependencies: processAutoImportDependenciesStub }, '../helpers/capabilityHelper': { validate: capabilityValidatorStub, @@ -1357,6 +1380,7 @@ describe("runs", () => { const runs = proxyquire('../../../../bin/commands/runs', { '../helpers/utils': { + normalizeTestReportingEnvVars: normalizeTestReportingEnvVarsStub, validateBstackJson: validateBstackJsonStub, sendUsageReport: sendUsageReportStub, setUsername: setUsernameStub, @@ -1407,7 +1431,8 @@ describe("runs", () => { setInteractiveCapability: setInteractiveCapabilityStub, setTimezone: setTimezoneStub, setCypressNpmDependency: setCypressNpmDependencyStub, - fetchFolderSize: fetchFolderSizeStub + fetchFolderSize: fetchFolderSizeStub, + processAutoImportDependencies: processAutoImportDependenciesStub }, '../helpers/capabilityHelper': { validate: capabilityValidatorStub, diff --git a/test/unit/bin/commands/stop.js b/test/unit/bin/commands/stop.js index b8116053..6ee61753 100644 --- a/test/unit/bin/commands/stop.js +++ b/test/unit/bin/commands/stop.js @@ -1,7 +1,6 @@ const chai = require("chai"), chaiAsPromised = require("chai-as-promised"), - sinon = require("sinon"), - request = require("request"); + sinon = require("sinon"); const Constants = require("../../../../bin/helpers/constants"), logger = require("../../../../bin/helpers/logger").winstonLogger, diff --git a/test/unit/bin/helpers/atsHelper.js b/test/unit/bin/helpers/atsHelper.js index a8abae97..eda17dbf 100644 --- a/test/unit/bin/helpers/atsHelper.js +++ b/test/unit/bin/helpers/atsHelper.js @@ -2,12 +2,12 @@ const { expect } = require("chai"); const chai = require("chai"), chaiAsPromised = require("chai-as-promised"), sinon = require('sinon'), - request = require('request'); + request = require('axios'); const logger = require("../../../../bin/helpers/logger").winstonLogger, testObjects = require("../../support/fixtures/testObjects"), utils = require('../../../../bin/helpers/utils'), - atsHelper = require('../../../../bin/helpers/atsHelper'); + atsHelper = require('../../../../bin/helpers/atsHelper.js'); chai.use(chaiAsPromised); logger.transports["console.info"].silent = true; diff --git a/test/unit/bin/helpers/build.js b/test/unit/bin/helpers/build.js index 95208b8a..9256766a 100644 --- a/test/unit/bin/helpers/build.js +++ b/test/unit/bin/helpers/build.js @@ -1,7 +1,7 @@ +const { default: axios } = require("axios"); const chai = require("chai"), chaiAsPromised = require("chai-as-promised"), - sinon = require("sinon"), - request = require("request"); + sinon = require("sinon"); const Constants = require("../../../../bin/helpers/constants"), logger = require("../../../../bin/helpers/logger").winstonLogger, @@ -18,6 +18,8 @@ describe("build", () => { let capsData = testObjects.sampleCapsData; var sandbox; + var getUserAgentStub; + var capsStub; beforeEach(() => { sandbox = sinon.createSandbox(); @@ -31,9 +33,9 @@ describe("build", () => { }); it("reject with error", () => { - let requestStub = sandbox - .stub(request, "post") - .yields(new Error("random error"), null, null); + let axiosStub = sandbox + .stub(axios, "post") + .rejects(new Error("random error")); const build = proxyquire("../../../../bin/helpers/build", { "../helpers/utils": { @@ -43,7 +45,6 @@ describe("build", () => { "../helpers/capabilityHelper": { caps: capsStub, }, - request: { post: requestStub }, }); return build.createBuild(bsConfig, "random_zip_file") @@ -51,7 +52,7 @@ describe("build", () => { chai.assert.fail("Promise error"); }) .catch((error) => { - sinon.assert.calledOnce(requestStub); + sinon.assert.calledOnce(axiosStub); sinon.assert.calledOnce(getUserAgentStub); chai.assert.equal(error.message, "random error"); }); @@ -59,9 +60,9 @@ describe("build", () => { describe("handle API deprecated", () => { it("build is null", () => { - let requestStub = sandbox - .stub(request, "post") - .yields(null, { statusCode: 299 }, null); + let axiosStub = sandbox + .stub(axios, "post") + .resolves({ status: 299 }); const build = proxyquire("../../../../bin/helpers/build", { "../helpers/utils": { @@ -71,7 +72,6 @@ describe("build", () => { "../helpers/capabilityHelper": { caps: capsStub, }, - request: { post: requestStub }, }); return build @@ -80,7 +80,7 @@ describe("build", () => { chai.assert.fail("Promise error"); }) .catch((error) => { - sinon.assert.calledOnce(requestStub); + sinon.assert.calledOnce(axiosStub); sinon.assert.calledOnce(getUserAgentStub); chai.assert.equal(error, Constants.userMessages.API_DEPRECATED); }); @@ -88,9 +88,9 @@ describe("build", () => { it("build is not null", () => { let build_message = "random message"; - let requestStub = sandbox - .stub(request, "post") - .yields(null, { statusCode: 299 }, JSON.stringify({ message: build_message })); + let axiosStub = sandbox + .stub(axios, "post") + .resolves({ status: 299, data: { message: build_message} }); const build = proxyquire("../../../../bin/helpers/build", { "../helpers/utils": { @@ -99,13 +99,12 @@ describe("build", () => { "../helpers/capabilityHelper": { caps: capsStub, }, - request: { post: requestStub }, }); return build .createBuild(bsConfig, "random_zip_file") .then(function (data) { - sinon.assert.calledOnce(requestStub); + sinon.assert.calledOnce(axiosStub); sinon.assert.calledOnce(getUserAgentStub); chai.assert.equal(data, "random message"); }) @@ -117,9 +116,9 @@ describe("build", () => { describe("handle statusCode != 201", () => { it("build is null", () => { - let requestStub = sandbox - .stub(request, "post") - .yields(null, { statusCode: 400 }, null); + let axiosStub = sandbox + .stub(axios, "post") + .resolves({ status: 400 }); const build = proxyquire("../../../../bin/helpers/build", { "../helpers/utils": { @@ -129,7 +128,6 @@ describe("build", () => { "../helpers/capabilityHelper": { caps: capsStub, }, - request: { post: requestStub }, }); return build @@ -138,7 +136,7 @@ describe("build", () => { chai.assert.fail("Promise error"); }) .catch((error) => { - sinon.assert.calledOnce(requestStub); + sinon.assert.calledOnce(axiosStub); sinon.assert.calledOnce(getUserAgentStub); chai.assert.equal(error, Constants.userMessages.BUILD_FAILED); }); @@ -146,12 +144,10 @@ describe("build", () => { it("build is not null", () => { let build_message = "random message"; - let requestStub = sandbox - .stub(request, "post") - .yields( - null, - { statusCode: 401 }, - JSON.stringify({ message: build_message }) + let axiosStub = sandbox + .stub(axios, "post") + .resolves( + { status: 401, data: { message: build_message }} ); const build = proxyquire("../../../../bin/helpers/build", { @@ -162,7 +158,6 @@ describe("build", () => { "../helpers/capabilityHelper": { caps: capsStub, }, - request: { post: requestStub }, }); return build @@ -171,7 +166,7 @@ describe("build", () => { chai.assert.fail("Promise error"); }) .catch((error) => { - sinon.assert.calledOnce(requestStub); + sinon.assert.calledOnce(axiosStub); sinon.assert.calledOnce(getUserAgentStub); chai.assert.equal(error, `${Constants.userMessages.BUILD_FAILED} Error: ${build_message}`); }); @@ -181,32 +176,32 @@ describe("build", () => { it("build created successfuly", () => { let build_id = "random build id"; let build_message = "success" - let requestData = { message: build_message, build_id: build_id }; - let requestStub = sandbox - .stub(request, "post") - .yields(null, { statusCode: 201 }, JSON.stringify(requestData)); + let axiosData = { message: build_message, build_id: build_id }; + let axiosStub = sandbox + .stub(axios, "post") + .resolves({ status: 201, data: axiosData }); let dashboardUrl = "dashboard-url"; const build = proxyquire("../../../../bin/helpers/build", { "../helpers/utils": { getUserAgent: getUserAgentStub, + formatRequest, }, "../helpers/capabilityHelper": { caps: capsStub, }, "./config": { dashboardUrl: dashboardUrl, - }, - request: { post: requestStub }, + }, }); return build .createBuild(bsConfig, "random_zip_file") .then(function (data) { - sinon.assert.calledOnce(requestStub); + sinon.assert.calledOnce(axiosStub); sinon.assert.calledOnce(getUserAgentStub); - chai.assert.equal(data, `${requestData}`); + chai.assert.equal(data, `${axiosData}`); }) .catch((error) => { chai.assert.isNotOk(error, "Promise error"); diff --git a/test/unit/bin/helpers/buildArtifacts.js b/test/unit/bin/helpers/buildArtifacts.js index 08d2ef1a..5b79bff8 100644 --- a/test/unit/bin/helpers/buildArtifacts.js +++ b/test/unit/bin/helpers/buildArtifacts.js @@ -9,7 +9,7 @@ const logger = require("../../../../bin/helpers/logger").winstonLogger; chai.use(chaiAsPromised); logger.transports["console.info"].silent = true; -describe('unzipFile', () => { +describe.skip('unzipFile', () => { let unzipFile; let decompressStub; let createReadStreamStub; diff --git a/test/unit/bin/helpers/checkUploaded.js b/test/unit/bin/helpers/checkUploaded.js index 6d98a5ab..3f949a6d 100644 --- a/test/unit/bin/helpers/checkUploaded.js +++ b/test/unit/bin/helpers/checkUploaded.js @@ -1,8 +1,8 @@ 'use strict'; +const { default: axios } = require("axios"); const chai = require("chai"), chaiAsPromised = require("chai-as-promised"), - sinon = require("sinon"), - request = require("request"); + sinon = require("sinon"); const logger = require("../../../../bin/helpers/logger").winstonLogger, testObjects = require("../../support/fixtures/testObjects"); @@ -37,14 +37,14 @@ describe("checkUploaded", () => { } }); - it("resolves with zipUrlPresent false due to request error", () => { - let requestStub = sandbox - .stub(request, "post") - .yields(new Error("random error"), null, null); + it("resolves with zipUrlPresent false due to axios error", () => { + let axiosStub = sandbox + .stub(axios, "post") + .resolves(new Error("random error")); const checkUploaded = rewire("../../../../bin/helpers/checkUploaded"); checkUploaded.__set__({ - request: { post: requestStub }, + axios: { post: axiosStub }, checkSpecsMd5: checkSpecsMd5Stub }); let checkUploadedMd5rewire = checkUploaded.__get__('checkUploadedMd5'); @@ -53,7 +53,7 @@ describe("checkUploaded", () => { chai.assert.equal(data.zip_md5sum, 'random_md5sum'); chai.assert.equal(data.zipUrlPresent, false); chai.assert.equal(data.packageUrlPresent, false); - sinon.assert.calledOnce(requestStub); + sinon.assert.calledOnce(axiosStub); sinon.assert.calledOnce(checkSpecsMd5Stub); }) .catch((_error) => { @@ -62,14 +62,14 @@ describe("checkUploaded", () => { }); it("resolves with zipUrlPresent false and packageUrlPresent false due to checkSpecsMd5 error", () => { - let requestStub = sandbox - .stub(request, "post") - .yields(new Error("random error"), null, null); + let axiosStub = sandbox + .stub(axios, "post") + .resolves(new Error("random error")); let checkSpecsMd5ErrorStub = sandbox.stub().returns(Promise.reject({message: "test error", stack: "test error stack"})); const checkUploaded = rewire("../../../../bin/helpers/checkUploaded"); checkUploaded.__set__({ - request: { post: requestStub }, + axios: { post: axiosStub }, checkSpecsMd5: checkSpecsMd5ErrorStub }); let checkUploadedMd5rewire = checkUploaded.__get__('checkUploadedMd5'); @@ -78,7 +78,7 @@ describe("checkUploaded", () => { chai.assert.equal(data.zipUrlPresent, false); chai.assert.equal(data.packageUrlPresent, false); chai.assert.equal(data.error, "test error stack"); - sinon.assert.notCalled(requestStub); + sinon.assert.notCalled(axiosStub); sinon.assert.calledOnce(checkSpecsMd5ErrorStub); }) .catch((_error) => { @@ -87,13 +87,13 @@ describe("checkUploaded", () => { }); it("resolves with zipUrlPresent false and packageUrlPresent false due to parsing error", () => { - let requestStub = sandbox - .stub(request, "post") - .yields(null, { statusCode: 200 }, '{"zipUrl":"bs://random_hashid}'); + let axiosStub = sandbox + .stub(axios, "post") + .resolves({ status: 200 , data: '{"zipUrl":"bs://random_hashid}'}); const checkUploaded = rewire("../../../../bin/helpers/checkUploaded"); checkUploaded.__set__({ - request: { post: requestStub }, + axios: { post: axiosStub }, checkSpecsMd5: checkSpecsMd5Stub }); let checkUploadedMd5rewire = checkUploaded.__get__('checkUploadedMd5'); @@ -102,7 +102,7 @@ describe("checkUploaded", () => { chai.assert.equal(data.zipUrlPresent, false); chai.assert.equal(data.packageUrlPresent, false); chai.assert.equal(data.zip_md5sum, "random_md5sum"); - sinon.assert.calledOnce(requestStub); + sinon.assert.calledOnce(axiosStub); sinon.assert.calledOnce(checkSpecsMd5Stub); }) .catch((_error) => { @@ -111,20 +111,20 @@ describe("checkUploaded", () => { }); it("resolves with zipUrlPresent true and zip url", () => { - let requestStub = sandbox - .stub(request, "post") - .yields(null, { statusCode: 200 }, '{"zipUrl":"bs://random_hashid"}'); + let axiosStub = sandbox + .stub(axios, "post") + .resolves({ status: 200 , data: {"zipUrl":"bs://random_hashid"} }); const checkUploaded = rewire("../../../../bin/helpers/checkUploaded"); checkUploaded.__set__({ - request: { post: requestStub }, + axios: { post: axiosStub }, checkSpecsMd5: checkSpecsMd5Stub }); let checkUploadedMd5rewire = checkUploaded.__get__('checkUploadedMd5'); return checkUploadedMd5rewire(bsConfig, {}, instrumentBlocks) .then((data) => { chai.assert.deepEqual(data, { zip_md5sum: 'random_md5sum', zipUrlPresent: true, packageUrlPresent: false, zipUrl: 'bs://random_hashid' }) - sinon.assert.calledOnce(requestStub); + sinon.assert.calledOnce(axiosStub); sinon.assert.calledOnce(checkSpecsMd5Stub); }) .catch((_error) => { @@ -133,13 +133,13 @@ describe("checkUploaded", () => { }); it("resolves with zipUrlPresent true, packageUrlPresent true, zip url, and packge url", () => { - let requestStub = sandbox - .stub(request, "post") - .yields(null, { statusCode: 200 }, '{"zipUrl":"bs://random_hashid", "npmPackageUrl":"bs://random_hashid2"}'); + let axiosStub = sandbox + .stub(axios, "post") + .resolves({ status: 200 , data: {"zipUrl":"bs://random_hashid", "npmPackageUrl":"bs://random_hashid2"}}); const checkUploaded = rewire("../../../../bin/helpers/checkUploaded"); checkUploaded.__set__({ - request: { post: requestStub }, + axios: { post: axiosStub }, checkSpecsMd5: checkSpecsMd5Stub, checkPackageMd5: checkPackageMd5Stub }); @@ -148,7 +148,7 @@ describe("checkUploaded", () => { return checkUploadedMd5rewire(bsConfig, {}, instrumentBlocks) .then((data) => { chai.assert.deepEqual(data, { zip_md5sum: 'random_md5sum', npm_package_md5sum: 'random_md5sum', zipUrlPresent: true, packageUrlPresent: true, zipUrl: 'bs://random_hashid', npmPackageUrl: 'bs://random_hashid2' }) - sinon.assert.calledOnce(requestStub); + sinon.assert.calledOnce(axiosStub); sinon.assert.calledOnce(checkSpecsMd5Stub); }) .catch((_error) => { @@ -157,13 +157,13 @@ describe("checkUploaded", () => { }); it("resolves with zipUrlPresent false as not found in db", () => { - let requestStub = sandbox - .stub(request, "post") - .yields(null, { statusCode: 404 }, '{"message":"zip_url for md5sum random_md5sum not found."}'); + let axiosStub = sandbox + .stub(axios, "post") + .resolves({ status: 404 , data: {"message":"zip_url for md5sum random_md5sum not found."}}); const checkUploaded = rewire("../../../../bin/helpers/checkUploaded"); checkUploaded.__set__({ - request: { post: requestStub }, + axios: { post: axiosStub }, checkSpecsMd5: checkSpecsMd5Stub, checkPackageMd5: checkPackageMd5Stub }); @@ -172,7 +172,7 @@ describe("checkUploaded", () => { return checkUploadedMd5rewire(bsConfig, {}, instrumentBlocks) .then((data) => { chai.assert.deepEqual(data, { zip_md5sum: 'random_md5sum', zipUrlPresent: false, packageUrlPresent: false, }) - sinon.assert.calledOnce(requestStub); + sinon.assert.calledOnce(axiosStub); sinon.assert.calledOnce(checkSpecsMd5Stub); }) .catch((_error) => { @@ -181,13 +181,13 @@ describe("checkUploaded", () => { }); it("resolves with zipUrlPresent and packageUrlPresent false if force-upload enabled and cache_dependencies disabled", () => { - let requestStub = sandbox - .stub(request, "post") - .yields(null, { statusCode: 404 }, '{"message":"zip_url for md5sum random_md5sum not found."}'); + let axiosStub = sandbox + .stub(axios, "post") + .resolves({ status: 404 , data: '{"message":"zip_url for md5sum random_md5sum not found."}'}); const checkUploaded = rewire("../../../../bin/helpers/checkUploaded"); checkUploaded.__set__({ - request: { post: requestStub }, + axios: { post: axiosStub }, checkSpecsMd5: checkSpecsMd5Stub, checkPackageMd5: checkPackageMd5Stub }); @@ -196,7 +196,7 @@ describe("checkUploaded", () => { return checkUploadedMd5rewire(bsConfig, {"force-upload": true}, instrumentBlocks) .then((data) => { chai.assert.deepEqual(data, { zipUrlPresent: false, packageUrlPresent: false }) - sinon.assert.notCalled(requestStub); + sinon.assert.notCalled(axiosStub); sinon.assert.notCalled(checkSpecsMd5Stub); }) .catch((_error) => { @@ -205,13 +205,13 @@ describe("checkUploaded", () => { }); it("resolves with zipUrlPresent false and packageUrlPresent false if force-upload enabled and cache_dependencies enabled", () => { - let requestStub = sandbox - .stub(request, "post") - .yields(null, { statusCode: 200 }, '{"npmPackageUrl":"bs://random_hashid2"}'); + let axiosStub = sandbox + .stub(axios, "post") + .resolves({ status: 200 , data: '{"npmPackageUrl":"bs://random_hashid2"}'}); const checkUploaded = rewire("../../../../bin/helpers/checkUploaded"); checkUploaded.__set__({ - request: { post: requestStub }, + axios: { post: axiosStub }, checkSpecsMd5: checkSpecsMd5Stub, checkPackageMd5: checkPackageMd5Stub }); @@ -220,23 +220,22 @@ describe("checkUploaded", () => { return checkUploadedMd5rewire(bsConfig, {"force-upload": true}, instrumentBlocks) .then((data) => { chai.assert.deepEqual(data, { zipUrlPresent: false, packageUrlPresent: false }) - sinon.assert.notCalled(requestStub); + sinon.assert.notCalled(axiosStub); sinon.assert.notCalled(checkSpecsMd5Stub); }) .catch((_error) => { - console.log(_error) chai.assert.fail("Promise error"); }); }); it("resolves with zipUrlPresent false and packageUrlPresent false if diabled from rails", () => { - let requestStub = sandbox - .stub(request, "post") - .yields(null, { statusCode: 200 }, '{"disableNpmSuiteCache": true, "disableTestSuiteCache": true }'); + let axiosStub = sandbox + .stub(axios, "post") + .resolves( { status: 200 , data: {"disableNpmSuiteCache": true, "disableTestSuiteCache": true }}); const checkUploaded = rewire("../../../../bin/helpers/checkUploaded"); checkUploaded.__set__({ - request: { post: requestStub }, + axios: { post: axiosStub }, checkSpecsMd5: checkSpecsMd5Stub, checkPackageMd5: checkPackageMd5Stub }); @@ -245,7 +244,7 @@ describe("checkUploaded", () => { return checkUploadedMd5rewire(bsConfig, {}, instrumentBlocks) .then((data) => { chai.assert.deepEqual(data, { zipUrlPresent: false, packageUrlPresent: false }) - sinon.assert.calledOnce(requestStub); + sinon.assert.calledOnce(axiosStub); sinon.assert.calledOnce(checkSpecsMd5Stub); }) .catch((_error) => { @@ -276,7 +275,9 @@ describe("checkUploaded", () => { }; }); - it("resolves early due to force upload", () => { + // https://github.com/browserstack/browserstack-cypress-cli/commit/e55b6232ddb6cdc08ab80a0e3275c8c8c1191639#diff-e4adc3c50732024d070d2df81b167010ada80e39e52edfc8f3f48b0e9b44a501L16-L18 + // Skipping because in above PR this was removed. + it.skip("resolves early due to force upload", () => { let hashElementstub = sandbox.stub().returns(Promise.resolve("random_md5sum")); const checkUploaded = rewire("../../../../bin/helpers/checkUploaded"); checkUploaded.__set__({ diff --git a/test/unit/bin/helpers/getInitialDetails.js b/test/unit/bin/helpers/getInitialDetails.js index b506e150..617a9f7f 100644 --- a/test/unit/bin/helpers/getInitialDetails.js +++ b/test/unit/bin/helpers/getInitialDetails.js @@ -1,8 +1,8 @@ +const { default: axios } = require("axios"); const { expect } = require("chai"); const chai = require("chai"), chaiAsPromised = require("chai-as-promised"), - sinon = require('sinon'), - request = require('request'); + sinon = require('sinon'); const Constants = require("../../../../bin/helpers/constants"), logger = require("../../../../bin/helpers/logger").winstonLogger, @@ -17,13 +17,13 @@ describe('#getInitialDetails', () => { let args = testObjects.buildInfoSampleArgs; let rawArgs = testObjects.buildInfoSampleRawArgs; let bsConfig = testObjects.sampleBsConfig; - let sendUsageReportStub = null, requestStub = null, formatRequestStub = null; + let sendUsageReportStub = null, axiosStub = null, formatRequestStub = null; let messageType = Constants.messageTypes.ERROR; let errorCode = 'get_initial_details_failed' beforeEach(() => { sendUsageReportStub = sinon.stub(utils, 'sendUsageReport'); - requestStub = sinon.stub(request, "get"); + axiosStub = sinon.stub(axios, "get"); formatRequestStub = sinon.stub(utils, "formatRequest"); }); @@ -33,8 +33,8 @@ describe('#getInitialDetails', () => { it('sends usage report if error occurred in getInitialDetails call', () => { let error_message = "error occurred"; - requestStub.yields(error_message, null, null); - formatRequestStub.returns({err: error_message, statusCode: null, body: null}) + axiosStub.resolves(error_message); + formatRequestStub.returns({err: error_message, status: null, body: null}) sendUsageReportStub.calledOnceWithExactly(bsConfig, args, error_message, messageType, errorCode, null, rawArgs); getInitialDetails(bsConfig, args, rawArgs).then((result) => { expect(result).to.eq({}); @@ -43,7 +43,7 @@ describe('#getInitialDetails', () => { it('resolves with data if getInitialDetails call is successful', () => { let body = {"user_id": 1234}; - requestStub.yields(null, { statusCode: 200 }, body); + axiosStub.resolves({ status: 200 , data: body}); formatRequestStub.notCalled; sendUsageReportStub.notCalled; getInitialDetails(bsConfig, args, rawArgs).then((result) => { @@ -53,7 +53,7 @@ describe('#getInitialDetails', () => { it('send usage report if error response from getInitialDetails call', () => { let body = {"error": 'user not found'}; - requestStub.yields(null, { statusCode: 422 }, body); + axiosStub.resolves({ status: 422 , data: body}); formatRequestStub.notCalled; sendUsageReportStub.calledOnceWithExactly(bsConfig, args, body["error"], messageType, errorCode, null, rawArgs); getInitialDetails(bsConfig, args, rawArgs).then((result) => { diff --git a/test/unit/bin/helpers/hashUtil.js b/test/unit/bin/helpers/hashUtil.js index 0b974c2f..0d8ab39c 100644 --- a/test/unit/bin/helpers/hashUtil.js +++ b/test/unit/bin/helpers/hashUtil.js @@ -121,7 +121,6 @@ describe("md5util", () => { sinon.assert.calledOnce(digestStub); }) .catch((error) => { - console.log("error is ",error) chai.assert.fail("Promise error"); }); }); diff --git a/test/unit/bin/helpers/readCypressConfigUtil.js b/test/unit/bin/helpers/readCypressConfigUtil.js index 3b20d89f..dd9ecd0e 100644 --- a/test/unit/bin/helpers/readCypressConfigUtil.js +++ b/test/unit/bin/helpers/readCypressConfigUtil.js @@ -3,7 +3,8 @@ const chai = require("chai"), expect = chai.expect, sinon = require("sinon"), path = require('path'), - EventEmitter = require('events'); + EventEmitter = require('events'), + rewire = require('rewire'); const logger = require("../../../../bin/helpers/logger").winstonLogger; @@ -74,23 +75,173 @@ describe("readCypressConfigUtil", () => { }); }); + describe('resolveTsConfigPath', () => { + let readCypressConfigUtilRewired, resolveTsConfigPath; + + beforeEach(() => { + readCypressConfigUtilRewired = rewire('../../../../bin/helpers/readCypressConfigUtil'); + resolveTsConfigPath = readCypressConfigUtilRewired.__get__('resolveTsConfigPath'); + }); + + it('should return user specified tsconfig path if exists', () => { + const bsConfig = { + run_settings: { + ts_config_file_path: 'custom/tsconfig.json' + } + }; + const existsSyncStub = sandbox.stub(fs, 'existsSync'); + existsSyncStub.withArgs(path.resolve('custom/tsconfig.json')).returns(true); + + const result = resolveTsConfigPath(bsConfig, 'path/to/cypress.config.ts'); + + expect(result).to.eql(path.resolve('custom/tsconfig.json')); + }); + + it('should return null if no tsconfig found', () => { + const bsConfig = { run_settings: {} }; + const existsSyncStub = sandbox.stub(fs, 'existsSync').returns(false); + + const result = resolveTsConfigPath(bsConfig, 'path/to/cypress.config.ts'); + + expect(result).to.be.null; + }); + + it('should find tsconfig in cypress config directory', () => { + const bsConfig = { run_settings: {} }; + const existsSyncStub = sandbox.stub(fs, 'existsSync'); + existsSyncStub.withArgs(path.resolve('path/to/tsconfig.json')).returns(true); + existsSyncStub.returns(false); // default behavior + + const result = resolveTsConfigPath(bsConfig, 'path/to/cypress.config.ts'); + + expect(result).to.eql(path.resolve('path/to/tsconfig.json')); + }); + }); + + describe('generateTscCommandAndTempTsConfig', () => { + let readCypressConfigUtilRewired, generateTscCommandAndTempTsConfig; + + beforeEach(() => { + readCypressConfigUtilRewired = rewire('../../../../bin/helpers/readCypressConfigUtil'); + generateTscCommandAndTempTsConfig = readCypressConfigUtilRewired.__get__('generateTscCommandAndTempTsConfig'); + }); + + it('should use extends approach when valid tsconfig exists', () => { + const bsConfig = { + run_settings: { + ts_config_file_path: 'existing/tsconfig.json' + } + }; + const existsSyncStub = sandbox.stub(fs, 'existsSync'); + existsSyncStub.withArgs(path.resolve('existing/tsconfig.json')).returns(true); + const readFileSyncStub = sandbox.stub(fs, 'readFileSync').returns('{}'); + const writeFileSyncStub = sandbox.stub(fs, 'writeFileSync'); + + const result = generateTscCommandAndTempTsConfig(bsConfig, 'path/to/tmpBstackPackages', 'path/to/tmpBstackCompiledJs', 'path/to/cypress.config.ts'); + + expect(result.tscCommand).to.include('--project'); + expect(result.tempTsConfigPath).to.include('tsconfig.singlefile.tmp.json'); + + // Verify the temp tsconfig uses extends + const writeCall = writeFileSyncStub.getCall(0); + const tempConfig = JSON.parse(writeCall.args[1]); + expect(tempConfig.extends).to.eql(path.resolve('existing/tsconfig.json')); + }); + + it('should use standalone config when no tsconfig exists', () => { + const bsConfig = { run_settings: {} }; + const existsSyncStub = sandbox.stub(fs, 'existsSync').returns(false); + const writeFileSyncStub = sandbox.stub(fs, 'writeFileSync'); + + const result = generateTscCommandAndTempTsConfig(bsConfig, 'path/to/tmpBstackPackages', 'path/to/tmpBstackCompiledJs', 'path/to/cypress.config.ts'); + + expect(result.tscCommand).to.include('--project'); + + // Verify the temp tsconfig has standalone config + const writeCall = writeFileSyncStub.getCall(0); + const tempConfig = JSON.parse(writeCall.args[1]); + expect(tempConfig.extends).to.be.undefined; + expect(tempConfig.compilerOptions.module).to.eql('commonjs'); + expect(tempConfig.compilerOptions.allowSyntheticDefaultImports).to.be.true; + expect(tempConfig.compilerOptions.target).to.eql('es2017'); + }); + + it('should handle invalid tsconfig and fallback to standalone', () => { + const bsConfig = { + run_settings: { + ts_config_file_path: 'invalid/tsconfig.json' + } + }; + const existsSyncStub = sandbox.stub(fs, 'existsSync'); + existsSyncStub.withArgs(path.resolve('invalid/tsconfig.json')).returns(true); + const readFileSyncStub = sandbox.stub(fs, 'readFileSync').throws(new Error('Invalid JSON')); + const writeFileSyncStub = sandbox.stub(fs, 'writeFileSync'); + + const result = generateTscCommandAndTempTsConfig(bsConfig, 'path/to/tmpBstackPackages', 'path/to/tmpBstackCompiledJs', 'path/to/cypress.config.ts'); + + // Should fallback to standalone config + const writeCall = writeFileSyncStub.getCall(0); + const tempConfig = JSON.parse(writeCall.args[1]); + expect(tempConfig.extends).to.be.undefined; + expect(tempConfig.compilerOptions.module).to.eql('commonjs'); + }); + + it('should generate Windows command correctly', () => { + sinon.stub(process, 'platform').value('win32'); + const bsConfig = { run_settings: {} }; + const existsSyncStub = sandbox.stub(fs, 'existsSync').returns(false); + const writeFileSyncStub = sandbox.stub(fs, 'writeFileSync'); + + const result = generateTscCommandAndTempTsConfig(bsConfig, 'path/to/tmpBstackPackages', 'path/to/tmpBstackCompiledJs', 'path/to/cypress.config.ts'); + + expect(result.tscCommand).to.include('set NODE_PATH='); + expect(result.tscCommand).to.include('&&'); + }); + + it('should generate Unix command correctly', () => { + sinon.stub(process, 'platform').value('linux'); + const bsConfig = { run_settings: {} }; + const existsSyncStub = sandbox.stub(fs, 'existsSync').returns(false); + const writeFileSyncStub = sandbox.stub(fs, 'writeFileSync'); + + const result = generateTscCommandAndTempTsConfig(bsConfig, 'path/to/tmpBstackPackages', 'path/to/tmpBstackCompiledJs', 'path/to/cypress.config.ts'); + + expect(result.tscCommand).to.include('NODE_PATH=path/to/tmpBstackPackages'); + expect(result.tscCommand).to.include('tsc-alias'); + }); + }); + describe('convertTsConfig', () => { - it('should compile cypress.config.ts to cypress.config.js', () => { + it('should compile cypress.config.ts to cypress.config.js with new approach', () => { const bsConfig = { run_settings: { cypressConfigFilePath: 'path/to/cypress.config.ts', cypress_config_filename: 'cypress.config.ts' } }; + const existsSyncStub = sandbox.stub(fs, 'existsSync'); + existsSyncStub.withArgs(sinon.match(/tsconfig\.singlefile\.tmp\.json/)).returns(true); + existsSyncStub.returns(false); // for tsconfig search + + const writeFileSyncStub = sandbox.stub(fs, 'writeFileSync'); + const unlinkSyncStub = sandbox.stub(fs, 'unlinkSync'); const compileTsStub = sandbox.stub(cp, "execSync").returns("TSFILE: path/to/compiled/cypress.config.js"); - const result = readCypressConfigUtil.convertTsConfig(bsConfig, 'path/to/cypress.config.ts', 'path/to/tmpBstackPackages'); + const result = readCypressConfigUtil.convertTsConfig(bsConfig, 'path/to/cypress.config.ts', 'path/to/tmpBstackPackages'); expect(result).to.eql('path/to/compiled/cypress.config.js'); - sinon.assert.calledOnceWithExactly(compileTsStub, `NODE_PATH=path/to/tmpBstackPackages node "path/to/tmpBstackPackages/typescript/bin/tsc" --outDir "path/to/tmpBstackCompiledJs" --listEmittedFiles true --allowSyntheticDefaultImports --module commonjs --declaration false "path/to/cypress.config.ts"`, { cwd: 'path/to' }); + + // Verify temp tsconfig was created and cleaned up + sinon.assert.calledOnce(writeFileSyncStub); + sinon.assert.calledOnce(unlinkSyncStub); + + // Verify command uses --project flag + const tscCommand = compileTsStub.getCall(0).args[0]; + expect(tscCommand).to.include('--project'); + expect(tscCommand).to.include('tsconfig.singlefile.tmp.json'); }); - it('should compile cypress.config.ts to cypress.config.js for win', () => { + it('should compile cypress.config.ts to cypress.config.js for Windows with new approach', () => { sinon.stub(process, 'platform').value('win32'); const bsConfig = { run_settings: { @@ -98,45 +249,104 @@ describe("readCypressConfigUtil", () => { cypress_config_filename: 'cypress.config.ts' } }; + const existsSyncStub = sandbox.stub(fs, 'existsSync'); + existsSyncStub.withArgs(sinon.match(/tsconfig\.singlefile\.tmp\.json/)).returns(true); + existsSyncStub.returns(false); + + const writeFileSyncStub = sandbox.stub(fs, 'writeFileSync'); + const unlinkSyncStub = sandbox.stub(fs, 'unlinkSync'); const compileTsStub = sandbox.stub(cp, "execSync").returns("TSFILE: path/to/compiled/cypress.config.js"); - const result = readCypressConfigUtil.convertTsConfig(bsConfig, 'path/to/cypress.config.ts', 'path/to/tmpBstackPackages'); + const result = readCypressConfigUtil.convertTsConfig(bsConfig, 'path/to/cypress.config.ts', 'path/to/tmpBstackPackages'); expect(result).to.eql('path/to/compiled/cypress.config.js'); - sinon.assert.calledOnceWithExactly(compileTsStub, `set NODE_PATH=path/to/tmpBstackPackages&& node "path/to/tmpBstackPackages/typescript/bin/tsc" --outDir "path/to/tmpBstackCompiledJs" --listEmittedFiles true --allowSyntheticDefaultImports --module commonjs --declaration false "path/to/cypress.config.ts"`, { cwd: 'path/to' }); + + // Verify Windows command format + const tscCommand = compileTsStub.getCall(0).args[0]; + expect(tscCommand).to.include('set NODE_PATH='); + expect(tscCommand).to.include('&&'); }); - it('should return null if compilation fails', () => { + it('should return null if compilation fails with new approach', () => { const bsConfig = { run_settings: { cypressConfigFilePath: 'path/to/cypress.config.ts', cypress_config_filename: 'cypress.config.ts' } }; + const existsSyncStub = sandbox.stub(fs, 'existsSync'); + existsSyncStub.withArgs(sinon.match(/tsconfig\.singlefile\.tmp\.json/)).returns(true); + existsSyncStub.returns(false); + + const writeFileSyncStub = sandbox.stub(fs, 'writeFileSync'); + const unlinkSyncStub = sandbox.stub(fs, 'unlinkSync'); sandbox.stub(cp, "execSync").returns("Error: some error\n"); - const result = readCypressConfigUtil.convertTsConfig(bsConfig, 'path/to/cypress.config.ts', 'path/to/tmpBstackPackages'); + const result = readCypressConfigUtil.convertTsConfig(bsConfig, 'path/to/cypress.config.ts', 'path/to/tmpBstackPackages'); expect(result).to.eql(null); + + // Verify cleanup still happens + sinon.assert.calledOnce(unlinkSyncStub); }); - it('should compile cypress.config.ts to cypress.config.js if unrelevant error', () => { + it('should compile cypress.config.ts to cypress.config.js if irrelevant error with new approach', () => { const bsConfig = { run_settings: { cypressConfigFilePath: 'path/to/folder/cypress.config.ts', cypress_config_filename: 'cypress.config.ts' } }; - const execSyncStub = sandbox.stub(cp, "execSync") - execSyncStub - .withArgs(`NODE_PATH=path/to/tmpBstackPackages node "path/to/tmpBstackPackages/typescript/bin/tsc" --outDir "path/to/tmpBstackCompiledJs" --listEmittedFiles true --allowSyntheticDefaultImports --module commonjs --declaration false "path/to/cypress.config.ts"`, { cwd: 'path/to' }) - .throws({ - output: Buffer.from("Error: Some Error \n TSFILE: path/to/compiled/cypress.config.js") - }); + const existsSyncStub = sandbox.stub(fs, 'existsSync'); + existsSyncStub.withArgs(sinon.match(/tsconfig\.singlefile\.tmp\.json/)).returns(true); + existsSyncStub.returns(false); + + const writeFileSyncStub = sandbox.stub(fs, 'writeFileSync'); + const unlinkSyncStub = sandbox.stub(fs, 'unlinkSync'); + const execSyncStub = sandbox.stub(cp, "execSync"); + + execSyncStub.throws({ + output: Buffer.from("Error: Some Error \n TSFILE: path/to/compiled/cypress.config.js") + }); + + const result = readCypressConfigUtil.convertTsConfig(bsConfig, 'path/to/cypress.config.ts', 'path/to/tmpBstackPackages'); + + expect(result).to.eql('path/to/compiled/cypress.config.js'); + + // Verify cleanup happens even on error + sinon.assert.calledOnce(unlinkSyncStub); + }); + + it('should preserve backwards compatibility with fallback configuration', () => { + const bsConfig = { + run_settings: { + cypressConfigFilePath: 'path/to/cypress.config.ts', + cypress_config_filename: 'cypress.config.ts' + } + }; + const existsSyncStub = sandbox.stub(fs, 'existsSync'); + existsSyncStub.withArgs(sinon.match(/tsconfig\.singlefile\.tmp\.json/)).returns(true); + existsSyncStub.returns(false); // No existing tsconfig + + const writeFileSyncStub = sandbox.stub(fs, 'writeFileSync'); + const unlinkSyncStub = sandbox.stub(fs, 'unlinkSync'); + const compileTsStub = sandbox.stub(cp, "execSync").returns("TSFILE: path/to/compiled/cypress.config.js"); - const result = readCypressConfigUtil.convertTsConfig(bsConfig, 'path/to/cypress.config.ts', 'path/to/tmpBstackPackages'); + const result = readCypressConfigUtil.convertTsConfig(bsConfig, 'path/to/cypress.config.ts', 'path/to/tmpBstackPackages'); expect(result).to.eql('path/to/compiled/cypress.config.js'); + + // Verify the temp config contains all old command-line parameters + const writeCall = writeFileSyncStub.getCall(0); + const tempConfig = JSON.parse(writeCall.args[1]); + const compilerOptions = tempConfig.compilerOptions; + + expect(compilerOptions.allowSyntheticDefaultImports).to.be.true; + expect(compilerOptions.module).to.eql('commonjs'); + expect(compilerOptions.declaration).to.be.false; + expect(compilerOptions.listEmittedFiles).to.be.true; + expect(compilerOptions.target).to.eql('es2017'); + expect(compilerOptions.moduleResolution).to.eql('node'); }); }); @@ -194,4 +404,63 @@ describe("readCypressConfigUtil", () => { }, null, 'Error while reading cypress config: Some error', 'warning','cypress_config_file_read_failed', null, null) }); }); + + describe('Edge Cases and Error Scenarios', () => { + it('should handle missing typescript package gracefully', () => { + const bsConfig = { + run_settings: { + cypressConfigFilePath: 'path/to/cypress.config.ts', + cypress_config_filename: 'cypress.config.ts' + } + }; + const existsSyncStub = sandbox.stub(fs, 'existsSync'); + existsSyncStub.withArgs(sinon.match(/tsconfig\.singlefile\.tmp\.json/)).returns(true); + existsSyncStub.returns(false); + + const writeFileSyncStub = sandbox.stub(fs, 'writeFileSync'); + const unlinkSyncStub = sandbox.stub(fs, 'unlinkSync'); + const execSyncStub = sandbox.stub(cp, "execSync").throws(new Error('typescript not found')); + + const result = readCypressConfigUtil.convertTsConfig(bsConfig, 'path/to/cypress.config.ts', 'path/to/tmpBstackPackages'); + + expect(result).to.be.null; + sinon.assert.calledOnce(unlinkSyncStub); // Cleanup should still happen + }); + + it('should handle temp file creation failure', () => { + const bsConfig = { + run_settings: { + cypressConfigFilePath: 'path/to/cypress.config.ts', + cypress_config_filename: 'cypress.config.ts' + } + }; + const existsSyncStub = sandbox.stub(fs, 'existsSync').returns(false); + const writeFileSyncStub = sandbox.stub(fs, 'writeFileSync').throws(new Error('Permission denied')); + + expect(() => { + readCypressConfigUtil.convertTsConfig(bsConfig, 'path/to/cypress.config.ts', 'path/to/tmpBstackPackages'); + }).to.throw('Permission denied'); + }); + + it('should handle execution correctly without duplication', () => { + const bsConfig = { + run_settings: { + cypressConfigFilePath: 'path/to/cypress.config.ts', + cypress_config_filename: 'cypress.config.ts' + } + }; + const existsSyncStub = sandbox.stub(fs, 'existsSync'); + existsSyncStub.withArgs(sinon.match(/tsconfig\.singlefile\.tmp\.json/)).returns(true); + existsSyncStub.returns(false); + + const writeFileSyncStub = sandbox.stub(fs, 'writeFileSync'); + const unlinkSyncStub = sandbox.stub(fs, 'unlinkSync'); + const execSyncStub = sandbox.stub(cp, "execSync").returns("TSFILE: path/to/compiled/cypress.config.js"); + + readCypressConfigUtil.convertTsConfig(bsConfig, 'path/to/cypress.config.ts', 'path/to/tmpBstackPackages'); + + // Verify execSync is called only once (fixed duplicate execution issue) + expect(execSyncStub.callCount).to.eql(1); + }); + }); }); diff --git a/test/unit/bin/helpers/reporterHTML.js b/test/unit/bin/helpers/reporterHTML.js index 56276464..b5489053 100644 --- a/test/unit/bin/helpers/reporterHTML.js +++ b/test/unit/bin/helpers/reporterHTML.js @@ -1,244 +1,231 @@ -const { expect } = require("chai"); -const chai = require("chai"), - chaiAsPromised = require("chai-as-promised"), +const { default: axios } = require('axios'); +const chai = require('chai'), + chaiAsPromised = require('chai-as-promised'), sinon = require('sinon'), rewire = require('rewire'); const fs = require('fs'), path = require('path'), - request = require('request'), - decompress = require('decompress'); Constants = require("../../../../bin/helpers/constants"), logger = require("../../../../bin/helpers/logger").winstonLogger, - testObjects = require("../../support/fixtures/testObjects"), - formatRequest = require("../../../../bin/helpers/utils").formatRequest; - -const proxyquire = require("proxyquire").noCallThru(); + testObjects = require("../../support/fixtures/testObjects") +const utils = require('../../../../bin/helpers/utils'); +const reporterHTML = require('../../../../bin/helpers/reporterHTML'); chai.use(chaiAsPromised); -logger.transports["console.info"].silent = true; +logger.transports['console.info'].silent = true; -describe("getReportResponse", () => { - var sandbox; - beforeEach(() => { - sandbox = sinon.createSandbox(); - }); - - afterEach(() => { - sandbox.restore(); - sinon.restore(); - }); +describe('reporterHTML', () => { + let sendUsageReportStub = null, + axiosStub = null, + formatRequestStub = null, + getUserAgentStub = null; - it("retrieve response from report url", () => { - let requestStub = sandbox.stub(request, "get").yields(null, { statusCode: 200 }, "abc"); - let reporterHTML = proxyquire('../../../../bin/helpers/reporterHTML', { - request: {get: requestStub} - }); - let rewireReporterHTML = rewire('../../../../bin/helpers/reporterHTML'); - let getReportResponse = rewireReporterHTML.__get__("getReportResponse"); - let unzipFileStub = sinon.stub(); - let writerStub = sinon.stub(fs, "createWriteStream"); - let unlinkSyncStub = sinon.stub(fs, "unlinkSync"); - let pathStub = sinon.stub(path, 'join'); - unzipFileStub.returns(true); - rewireReporterHTML.__set__('unzipFile', unzipFileStub); - pathStub.returns("abc/efg"); - writerStub.returns(true); - unlinkSyncStub.returns(true); - getReportResponse("abc", "efg", "url"); - sinon.assert.calledOnce(requestStub); - }) -}); - -describe("calls API to generate report", () => { - var sandbox,sendUsageReportStub,sendUsageReportStub; - let args = testObjects.generateReportInputArgs, - rawArgs = testObjects.generateReportInputRawArgs - buildId = 'buildId', - bsConfig = testObjects.sampleBsConfig; - beforeEach(() => { - sandbox = sinon.createSandbox(); - sendUsageReportStub = sandbox.stub().callsFake(function () { - return "end"; - }); - getUserAgentStub = sandbox.stub().returns("random user-agent"); + getUserAgentStub = sinon.stub(utils, 'getUserAgent'); + sendUsageReportStub = sinon.stub(utils, 'sendUsageReport'); + axiosStub = sinon.stub(axios, 'get'); + formatRequestStub = sinon.stub(utils, 'formatRequest'); }); afterEach(() => { - sandbox.restore(); sinon.restore(); }); - it("is deprecated, i.e. 299, does not have build message", () => { - let requestStub = sandbox.stub(request, "get").yields(null, { statusCode: 299 }, null); - let message = Constants.userMessages.API_DEPRECATED; - let messageType = Constants.messageTypes.INFO; - let errorCode = "api_deprecated"; - - const reporterHTML = proxyquire('../../../../bin/helpers/reporterHTML', { - './utils': { - sendUsageReport: sendUsageReportStub, - getUserAgent: getUserAgentStub - }, - request: {get: requestStub} + describe('getReportResponse', () => { + it('retrieve response from report url', () => { + axiosStub.resolves({ status: 200, data: 'abc' }); + let rewireReporterHTML = rewire('../../../../bin/helpers/reporterHTML'); + let getReportResponse = rewireReporterHTML.__get__('getReportResponse'); + let unzipFileStub = sinon.stub(); + let writerStub = sinon.stub(fs, 'createWriteStream'); + let unlinkSyncStub = sinon.stub(fs, 'unlinkSync'); + let pathStub = sinon.stub(path, 'join'); + unzipFileStub.returns(true); + rewireReporterHTML.__set__('unzipFile', unzipFileStub); + pathStub.returns('abc/efg'); + writerStub.returns(true); + unlinkSyncStub.returns(true); + getReportResponse('abc', 'efg', 'url'); + sinon.assert.calledOnce(axiosStub); + pathStub.restore(); }); - - reporterHTML.reportGenerator(bsConfig, buildId, args, rawArgs, {}); - - sinon.assert.calledOnce(requestStub); - sinon.assert.calledOnce(getUserAgentStub); - sinon.assert.calledOnceWithExactly(sendUsageReportStub, bsConfig, args, message, messageType, errorCode, {}, rawArgs); }); - it("is deprecated, i.e. 299", () => { - let build = { buildId: buildId, message: 'API has been deprecated', rows: [] }; - let body = JSON.stringify(build); - let requestStub = sandbox.stub(request, "get").yields(null, { statusCode: 299 }, body); - let message = build.message; - let messageType = Constants.messageTypes.INFO; - let errorCode = "api_deprecated"; - - const reporterHTML = proxyquire('../../../../bin/helpers/reporterHTML', { - './utils': { - sendUsageReport: sendUsageReportStub, - getUserAgent: getUserAgentStub - }, - request: {get: requestStub} + describe('calls API to generate report', () => { + let args = testObjects.generateReportInputArgs, + rawArgs = testObjects.generateReportInputRawArgs; + (buildId = 'buildId'), (bsConfig = testObjects.sampleBsConfig); + + it('is deprecated, i.e. 299, does not have build message', () => { + axiosStub.resolves({ status: 299 }); + let message = Constants.userMessages.API_DEPRECATED; + let messageType = Constants.messageTypes.INFO; + let errorCode = 'api_deprecated'; + + reporterHTML.reportGenerator(bsConfig, buildId, args, rawArgs, {}).then(() => { + sinon.assert.calledOnce(axiosStub); + sinon.assert.calledOnce(getUserAgentStub); + sinon.assert.calledOnceWithExactly( + sendUsageReportStub, + bsConfig, + args, + message, + messageType, + errorCode, + {}, + rawArgs + ); + }); }); - reporterHTML.reportGenerator(bsConfig, buildId, args, rawArgs, {}); - - sinon.assert.calledOnce(requestStub); - sinon.assert.calledOnce(getUserAgentStub); - sinon.assert.calledOnceWithExactly(sendUsageReportStub, bsConfig, args, message, messageType, errorCode, {}, rawArgs); - }); - - context("non 200 response", () => { - it("422 status, build available but running, cannot generate report", () => { - let build = { message: 'The report cannot be generated as the build is running' }; - let body = JSON.stringify(build); - let requestStub = sandbox.stub(request, "get").yields(null, { statusCode: 422 }, body); + it('is deprecated, i.e. 299', () => { + let build = { + buildId: buildId, + message: 'API has been deprecated', + rows: [], + }; + axiosStub.resolves({ status: 299, data: build }); let message = build.message; - let messageType = Constants.messageTypes.ERROR; - let errorCode = 'api_failed_build_generate_report'; - - const reporterHTML = proxyquire('../../../../bin/helpers/reporterHTML', { - './utils': { - sendUsageReport: sendUsageReportStub, - getUserAgent: getUserAgentStub, - formatRequest - }, - request: {get: requestStub} + let messageType = Constants.messageTypes.INFO; + let errorCode = 'api_deprecated'; + + reporterHTML.reportGenerator(bsConfig, buildId, args, rawArgs, {}).then(() => { + sinon.assert.calledOnce(axiosStub); + sinon.assert.calledOnce(getUserAgentStub); + sinon.assert.calledOnceWithExactly( + sendUsageReportStub, + bsConfig, + args, + message, + messageType, + errorCode, + {}, + rawArgs + ); }); - - reporterHTML.reportGenerator(bsConfig, buildId, args, rawArgs, {}); - - sinon.assert.calledOnce(requestStub); - sinon.assert.calledOnce(getUserAgentStub); - sinon.assert.calledOnceWithExactly(sendUsageReportStub, bsConfig, args, message, messageType, errorCode, {}, rawArgs); }); - it("400 status, build available, cannot generate report", () => { - let build = { buildId: buildId, message: 'success', rows: [] }; - let body = JSON.stringify(build); - let requestStub = sandbox.stub(request, "get").yields(null, { statusCode: 400 }, body); - let message = `${ - Constants.userMessages.BUILD_GENERATE_REPORT_FAILED.replace('', buildId) - } with error: \n${JSON.stringify(build, null, 2)}`; - let messageType = Constants.messageTypes.ERROR; - let errorCode = 'api_failed_build_generate_report'; - - const reporterHTML = proxyquire('../../../../bin/helpers/reporterHTML', { - './utils': { - sendUsageReport: sendUsageReportStub, - getUserAgent: getUserAgentStub, - formatRequest - }, - request: {get: requestStub} + context('non 200 response', () => { + it('422 status, build available but running, cannot generate report', () => { + let build = { + message: 'The report cannot be generated as the build is running', + }; + axiosStub.resolves({ status: 422, data: build }); + let message = build.message; + let messageType = Constants.messageTypes.ERROR; + let errorCode = 'api_failed_build_generate_report'; + + reporterHTML.reportGenerator(bsConfig, buildId, args, rawArgs, {}).then(() => { + sinon.assert.calledOnce(axiosStub); + sinon.assert.calledOnce(getUserAgentStub); + sinon.assert.calledOnceWithExactly( + sendUsageReportStub, + bsConfig, + args, + message, + messageType, + errorCode, + {}, + rawArgs + ); + }); }); - reporterHTML.reportGenerator(bsConfig, buildId, args, rawArgs, {}); - - sinon.assert.calledOnce(requestStub); - sinon.assert.calledOnce(getUserAgentStub); - sinon.assert.calledOnceWithExactly(sendUsageReportStub, bsConfig, args, message, messageType, errorCode, {}, rawArgs); - }); - - it("user is unauthorized", () => { - let build = { buildId: buildId, message: 'Unauthorized', rows: [] }; - let body = JSON.stringify(build); - let requestStub = sandbox.stub(request, "get").yields(null, { statusCode: 401 }, body); - let message = `${ - Constants.userMessages.BUILD_GENERATE_REPORT_FAILED.replace('', buildId) - } with error: \n${JSON.stringify(build, null, 2)}`; - let messageType = Constants.messageTypes.ERROR; - let errorCode = 'api_auth_failed'; - - const reporterHTML = proxyquire('../../../../bin/helpers/reporterHTML', { - './utils': { - sendUsageReport: sendUsageReportStub, - getUserAgent: getUserAgentStub - }, - request: {get: requestStub} + it('400 status, build available, cannot generate report', () => { + let build = { buildId: buildId, message: 'success', rows: [] }; + axiosStub.resolves({ status: 400, data: build }); + let message = `${Constants.userMessages.BUILD_GENERATE_REPORT_FAILED.replace( + '', + buildId + )} with error: \n${JSON.stringify(build, null, 2)}`; + let messageType = Constants.messageTypes.ERROR; + let errorCode = 'api_failed_build_generate_report'; + + reporterHTML.reportGenerator(bsConfig, buildId, args, rawArgs, {}).then(() => { + sinon.assert.calledOnce(axiosStub); + sinon.assert.calledOnce(getUserAgentStub); + sinon.assert.calledOnceWithExactly( + sendUsageReportStub, + bsConfig, + args, + message, + messageType, + errorCode, + {}, + rawArgs + ); + }); }); - reporterHTML.reportGenerator(bsConfig, buildId, args, rawArgs, {}); + it('user is unauthorized', () => { + let build = { buildId: buildId, message: 'Unauthorized', rows: [] }; + let body = build; + axiosStub.resolves({ status: 401, data: body }); + let message = `${Constants.userMessages.BUILD_GENERATE_REPORT_FAILED.replace( + '', + buildId + )} with error: \n${JSON.stringify(build, null, 2)}`; + let messageType = Constants.messageTypes.ERROR; + let errorCode = 'api_auth_failed'; + + reporterHTML.reportGenerator(bsConfig, buildId, args, rawArgs, {}).then(() => { + sinon.assert.calledOnce(axiosStub); + sinon.assert.calledOnce(getUserAgentStub); + sinon.assert.calledOnceWithExactly( + sendUsageReportStub, + bsConfig, + args, + message, + messageType, + errorCode, + {}, + rawArgs + ); + }); + }); - sinon.assert.calledOnce(requestStub); - sinon.assert.calledOnce(getUserAgentStub); - sinon.assert.calledOnceWithExactly(sendUsageReportStub, bsConfig, args, message, messageType, errorCode, {}, rawArgs); + it('400 status, build not available, cannot generate report', () => { + axiosStub.resolves({ status: 400 }); + let message = Constants.userMessages.BUILD_GENERATE_REPORT_FAILED.replace('', buildId); + let messageType = Constants.messageTypes.ERROR; + let errorCode = 'api_failed_build_generate_report'; + + reporterHTML.reportGenerator(bsConfig, buildId, args, rawArgs, {}).then(() => { + sinon.assert.calledOnce(axiosStub); + sinon.assert.calledOnce(getUserAgentStub); + sinon.assert.calledOnceWithExactly( + sendUsageReportStub, + bsConfig, + args, + message, + messageType, + errorCode, + {}, + rawArgs + ); + }); + }); }); - it("400 status, build not available, cannot generate report", () => { - let requestStub = sandbox.stub(request, "get").yields(null, { statusCode: 400 }, null); - let message = Constants.userMessages.BUILD_GENERATE_REPORT_FAILED.replace('', buildId); - let messageType = Constants.messageTypes.ERROR; - let errorCode = 'api_failed_build_generate_report'; - - const reporterHTML = proxyquire('../../../../bin/helpers/reporterHTML', { - './utils': { - sendUsageReport: sendUsageReportStub, - getUserAgent: getUserAgentStub, - formatRequest - }, - request: {get: requestStub} - }); + it('200 response code', () => { + let build = { buildId: buildId, message: 'success', rows: [] }; + axiosStub.resolves({ status: 200, data: 'abc' }); + let message = `Report for build: ${buildId} was successfully created.`; + let messageType = Constants.messageTypes.SUCCESS; + let errorCode = null; + let rewireReporterHTML = rewire('../../../../bin/helpers/reporterHTML'); + let generateCypressBuildReportStub = sinon.stub(); + generateCypressBuildReportStub.returns(true); + rewireReporterHTML.__set__('generateCypressBuildReport', generateCypressBuildReportStub); reporterHTML.reportGenerator(bsConfig, buildId, args, rawArgs, {}); - sinon.assert.calledOnce(requestStub); + sinon.assert.calledOnce(axiosStub); sinon.assert.calledOnce(getUserAgentStub); - sinon.assert.calledOnceWithExactly(sendUsageReportStub, bsConfig, args, message, messageType, errorCode, {}, rawArgs); + sendUsageReportStub.calledOnceWithExactly(bsConfig, args, message, messageType, errorCode, {}, rawArgs); }); }); - - it("200 response code", () => { - let build = { buildId: buildId, message: 'success', rows: [] }; - let body = JSON.stringify(build); - let requestStub = sandbox.stub(request, "get").yields(null, { statusCode: 200 }, "abc"); - let message = `Report for build: ${buildId} was successfully created.`; - let messageType = Constants.messageTypes.SUCCESS; - let errorCode = null; - let rewireReporterHTML = rewire('../../../../bin/helpers/reporterHTML'); - let generateCypressBuildReportStub = sinon.stub(); - generateCypressBuildReportStub.returns(true); - rewireReporterHTML.__set__('generateCypressBuildReport', generateCypressBuildReportStub); - - const reporterHTML = proxyquire('../../../../bin/helpers/reporterHTML', { - './utils': { - sendUsageReport: sendUsageReportStub, - getUserAgent: getUserAgentStub - }, - request: {get: requestStub} - }); - - reporterHTML.reportGenerator(bsConfig, buildId, args, rawArgs, {}); - - sinon.assert.calledOnce(requestStub); - sinon.assert.calledOnce(getUserAgentStub); - sendUsageReportStub.calledOnceWithExactly(bsConfig, args, message, messageType, errorCode, {}, rawArgs); - }); }); describe("unzipFile", () => { diff --git a/test/unit/bin/helpers/sync/syncSpecsLogs.js b/test/unit/bin/helpers/sync/syncSpecsLogs.js index e19a10c2..5f5ac969 100644 --- a/test/unit/bin/helpers/sync/syncSpecsLogs.js +++ b/test/unit/bin/helpers/sync/syncSpecsLogs.js @@ -1,22 +1,22 @@ -"use strict"; -const chai = require("chai"), +'use strict'; +const chai = require('chai'), expect = chai.expect, - rewire = require("rewire"), - chaiAsPromised = require("chai-as-promised"), - chalk = require('chalk'), - request = require("request"); + rewire = require('rewire'), + chaiAsPromised = require('chai-as-promised'), + chalk = require('chalk'); -const sinon = require("sinon"); +const { default: axios } = require('axios'); +const sinon = require('sinon'); chai.use(chaiAsPromised); -var syncSpecsLogs = rewire("../../../../../bin/helpers/sync/syncSpecsLogs.js"); -var logger = require("../../../../../bin/helpers/logger").syncCliLogger; -var Constants = require("../../../../../bin/helpers/constants.js"); -var config = require("../../../../../bin/helpers/config.js"); -var utils = require("../../../../../bin/helpers/utils"); +var syncSpecsLogs = rewire('../../../../../bin/helpers/sync/syncSpecsLogs.js'); +var logger = require('../../../../../bin/helpers/logger').syncCliLogger; +var Constants = require('../../../../../bin/helpers/constants.js'); +var config = require('../../../../../bin/helpers/config.js'); +var utils = require('../../../../../bin/helpers/utils'); -describe("syncSpecsLogs", () => { +describe('syncSpecsLogs', () => { var sandbox; beforeEach(() => { @@ -29,115 +29,131 @@ describe("syncSpecsLogs", () => { utils.sendUsageReport.restore(); }); - context("getCombinationName", () => { - const get_path = syncSpecsLogs.__get__("getCombinationName");; + context('getCombinationName', () => { + const get_path = syncSpecsLogs.__get__('getCombinationName'); let spec = { - "os": "Windows", - "osVersion": "10", - "browser": "chrome", - "browserVersion": "86" - } - it("returns combination name", () => { + os: 'Windows', + osVersion: '10', + browser: 'chrome', + browserVersion: '86', + }; + it('returns combination name', () => { let expectedCombination = `Chrome 86 (Windows 10)`; expect(get_path(spec)).to.equal(expectedCombination); }); }); - context("getStatus", () => { - const getStatus = syncSpecsLogs.__get__("getStatus");; + context('getStatus', () => { + const getStatus = syncSpecsLogs.__get__('getStatus'); - it("returns return ✔ in green when status is passes", () => { - expect(getStatus("passed")).to.equal(chalk.green("✔")); + it('returns return ✔ in green when status is passes', () => { + expect(getStatus('passed')).to.equal(chalk.green('✔')); }); - it("returns return ✘ in red when status is failed", () => { - expect(getStatus("failed")).to.equal(chalk.red("✘")); + it('returns return ✘ in red when status is failed', () => { + expect(getStatus('failed')).to.equal(chalk.red('✘')); }); - it("returns return [status] in yellow when status is skipped or ignored (anything else from pass/fail)", () => { - expect(getStatus("skipped")).to.equal(chalk.blue("[skipped]")); - expect(getStatus("ignored")).to.equal(chalk.blue("[ignored]")); + it('returns return [status] in yellow when status is skipped or ignored (anything else from pass/fail)', () => { + expect(getStatus('skipped')).to.equal(chalk.blue('[skipped]')); + expect(getStatus('ignored')).to.equal(chalk.blue('[ignored]')); }); }); - context("printInitialLog", () => { - const printInitialLog = syncSpecsLogs.__get__("printInitialLog"); + context('printInitialLog', () => { + const printInitialLog = syncSpecsLogs.__get__('printInitialLog'); - it("should print inital logs for specs in sync", () => { - - printInitialLog() - - expect(syncSpecsLogs.__get__("n")).to.equal(Constants.syncCLI.INITIAL_DELAY_MULTIPLIER); - expect(syncSpecsLogs.__get__("startTime")).to.not.be.null; + it('should print inital logs for specs in sync', () => { + printInitialLog(); + expect(syncSpecsLogs.__get__('n')).to.equal(Constants.syncCLI.INITIAL_DELAY_MULTIPLIER); + expect(syncSpecsLogs.__get__('startTime')).to.not.be.null; }); }); - context("getOptions", () => { - const getOptions = syncSpecsLogs.__get__("getOptions"); - let auth = {username: "cypress", access_key: "abcd"} - let build_id = "build1" + context('getOptions', () => { + const getOptions = syncSpecsLogs.__get__('getOptions'); + let auth = { username: 'cypress', access_key: 'abcd' }; + let build_id = 'build1'; - it('should return proper request option for polling', () => { + it('should return proper axios option for polling', () => { let options = getOptions(auth, build_id); expect(options.url).to.equal(`${config.buildUrlV2}${build_id}`); expect(options.auth.user).to.equal(auth.username); expect(options.auth.password).to.equal(auth.access_key); - expect(options.headers["Content-Type"]).to.equal("application/json"); + expect(options.headers['Content-Type']).to.equal('application/json'); }); }); - context("addCustomErrorToPrint", () => { - const addCustomErrorToPrint = syncSpecsLogs.__get__("addCustomErrorToPrint"); + context('addCustomErrorToPrint', () => { + const addCustomErrorToPrint = syncSpecsLogs.__get__('addCustomErrorToPrint'); let specSummary = { - "buildError": null, - "specs": [], - "duration": null, - "parallels": null, - "cliDuration": null, - "customErrorsToPrint": [ - { id: "custom_error_1", type: "custom_errors_to_print", level: "warn", should_be_unique: true, message: "custom error message" } - ] - } - const uniqueError = { id: "custom_error_1", type: "custom_errors_to_print", level: "warn", should_be_unique: true, message: "custom error message" }; - const notUniqueError = { id: "custom_error_2", type: "custom_errors_to_print", level: "warn", should_be_unique: false, message: "custom error message" }; + buildError: null, + specs: [], + duration: null, + parallels: null, + cliDuration: null, + customErrorsToPrint: [ + { + id: 'custom_error_1', + type: 'custom_errors_to_print', + level: 'warn', + should_be_unique: true, + message: 'custom error message', + }, + ], + }; + const uniqueError = { + id: 'custom_error_1', + type: 'custom_errors_to_print', + level: 'warn', + should_be_unique: true, + message: 'custom error message', + }; + const notUniqueError = { + id: 'custom_error_2', + type: 'custom_errors_to_print', + level: 'warn', + should_be_unique: false, + message: 'custom error message', + }; it('should add a new Error if its meant to be unique and not added to the error list', () => { addCustomErrorToPrint(uniqueError); - expect(JSON.stringify(syncSpecsLogs.__get__("specSummary"))).to.equal(JSON.stringify(specSummary)); + expect(JSON.stringify(syncSpecsLogs.__get__('specSummary'))).to.equal(JSON.stringify(specSummary)); }); it('should not add a new Error if its meant to be unique and already added to the error list', () => { addCustomErrorToPrint(uniqueError); - expect(JSON.stringify(syncSpecsLogs.__get__("specSummary"))).to.equal(JSON.stringify(specSummary)); + expect(JSON.stringify(syncSpecsLogs.__get__('specSummary'))).to.equal(JSON.stringify(specSummary)); }); it('should add a new Error if its not meant to be unique and not added to the error list', () => { addCustomErrorToPrint(notUniqueError); specSummary.customErrorsToPrint.push(notUniqueError); - expect(JSON.stringify(syncSpecsLogs.__get__("specSummary"))).to.equal(JSON.stringify(specSummary)); + expect(JSON.stringify(syncSpecsLogs.__get__('specSummary'))).to.equal(JSON.stringify(specSummary)); }); it('should not add a new Error if its not meant to not be unique and already added to the error list', () => { addCustomErrorToPrint(notUniqueError); specSummary.customErrorsToPrint.push(notUniqueError); - expect(JSON.stringify(syncSpecsLogs.__get__("specSummary"))).to.equal(JSON.stringify(specSummary)); + expect(JSON.stringify(syncSpecsLogs.__get__('specSummary'))).to.equal(JSON.stringify(specSummary)); }); }); - context("getTableConfig", () => { - const getTableConfig = syncSpecsLogs.__get__("getTableConfig"); + context('getTableConfig', () => { + const getTableConfig = syncSpecsLogs.__get__('getTableConfig'); it('should return proper table config option for spec table', () => { var getBorderConfigStub = sandbox.stub(); syncSpecsLogs.__set__('getBorderConfig', getBorderConfigStub); - let options = getTableConfig((process.stdout.columns) * 0.9); - expect(options.columnDefault.width).to.equal(Math.floor(((process.stdout.columns) * 0.9) * 0.2)); + let options = getTableConfig(process.stdout.columns * 0.9); + expect(options.columnDefault.width).to.equal(Math.floor(process.stdout.columns * 0.9 * 0.2)); expect(options.columns[1].alignment).to.equal('center'); expect(options.columns[2].alignment).to.equal('left'); - expect(options.columns[1].width).to.equal(Math.ceil(((process.stdout.columns) * 0.9) * 0.01)); - expect(options.columns[2].width).to.equal(Math.floor(((process.stdout.columns) * 0.9) * 0.75)); + expect(options.columns[1].width).to.equal(Math.ceil(process.stdout.columns * 0.9 * 0.01)); + expect(options.columns[2].width).to.equal(Math.floor(process.stdout.columns * 0.9 * 0.75)); expect(options.columnCount).to.equal(3); expect(getBorderConfigStub.calledOnce).to.be.true; }); @@ -157,46 +173,54 @@ describe("syncSpecsLogs", () => { }); }); - context("getBorderConfig", () => { - const getBorderConfig = syncSpecsLogs.__get__("getBorderConfig"); + context('getBorderConfig', () => { + const getBorderConfig = syncSpecsLogs.__get__('getBorderConfig'); it('should return proper border option for spec table', () => { let options = getBorderConfig(); - expect(options.topBody).to.equal(""); - expect(options.bottomBody).to.equal(""); + expect(options.topBody).to.equal(''); + expect(options.bottomBody).to.equal(''); }); }); - context("writeToTable", () => { - const writeToTable = syncSpecsLogs.__get__("writeToTable"); + context('writeToTable', () => { + const writeToTable = syncSpecsLogs.__get__('writeToTable'); it('should print spec details to the table', () => { const stream = sandbox.stub(); stream.write = sandbox.stub(); syncSpecsLogs.__set__('stream', stream); - let combination = "Windows 10", path = "path", status = "passed", statusMark = "passed"; + let combination = 'Windows 10', + path = 'path', + status = 'passed', + statusMark = 'passed'; writeToTable(combination, path, status, statusMark); - sinon.assert.calledOnceWithExactly(stream.write, [combination , ":", `${path} ${statusMark} [${status}]`]); + sinon.assert.calledOnceWithExactly(stream.write, [combination, ':', `${path} ${statusMark} [${status}]`]); }); }); - context("addSpecToSummary", () => { - const addSpecToSummary = syncSpecsLogs.__get__("addSpecToSummary"); + context('addSpecToSummary', () => { + const addSpecToSummary = syncSpecsLogs.__get__('addSpecToSummary'); it('should add spec details to specSummary', () => { - let specSummary = { specs: [] } + let specSummary = { specs: [] }; syncSpecsLogs.__set__('specSummary', specSummary); - let specName = "spec", status = "status", combination = "combo", session_id = "id"; + let specName = 'spec', + status = 'status', + combination = 'combo', + session_id = 'id'; addSpecToSummary(specName, status, combination, session_id); - expect(specSummary.specs).deep.to.equal([{"specName": specName, "status": status, "combination": combination, "sessionId": session_id}]) + expect(specSummary.specs).deep.to.equal([ + { specName: specName, status: status, combination: combination, sessionId: session_id }, + ]); }); }); - context("printSpecData", () => { - const printSpecData = syncSpecsLogs.__get__("printSpecData"); + context('printSpecData', () => { + const printSpecData = syncSpecsLogs.__get__('printSpecData'); it('Should print combination and status to the spec table and add spec details to spec array', () => { - let data = { spec: { status: "passed" }, path: "path", session_id: "id" } + let data = { spec: { status: 'passed' }, path: 'path', session_id: 'id' }; var getCombinationName = sandbox.stub(); syncSpecsLogs.__set__('getCombinationName', getCombinationName); var getStatus = sandbox.stub(); @@ -206,76 +230,89 @@ describe("syncSpecsLogs", () => { var addSpecToSummary = sandbox.stub(); syncSpecsLogs.__set__('addSpecToSummary', addSpecToSummary); - printSpecData(data); - sinon.assert.calledOnceWithExactly(getCombinationName, data["spec"]); - sinon.assert.calledOnceWithExactly(getStatus, data["spec"]["status"]); + sinon.assert.calledOnceWithExactly(getCombinationName, data['spec']); + sinon.assert.calledOnceWithExactly(getStatus, data['spec']['status']); sinon.assert.calledOnce(writeToTable); sinon.assert.calledOnce(addSpecToSummary); }); }); - - context("showSpecsStatus", () => { - const showSpecsStatus = syncSpecsLogs.__get__("showSpecsStatus"); - const buildCreatedStatusCode = 202 - const buildRunningStatusCode = 204 - const buildCompletedStatusCode = 200 + context('showSpecsStatus', () => { + const showSpecsStatus = syncSpecsLogs.__get__('showSpecsStatus'); + const buildCreatedstatus = 202; + const buildRunningstatus = 204; + const buildCompletedstatus = 200; it('should not print initial log for running specs when it is the 1st polling response', () => { - let data = JSON.stringify({ "specData": ["created"], "buildData": {"duration": "NA", "parallels": "NA"}}) + let data = { specData: ['created'], buildData: { duration: 'NA', parallels: 'NA' } }; var printInitialLog = sandbox.stub(); syncSpecsLogs.__set__('printInitialLog', printInitialLog); - showSpecsStatus(data, buildCreatedStatusCode); + showSpecsStatus(data, buildCreatedstatus); expect(printInitialLog.calledOnce).to.be.false; }); it('should print spec details when spec related data is sent in polling response', () => { - let specResult = JSON.stringify({"path": "path"}) - let data = JSON.stringify({ "specData": [specResult], "buildData": {"duration": "NA", "parallels": "NA"}}) + let specResult = JSON.stringify({ path: 'path' }); + let data = { specData: [specResult], buildData: { duration: 'NA', parallels: 'NA' } }; var printSpecData = sandbox.stub(); syncSpecsLogs.__set__('printSpecData', printSpecData); - showSpecsStatus(data, buildRunningStatusCode); + showSpecsStatus(data, buildRunningstatus); expect(printSpecData.calledOnce).to.be.true; }); it('should print initial and spec details when spec related data is sent in polling response', () => { - let specResult = JSON.stringify({"path": "path"}) - syncSpecsLogs.__set__('buildStarted', false) - let data = JSON.stringify({ "specData": [specResult], "buildData": {"duration": "NA", "parallels": "NA"}}) + let specResult = JSON.stringify({ path: 'path' }); + syncSpecsLogs.__set__('buildStarted', false); + let data = { specData: [specResult], buildData: { duration: 'NA', parallels: 'NA' } }; var printSpecData = sandbox.stub(); syncSpecsLogs.__set__('printSpecData', printSpecData); var printInitialLog = sandbox.stub(); syncSpecsLogs.__set__('printInitialLog', printInitialLog); - showSpecsStatus(data, buildCreatedStatusCode); + showSpecsStatus(data, buildCreatedstatus); expect(printSpecData.calledOnce).to.be.true; expect(printInitialLog.calledOnce).to.be.true; }); it('should add custom error, print initial and spec details when spec related data is sent in polling response', () => { - let specResult = JSON.stringify({"path": "path"}) - let customError = { id: "custom_error_1", type: "custom_errors_to_print", level: "warn", should_be_unique: true, message: "custom error message" } - syncSpecsLogs.__set__('buildStarted', false) - let data = JSON.stringify({ "specData": ["created", specResult, customError], "buildData": {"duration": "NA", "parallels": "NA"}}) + let specResult = JSON.stringify({ path: 'path' }); + let customError = { + id: 'custom_error_1', + type: 'custom_errors_to_print', + level: 'warn', + should_be_unique: true, + message: 'custom error message', + }; + syncSpecsLogs.__set__('buildStarted', false); + let data = { + specData: ['created', specResult, customError], + buildData: { duration: 'NA', parallels: 'NA' }, + }; var printSpecData = sandbox.stub(); syncSpecsLogs.__set__('printSpecData', printSpecData); var printInitialLog = sandbox.stub(); syncSpecsLogs.__set__('printInitialLog', printInitialLog); var addCustomErrorToPrint = sandbox.stub(); syncSpecsLogs.__set__('addCustomErrorToPrint', addCustomErrorToPrint); - showSpecsStatus(data, buildRunningStatusCode); + showSpecsStatus(data, buildRunningstatus); expect(printSpecData.calledOnce).to.be.true; expect(printInitialLog.calledOnce).to.be.true; expect(addCustomErrorToPrint.calledOnce).to.be.true; }); }); - context("printSpecsStatus", () => { - const printSpecsStatus = syncSpecsLogs.__get__("printSpecsStatus"); - let startTime = Date.now(), endTime = Date.now() + 10, counter = 0; - let specSummary = { specs: [] }, getOptions, getTableConfig, tableStream, whileProcess; + context('printSpecsStatus', () => { + const printSpecsStatus = syncSpecsLogs.__get__('printSpecsStatus'); + let startTime = Date.now(), + endTime = Date.now() + 10, + counter = 0; + let specSummary = { specs: [] }, + getOptions, + getTableConfig, + tableStream, + whileProcess; beforeEach(() => { counter = 0; @@ -290,11 +327,13 @@ describe("syncSpecsLogs", () => { syncSpecsLogs.__set__('tableStream', tableStream); whileProcess = sandbox.stub().callsFake(function (whilstCallback) { - counter++ - if(counter >= 3) { + counter++; + if (counter >= 3) { syncSpecsLogs.__set__('whileLoop', false); - whilstCallback(new Error("ggg"), {}); - } else {whileProcess(whilstCallback, 10, null)} + whilstCallback(new Error('ggg'), {}); + } else { + whileProcess(whilstCallback, 10, null); + } }); syncSpecsLogs.__set__('whileProcess', whileProcess); @@ -311,7 +350,7 @@ describe("syncSpecsLogs", () => { expect(getTableConfig.calledOnce).to.be.true; expect(tableStream.calledOnce).to.be.true; expect(whileProcess.calledOnce).to.be.false; - expect(specSummary.specs).deep.to.equal([]) + expect(specSummary.specs).deep.to.equal([]); expect(specSummary.cliDuration).to.eql(endTime - startTime); }); }); @@ -332,45 +371,44 @@ describe("syncSpecsLogs", () => { }); }); - context("whileProcess", () => { - const whileProcess = syncSpecsLogs.__get__("whileProcess"); + context('whileProcess', () => { + const whileProcess = syncSpecsLogs.__get__('whileProcess'); - it('Should retry when request fails with error', () => { - let delayed_n = 2, timeout = 3000, n = 1; - let error = new Error("error"); + it('Should retry when axios fails with error', () => { + let delayed_n = 2, + timeout = 3000, + n = 1; + let error = new Error('error'); - let requestStub = sandbox.stub(); + let axiosStub = sandbox.stub(); - let postStub = sandbox - .stub(request, "post") - .yields(error, { statusCode: 502 }, JSON.stringify({})); + let postStub = sandbox.stub(axios, 'post').resolves({ status: 502, error: error, data: {} }); - requestStub.post = postStub; + axiosStub.post = postStub; let setTimeout = sandbox.stub(); syncSpecsLogs.__set__('setTimeout', setTimeout); syncSpecsLogs.__set__('n', n); syncSpecsLogs.__set__('timeout', timeout); - syncSpecsLogs.__set__('request', requestStub); + syncSpecsLogs.__set__('axios', axiosStub); syncSpecsLogs.__set__('whileTries', 5); let whilstCallback = sandbox.stub(); whileProcess(whilstCallback); sinon.assert.calledWith(setTimeout, whilstCallback, timeout * delayed_n, null); - expect(syncSpecsLogs.__get__("whileTries")).to.equal(4); + expect(syncSpecsLogs.__get__('whileTries')).to.equal(4); }); it('Should exit after defined number of retries in case of error', () => { - let error = new Error("error"), requestStub = sandbox.stub(); + let error = new Error('error'), + axiosStub = sandbox.stub(); - let postStub = sandbox - .stub(request, "post") - .yields(error, { statusCode: 502 }, JSON.stringify({})); + let postStub = sandbox.stub(axios, 'post').resolves({ status: 502, error, data: {} }); - requestStub.post = postStub; + axiosStub.post = postStub; - syncSpecsLogs.__set__('request', requestStub); + syncSpecsLogs.__set__('axios', axiosStub); syncSpecsLogs.__set__('whileTries', 1); syncSpecsLogs.__set__('specSummary', {}); syncSpecsLogs.__set__('whileLoop', true); @@ -378,20 +416,23 @@ describe("syncSpecsLogs", () => { let whilstCallback = sandbox.stub(); whileProcess(whilstCallback); - sinon.assert.calledWith(whilstCallback, { status: 504, message: "Tries limit reached" }); - expect(syncSpecsLogs.__get__("whileTries")).to.equal(0); - expect(syncSpecsLogs.__get__("whileLoop")).to.equal(false); - expect(syncSpecsLogs.__get__("specSummary.exitCode")).to.equal(2); + sinon.assert.calledWith(whilstCallback, { status: 504, message: 'Tries limit reached' }); + expect(syncSpecsLogs.__get__('whileTries')).to.equal(0); + expect(syncSpecsLogs.__get__('whileLoop')).to.equal(false); + expect(syncSpecsLogs.__get__('specSummary.exitCode')).to.equal(2); }); it('Should print spec details when data is returned from server', () => { - let error = null, body={}, status = 202, n = 1, delayed_n = 2, timeout = 3000; - let requestStub = sandbox.stub(); - let postStub = sandbox - .stub(request, "post") - .yields(error, { statusCode: status }, JSON.stringify(body)); - requestStub.post = postStub; - syncSpecsLogs.__set__('request', requestStub); + let error = null, + body = {}, + status = 202, + n = 1, + delayed_n = 2, + timeout = 3000; + let axiosStub = sandbox.stub(); + let postStub = sandbox.stub(axios, 'post').resolves({ status: status, data: body }); + axiosStub.post = postStub; + syncSpecsLogs.__set__('axios', axiosStub); let showSpecsStatus = sandbox.stub(); syncSpecsLogs.__set__('showSpecsStatus', showSpecsStatus); @@ -404,19 +445,21 @@ describe("syncSpecsLogs", () => { let whilstCallback = sandbox.stub(); whileProcess(whilstCallback); - expect(syncSpecsLogs.__get__("n")).to.equal(delayed_n); + expect(syncSpecsLogs.__get__('n')).to.equal(delayed_n); sinon.assert.calledWith(setTimeout, whilstCallback, timeout * delayed_n, null); - sinon.assert.calledWith(showSpecsStatus, JSON.stringify(body)); }); it('Should poll for data when server responds with no data available', () => { - let error = null, body={}, status = 204, n = 1, delayed_n = 1, timeout = 3000; - let requestStub = sandbox.stub(); - let postStub = sandbox - .stub(request, "post") - .yields(error, { statusCode: status }, JSON.stringify(body)); - requestStub.post = postStub; - syncSpecsLogs.__set__('request', requestStub); + let error = null, + body = {}, + status = 204, + n = 1, + delayed_n = 2, + timeout = 3000; + let axiosStub = sandbox.stub(); + let postStub = sandbox.stub(axios, 'post').resolves({ status, data: body }); + axiosStub.post = postStub; + syncSpecsLogs.__set__('axios', axiosStub); let showSpecsStatus = sandbox.stub(); syncSpecsLogs.__set__('showSpecsStatus', showSpecsStatus); @@ -429,51 +472,8 @@ describe("syncSpecsLogs", () => { let whilstCallback = sandbox.stub(); whileProcess(whilstCallback); - expect(syncSpecsLogs.__get__("n")).to.equal(delayed_n); + expect(syncSpecsLogs.__get__('n')).to.equal(delayed_n); sinon.assert.calledWith(setTimeout, whilstCallback, timeout * delayed_n, null); }); - - it('Should stop polling for data when server responds build is completed', () => { - let error = null, body={}, status = 200, n = 1, timeout = 3000; - let requestStub = sandbox.stub(); - let postStub = sandbox.stub(request, "post").yields(error, { statusCode: status }, JSON.stringify(body)); - requestStub.post = postStub; - syncSpecsLogs.__set__('request', requestStub); - - let showSpecsStatus = sandbox.stub(); - syncSpecsLogs.__set__('showSpecsStatus', showSpecsStatus); - - syncSpecsLogs.__set__('whileLoop', true); - syncSpecsLogs.__set__('n', n); - syncSpecsLogs.__set__('timeout', timeout); - - let whilstCallback = sandbox.stub(); - whileProcess(whilstCallback); - - expect(syncSpecsLogs.__get__("whileLoop")).to.be.false; - sinon.assert.calledWith(whilstCallback, null, JSON.stringify(body)); - sinon.assert.calledWith(showSpecsStatus, JSON.stringify(body)); - }); - - it('Should stop polling for data when server responds with error ', () => { - let error = null, body={}, status = 404, n = 1, timeout = 3000; - let requestStub = sandbox.stub(); - let postStub = sandbox.stub(request, "post").yields(error, { statusCode: status }, JSON.stringify(body)); - requestStub.post = postStub; - syncSpecsLogs.__set__('request', requestStub); - - let showSpecsStatus = sandbox.stub(); - syncSpecsLogs.__set__('showSpecsStatus', showSpecsStatus); - - syncSpecsLogs.__set__('whileLoop', true); - syncSpecsLogs.__set__('n', n); - syncSpecsLogs.__set__('timeout', timeout); - - let whilstCallback = sandbox.stub(); - whileProcess(whilstCallback); - - expect(syncSpecsLogs.__get__("whileLoop")).to.be.false; - sinon.assert.calledWith(whilstCallback, {message: JSON.stringify(body), status: status}); - }); }); }); diff --git a/test/unit/bin/helpers/utils.js b/test/unit/bin/helpers/utils.js index c863a3ae..1d24ced6 100644 --- a/test/unit/bin/helpers/utils.js +++ b/test/unit/bin/helpers/utils.js @@ -2,7 +2,7 @@ const path = require('path'); var sandbox = require('sinon').createSandbox(); -const request = require('request'); +const axios = require('axios'); const chai = require('chai'), expect = chai.expect, sinon = require('sinon'), @@ -2900,22 +2900,25 @@ describe('utils', () => { }); describe('#checkLocalBinaryRunning', () => { + let axiosStub; afterEach(() => { sinon.restore(); + axiosStub.restore(); }); it('if the bsConfig localIdentifier is not present within the response body then function should resolve with false', () => { + const responseBody = { + "should_spawn_binary": true + }; const responseObject = { - statusCode: 200, + status: 200, headers: { 'content-type': 'application/json', }, + data: responseBody }; - const responseBody = { - "should_spawn_binary": true - }; - sinon - .stub(request, 'post') - .yields(undefined, responseObject, JSON.stringify(responseBody)); + axiosStub = sinon + .stub(axios, 'post') + .resolves(responseObject); let bsConfig = { auth: { @@ -2933,16 +2936,18 @@ describe('utils', () => { }); it('if the bsConfig localIdentifier is present within the response body', () => { + const responseBody = { "should_spawn_binary": false }; const responseObject = { - statusCode: 200, + status: 200, headers: { 'content-type': 'application/json', }, + data: responseBody }; - const responseBody = { "should_spawn_binary": false }; - sinon - .stub(request, 'post') - .yields(undefined, responseObject, JSON.stringify(responseBody)); + + axiosStub = sinon + .stub(axios, 'post') + .resolves(responseObject); let bsConfig = { auth: { @@ -2952,11 +2957,12 @@ describe('utils', () => { }; let localIdentifier = 'lmno'; - return utils + Promise.resolve(utils .checkLocalBinaryRunning(bsConfig, localIdentifier) .then((result) => { chai.assert.deepEqual(result, {"should_spawn_binary": false}) - }); + })) + return ; }); }); @@ -3442,56 +3448,57 @@ describe('utils', () => { it('message thrown if API deprecated', async () => { let api_deprecated_response = { - statusCode: 299 + status: 299 } message = constant.userMessages.API_DEPRECATED; messageType = constant.messageTypes.INFO; errorCode = 'api_deprecated'; - let requestStub = sinon.stub(request, 'post').yields(undefined, api_deprecated_response, null); + let axiosStub = sinon.stub(axios, 'post').resolves(api_deprecated_response); await utils.stopBrowserStackBuild(bsConfig, args, buildId, rawArgs); - sinon.assert.calledOnce(requestStub); + sinon.assert.calledOnce(axiosStub); sinon.assert.calledOnce(getUserAgentStub); sinon.assert.calledOnceWithExactly(sendUsageReportStub, bsConfig, args, message, messageType, errorCode, null, rawArgs); - requestStub.restore(); + axiosStub.restore(); }); it('message thrown if build returned', async () => { let api_deprecated_response = { - statusCode: 299, + status: 299, + data: body } message = body.message; messageType = constant.messageTypes.INFO; errorCode = 'api_deprecated'; - let requestStub = sinon.stub(request, 'post').yields(undefined, api_deprecated_response, JSON.stringify(body)); + let axiosStub = sinon.stub(axios, 'post').resolves(api_deprecated_response); await utils.stopBrowserStackBuild(bsConfig, args, buildId, rawArgs); - sinon.assert.calledOnce(requestStub); + sinon.assert.calledOnce(axiosStub); sinon.assert.calledOnce(getUserAgentStub); sinon.assert.calledOnceWithExactly(sendUsageReportStub, bsConfig, args, message, messageType, errorCode, null, rawArgs); - requestStub.restore(); + axiosStub.restore(); }); - it('message thrown if statusCode != 200', async () => { + it('message thrown if status != 200', async () => { let non_200_status_response = { - statusCode: 400 + status: 400 } message = constant.userMessages.BUILD_STOP_FAILED; messageType = constant.messageTypes.ERROR; errorCode = 'api_failed_build_stop'; - let requestStub = sinon.stub(request, 'post').yields(undefined, non_200_status_response, null); + let axiosStub = sinon.stub(axios, 'post').resolves(non_200_status_response); await utils.stopBrowserStackBuild(bsConfig, args, buildId, rawArgs); - sinon.assert.calledOnce(requestStub); + sinon.assert.calledOnce(axiosStub); sinon.assert.calledOnce(getUserAgentStub); sinon.assert.calledOnceWithExactly(sendUsageReportStub, bsConfig, args, message, messageType, errorCode, null, rawArgs); - requestStub.restore(); + axiosStub.restore(); }); - it('message thrown if statusCode != 200 and user unauthorized', async () => { + it('message thrown if status != 200 and user unauthorized', async () => { let body_with_message = { ...body, "message": "Unauthorized", }; let non_200_status_response = { - statusCode: 401, + status: 401, data: body_with_message } @@ -3500,17 +3507,18 @@ describe('utils', () => { } with error: \n${JSON.stringify(body_with_message, null, 2)}`; messageType = constant.messageTypes.ERROR; errorCode = 'api_auth_failed'; - let requestStub = sinon.stub(request, 'post').yields(undefined, non_200_status_response, JSON.stringify(body_with_message)); + let axiosStub = sinon.stub(axios, 'post').resolves(non_200_status_response); await utils.stopBrowserStackBuild(bsConfig, args, buildId, rawArgs); - sinon.assert.calledOnce(requestStub); + sinon.assert.calledOnce(axiosStub); sinon.assert.calledOnce(getUserAgentStub); sinon.assert.calledOnceWithExactly(sendUsageReportStub, bsConfig, args, message, messageType, errorCode, null, rawArgs); - requestStub.restore(); + axiosStub.restore(); }); - it('message thrown if statusCode != 200 and build is present', async () => { + it('message thrown if status != 200 and build is present', async () => { let non_200_status_response = { - statusCode: 402, + status: 402, + data: body } message = `${ @@ -3518,28 +3526,29 @@ describe('utils', () => { } with error: \n${JSON.stringify(body, null, 2)}`; messageType = constant.messageTypes.ERROR; errorCode = 'api_failed_build_stop'; - let requestStub = sinon.stub(request, 'post').yields(undefined, non_200_status_response, JSON.stringify(body)); + let axiosStub = sinon.stub(axios, 'post').resolves(non_200_status_response); await utils.stopBrowserStackBuild(bsConfig, args, buildId, rawArgs); - sinon.assert.calledOnce(requestStub); + sinon.assert.calledOnce(axiosStub); sinon.assert.calledOnce(getUserAgentStub); sinon.assert.calledOnceWithExactly(sendUsageReportStub, bsConfig, args, message, messageType, errorCode, null, rawArgs); - requestStub.restore(); + axiosStub.restore(); }); it('message thrown if API success', async () => { let success_response = { - statusCode: 200, + status: 200, + data: body } message = `${JSON.stringify(body, null, 2)}`; messageType = constant.messageTypes.SUCCESS; errorCode = null; - let requestStub = sinon.stub(request, 'post').yields(undefined, success_response, JSON.stringify(body)); + let axiosStub = sinon.stub(axios, 'post').resolves(success_response); await utils.stopBrowserStackBuild(bsConfig, args, buildId, rawArgs); - sinon.assert.calledOnce(requestStub); + sinon.assert.calledOnce(axiosStub); sinon.assert.calledOnce(getUserAgentStub); sinon.assert.calledOnceWithExactly(sendUsageReportStub, bsConfig, args, message, messageType, errorCode, null, rawArgs); - requestStub.restore(); + axiosStub.restore(); }); }); @@ -4238,10 +4247,10 @@ describe('utils', () => { it('should return correct JSON', () => { expect(utils.formatRequest('Something went wrong.', undefined, undefined)).to.be.eql({err: 'Something went wrong.', status: null, body: null}); const body = {message: "Something went wrong"}; - expect(utils.formatRequest(null, {statusCode: 400}, body)).to.be.eql({err: null, status: 400, body: JSON.stringify(body)}); + expect(utils.formatRequest(null, {status: 400}, body)).to.be.eql({err: null, status: 400, body: JSON.stringify(body)}); const cricularBody = {message: "Something went wrong"}; cricularBody.body = cricularBody; - expect(utils.formatRequest(null, {statusCode: 500}, cricularBody)).to.be.eql({err: null, status: 500, body: '[Circular]'}); + expect(utils.formatRequest(null, {status: 500}, cricularBody)).to.be.eql({err: null, status: 500, body: '[Circular]'}); }); }); @@ -4522,5 +4531,1066 @@ describe('utils', () => { }); }); + describe('#validateAutoImportConflict', () => { + it('should pass when auto_import_dev_dependencies is true and no manual dependencies defined', () => { + const runSettings = { + auto_import_dev_dependencies: true + }; + expect(() => utils.validateAutoImportConflict(runSettings)).to.not.throw(); + }); + + it('should pass when auto_import_dev_dependencies is true and npm_dependencies is empty object', () => { + const runSettings = { + auto_import_dev_dependencies: true, + npm_dependencies: {} + }; + expect(() => utils.validateAutoImportConflict(runSettings)).to.not.throw(); + }); + + it('should pass when auto_import_dev_dependencies is true and npm_dependencies is null', () => { + const runSettings = { + auto_import_dev_dependencies: true, + npm_dependencies: null + }; + expect(() => utils.validateAutoImportConflict(runSettings)).to.not.throw(); + }); + + it('should pass when auto_import_dev_dependencies is true and npm_dependencies is undefined', () => { + const runSettings = { + auto_import_dev_dependencies: true, + npm_dependencies: undefined + }; + expect(() => utils.validateAutoImportConflict(runSettings)).to.not.throw(); + }); + + it('should throw error when auto_import_dev_dependencies is true and npm_dependencies has values', () => { + const runSettings = { + auto_import_dev_dependencies: true, + npm_dependencies: { + lodash: '^4.17.21' + } + }; + expect(() => utils.validateAutoImportConflict(runSettings)).to.throw(); + }); + + it('should throw error when auto_import_dev_dependencies is true and win_npm_dependencies has values', () => { + const runSettings = { + auto_import_dev_dependencies: true, + win_npm_dependencies: { + lodash: '^4.17.21' + } + }; + expect(() => utils.validateAutoImportConflict(runSettings)).to.throw(); + }); + + it('should throw error when auto_import_dev_dependencies is true and mac_npm_dependencies has values', () => { + const runSettings = { + auto_import_dev_dependencies: true, + mac_npm_dependencies: { + lodash: '^4.17.21' + } + }; + expect(() => utils.validateAutoImportConflict(runSettings)).to.throw(); + }); + + it('should throw error when auto_import_dev_dependencies is true and all manual dependency types have values', () => { + const runSettings = { + auto_import_dev_dependencies: true, + npm_dependencies: { + lodash: '^4.17.21' + }, + win_npm_dependencies: { + winonly: '^1.0.0' + }, + mac_npm_dependencies: { + maconly: '^1.0.0' + } + }; + expect(() => utils.validateAutoImportConflict(runSettings)).to.throw(); + }); + + it('should pass when auto_import_dev_dependencies is false and manual dependencies exist', () => { + const runSettings = { + auto_import_dev_dependencies: false, + npm_dependencies: { + lodash: '^4.17.21' + } + }; + expect(() => utils.validateAutoImportConflict(runSettings)).to.not.throw(); + }); + + it('should pass when auto_import_dev_dependencies is undefined and manual dependencies exist', () => { + const runSettings = { + npm_dependencies: { + lodash: '^4.17.21' + } + }; + expect(() => utils.validateAutoImportConflict(runSettings)).to.not.throw(); + }); + + it('should throw error with proper message when auto_import conflicts with npm_dependencies', () => { + const runSettings = { + auto_import_dev_dependencies: true, + npm_dependencies: { + lodash: '^4.17.21' + } + }; + expect(() => utils.validateAutoImportConflict(runSettings)).to.throw(/auto_import_dev_dependencies.*npm_dependencies/i); + }); + + it('should throw error for invalid boolean value of auto_import_dev_dependencies', () => { + const runSettings = { + auto_import_dev_dependencies: "true" + }; + expect(() => utils.validateAutoImportConflict(runSettings)).to.throw(/must be a boolean/i); + }); + + it('should throw error for invalid type of auto_import_dev_dependencies', () => { + const runSettings = { + auto_import_dev_dependencies: 1 + }; + expect(() => utils.validateAutoImportConflict(runSettings)).to.throw(/must be a boolean/i); + }); + + it('should pass when both auto_import_dev_dependencies and manual deps have empty objects', () => { + const runSettings = { + auto_import_dev_dependencies: true, + npm_dependencies: {}, + win_npm_dependencies: {}, + mac_npm_dependencies: {} + }; + expect(() => utils.validateAutoImportConflict(runSettings)).to.not.throw(); + }); + }); + + describe('#readPackageJsonDevDependencies', () => { + let fsStub, pathStub; + + beforeEach(() => { + fsStub = sinon.stub(fs, 'readFileSync'); + pathStub = sinon.stub(path, 'join'); + }); + + afterEach(() => { + fsStub.restore(); + pathStub.restore(); + }); + + it('should read valid package.json and return devDependencies', () => { + const validPackageJson = { + "name": "test-project", + "version": "1.0.0", + "devDependencies": { + "jest": "^29.0.0", + "prettier": "^2.8.0", + "eslint": "^8.30.0", + "cypress": "^12.0.0" + } + }; + pathStub.returns('/fake/path/package.json'); + fsStub.returns(JSON.stringify(validPackageJson)); + + const result = utils.readPackageJsonDevDependencies('/fake/path'); + expect(result).to.deep.equal(validPackageJson.devDependencies); + }); + + it('should return empty object when devDependencies field is empty', () => { + const emptyDevDepsPackage = { + "name": "test-project", + "version": "1.0.0", + "devDependencies": {} + }; + pathStub.returns('/fake/path/package.json'); + fsStub.returns(JSON.stringify(emptyDevDepsPackage)); + + const result = utils.readPackageJsonDevDependencies('/fake/path'); + expect(result).to.deep.equal({}); + }); + + it('should return empty object when devDependencies field is missing', () => { + const noDevDepsPackage = { + "name": "test-project", + "version": "1.0.0", + "dependencies": { + "lodash": "^4.17.21", + "axios": "^1.3.0" + } + }; + pathStub.returns('/fake/path/package.json'); + fsStub.returns(JSON.stringify(noDevDepsPackage)); + + const result = utils.readPackageJsonDevDependencies('/fake/path'); + expect(result).to.deep.equal({}); + }); + + it('should handle scoped packages correctly', () => { + const scopedPackage = { + "name": "test-project", + "version": "1.0.0", + "devDependencies": { + "@types/node": "^18.0.0", + "@types/jest": "^29.0.0", + "@testing-library/react": "^13.0.0", + "@testing-library/jest-dom": "^5.16.0", + "@eslint/config": "^1.0.0", + "regular-package": "^1.0.0", + "@babel/core": "^7.20.0" + } + }; + pathStub.returns('/fake/path/package.json'); + fsStub.returns(JSON.stringify(scopedPackage)); + + const result = utils.readPackageJsonDevDependencies('/fake/path'); + expect(result).to.deep.equal(scopedPackage.devDependencies); + expect(result).to.have.property('@types/node'); + expect(result).to.have.property('@testing-library/react'); + }); + + it('should handle cypress and cypress-related packages', () => { + const cypressPackage = { + "name": "test-project", + "version": "1.0.0", + "devDependencies": { + "cypress": "^12.5.0", + "@cypress/webpack-preprocessor": "^5.17.0", + "cypress-mochawesome-reporter": "^3.4.0", + "cypress-real-events": "^1.8.0", + "@testing-library/cypress": "^9.0.0" + } + }; + pathStub.returns('/fake/path/package.json'); + fsStub.returns(JSON.stringify(cypressPackage)); + + const result = utils.readPackageJsonDevDependencies('/fake/path'); + expect(result).to.deep.equal(cypressPackage.devDependencies); + expect(result).to.have.property('cypress'); + expect(result).to.have.property('@cypress/webpack-preprocessor'); + }); + + it('should handle git URLs and various dependency formats', () => { + const gitDepsPackage = { + "name": "test-project", + "version": "1.0.0", + "devDependencies": { + "git-package": "git+https://github.com/user/repo.git#branch", + "github-shorthand": "user/repo#v1.0.0", + "file-package": "file:../local-package", + "tarball-package": "https://registry.npmjs.org/package/-/package-1.0.0.tgz", + "normal-package": "^1.0.0", + "prerelease": "1.0.0-alpha.1", + "range-package": ">=1.0.0 <2.0.0" + } + }; + pathStub.returns('/fake/path/package.json'); + fsStub.returns(JSON.stringify(gitDepsPackage)); + + const result = utils.readPackageJsonDevDependencies('/fake/path'); + expect(result).to.deep.equal(gitDepsPackage.devDependencies); + expect(result).to.have.property('git-package'); + expect(result).to.have.property('file-package'); + }); + + it('should handle unicode package names', () => { + const unicodePackage = { + "name": "test-project", + "version": "1.0.0", + "devDependencies": { + "normal-package": "^1.0.0", + "package-with-ümlauts": "^1.0.0", + "pàckagé-wïth-áccénts": "^2.0.0", + "包名测试": "^1.0.0", + "пакет-кириллица": "^1.0.0", + "package.with.dots": "^1.0.0", + "package_with_underscores": "^1.0.0", + "package-with-dashes": "^1.0.0" + } + }; + pathStub.returns('/fake/path/package.json'); + fsStub.returns(JSON.stringify(unicodePackage)); + + const result = utils.readPackageJsonDevDependencies('/fake/path'); + expect(result).to.deep.equal(unicodePackage.devDependencies); + expect(result).to.have.property('pàckagé-wïth-áccénts'); + }); + + it('should throw error when package.json file does not exist', () => { + pathStub.returns('/fake/path/package.json'); + fsStub.throws(new Error('ENOENT: no such file or directory')); + + expect(() => utils.readPackageJsonDevDependencies('/fake/path')).to.throw(/Cannot read package.json/i); + }); + + it('should throw error when package.json has permission issues', () => { + pathStub.returns('/fake/path/package.json'); + fsStub.throws(new Error('EACCES: permission denied')); + + expect(() => utils.readPackageJsonDevDependencies('/fake/path')).to.throw(/cannot read package.json/i); + }); + + it('should throw error when package.json is malformed JSON', () => { + pathStub.returns('/fake/path/package.json'); + fsStub.returns('{ "name": "test", // invalid comment }'); + + expect(() => utils.readPackageJsonDevDependencies('/fake/path')).to.throw(/invalid JSON syntax/i); + }); + + it('should throw error when package.json is not a JSON object', () => { + pathStub.returns('/fake/path/package.json'); + fsStub.returns('"this is a string not an object"'); + + expect(() => utils.readPackageJsonDevDependencies('/fake/path')).to.throw(/must contain a JSON object/i); + }); + + it('should throw error when devDependencies is not an object', () => { + pathStub.returns('/fake/path/package.json'); + fsStub.returns('{"name": "test-project", "version": "1.0.0", "devDependencies": ["this-should-be-object-not-array"]}'); + + expect(() => utils.readPackageJsonDevDependencies('/fake/path')).to.throw(/devDependencies.*must be an object/i); + }); + + it('should throw error when devDependencies is null', () => { + pathStub.returns('/fake/path/package.json'); + fsStub.returns('{"name": "test", "devDependencies": null}'); + + expect(() => utils.readPackageJsonDevDependencies('/fake/path')).to.throw(/devDependencies field.*must be an object/i); + }); + + it('should handle empty package.json file', () => { + pathStub.returns('/fake/path/package.json'); + fsStub.returns(''); + + expect(() => utils.readPackageJsonDevDependencies('/fake/path')).to.throw(/invalid JSON syntax/i); + }); + + it('should construct correct path with path.join', () => { + const validPackageJson = { + "name": "test-project", + "version": "1.0.0", + "devDependencies": { + "jest": "^29.0.0" + } + }; + pathStub.withArgs('/project/dir', 'package.json').returns('/project/dir/package.json'); + fsStub.withArgs('/project/dir/package.json', 'utf8').returns(JSON.stringify(validPackageJson)); + + utils.readPackageJsonDevDependencies('/project/dir'); + + sinon.assert.calledWith(pathStub, '/project/dir', 'package.json'); + sinon.assert.calledWith(fsStub, '/project/dir/package.json', 'utf8'); + }); + + it('should handle BOM (Byte Order Mark) in package.json', () => { + const validPackageJson = { + "name": "test-project", + "version": "1.0.0", + "devDependencies": { + "jest": "^29.0.0" + } + }; + pathStub.returns('/fake/path/package.json'); + // Add BOM to the beginning of the JSON string + const jsonWithBOM = '\ufeff' + JSON.stringify(validPackageJson); + fsStub.returns(jsonWithBOM); + + const result = utils.readPackageJsonDevDependencies('/fake/path'); + expect(result).to.deep.equal(validPackageJson.devDependencies); + }); + }); + + describe('#filterDependenciesWithRegex', () => { + + it('should return all dependencies when exclude_dependencies is empty', () => { + const dependencies = { + lodash: '^4.17.21', + axios: '^1.3.0', + jest: '^29.0.0' + }; + const excludePatterns = []; + + const result = utils.filterDependenciesWithRegex(dependencies, excludePatterns); + expect(result).to.deep.equal(dependencies); + }); + + it('should return all dependencies when exclude_dependencies is undefined', () => { + const dependencies = { + lodash: '^4.17.21', + axios: '^1.3.0' + }; + + const result = utils.filterDependenciesWithRegex(dependencies, undefined); + expect(result).to.deep.equal(dependencies); + }); + + it('should filter out dependencies matching single regex pattern', () => { + const dependencies = { + lodash: '^4.17.21', + axios: '^1.3.0', + jest: '^29.0.0', + 'testing-library': '^1.0.0' + }; + const excludePatterns = ['jest']; + + const result = utils.filterDependenciesWithRegex(dependencies, excludePatterns); + expect(result).to.deep.equal({ + lodash: '^4.17.21', + axios: '^1.3.0', + 'testing-library': '^1.0.0' + }); + }); + + it('should filter out dependencies matching multiple regex patterns', () => { + const dependencies = { + lodash: '^4.17.21', + axios: '^1.3.0', + jest: '^29.0.0', + 'jest-environment': '^1.0.0', + eslint: '^8.30.0', + 'eslint-config': '^1.0.0' + }; + const excludePatterns = ['^jest', 'eslint']; + + const result = utils.filterDependenciesWithRegex(dependencies, excludePatterns); + expect(result).to.deep.equal({ + lodash: '^4.17.21', + axios: '^1.3.0' + }); + }); + + it('should handle scoped packages filtering correctly', () => { + const dependencies = { + '@types/node': '^18.0.0', + '@types/jest': '^29.0.0', + '@testing-library/react': '^13.0.0', + '@babel/core': '^7.20.0', + 'regular-package': '^1.0.0' + }; + const excludePatterns = ['^@types/', '^@testing-library/']; + + const result = utils.filterDependenciesWithRegex(dependencies, excludePatterns); + expect(result).to.deep.equal({ + '@babel/core': '^7.20.0', + 'regular-package': '^1.0.0' + }); + }); + + it('should handle unicode package names in filtering', () => { + const dependencies = { + 'normal-package': '^1.0.0', + 'package-with-ümlauts': '^1.0.0', + 'pàckagé-wïth-áccénts': '^2.0.0', + '包名测试': '^1.0.0' + }; + const excludePatterns = ['ümlaut', 'áccént']; + + const result = utils.filterDependenciesWithRegex(dependencies, excludePatterns); + expect(result).to.deep.equal({ + 'normal-package': '^1.0.0', + '包名测试': '^1.0.0' + }); + }); + + it('should handle special characters in package names', () => { + const dependencies = { + 'package.with.dots': '^1.0.0', + 'package_with_underscores': '^1.0.0', + 'package-with-dashes': '^1.0.0', + 'package+with+plus': '^1.0.0' + }; + const excludePatterns = ['\\.', '_']; + + const result = utils.filterDependenciesWithRegex(dependencies, excludePatterns); + expect(result).to.deep.equal({ + 'package-with-dashes': '^1.0.0', + 'package+with+plus': '^1.0.0' + }); + }); + + it('should throw error for invalid regex patterns', () => { + const dependencies = { lodash: '^4.17.21' }; + const excludePatterns = ['[unclosed']; + + expect(() => utils.filterDependenciesWithRegex(dependencies, excludePatterns)).to.throw(/invalid regex pattern/i); + }); + + it('should throw error for non-string regex patterns', () => { + const dependencies = { lodash: '^4.17.21' }; + const excludePatterns = [123, true]; + + expect(() => utils.filterDependenciesWithRegex(dependencies, excludePatterns)).to.throw(/must contain only string values/i); + }); + + it('should throw error when exclude_dependencies is not an array', () => { + const dependencies = { lodash: '^4.17.21' }; + const excludePatterns = 'not-an-array'; + + expect(() => utils.filterDependenciesWithRegex(dependencies, excludePatterns)).to.throw(/must be an array/i); + }); + + it('should handle empty string regex pattern', () => { + const dependencies = { + lodash: '^4.17.21', + axios: '^1.3.0' + }; + const excludePatterns = ['']; + + const result = utils.filterDependenciesWithRegex(dependencies, excludePatterns); + expect(result).to.deep.equal(dependencies); + }); + + it('should handle regex that matches everything (.*)', () => { + const dependencies = { + lodash: '^4.17.21', + axios: '^1.3.0' + }; + const excludePatterns = ['.*']; + + const result = utils.filterDependenciesWithRegex(dependencies, excludePatterns); + expect(result).to.deep.equal({}); + }); + + it('should handle regex that matches nothing', () => { + const dependencies = { + lodash: '^4.17.21', + axios: '^1.3.0' + }; + const excludePatterns = ['(?!.*)']; + + const result = utils.filterDependenciesWithRegex(dependencies, excludePatterns); + expect(result).to.deep.equal(dependencies); + }); + + it('should be case sensitive by default', () => { + const dependencies = { + Lodash: '^4.17.21', + lodash: '^4.17.21', + AXIOS: '^1.3.0' + }; + const excludePatterns = ['lodash']; + + const result = utils.filterDependenciesWithRegex(dependencies, excludePatterns); + expect(result).to.deep.equal({ + Lodash: '^4.17.21', + AXIOS: '^1.3.0' + }); + }); + + it('should handle complex regex patterns with word boundaries', () => { + const dependencies = { + test: '^1.0.0', + 'test-utils': '^1.0.0', + 'my-test': '^1.0.0', + 'testing': '^1.0.0' + }; + const excludePatterns = ['^test$']; + + const result = utils.filterDependenciesWithRegex(dependencies, excludePatterns); + expect(result).to.deep.equal({ + 'test-utils': '^1.0.0', + 'my-test': '^1.0.0', + 'testing': '^1.0.0' + }); + }); + + it('should handle regex with lookahead and lookbehind', () => { + const dependencies = { + 'test-package': '^1.0.0', + 'test-dev': '^1.0.0', + 'prod-test': '^1.0.0', + 'other': '^1.0.0' + }; + const excludePatterns = ['test(?=-dev)']; + + const result = utils.filterDependenciesWithRegex(dependencies, excludePatterns); + expect(result).to.deep.equal({ + 'test-package': '^1.0.0', + 'prod-test': '^1.0.0', + 'other': '^1.0.0' + }); + }); + + it('should validate dependencies parameter', () => { + const excludePatterns = ['test']; + + expect(() => utils.filterDependenciesWithRegex(null, excludePatterns)).to.throw(/Dependencies parameter must be an object/i); + expect(() => utils.filterDependenciesWithRegex('not-object', excludePatterns)).to.throw(/Dependencies parameter must be an object/i); + expect(() => utils.filterDependenciesWithRegex([], excludePatterns)).to.throw(/Dependencies parameter must be an object/i); + }); + + it('should handle very long package names and regex patterns', () => { + const longPackageName = 'very-long-package-name-that-exceeds-normal-length-expectations-and-continues-for-testing-purposes'; + const dependencies = {}; + dependencies[longPackageName] = '^1.0.0'; + dependencies['short'] = '^1.0.0'; + + const longRegexPattern = 'very-long.*testing-purposes'; + const excludePatterns = [longRegexPattern]; + + const result = utils.filterDependenciesWithRegex(dependencies, excludePatterns); + expect(result).to.deep.equal({ + 'short': '^1.0.0' + }); + }); + }); + + describe('#processAutoImportDependencies', () => { + let validateStub, readPackageStub, filterStub; + + beforeEach(() => { + validateStub = sinon.stub(utils, 'validateAutoImportConflict'); + readPackageStub = sinon.stub(utils, 'readPackageJsonDevDependencies'); + filterStub = sinon.stub(utils, 'filterDependenciesWithRegex'); + }); + + afterEach(() => { + validateStub.restore(); + readPackageStub.restore(); + filterStub.restore(); + }); + + it('should process full workflow successfully', () => { + const runSettings = { + auto_import_dev_dependencies: true, + exclude_dependencies: ['^@types/'], + home_directory: '/project/dir' + }; + + const mockDevDeps = { + '@types/node': '^18.0.0', + 'jest': '^29.0.0', + 'lodash': '^4.17.21' + }; + + const expectedFiltered = { + 'jest': '^29.0.0', + 'lodash': '^4.17.21' + }; + + validateStub.returns(); + readPackageStub.returns(mockDevDeps); + filterStub.returns(expectedFiltered); + + utils.processAutoImportDependencies(runSettings); + + sinon.assert.calledOnceWithExactly(validateStub, runSettings); + sinon.assert.calledOnceWithExactly(readPackageStub, '/project/dir'); + sinon.assert.calledOnceWithExactly(filterStub, mockDevDeps, ['^@types/']); + expect(runSettings.npm_dependencies).to.deep.equal(expectedFiltered); + }); + + it('should use cypressConfigFilePath directory when home_directory is not set', () => { + const runSettings = { + auto_import_dev_dependencies: true, + cypressConfigFilePath: '/project/cypress.config.js' + }; + + const mockDevDeps = { jest: '^29.0.0' }; + + validateStub.returns(); + readPackageStub.returns(mockDevDeps); + filterStub.returns(mockDevDeps); + + utils.processAutoImportDependencies(runSettings); + + sinon.assert.calledOnceWithExactly(readPackageStub, '/project'); + expect(runSettings.npm_dependencies).to.deep.equal(mockDevDeps); + }); + + it('should skip processing when auto_import_dev_dependencies is false', () => { + const runSettings = { + auto_import_dev_dependencies: false, + npm_dependencies: { existing: '^1.0.0' } + }; + + utils.processAutoImportDependencies(runSettings); + + sinon.assert.calledOnceWithExactly(validateStub, runSettings); + sinon.assert.notCalled(readPackageStub); + sinon.assert.notCalled(filterStub); + expect(runSettings.npm_dependencies).to.deep.equal({ existing: '^1.0.0' }); + }); + + it('should skip processing when auto_import_dev_dependencies is undefined', () => { + const runSettings = { + npm_dependencies: { existing: '^1.0.0' } + }; + + utils.processAutoImportDependencies(runSettings); + + sinon.assert.calledOnceWithExactly(validateStub, runSettings); + sinon.assert.notCalled(readPackageStub); + sinon.assert.notCalled(filterStub); + expect(runSettings.npm_dependencies).to.deep.equal({ existing: '^1.0.0' }); + }); + + it('should handle empty devDependencies from package.json', () => { + const runSettings = { + auto_import_dev_dependencies: true, + home_directory: '/project/dir' + }; + + validateStub.returns(); + readPackageStub.returns({}); + filterStub.returns({}); + + utils.processAutoImportDependencies(runSettings); + + sinon.assert.calledOnce(readPackageStub); + sinon.assert.calledOnce(filterStub); + // Should now have browserstack-cypress-cli added automatically + expect(runSettings.npm_dependencies).to.have.property('browserstack-cypress-cli'); + }); + + it('should merge auto-imported deps with existing empty npm_dependencies', () => { + const runSettings = { + auto_import_dev_dependencies: true, + npm_dependencies: {}, + home_directory: '/project/dir' + }; + + const mockDevDeps = { jest: '^29.0.0' }; + + validateStub.returns(); + readPackageStub.returns(mockDevDeps); + filterStub.returns(mockDevDeps); + + utils.processAutoImportDependencies(runSettings); + + expect(runSettings.npm_dependencies).to.deep.equal(mockDevDeps); + }); + + it('should handle exclude_dependencies undefined', () => { + const runSettings = { + auto_import_dev_dependencies: true, + home_directory: '/project/dir' + }; + + const mockDevDeps = { jest: '^29.0.0' }; + + validateStub.returns(); + readPackageStub.returns(mockDevDeps); + filterStub.returns(mockDevDeps); + + utils.processAutoImportDependencies(runSettings); + + sinon.assert.calledOnceWithExactly(filterStub, mockDevDeps, undefined); + expect(runSettings.npm_dependencies).to.deep.equal(mockDevDeps); + }); + + it('should handle exclude_dependencies empty array', () => { + const runSettings = { + auto_import_dev_dependencies: true, + exclude_dependencies: [], + home_directory: '/project/dir' + }; + + const mockDevDeps = { jest: '^29.0.0' }; + + validateStub.returns(); + readPackageStub.returns(mockDevDeps); + filterStub.returns(mockDevDeps); + + utils.processAutoImportDependencies(runSettings); + + sinon.assert.calledOnceWithExactly(filterStub, mockDevDeps, []); + expect(runSettings.npm_dependencies).to.deep.equal(mockDevDeps); + }); + + it('should propagate validation errors', () => { + const runSettings = { + auto_import_dev_dependencies: true, + npm_dependencies: { existing: '^1.0.0' } + }; + + const validationError = new Error('Validation failed'); + validateStub.throws(validationError); + + expect(() => utils.processAutoImportDependencies(runSettings)).to.throw('Validation failed'); + sinon.assert.calledOnce(validateStub); + sinon.assert.notCalled(readPackageStub); + }); + + it('should propagate package reading errors', () => { + const runSettings = { + auto_import_dev_dependencies: true, + home_directory: '/project/dir' + }; + + const readError = new Error('Cannot read package.json'); + validateStub.returns(); + readPackageStub.throws(readError); + + expect(() => utils.processAutoImportDependencies(runSettings)).to.throw('Cannot read package.json'); + sinon.assert.calledOnce(validateStub); + sinon.assert.calledOnce(readPackageStub); + sinon.assert.notCalled(filterStub); + }); + + it('should propagate filtering errors', () => { + const runSettings = { + auto_import_dev_dependencies: true, + exclude_dependencies: ['[invalid'], + home_directory: '/project/dir' + }; + + const mockDevDeps = { jest: '^29.0.0' }; + const filterError = new Error('Invalid regex pattern'); + + validateStub.returns(); + readPackageStub.returns(mockDevDeps); + filterStub.throws(filterError); + + expect(() => utils.processAutoImportDependencies(runSettings)).to.throw('Invalid regex pattern'); + sinon.assert.calledOnce(validateStub); + sinon.assert.calledOnce(readPackageStub); + sinon.assert.calledOnce(filterStub); + }); + + it('should handle complex integration scenario with cypress precedence', () => { + const runSettings = { + auto_import_dev_dependencies: true, + exclude_dependencies: ['^@testing-library/'], + home_directory: '/project/dir' + }; + + const mockDevDeps = { + 'cypress': '^12.5.0', + '@testing-library/cypress': '^9.0.0', + 'cypress-real-events': '^1.8.0', + 'jest': '^29.0.0' + }; + + const expectedFiltered = { + 'cypress': '^12.5.0', + 'cypress-real-events': '^1.8.0', + 'jest': '^29.0.0' + }; + + validateStub.returns(); + readPackageStub.returns(mockDevDeps); + filterStub.returns(expectedFiltered); + + utils.processAutoImportDependencies(runSettings); + + expect(runSettings.npm_dependencies).to.deep.equal(expectedFiltered); + expect(runSettings.npm_dependencies.cypress).to.equal('^12.5.0'); + }); + + it('should handle large number of dependencies efficiently', () => { + const runSettings = { + auto_import_dev_dependencies: true, + home_directory: '/project/dir' + }; + + // Create a large number of mock dependencies + const mockDevDeps = {}; + for (let i = 0; i < 500; i++) { + mockDevDeps[`package-${i}`] = '^1.0.0'; + } + + validateStub.returns(); + readPackageStub.returns(mockDevDeps); + filterStub.returns(mockDevDeps); + + const startTime = Date.now(); + utils.processAutoImportDependencies(runSettings); + const endTime = Date.now(); + + expect(endTime - startTime).to.be.lessThan(1000); // Should complete within 1 second + expect(Object.keys(runSettings.npm_dependencies)).to.have.lengthOf(501); // 500 + browserstack-cypress-cli + }); + + it('should initialize npm_dependencies when it does not exist', () => { + const runSettings = { + auto_import_dev_dependencies: true, + home_directory: '/project/dir' + }; + + const mockDevDeps = { jest: '^29.0.0' }; + + validateStub.returns(); + readPackageStub.returns(mockDevDeps); + filterStub.returns(mockDevDeps); + + utils.processAutoImportDependencies(runSettings); + + expect(runSettings).to.have.property('npm_dependencies'); + expect(runSettings.npm_dependencies).to.deep.equal(mockDevDeps); + }); + + it('should call ensureBrowserstackCypressCliDependency when auto import is enabled', () => { + const runSettings = { + auto_import_dev_dependencies: true, + home_directory: '/project/dir' + }; + + const mockDevDeps = { jest: '^29.0.0' }; + const ensureStub = sinon.stub(utils, 'ensureBrowserstackCypressCliDependency'); + + validateStub.returns(); + readPackageStub.returns(mockDevDeps); + filterStub.returns(mockDevDeps); + + utils.processAutoImportDependencies(runSettings); + + sinon.assert.calledOnceWithExactly(ensureStub, mockDevDeps); + ensureStub.restore(); + }); + + it('should not call ensureBrowserstackCypressCliDependency when auto import is disabled', () => { + const runSettings = { + auto_import_dev_dependencies: false + }; + + const ensureStub = sinon.stub(utils, 'ensureBrowserstackCypressCliDependency'); + + utils.processAutoImportDependencies(runSettings); + + sinon.assert.notCalled(ensureStub); + ensureStub.restore(); + }); + }); + + describe('#ensureBrowserstackCypressCliDependency', () => { + let loggerWarnStub, loggerDebugStub, fsExistsSyncStub; + + beforeEach(() => { + loggerWarnStub = sinon.stub(logger, 'warn'); + loggerDebugStub = sinon.stub(logger, 'debug'); + fsExistsSyncStub = sinon.stub(fs, 'existsSync'); + }); + + afterEach(() => { + loggerWarnStub.restore(); + loggerDebugStub.restore(); + fsExistsSyncStub.restore(); + // Clear require cache + delete require.cache[require.resolve('../../../../package.json')]; + }); + + it('should add browserstack-cypress-cli when not present with version from package.json', () => { + const npmDependencies = { + 'cypress': '^12.0.0', + 'jest': '^29.0.0' + }; + + fsExistsSyncStub.returns(true); + + // Mock require to return a version + const mockPackageJson = { version: '1.2.3' }; + + // Temporarily replace require + const Module = require('module'); + const originalLoad = Module._load; + Module._load = function(request, parent) { + if (request.includes('package.json')) { + return mockPackageJson; + } + return originalLoad.call(this, request, parent); + }; + + utils.ensureBrowserstackCypressCliDependency(npmDependencies); + + expect(npmDependencies).to.have.property('browserstack-cypress-cli', '1.2.3'); + sinon.assert.calledWith(loggerWarnStub, 'Missing browserstack-cypress-cli not found in npm_dependencies'); + sinon.assert.calledWith(loggerWarnStub, 'Adding browserstack-cypress-cli version 1.2.3 in npm_dependencies'); + + // Restore require + Module._load = originalLoad; + }); + + it('should add browserstack-cypress-cli with "latest" when package.json does not exist', () => { + const npmDependencies = { + 'cypress': '^12.0.0' + }; + + fsExistsSyncStub.returns(false); + + utils.ensureBrowserstackCypressCliDependency(npmDependencies); + + expect(npmDependencies).to.have.property('browserstack-cypress-cli', 'latest'); + sinon.assert.calledWith(loggerWarnStub, 'Missing browserstack-cypress-cli not found in npm_dependencies'); + sinon.assert.calledWith(loggerWarnStub, 'Adding browserstack-cypress-cli version latest in npm_dependencies'); + }); + + it('should add browserstack-cypress-cli with "latest" when require throws error', () => { + const npmDependencies = { + 'cypress': '^12.0.0' + }; + + fsExistsSyncStub.returns(true); + + // Mock require to throw an error + const Module = require('module'); + const originalLoad = Module._load; + Module._load = function(request, parent) { + if (request.includes('package.json')) { + throw new Error('Cannot read file'); + } + return originalLoad.call(this, request, parent); + }; + + utils.ensureBrowserstackCypressCliDependency(npmDependencies); + + expect(npmDependencies).to.have.property('browserstack-cypress-cli', 'latest'); + sinon.assert.calledWith(loggerDebugStub, "Could not read package.json version, using 'latest'"); + sinon.assert.calledWith(loggerWarnStub, 'Adding browserstack-cypress-cli version latest in npm_dependencies'); + + // Restore require + Module._load = originalLoad; + }); + + it('should not modify npmDependencies when browserstack-cypress-cli already exists', () => { + const npmDependencies = { + 'browserstack-cypress-cli': '^2.5.0', + 'cypress': '^12.0.0' + }; + + utils.ensureBrowserstackCypressCliDependency(npmDependencies); + + expect(npmDependencies['browserstack-cypress-cli']).to.equal('^2.5.0'); + sinon.assert.notCalled(loggerWarnStub); + sinon.assert.notCalled(fsExistsSyncStub); + }); + + it('should handle undefined npmDependencies parameter', () => { + utils.ensureBrowserstackCypressCliDependency(undefined); + + sinon.assert.notCalled(loggerWarnStub); + sinon.assert.notCalled(fsExistsSyncStub); + }); + + it('should handle null npmDependencies parameter', () => { + utils.ensureBrowserstackCypressCliDependency(null); + + sinon.assert.notCalled(loggerWarnStub); + sinon.assert.notCalled(fsExistsSyncStub); + }); + + it('should handle non-object npmDependencies parameter', () => { + utils.ensureBrowserstackCypressCliDependency('not an object'); + + sinon.assert.notCalled(loggerWarnStub); + sinon.assert.notCalled(fsExistsSyncStub); + }); + + it('should handle array npmDependencies parameter', () => { + utils.ensureBrowserstackCypressCliDependency(['not', 'an', 'object']); + + sinon.assert.notCalled(loggerWarnStub); + sinon.assert.notCalled(fsExistsSyncStub); + }); + + it('should handle empty npmDependencies object', () => { + const npmDependencies = {}; + + fsExistsSyncStub.returns(false); + + utils.ensureBrowserstackCypressCliDependency(npmDependencies); + + expect(npmDependencies).to.have.property('browserstack-cypress-cli', 'latest'); + sinon.assert.calledWith(loggerWarnStub, 'Missing browserstack-cypress-cli not found in npm_dependencies'); + }); + }); + }); diff --git a/test/unit/bin/helpers/zipUpload.js b/test/unit/bin/helpers/zipUpload.js index fa418b7d..32241964 100644 --- a/test/unit/bin/helpers/zipUpload.js +++ b/test/unit/bin/helpers/zipUpload.js @@ -2,8 +2,9 @@ const chai = require("chai"), chaiAsPromised = require("chai-as-promised"), sinon = require("sinon"), - fs = require('fs'), - request = require("request"); + fs = require('fs'); + +const { default: axios } = require("axios"); const logger = require("../../../../bin/helpers/logger").winstonLogger, constant = require('../../../../bin/helpers/constants'), @@ -45,7 +46,15 @@ describe("zipUpload", () => { const zipUploader = rewire("../../../../bin/helpers/zipUpload"); beforeEach(() => { utilsStub = { - generateUploadParams: sinon.stub().returns({}), + generateUploadParams: sinon.stub().returns({ + auth: { + user: "someuser", + password: "someuser", + }, + headers: { + "someheader": "header" + } + }), formatRequest, }; loggerStub = { @@ -59,36 +68,6 @@ describe("zipUpload", () => { fs.lstatSync.restore(); }); - it("reject with error", () => { - let requestStub = sandbox - .stub(request, "post") - .yields(new Error("test error"), null, null); - - zipUploader.__set__({ - request: { post: requestStub }, - utils: utilsStub, - logger: loggerStub - }); - let uploadSuitsrewire = zipUploader.__get__('uploadSuits'); - let opts = { - archivePresent: true, - messages: {} - } - let obj = { - bar1: null, - zipInterval: null, - size: 0, - startTime: null - } - return uploadSuitsrewire(bsConfig, filePath, opts, obj) - .then((_data) => { - chai.assert.fail("Promise error"); - }) - .catch((error) => { - chai.assert.equal(error.message.message, "test error"); - }); - }); - it("resolve with url if already present", () => { let uploadSuitsrewire = zipUploader.__get__('uploadSuits'); let opts = { @@ -133,12 +112,12 @@ describe("zipUpload", () => { }); it("resolve with nothing if parsing error", () => { - let requestStub = sandbox - .stub(request, "post") - .yields(null, { statusCode: 200 }, '{ random: "test }'); + let axiosStub = sandbox + .stub(axios, "post") + .resolves({ status: 200, data: { "random": "test" }}); zipUploader.__set__({ - request: { post: requestStub }, + axios: { post: axiosStub }, utils: utilsStub, logger: loggerStub }); @@ -146,7 +125,12 @@ describe("zipUpload", () => { let opts = { cleanupMethod: sinon.stub().returns(null), archivePresent: true, - messages: {} + messages: {}, + fileDetails: { + filetype: "zip", + filename: "abc" + }, + md5ReturnKey: "random" } let obj = { bar1: null, @@ -156,20 +140,20 @@ describe("zipUpload", () => { } return uploadSuitsrewire(bsConfig, filePath, opts, obj) .then((data) => { - chai.assert.hasAllKeys(data, ["time"]); + chai.assert.hasAllKeys(data, ["time", "random"]); }) .catch((_error) => { chai.assert.fail("Promise error"); }); }); - it("resolve with message if statusCode = 200", () => { - let requestStub = sandbox - .stub(request, "post") - .yields(null, { statusCode: 200 }, JSON.stringify({ zip_url: "zip_url" })); + it("resolve with message if status = 200", () => { + let axiosStub = sandbox + .stub(axios, "post") + .resolves({ status: 200 , data: { zip_url: "zip_url" }}); zipUploader.__set__({ - request: { post: requestStub }, + axios: { post: axiosStub }, utils: utilsStub, logger: loggerStub }); @@ -177,7 +161,11 @@ describe("zipUpload", () => { let opts = { cleanupMethod: sinon.stub().returns(null), archivePresent: true, - messages: {} + messages: {}, + fileDetails: { + filetype: "zip", + filename: "abc" + }, } let obj = { bar1: null, @@ -195,19 +183,23 @@ describe("zipUpload", () => { }); it("reject with returned message if auth failed with message", () => { - let requestStub = sandbox - .stub(request, "post") - .yields(null, { statusCode: 401 }, JSON.stringify({ error: "auth failed" })); + let axiosStub = sandbox + .stub(axios, "post") + .rejects({response: { status: 401 , data: { error: "auth failed" }}}); zipUploader.__set__({ - request: { post: requestStub }, + axios: { post: axiosStub }, utils: utilsStub, logger: loggerStub }); let uploadSuitsrewire = zipUploader.__get__('uploadSuits'); let opts = { archivePresent: true, - messages: {} + messages: {}, + fileDetails: { + filetype: "zip", + filename: "abc" + }, } let obj = { bar1: null, @@ -225,19 +217,23 @@ describe("zipUpload", () => { }); it("reject with predefined message if auth failed without message", () => { - let requestStub = sandbox - .stub(request, "post") - .yields(null, { statusCode: 401 }, JSON.stringify({ })); + let axiosStub = sandbox + .stub(axios, "post") + .rejects({response: { status: 401 , data: { }}}); zipUploader.__set__({ - request: { post: requestStub }, + axios: { post: axiosStub }, utils: utilsStub, logger: loggerStub }); let uploadSuitsrewire = zipUploader.__get__('uploadSuits'); let opts = { archivePresent: true, - messages: {} + messages: {}, + fileDetails: { + filetype: "zip", + filename: "abc" + }, } let obj = { bar1: null, @@ -254,13 +250,13 @@ describe("zipUpload", () => { }); }); - it("resolve with nothing if request error but no propogation", () => { - let requestStub = sandbox - .stub(request, "post") - .yields(null, { statusCode: 402 }, JSON.stringify({ })); + it("resolve with nothing if axios error but no propogation", () => { + let axiosStub = sandbox + .stub(axios, "post") + .rejects({response: { status: 402 , data: { }}}); zipUploader.__set__({ - request: { post: requestStub }, + axios: { post: axiosStub }, utils: utilsStub, logger: loggerStub }); @@ -268,7 +264,12 @@ describe("zipUpload", () => { let opts = { archivePresent: true, messages: {}, - propogateError: false + propogateError: false, + fileDetails: { + filetype: "zip", + filename: "abc" + }, + cleanupMethod: () => {} } let obj = { bar1: null, @@ -285,13 +286,13 @@ describe("zipUpload", () => { }); }); - it("reject with error if request error", () => { - let requestStub = sandbox - .stub(request, "post") - .yields(null, { statusCode: 402 }, JSON.stringify({ error: "test error" })); + it("reject with error if axios error", () => { + let axiosStub = sandbox + .stub(axios, "post") + .rejects({response: { status: 402 , data: { error: "test error" }}}); zipUploader.__set__({ - request: { post: requestStub }, + axios: { post: axiosStub }, utils: utilsStub, logger: loggerStub }); @@ -299,7 +300,12 @@ describe("zipUpload", () => { let opts = { archivePresent: true, messages: {}, - propogateError: true + propogateError: true, + fileDetails: { + filetype: "zip", + filename: "abc" + }, + cleanupMethod: () => {} } let obj = { bar1: null, @@ -317,12 +323,12 @@ describe("zipUpload", () => { }); it("reject with limit exceeded error", () => { - let requestStub = sandbox - .stub(request, "post") - .yields(null, { statusCode: 413 }, JSON.stringify({ })); + let axiosStub = sandbox + .stub(axios, "post") + .rejects({response:{ status: 413 , data: { }}}); zipUploader.__set__({ - request: { post: requestStub }, + axios: { post: axiosStub }, utils: utilsStub, logger: loggerStub }); @@ -330,7 +336,12 @@ describe("zipUpload", () => { let opts = { archivePresent: true, messages: {}, - propogateError: true + propogateError: true, + fileDetails: { + filetype: "zip", + filename: "abc" + }, + cleanupMethod: () => {} } let obj = { bar1: null, @@ -348,12 +359,12 @@ describe("zipUpload", () => { }); it("reject with not reachable error", () => { - let requestStub = sandbox - .stub(request, "post") - .yields(null, { statusCode: 414 }, JSON.stringify({ })); + let axiosStub = sandbox + .stub(axios, "post") + .rejects({response: { status: 414 , data: { }}}); zipUploader.__set__({ - request: { post: requestStub }, + axios: { post: axiosStub }, utils: utilsStub, logger: loggerStub }); @@ -361,7 +372,12 @@ describe("zipUpload", () => { let opts = { archivePresent: true, messages: {}, - propogateError: true + propogateError: true, + fileDetails: { + filetype: "zip", + filename: "abc" + }, + cleanupMethod: () => {} } let obj = { bar1: null, diff --git a/test/unit/support/fixtures/package-cypress-deps.json b/test/unit/support/fixtures/package-cypress-deps.json new file mode 100644 index 00000000..c290bcf3 --- /dev/null +++ b/test/unit/support/fixtures/package-cypress-deps.json @@ -0,0 +1,12 @@ +{ + "name": "test-project", + "version": "1.0.0", + "devDependencies": { + "cypress": "^12.5.0", + "@cypress/webpack-preprocessor": "^5.17.0", + "cypress-mochawesome-reporter": "^3.4.0", + "cypress-real-events": "^1.8.0", + "@testing-library/cypress": "^9.0.0" + } +} + diff --git a/test/unit/support/fixtures/package-empty-devdeps.json b/test/unit/support/fixtures/package-empty-devdeps.json new file mode 100644 index 00000000..3986c369 --- /dev/null +++ b/test/unit/support/fixtures/package-empty-devdeps.json @@ -0,0 +1,9 @@ +{ + "name": "test-project", + "version": "1.0.0", + "dependencies": { + "lodash": "^4.17.21" + }, + "devDependencies": {} +} + diff --git a/test/unit/support/fixtures/package-git-deps.json b/test/unit/support/fixtures/package-git-deps.json new file mode 100644 index 00000000..dea607b3 --- /dev/null +++ b/test/unit/support/fixtures/package-git-deps.json @@ -0,0 +1,14 @@ +{ + "name": "test-project", + "version": "1.0.0", + "devDependencies": { + "git-package": "git+https://github.com/user/repo.git#branch", + "github-shorthand": "user/repo#v1.0.0", + "file-package": "file:../local-package", + "tarball-package": "https://registry.npmjs.org/package/-/package-1.0.0.tgz", + "normal-package": "^1.0.0", + "prerelease": "1.0.0-alpha.1", + "range-package": ">=1.0.0 <2.0.0" + } +} + diff --git a/test/unit/support/fixtures/package-invalid-devdeps.json b/test/unit/support/fixtures/package-invalid-devdeps.json new file mode 100644 index 00000000..e771a7b9 --- /dev/null +++ b/test/unit/support/fixtures/package-invalid-devdeps.json @@ -0,0 +1,8 @@ +{ + "name": "test-project", + "version": "1.0.0", + "devDependencies": [ + "this-should-be-object-not-array" + ] +} + diff --git a/test/unit/support/fixtures/package-malformed.json b/test/unit/support/fixtures/package-malformed.json new file mode 100644 index 00000000..4fb4ae38 --- /dev/null +++ b/test/unit/support/fixtures/package-malformed.json @@ -0,0 +1,12 @@ +{ + "name": "test-project", + "version": "1.0.0", + "dependencies": { + "lodash": "^4.17.21" + }, + "devDependencies": { + "jest": "^29.0.0", + // this comment makes it invalid JSON + "prettier": "^2.8.0" + } + diff --git a/test/unit/support/fixtures/package-no-devdeps.json b/test/unit/support/fixtures/package-no-devdeps.json new file mode 100644 index 00000000..970bad3f --- /dev/null +++ b/test/unit/support/fixtures/package-no-devdeps.json @@ -0,0 +1,9 @@ +{ + "name": "test-project", + "version": "1.0.0", + "dependencies": { + "lodash": "^4.17.21", + "axios": "^1.3.0" + } +} + diff --git a/test/unit/support/fixtures/package-scoped-deps.json b/test/unit/support/fixtures/package-scoped-deps.json new file mode 100644 index 00000000..1debb558 --- /dev/null +++ b/test/unit/support/fixtures/package-scoped-deps.json @@ -0,0 +1,14 @@ +{ + "name": "test-project", + "version": "1.0.0", + "devDependencies": { + "@types/node": "^18.0.0", + "@types/jest": "^29.0.0", + "@testing-library/react": "^13.0.0", + "@testing-library/jest-dom": "^5.16.0", + "@eslint/config": "^1.0.0", + "regular-package": "^1.0.0", + "@babel/core": "^7.20.0" + } +} + diff --git a/test/unit/support/fixtures/package-unicode.json b/test/unit/support/fixtures/package-unicode.json new file mode 100644 index 00000000..9ba25487 --- /dev/null +++ b/test/unit/support/fixtures/package-unicode.json @@ -0,0 +1,15 @@ +{ + "name": "test-project", + "version": "1.0.0", + "devDependencies": { + "normal-package": "^1.0.0", + "package-with-ümlauts": "^1.0.0", + "pàckagé-wïth-áccénts": "^2.0.0", + "包名测试": "^1.0.0", + "пакет-кириллица": "^1.0.0", + "package.with.dots": "^1.0.0", + "package_with_underscores": "^1.0.0", + "package-with-dashes": "^1.0.0" + } +} + diff --git a/test/unit/support/fixtures/package-valid.json b/test/unit/support/fixtures/package-valid.json new file mode 100644 index 00000000..e2e9b822 --- /dev/null +++ b/test/unit/support/fixtures/package-valid.json @@ -0,0 +1,15 @@ +{ + "name": "test-project", + "version": "1.0.0", + "dependencies": { + "lodash": "^4.17.21", + "axios": "^1.3.0" + }, + "devDependencies": { + "jest": "^29.0.0", + "prettier": "^2.8.0", + "eslint": "^8.30.0", + "cypress": "^12.0.0" + } +} +