From 74addc971973385685a65c4853d07371affb6f29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Copin?= Date: Tue, 28 Nov 2023 14:39:24 +0100 Subject: [PATCH 1/6] Fix PandaCSS image path --- src/{drivers/webextension => }/images/icons/PandaCSS.svg | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/{drivers/webextension => }/images/icons/PandaCSS.svg (100%) diff --git a/src/drivers/webextension/images/icons/PandaCSS.svg b/src/images/icons/PandaCSS.svg similarity index 100% rename from src/drivers/webextension/images/icons/PandaCSS.svg rename to src/images/icons/PandaCSS.svg From 290ee2924a77634fd9ca2cca97536d71678f5bb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Copin?= Date: Tue, 28 Nov 2023 14:41:11 +0100 Subject: [PATCH 2/6] Re-add CLI-mode & minor fixes for build --- .gitignore | 3 + README.md | 8 +- bin/build.js | 11 +- bin/link.js | 21 + package.json | 5 +- src/drivers/npm/Dockerfile | 31 + src/drivers/npm/README.md | 155 ++++ src/drivers/npm/cli.js | 194 +++++ src/drivers/npm/driver.js | 1481 ++++++++++++++++++++++++++++++++++ src/drivers/npm/package.json | 46 ++ src/drivers/npm/yarn.lock | 525 ++++++++++++ src/technologies/_.json | 2 +- src/technologies/a.json | 6 +- src/technologies/f.json | 2 +- src/technologies/g.json | 2 +- src/technologies/i.json | 24 +- src/technologies/k.json | 6 +- src/technologies/l.json | 40 +- src/technologies/o.json | 2 +- src/technologies/p.json | 30 +- src/technologies/s.json | 8 +- 21 files changed, 2532 insertions(+), 70 deletions(-) create mode 100644 bin/link.js create mode 100644 src/drivers/npm/Dockerfile create mode 100644 src/drivers/npm/README.md create mode 100644 src/drivers/npm/cli.js create mode 100644 src/drivers/npm/driver.js create mode 100644 src/drivers/npm/package.json create mode 100644 src/drivers/npm/yarn.lock diff --git a/.gitignore b/.gitignore index 97f83b858..fc4956cd9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,7 @@ /build/* +/src/drivers/**/categories.json +/src/drivers/**/technologies/* +/src/drivers/**/wappalyzer.js /src/images/icons/converted/* /src/manifest.json /src/manifest.bak.json diff --git a/README.md b/README.md index e307ddf9d..65b453894 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,12 @@ yarn install ## Usage +### Command line + +```sh +node src/drivers/npm/cli.js https://example.com +``` + ### Chrome extension - Go to `about:extensions` @@ -507,4 +513,4 @@ Application version information can be obtained from a pattern using a capture g - + \ No newline at end of file diff --git a/bin/build.js b/bin/build.js index 04f62203f..d74c1bd73 100755 --- a/bin/build.js +++ b/bin/build.js @@ -5,18 +5,17 @@ const currentVersion = JSON.parse( fs.readFileSync('./src/manifest-v3.json') ).version -const version = process.argv[2] +version = process.argv[2] if (!version) { // eslint-disable-next-line no-console - console.error( - `No version number specified. Current version is ${currentVersion}.` + console.warn( + `No version number specified. Current version is ${currentVersion}: will it use it.` ) - - process.exit(1) + version = currentVersion } -;['./src/manifest-v2.json', './src/manifest-v3.json'].forEach((file) => { +;['./src/drivers/npm/package.json', './src/manifest-v2.json', './src/manifest-v3.json'].forEach((file) => { const json = JSON.parse(fs.readFileSync(file)) json.version = version diff --git a/bin/link.js b/bin/link.js new file mode 100644 index 000000000..fd0b336d5 --- /dev/null +++ b/bin/link.js @@ -0,0 +1,21 @@ +const fs = require('fs') + +const link = (src, dest) => { + if (fs.existsSync(dest)) { + fs.unlinkSync(dest) + } + + fs.linkSync(src, dest) +} + +link('./src/js/wappalyzer.js', './src/drivers/npm/wappalyzer.js') +link('./src/categories.json', './src/drivers/npm/categories.json') + +for (const index of Array(27).keys()) { + const character = index ? String.fromCharCode(index + 96) : '_' + + link( + `./src/technologies/${character}.json`, + `./src/drivers/npm/technologies/${character}.json` + ) +} diff --git a/package.json b/package.json index 29f3b0023..c90b5af94 100644 --- a/package.json +++ b/package.json @@ -17,13 +17,14 @@ "terminal-overwrite": "^2.0.1" }, "scripts": { + "link": "node ./bin/link.js; node ./bin/manifest.js v3", "lint": "eslint src/**/*.{js,json}", "lint:fix": "eslint --fix src/**/*.{js,json}", "validate": "yarn run lint && jsonlint -qV ./schema.json ./src/technologies/ && node ./bin/validate.js", "convert": "node --no-warnings ./bin/convert.js", "prettify": "jsonlint -si --trim-trailing-commas --enforce-double-quotes ./src/categories.json ./src/technologies/*.json", - "build": "yarn run validate && yarn run prettify && yarn run convert && node ./bin/build.js", - "build:safari": "xcrun safari-web-extension-converter --swift --project-location build --force src", + "build": "yarn run link && yarn run validate && yarn run prettify && yarn run convert && node ./bin/build.js", + "build:safari": "xcrun safari-web-extension-converter --swift --project-location build --force src/drivers/webextension", "manifest": "node ./bin/manifest.js" } } diff --git a/src/drivers/npm/Dockerfile b/src/drivers/npm/Dockerfile new file mode 100644 index 000000000..ebebe2237 --- /dev/null +++ b/src/drivers/npm/Dockerfile @@ -0,0 +1,31 @@ +FROM node:14-alpine + +MAINTAINER Wappalyzer + +ENV WAPPALYZER_ROOT /opt/wappalyzer +ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD true +ENV CHROMIUM_BIN /usr/bin/chromium-browser + +RUN apk update && apk add -u --no-cache \ + nodejs \ + udev \ + chromium \ + ttf-freefont \ + yarn + +RUN mkdir -p "$WAPPALYZER_ROOT/browsers" + +WORKDIR "$WAPPALYZER_ROOT" + +COPY technologies ./technologies +COPY \ + cli.js \ + categories.json \ + driver.js \ + package.json \ + wappalyzer.js \ + yarn.lock ./ + +RUN yarn install + +ENTRYPOINT ["node", "cli.js"] diff --git a/src/drivers/npm/README.md b/src/drivers/npm/README.md new file mode 100644 index 000000000..f856e4b9d --- /dev/null +++ b/src/drivers/npm/README.md @@ -0,0 +1,155 @@ +# Wappalyzer + +[Wappalyzer](https://www.wappalyzer.com/) indentifies technologies on websites. + +*Note:* The [wappalyzer-core](https://www.npmjs.com/package/wappalyzer-core) package provides a low-level API without dependencies. + +## Command line + +### Installation + +```shell +$ npm i -g wappalyzer +``` + +### Usage + +``` +wappalyzer [options] +``` + +#### Options + +``` +-b, --batch-size=... Process links in batches +-d, --debug Output debug messages +-t, --delay=ms Wait for ms milliseconds between requests +-h, --help This text +-H, --header Extra header to send with requests +--html-max-cols=... Limit the number of HTML characters per line processed +--html-max-rows=... Limit the number of HTML lines processed +-D, --max-depth=... Don't analyse pages more than num levels deep +-m, --max-urls=... Exit when num URLs have been analysed +-w, --max-wait=... Wait no more than ms milliseconds for page resources to load +-p, --probe=[basic|full] Perform a deeper scan by performing additional requests and inspecting DNS records +-P, --pretty Pretty-print JSON output +--proxy=... Proxy URL, e.g. 'http://user:pass@proxy:8080' +-r, --recursive Follow links on pages (crawler) +-a, --user-agent=... Set the user agent string +-n, --no-scripts Disabled JavaScript on web pages +-N, --no-redirect Disable cross-domain redirects +-e, --extended Output additional information +--local-storage=... JSON object to use as local storage +--session-storage=... JSON object to use as session storage +--defer=ms Defer scan for ms milliseconds after page load + +``` + + +## Dependency + +### Installation + +```shell +$ npm i wappalyzer +``` + +### Usage + +```javascript +const Wappalyzer = require('wappalyzer') + +const url = 'https://www.wappalyzer.com' + +const options = { + debug: false, + delay: 500, + headers: {}, + maxDepth: 3, + maxUrls: 10, + maxWait: 5000, + recursive: true, + probe: true, + proxy: false, + userAgent: 'Wappalyzer', + htmlMaxCols: 2000, + htmlMaxRows: 2000, + noScripts: false, + noRedirect: false, +}; + +const wappalyzer = new Wappalyzer(options) + +;(async function() { + try { + await wappalyzer.init() + + // Optionally set additional request headers + const headers = {} + + // Optionally set local and/or session storage + const storage = { + local: {} + session: {} + } + + const site = await wappalyzer.open(url, headers, storage) + + // Optionally capture and output errors + site.on('error', console.error) + + const results = await site.analyze() + + console.log(JSON.stringify(results, null, 2)) + } catch (error) { + console.error(error) + } + + await wappalyzer.destroy() +})() +``` + +Multiple URLs can be processed in parallel: + +```javascript +const Wappalyzer = require('wappalyzer'); + +const urls = ['https://www.wappalyzer.com', 'https://www.example.com'] + +const wappalyzer = new Wappalyzer() + +;(async function() { + try { + await wappalyzer.init() + + const results = await Promise.all( + urls.map(async (url) => { + const site = await wappalyzer.open(url) + + const results = await site.analyze() + + return { url, results } + }) + ) + + console.log(JSON.stringify(results, null, 2)) + } catch (error) { + console.error(error) + } + + await wappalyzer.destroy() +})() +``` + +### Events + +Listen to events with `site.on(eventName, callback)`. Use the `page` parameter to access the Puppeteer page instance ([reference](https://github.com/puppeteer/puppeteer/blob/main/docs/api.md#class-page)). + +| Event | Parameters | Description | +|-------------|--------------------------------|------------------------------------------| +| `log` | `message`, `source` | Debug messages | +| `error` | `message`, `source` | Error messages | +| `request` | `page`, `request` | Emitted at the start of a request | +| `response` | `page`, `request` | Emitted upon receiving a server response | +| `goto` | `page`, `url`, `html`, `cookies`, `scriptsSrc`, `scripts`, `meta`, `js`, `language` `links` | Emitted after a page has been analysed | +| `analyze` | `urls`, `technologies`, `meta` | Emitted when the site has been analysed | diff --git a/src/drivers/npm/cli.js b/src/drivers/npm/cli.js new file mode 100644 index 000000000..ac2c377b0 --- /dev/null +++ b/src/drivers/npm/cli.js @@ -0,0 +1,194 @@ +#!/usr/bin/env node + +const Wappalyzer = require('./driver') + +const args = process.argv.slice(2) + +const options = {} + +let url +let arg + +const aliases = { + a: 'userAgent', + b: 'batchSize', + d: 'debug', + f: 'fast', + t: 'delay', + h: 'help', + H: 'header', + D: 'maxDepth', + m: 'maxUrls', + p: 'probe', + P: 'pretty', + r: 'recursive', + w: 'maxWait', + n: 'noScripts', + N: 'noRedirect', + e: 'extended', +} + +while (true) { + // eslint-disable-line no-constant-condition + arg = args.shift() + + if (!arg) { + break + } + + const matches = /^-?-([^=]+)(?:=(.+)?)?/.exec(arg) + + if (matches) { + const key = + aliases[matches[1]] || + matches[1].replace(/-\w/g, (_matches) => _matches[1].toUpperCase()) + // eslint-disable-next-line no-nested-ternary + const value = matches[2] + ? matches[2] + : args[0] && !args[0].startsWith('-') + ? args.shift() + : true + + if (options[key]) { + if (!Array.isArray(options[key])) { + options[key] = [options[key]] + } + + options[key].push(value) + } else { + options[key] = value + } + } else { + url = arg + } +} + +if (!url || options.help) { + process.stdout.write(`Usage: + wappalyzer [options] + +Examples: + wappalyzer https://www.example.com + node cli.js https://www.example.com -r -D 3 -m 50 -H "Cookie: username=admin" + docker wappalyzer/cli https://www.example.com --pretty + +Options: + -b, --batch-size=... Process links in batches + -d, --debug Output debug messages + -f, --fast Prioritise speed over accuracy + -t, --delay=ms Wait for ms milliseconds between requests + -h, --help This text + -H, --header Extra header to send with requests + --html-max-cols=... Limit the number of HTML characters per line processed + --html-max-rows=... Limit the number of HTML lines processed + -D, --max-depth=... Don't analyse pages more than num levels deep + -m, --max-urls=... Exit when num URLs have been analysed + -w, --max-wait=... Wait no more than ms milliseconds for page resources to load + -p, --probe=[basic|full] Perform a deeper scan by performing additional requests and inspecting DNS records + -P, --pretty Pretty-print JSON output + --proxy=... Proxy URL, e.g. 'http://user:pass@proxy:8080' + -r, --recursive Follow links on pages (crawler) + -a, --user-agent=... Set the user agent string + -n, --no-scripts Disabled JavaScript on web pages + -N, --no-redirect Disable cross-domain redirects + -e, --extended Output additional information + --local-storage=... JSON object to use as local storage + --session-storage=... JSON object to use as session storage + --defer=ms Defer scan for ms milliseconds after page load +`) + process.exit(options.help ? 0 : 1) +} + +try { + const { hostname } = new URL(url) + + if (!hostname) { + throw new Error('Invalid URL') + } +} catch (error) { + // eslint-disable-next-line no-console + console.log(error.message || error.toString()) + + process.exit(1) +} + +const headers = {} + +if (options.header) { + ;(Array.isArray(options.header) ? options.header : [options.header]).forEach( + (header) => { + const [key, value] = header.split(':') + + headers[key.trim()] = (value || '').trim() + } + ) +} + +const storage = { + local: {}, + session: {}, +} + +for (const type of Object.keys(storage)) { + if (options[`${type}Storage`]) { + try { + storage[type] = JSON.parse(options[`${type}Storage`]) + + if ( + !options[`${type}Storage`] || + !Object.keys(options[`${type}Storage`]).length + ) { + throw new Error('Object has no properties') + } + } catch (error) { + // eslint-disable-next-line no-console + console.log(`${type}Storage error: ${error.message || error}`) + + process.exit(1) + } + } +} + +;(async function () { + const wappalyzer = new Wappalyzer(options) + + try { + await wappalyzer.init() + + const site = await wappalyzer.open(url, headers, storage) + + await new Promise((resolve) => + setTimeout(resolve, parseInt(options.defer || 0, 10)) + ) + + const results = await site.analyze() + + process.stdout.write( + `${JSON.stringify(results, null, options.pretty ? 2 : null)}\n` + ) + + await wappalyzer.destroy() + + process.exit(0) + } catch (error) { + try { + await Promise.race([ + wappalyzer.destroy(), + new Promise((resolve, reject) => + setTimeout( + () => reject(new Error('Attempt to close the browser timed out')), + 3000 + ) + ), + ]) + } catch (error) { + // eslint-disable-next-line no-console + console.error(error.message || String(error)) + } + + // eslint-disable-next-line no-console + console.error(error.message || String(error)) + + process.exit(1) + } +})() diff --git a/src/drivers/npm/driver.js b/src/drivers/npm/driver.js new file mode 100644 index 000000000..abfbcada2 --- /dev/null +++ b/src/drivers/npm/driver.js @@ -0,0 +1,1481 @@ +const fs = require('fs') +const dns = require('dns').promises +const path = require('path') +const http = require('http') +const https = require('https') +const puppeteer = require('puppeteer') +const Wappalyzer = require('./wappalyzer') + +const { setTechnologies, setCategories, analyze, analyzeManyToMany, resolve } = + Wappalyzer + +const { CHROMIUM_BIN, CHROMIUM_DATA_DIR, CHROMIUM_WEBSOCKET, CHROMIUM_ARGS } = + process.env + +const chromiumArgs = CHROMIUM_ARGS + ? CHROMIUM_ARGS.split(' ') + : [ + '--headless', + '--single-process', + '--no-sandbox', + '--no-zygote', + '--disable-gpu', + '--ignore-certificate-errors', + '--allow-running-insecure-content', + '--disable-web-security', + `--user-data-dir=${CHROMIUM_DATA_DIR || '/tmp/chromium'}`, + ] + +const extensions = /^([^.]+$|\.(asp|aspx|cgi|htm|html|jsp|php)$)/ + +const categories = JSON.parse( + fs.readFileSync(path.resolve(`${__dirname}/categories.json`)) +) + +let technologies = {} + +for (const index of Array(27).keys()) { + const character = index ? String.fromCharCode(index + 96) : '_' + + technologies = { + ...technologies, + ...JSON.parse( + fs.readFileSync( + path.resolve(`${__dirname}/technologies/${character}.json`) + ) + ), + } +} + +setTechnologies(technologies) +setCategories(categories) + +const xhrDebounce = [] + +function sleep(ms) { + return new Promise((resolve) => setTimeout(resolve, ms)) +} + +function getJs(page, technologies = Wappalyzer.technologies) { + return page.evaluate((technologies) => { + return technologies + .filter(({ js }) => Object.keys(js).length) + .map(({ name, js }) => ({ name, chains: Object.keys(js) })) + .reduce((technologies, { name, chains }) => { + chains.forEach((chain) => { + chain = chain.replace(/\[([^\]]+)\]/g, '.$1') + + const parts = chain.split('.') + + const root = /^[a-z_$][a-z0-9_$]*$/i.test(parts[0]) + ? // eslint-disable-next-line no-new-func + new Function( + `return typeof ${ + parts[0] + } === 'undefined' ? undefined : ${parts.shift()}` + )() + : window + + const value = parts.reduce( + (value, method) => + value && + value instanceof Object && + Object.prototype.hasOwnProperty.call(value, method) + ? value[method] + : '__UNDEFINED__', + root || '__UNDEFINED__' + ) + + if (value !== '__UNDEFINED__') { + technologies.push({ + name, + chain, + value: + typeof value === 'string' || typeof value === 'number' + ? value + : !!value, + }) + } + }) + + return technologies + }, []) + }, technologies) +} + +function analyzeJs(js, technologies = Wappalyzer.technologies) { + return js + .map(({ name, chain, value }) => { + return analyzeManyToMany( + technologies.find(({ name: _name }) => name === _name), + 'js', + { [chain]: [value] } + ) + }) + .flat() +} + +function getDom(page, technologies = Wappalyzer.technologies) { + return page.evaluate((technologies) => { + return technologies + .filter(({ dom }) => dom && dom.constructor === Object) + .reduce((technologies, { name, dom }) => { + const toScalar = (value) => + typeof value === 'string' || typeof value === 'number' + ? value + : !!value + + Object.keys(dom).forEach((selector) => { + let nodes = [] + + try { + nodes = document.querySelectorAll(selector) + } catch (error) { + // Continue + } + + if (!nodes.length) { + return + } + + dom[selector].forEach(({ exists, text, properties, attributes }) => { + nodes.forEach((node) => { + if ( + technologies.filter(({ name: _name }) => _name === name) + .length >= 50 + ) { + return + } + + if ( + exists && + technologies.findIndex( + ({ name: _name, selector: _selector, exists }) => + name === _name && selector === _selector && exists === '' + ) === -1 + ) { + technologies.push({ + name, + selector, + exists: '', + }) + } + + if (text) { + // eslint-disable-next-line unicorn/prefer-text-content + const value = ( + node.textContent ? node.textContent.trim() : '' + ).slice(0, 1000000) + + if ( + value && + technologies.findIndex( + ({ name: _name, selector: _selector, text }) => + name === _name && selector === _selector && text === value + ) === -1 + ) { + technologies.push({ + name, + selector, + text: value, + }) + } + } + + if (properties) { + Object.keys(properties).forEach((property) => { + if ( + Object.prototype.hasOwnProperty.call(node, property) && + technologies.findIndex( + ({ + name: _name, + selector: _selector, + property: _property, + value, + }) => + name === _name && + selector === _selector && + property === _property && + value === toScalar(value) + ) === -1 + ) { + const value = node[property] + + if (typeof value !== 'undefined') { + technologies.push({ + name, + selector, + property, + value: toScalar(value), + }) + } + } + }) + } + + if (attributes) { + Object.keys(attributes).forEach((attribute) => { + if ( + node.hasAttribute(attribute) && + technologies.findIndex( + ({ + name: _name, + selector: _selector, + attribute: _atrribute, + value, + }) => + name === _name && + selector === _selector && + attribute === _atrribute && + value === toScalar(value) + ) === -1 + ) { + const value = node.getAttribute(attribute) + + technologies.push({ + name, + selector, + attribute, + value: toScalar(value), + }) + } + }) + } + }) + }) + }) + + return technologies + }, []) + }, technologies) +} + +function analyzeDom(dom, technologies = Wappalyzer.technologies) { + return dom + .map(({ name, selector, exists, text, property, attribute, value }) => { + const technology = technologies.find(({ name: _name }) => name === _name) + + if (typeof exists !== 'undefined') { + return analyzeManyToMany(technology, 'dom.exists', { + [selector]: [''], + }) + } + + if (typeof text !== 'undefined') { + return analyzeManyToMany(technology, 'dom.text', { + [selector]: [text], + }) + } + + if (typeof property !== 'undefined') { + return analyzeManyToMany(technology, `dom.properties.${property}`, { + [selector]: [value], + }) + } + + if (typeof attribute !== 'undefined') { + return analyzeManyToMany(technology, `dom.attributes.${attribute}`, { + [selector]: [value], + }) + } + }) + .flat() +} + +function get(url, options = {}) { + const timeout = + options.timeout || + (this.options.fast + ? this.Math.min(this.options.maxWait, 3000) + : this.options.maxWait) + + if (['http:', 'https:'].includes(url.protocol)) { + const { get } = url.protocol === 'http:' ? http : https + + return new Promise((resolve, reject) => + get( + url, + { + rejectUnauthorized: false, + headers: { + 'User-Agent': options.userAgent, + }, + }, + (response) => { + if (response.statusCode >= 300) { + return reject( + new Error(`${response.statusCode} ${response.statusMessage}`) + ) + } + + response.setEncoding('utf8') + + let body = '' + + response.on('data', (data) => (body += data)) + response.on('error', (error) => reject(new Error(error.message))) + response.on('end', () => resolve(body)) + } + ) + .setTimeout(timeout, () => + reject(new Error(`Timeout (${url}, ${timeout}ms)`)) + ) + .on('error', (error) => reject(new Error(error.message))) + ) + } else { + throw new Error(`Invalid protocol: ${url.protocol}`) + } +} + +class Driver { + constructor(options = {}) { + this.options = { + batchSize: 5, + debug: false, + delay: 500, + htmlMaxCols: 2000, + htmlMaxRows: 3000, + maxDepth: 3, + maxUrls: 10, + maxWait: 30000, + recursive: false, + probe: false, + proxy: false, + noScripts: false, + userAgent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36', + extended: false, + ...options, + } + + this.options.debug = Boolean(+this.options.debug) + this.options.fast = Boolean(+this.options.fast) + this.options.recursive = Boolean(+this.options.recursive) + this.options.probe = + String(this.options.probe || '').toLowerCase() === 'basic' + ? 'basic' + : String(this.options.probe || '').toLowerCase() === 'full' + ? 'full' + : Boolean(+this.options.probe) && 'full' + this.options.delay = parseInt(this.options.delay, 10) + this.options.maxDepth = parseInt(this.options.maxDepth, 10) + this.options.maxUrls = parseInt(this.options.maxUrls, 10) + this.options.maxWait = parseInt(this.options.maxWait, 10) + this.options.htmlMaxCols = parseInt(this.options.htmlMaxCols, 10) + this.options.htmlMaxRows = parseInt(this.options.htmlMaxRows, 10) + this.options.noScripts = Boolean(+this.options.noScripts) + this.options.extended = Boolean(+this.options.extended) + + if (this.options.proxy) { + chromiumArgs.push(`--proxy-server=${this.options.proxy}`) + } + + this.destroyed = false + } + + async init() { + for (let attempt = 1; attempt <= 2; attempt++) { + this.log(`Launching browser (attempt ${attempt})...`) + + try { + if (CHROMIUM_WEBSOCKET) { + this.browser = await puppeteer.connect({ + ignoreHTTPSErrors: true, + acceptInsecureCerts: true, + browserWSEndpoint: CHROMIUM_WEBSOCKET, + }) + } else { + this.browser = await puppeteer.launch({ + ignoreHTTPSErrors: true, + acceptInsecureCerts: true, + args: chromiumArgs, + executablePath: CHROMIUM_BIN, + timeout: this.options.fast + ? Math.min(this.options.maxWait, 10000) + : this.options.maxWait, + }) + } + + break + } catch (error) { + this.log(error) + + if (attempt >= 2) { + throw new Error(error.message || error.toString()) + } + } + } + + this.browser.on('disconnected', () => { + this.browser = undefined + + this.log('Browser disconnected') + }) + } + + async destroy() { + if (this.browser) { + try { + await sleep(1) + + await this.browser.close() + + this.log('Browser closed') + } catch (error) { + throw new Error(error.toString()) + } + } + } + + async open(url, headers = {}, storage = {}) { + const site = new Site(url.split('#')[0], headers, this) + + if (storage.local || storage.session) { + this.log('Setting storage...') + + const page = await site.newPage(site.originalUrl) + + await page.setRequestInterception(true) + + page.on('request', (request) => + request.respond({ + status: 200, + contentType: 'text/plain', + body: 'ok', + }) + ) + + await page.goto(url) + + await page.evaluate((storage) => { + ;['local', 'session'].forEach((type) => { + Object.keys(storage[type] || {}).forEach((key) => { + window[`${type}Storage`].setItem(key, storage[type][key]) + }) + }) + }, storage) + + try { + await page.close() + } catch { + // Continue + } + } + + return site + } + + log(message, source = 'driver') { + if (this.options.debug) { + // eslint-disable-next-line no-console + console.log(`log | ${source} |`, message) + } + } +} + +class Site { + constructor(url, headers = {}, driver) { + ;({ + options: this.options, + browser: this.browser, + init: this.initDriver, + } = driver) + + this.options.headers = { + ...this.options.headers, + ...headers, + } + + this.driver = driver + + try { + this.originalUrl = new URL(url) + } catch (error) { + throw new Error(error.toString()) + } + + this.analyzedUrls = {} + this.analyzedXhr = {} + this.analyzedRequires = {} + this.detections = [] + + this.listeners = {} + + this.pages = [] + + this.cache = {} + + this.probed = false + } + + log(message, source = 'driver', type = 'log') { + if (this.options.debug) { + // eslint-disable-next-line no-console + console[type](`${type} | ${source} |`, message) + } + + this.emit(type, { message, source }) + } + + error(error, source = 'driver') { + this.log(error, source, 'error') + } + + on(event, callback) { + if (!this.listeners[event]) { + this.listeners[event] = [] + } + + this.listeners[event].push(callback) + } + + emit(event, params) { + if (this.listeners[event]) { + return Promise.allSettled( + this.listeners[event].map((listener) => listener(params)) + ) + } + } + + promiseTimeout( + promise, + fallback, + errorMessage = 'Operation took too long to complete', + maxWait = this.options.fast + ? Math.min(this.options.maxWait, 2000) + : this.options.maxWait + ) { + let timeout = null + + if (!(promise instanceof Promise)) { + return Promise.resolve(promise) + } + + return Promise.race([ + new Promise((resolve, reject) => { + timeout = setTimeout(() => { + clearTimeout(timeout) + + const error = new Error(errorMessage) + + error.code = 'PROMISE_TIMEOUT_ERROR' + + if (fallback !== undefined) { + this.error(error) + + resolve(fallback) + } else { + reject(error) + } + }, maxWait) + }), + promise.then((value) => { + clearTimeout(timeout) + + return value + }), + ]) + } + + async goto(url) { + // Return when the URL is a duplicate or maxUrls has been reached + if (this.analyzedUrls[url.href]) { + return [] + } + + this.log(`Navigate to ${url}`) + + this.analyzedUrls[url.href] = { + status: 0, + } + + const page = await this.newPage(url) + + await page.setRequestInterception(true) + + let responseReceived = false + + page.on('request', async (request) => { + try { + if (request.resourceType() === 'xhr') { + let hostname + + try { + ;({ hostname } = new URL(request.url())) + } catch (error) { + request.abort('blockedbyclient') + + return + } + + if (!xhrDebounce.includes(hostname)) { + xhrDebounce.push(hostname) + + setTimeout(async () => { + xhrDebounce.splice(xhrDebounce.indexOf(hostname), 1) + + this.analyzedXhr[url.hostname] = + this.analyzedXhr[url.hostname] || [] + + if (!this.analyzedXhr[url.hostname].includes(hostname)) { + this.analyzedXhr[url.hostname].push(hostname) + + await this.onDetect(url, analyze({ xhr: hostname })) + } + }, 1000) + } + } + + if ( + (responseReceived && request.isNavigationRequest()) || + request.frame() !== page.mainFrame() || + !['document', ...(this.options.noScripts ? [] : ['script'])].includes( + request.resourceType() + ) + ) { + request.abort('blockedbyclient') + } else { + await this.emit('request', { page, request }) + + if (Object.keys(this.options.headers).length) { + const headers = { + ...request.headers(), + ...this.options.headers, + } + + request.continue({ headers }) + } else { + request.continue() + } + } + } catch (error) { + error.message += ` (${url})` + + this.error(error) + } + }) + + page.on('response', async (response) => { + if (!page || page.__closed || page.isClosed()) { + return + } + + try { + if ( + response.status() < 300 && + response.frame().url() === url.href && + response.request().resourceType() === 'script' + ) { + const scripts = await response.text() + + await this.onDetect(response.url(), analyze({ scripts })) + } + } catch (error) { + if (error.constructor.name !== 'ProtocolError') { + error.message += ` (${url})` + + this.error(error) + } + } + + try { + if (response.url() === url.href) { + this.analyzedUrls[url.href] = { + status: response.status(), + } + + const rawHeaders = response.headers() + const headers = {} + + Object.keys(rawHeaders).forEach((key) => { + headers[key] = [ + ...(headers[key] || []), + ...(Array.isArray(rawHeaders[key]) + ? rawHeaders[key] + : [rawHeaders[key]]), + ] + }) + + // Prevent cross-domain redirects + if (response.status() >= 300 && response.status() < 400) { + if (headers.location) { + const _url = new URL(headers.location.slice(-1), url) + + const redirects = Object.keys(this.analyzedUrls).length - 1 + + if ( + _url.hostname.replace(/^www\./, '') === + this.originalUrl.hostname.replace(/^www\./, '') || + (redirects < 3 && !this.options.noRedirect) + ) { + url = _url + + return + } + } + } + + responseReceived = true + + const certIssuer = response.securityDetails() + ? response.securityDetails().issuer() + : '' + + await this.onDetect(url, analyze({ headers, certIssuer })) + + await this.emit('response', { page, response, headers, certIssuer }) + } + } catch (error) { + error.message += ` (${url})` + + this.error(error) + } + }) + + try { + await page.goto(url.href) + + if (page.url() === 'about:blank') { + const error = new Error(`The page failed to load (${url})`) + + error.code = 'WAPPALYZER_PAGE_EMPTY' + + throw error + } + + if (!this.options.noScripts) { + await sleep(this.options.fast ? 1000 : 3000) + } + + // page.on('console', (message) => this.log(message.text())) + + // Cookies + let cookies = [] + + try { + cookies = (await page.cookies()).reduce( + (cookies, { name, value }) => ({ + ...cookies, + [name.toLowerCase()]: [value], + }), + {} + ) + + // Change Google Analytics 4 cookie from _ga_XXXXXXXXXX to _ga_* + Object.keys(cookies).forEach((name) => { + if (/_ga_[A-Z0-9]+/.test(name)) { + cookies['_ga_*'] = cookies[name] + + delete cookies[name] + } + }) + } catch (error) { + error.message += ` (${url})` + + this.error(error) + } + + // HTML + let html = await this.promiseTimeout(page.content(), '', 'Timeout (html)') + + if (this.options.htmlMaxCols && this.options.htmlMaxRows) { + const batches = [] + const rows = html.length / this.options.htmlMaxCols + + for (let i = 0; i < rows; i += 1) { + if ( + i < this.options.htmlMaxRows / 2 || + i > rows - this.options.htmlMaxRows / 2 + ) { + batches.push( + html.slice( + i * this.options.htmlMaxCols, + (i + 1) * this.options.htmlMaxCols + ) + ) + } + } + + html = batches.join('\n') + } + + let links = [] + let text = '' + let css = '' + let scriptSrc = [] + let scripts = [] + let meta = [] + let js = [] + let dom = [] + + if (html) { + await Promise.all([ + (async () => { + // Links + links = !this.options.recursive + ? [] + : await this.promiseTimeout( + ( + await this.promiseTimeout( + page.evaluateHandle(() => + Array.from(document.getElementsByTagName('a')).map( + ({ + hash, + hostname, + href, + pathname, + protocol, + rel, + }) => ({ + hash, + hostname, + href, + pathname, + protocol, + rel, + }) + ) + ), + { jsonValue: () => [] }, + 'Timeout (links)' + ) + ).jsonValue(), + [], + 'Timeout (links)' + ) + })(), + (async () => { + // Text + text = await this.promiseTimeout( + ( + await this.promiseTimeout( + page.evaluateHandle( + () => + // eslint-disable-next-line unicorn/prefer-text-content + document.body && document.body.innerText + ), + { jsonValue: () => '' }, + 'Timeout (text)' + ) + ).jsonValue(), + '', + 'Timeout (text)' + ) + })(), + (async () => { + // CSS + css = await this.promiseTimeout( + ( + await this.promiseTimeout( + page.evaluateHandle((maxRows) => { + const css = [] + + try { + if (!document.styleSheets.length) { + return '' + } + + for (const sheet of Array.from(document.styleSheets)) { + for (const rules of Array.from(sheet.cssRules)) { + css.push(rules.cssText) + + if (css.length >= maxRows) { + break + } + } + } + } catch (error) { + return '' + } + + return css.join('\n') + }, this.options.htmlMaxRows), + { jsonValue: () => '' }, + 'Timeout (css)' + ) + ).jsonValue(), + '', + 'Timeout (css)' + ) + })(), + (async () => { + // Script tags + ;[scriptSrc, scripts] = await this.promiseTimeout( + ( + await this.promiseTimeout( + page.evaluateHandle(() => { + const nodes = Array.from( + document.getElementsByTagName('script') + ) + + return [ + nodes + .filter( + ({ src }) => + src && !src.startsWith('data:text/javascript;') + ) + .map(({ src }) => src), + nodes + .map((node) => node.textContent) + .filter((script) => script), + ] + }), + { jsonValue: () => [] }, + 'Timeout (scripts)' + ) + ).jsonValue(), + [], + 'Timeout (scripts)' + ) + })(), + (async () => { + // Meta tags + meta = await this.promiseTimeout( + ( + await this.promiseTimeout( + page.evaluateHandle(() => + Array.from(document.querySelectorAll('meta')).reduce( + (metas, meta) => { + const key = + meta.getAttribute('name') || + meta.getAttribute('property') + + if (key) { + metas[key.toLowerCase()] = + metas[key.toLowerCase()] || [] + + metas[key.toLowerCase()].push( + meta.getAttribute('content') + ) + } + + return metas + }, + {} + ) + ), + { jsonValue: () => [] }, + 'Timeout (meta)' + ) + ).jsonValue(), + [], + 'Timeout (meta)' + ) + })(), + (async () => { + // JavaScript + js = this.options.noScripts + ? [] + : await this.promiseTimeout(getJs(page), [], 'Timeout (js)') + })(), + (async () => { + // DOM + dom = await this.promiseTimeout(getDom(page), [], 'Timeout (dom)') + })(), + ]) + } + + this.cache[url.href] = { + page, + html, + text, + cookies, + scripts, + scriptSrc, + meta, + } + + await this.onDetect( + url, + [ + analyzeDom(dom), + analyzeJs(js), + analyze({ + url, + cookies, + html, + text, + css, + scripts, + scriptSrc, + meta, + }), + ].flat() + ) + + const reducedLinks = Array.prototype.reduce.call( + links, + (results, link) => { + if ( + results && + Object.prototype.hasOwnProperty.call( + Object.getPrototypeOf(results), + 'push' + ) && + link.protocol && + link.protocol.match(/https?:/) && + link.hostname === url.hostname && + extensions.test(link.pathname.slice(-5)) + ) { + results.push(new URL(link.href.split('#')[0])) + } + + return results + }, + [] + ) + + await this.emit('goto', { + page, + url, + links: reducedLinks, + ...this.cache[url.href], + }) + + page.__closed = true + + try { + await page.close() + + this.log(`Page closed (${url})`) + } catch (error) { + // Continue + } + + return reducedLinks + } catch (error) { + page.__closed = true + + try { + await page.close() + + this.log(`Page closed (${url})`) + } catch (error) { + // Continue + } + + if (error.message.includes('net::ERR_NAME_NOT_RESOLVED')) { + const newError = new Error( + `Hostname could not be resolved (${url.hostname})` + ) + + newError.code = 'WAPPALYZER_DNS_ERROR' + + throw newError + } + + if ( + error.constructor.name === 'TimeoutError' || + error.code === 'PROMISE_TIMEOUT_ERROR' + ) { + error.code = 'WAPPALYZER_TIMEOUT_ERROR' + } + + error.message += ` (${url})` + + throw error + } + } + + async newPage(url) { + if (!this.browser) { + await this.initDriver() + + if (!this.browser) { + throw new Error('Browser closed') + } + } + + let page + + try { + page = await this.browser.newPage() + + if (!page || page.isClosed()) { + throw new Error('Page did not open') + } + } catch (error) { + error.message += ` (${url})` + + this.error(error) + + await this.initDriver() + + page = await this.browser.newPage() + } + + this.pages.push(page) + + page.setJavaScriptEnabled(!this.options.noScripts) + + page.setDefaultTimeout(this.options.maxWait) + + await page.setUserAgent(this.options.userAgent) + + page.on('dialog', (dialog) => dialog.dismiss()) + + page.on('error', (error) => { + error.message += ` (${url})` + + this.error(error) + }) + + return page + } + + async analyze(url = this.originalUrl, index = 1, depth = 1) { + if (this.options.recursive) { + await sleep(this.options.delay * index) + } + + await Promise.allSettled([ + (async () => { + try { + const links = ((await this.goto(url)) || []).filter( + ({ href }) => !this.analyzedUrls[href] + ) + + if ( + links.length && + this.options.recursive && + Object.keys(this.analyzedUrls).length < this.options.maxUrls && + depth < this.options.maxDepth + ) { + await this.batch( + links.slice( + 0, + this.options.maxUrls - Object.keys(this.analyzedUrls).length + ), + depth + 1 + ) + } + } catch (error) { + this.analyzedUrls[url.href] = { + status: this.analyzedUrls[url.href]?.status || 0, + error: error.message || error.toString(), + } + + error.message += ` (${url})` + + this.error(error) + } + })(), + (async () => { + if (this.options.probe && !this.probed) { + this.probed = true + + await this.probe(url) + } + })(), + ]) + + const patterns = this.options.extended + ? this.detections.reduce( + ( + patterns, + { + technology: { name, implies, excludes }, + pattern: { regex, value, match, confidence, type, version }, + } + ) => { + patterns[name] = patterns[name] || [] + + patterns[name].push({ + type, + regex: regex.source, + value: String(value).length <= 250 ? value : null, + match: match.length <= 250 ? match : null, + confidence, + version, + implies: implies.map(({ name }) => name), + excludes: excludes.map(({ name }) => name), + }) + + return patterns + }, + {} + ) + : undefined + + const results = { + urls: this.analyzedUrls, + technologies: resolve(this.detections).map( + ({ + slug, + name, + description, + confidence, + version, + icon, + website, + cpe, + categories, + rootPath, + }) => ({ + slug, + name, + description, + confidence, + version: version || null, + icon, + website, + cpe, + categories: categories.map(({ id, slug, name }) => ({ + id, + slug, + name, + })), + rootPath, + }) + ), + patterns, + } + + await this.emit('analyze', results) + + return results + } + + async probe(url) { + const paths = [ + { + type: 'robots', + path: '/robots.txt', + }, + ] + + if (this.options.probe === 'full') { + Wappalyzer.technologies + .filter(({ probe }) => Object.keys(probe).length) + .forEach((technology) => { + paths.push( + ...Object.keys(technology.probe).map((path) => ({ + type: 'probe', + path, + technology, + })) + ) + }) + } + + // DNS + const records = {} + const resolveDns = (func, hostname) => { + return this.promiseTimeout( + func(hostname).catch((error) => { + if (error.code !== 'ENODATA') { + error.message += ` (${url})` + + this.error(error) + } + + return [] + }), + [], + 'Timeout (dns)', + this.options.fast + ? Math.min(this.options.maxWait, 15000) + : this.options.maxWait + ) + } + + const domain = url.hostname.replace(/^www\./, '') + + await Promise.allSettled([ + // Static files + ...paths.map(async ({ type, path, technology }, index) => { + try { + await sleep(this.options.delay * index) + + const body = await get(new URL(path, url.href), { + userAgent: this.options.userAgent, + timeout: Math.min(this.options.maxWait, 3000), + }) + + this.log(`Probe ok (${path})`) + + const text = body.slice(0, 100000) + + await this.onDetect( + url, + analyze( + { + [type]: path ? { [path]: [text] } : text, + }, + technology && [technology] + ) + ) + } catch (error) { + this.error(`Probe failed (${path}): ${error.message || error}`) + } + }), + // DNS + // eslint-disable-next-line no-async-promise-executor + new Promise(async (resolve, reject) => { + ;[records.cname, records.ns, records.mx, records.txt, records.soa] = + await Promise.all([ + resolveDns(dns.resolveCname, url.hostname), + resolveDns(dns.resolveNs, domain), + resolveDns(dns.resolveMx, domain), + resolveDns(dns.resolveTxt, domain), + resolveDns(dns.resolveSoa, domain), + ]) + + const dnsRecords = Object.keys(records).reduce((dns, type) => { + dns[type] = dns[type] || [] + + Array.prototype.push.apply( + dns[type], + Array.isArray(records[type]) + ? records[type].map((value) => { + return typeof value === 'object' + ? Object.values(value).join(' ') + : value + }) + : [Object.values(records[type]).join(' ')] + ) + + return dns + }, {}) + + this.log( + `Probe DNS ok: (${Object.values(dnsRecords).flat().length} records)` + ) + + await this.onDetect(url, analyze({ dns: dnsRecords })) + + resolve() + }), + ]) + } + + async batch(links, depth, batch = 0) { + if (links.length === 0) { + return + } + + const batched = links.splice(0, this.options.batchSize) + + await Promise.allSettled( + batched.map((link, index) => this.analyze(link, index, depth)) + ) + + await this.batch(links, depth, batch + 1) + } + + async onDetect(url, detections = []) { + this.detections = this.detections + .concat(detections) + .filter( + ( + { technology: { name }, pattern: { regex }, version }, + index, + detections + ) => + detections.findIndex( + ({ + technology: { name: _name }, + pattern: { regex: _regex }, + version: _version, + }) => + name === _name && + version === _version && + (!regex || regex.toString() === _regex.toString()) + ) === index + ) + + // Track if technology was identified on website's root path + detections.forEach(({ technology: { name } }) => { + const detection = this.detections.find( + ({ technology: { name: _name } }) => name === _name + ) + + detection.rootPath = detection.rootPath || url.pathname === '/' + }) + + if (this.cache[url.href]) { + const resolved = resolve(this.detections) + + const requires = [ + ...Wappalyzer.requires.filter(({ name }) => + resolved.some(({ name: _name }) => _name === name) + ), + ...Wappalyzer.categoryRequires.filter(({ categoryId }) => + resolved.some(({ categories }) => + categories.some(({ id }) => id === categoryId) + ) + ), + ] + + await Promise.allSettled( + requires.map(async ({ name, categoryId, technologies }) => { + const id = categoryId + ? `category:${categoryId}` + : `technology:${name}` + + this.analyzedRequires[url.href] = + this.analyzedRequires[url.href] || [] + + if (!this.analyzedRequires[url.href].includes(id)) { + this.analyzedRequires[url.href].push(id) + + const { page, cookies, html, text, css, scripts, scriptSrc, meta } = + this.cache[url.href] + + const js = await this.promiseTimeout( + getJs(page, technologies), + [], + 'Timeout (js)' + ) + const dom = await this.promiseTimeout( + getDom(page, technologies), + [], + 'Timeout (dom)' + ) + + await this.onDetect( + url, + [ + analyzeDom(dom, technologies), + analyzeJs(js, technologies), + await analyze( + { + url, + cookies, + html, + text, + css, + scripts, + scriptSrc, + meta, + }, + technologies + ), + ].flat() + ) + } + }) + ) + } + } + + async destroy() { + await Promise.allSettled( + this.pages.map(async (page) => { + if (page) { + page.__closed = true + + try { + await page.close() + } catch (error) { + // Continue + } + } + }) + ) + + this.log('Site closed') + } +} + +module.exports = Driver diff --git a/src/drivers/npm/package.json b/src/drivers/npm/package.json new file mode 100644 index 000000000..f9721e5b3 --- /dev/null +++ b/src/drivers/npm/package.json @@ -0,0 +1,46 @@ +{ + "name": "wappalyzer", + "description": "Identify technology on websites", + "keywords": [ + "analyze", + "identify", + "detect", + "detector", + "technology", + "cms", + "framework", + "library", + "software" + ], + "homepage": "https://www.wappalyzer.com/", + "version": "6.10.66", + "author": "Wappalyzer", + "license": "GPL-3.0", + "repository": { + "type": "git", + "url": "https://github.com/wappalyzer/wappalyzer" + }, + "funding": [ + { + "url": "https://github.com/sponsors/aliasio" + } + ], + "main": "driver.js", + "files": [ + "cli.js", + "categories.json", + "driver.js", + "index.js", + "technologies/*", + "wappalyzer.js" + ], + "bin": { + "wappalyzer": "./cli.js" + }, + "dependencies": { + "puppeteer": "~19.7.0" + }, + "engines": { + "node": ">=16" + } +} \ No newline at end of file diff --git a/src/drivers/npm/yarn.lock b/src/drivers/npm/yarn.lock new file mode 100644 index 000000000..11397257c --- /dev/null +++ b/src/drivers/npm/yarn.lock @@ -0,0 +1,525 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/code-frame@^7.0.0": + version "7.21.4" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.21.4.tgz#d0fa9e4413aca81f2b23b9442797bda1826edb39" + integrity sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g== + dependencies: + "@babel/highlight" "^7.18.6" + +"@babel/helper-validator-identifier@^7.18.6": + version "7.19.1" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2" + integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w== + +"@babel/highlight@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf" + integrity sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g== + dependencies: + "@babel/helper-validator-identifier" "^7.18.6" + chalk "^2.0.0" + js-tokens "^4.0.0" + +"@types/node@*": + version "18.15.11" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.15.11.tgz#b3b790f09cb1696cffcec605de025b088fa4225f" + integrity sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q== + +"@types/yauzl@^2.9.1": + version "2.10.0" + resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.10.0.tgz#b3248295276cf8c6f153ebe6a9aba0c988cb2599" + integrity sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw== + dependencies: + "@types/node" "*" + +agent-base@6: + version "6.0.2" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" + integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== + dependencies: + debug "4" + +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + +bl@^4.0.3: + version "4.1.0" + resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" + integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== + dependencies: + buffer "^5.5.0" + inherits "^2.0.4" + readable-stream "^3.4.0" + +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + +buffer-crc32@~0.2.3: + version "0.2.13" + resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" + integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ== + +buffer@^5.2.1, buffer@^5.5.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" + integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.1.13" + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +chalk@^2.0.0: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chownr@^1.1.1: + version "1.1.4" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" + integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== + +chromium-bidi@0.4.5: + version "0.4.5" + resolved "https://registry.yarnpkg.com/chromium-bidi/-/chromium-bidi-0.4.5.tgz#a352e755536dde609bd2c77e4b1f0906bff8784e" + integrity sha512-rkav9YzRfAshSTG3wNXF7P7yNiI29QAo1xBXElPoCoSQR5n20q3cOyVhDv6S7+GlF/CJ/emUxlQiR0xOPurkGg== + dependencies: + mitt "3.0.0" + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== + +cosmiconfig@8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-8.1.0.tgz#947e174c796483ccf0a48476c24e4fefb7e1aea8" + integrity sha512-0tLZ9URlPGU7JsKq0DQOQ3FoRsYX8xDZ7xMiATQfaiGMz7EHowNkbU9u1coAOmnh9p/1ySpm0RB3JNWRXM5GCg== + dependencies: + import-fresh "^3.2.1" + js-yaml "^4.1.0" + parse-json "^5.0.0" + path-type "^4.0.0" + +cross-fetch@3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.5.tgz#e1389f44d9e7ba767907f7af8454787952ab534f" + integrity sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw== + dependencies: + node-fetch "2.6.7" + +debug@4, debug@4.3.4, debug@^4.1.1: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + +devtools-protocol@0.0.1094867: + version "0.0.1094867" + resolved "https://registry.yarnpkg.com/devtools-protocol/-/devtools-protocol-0.0.1094867.tgz#2ab93908e9376bd85d4e0604aa2651258f13e374" + integrity sha512-pmMDBKiRVjh0uKK6CT1WqZmM3hBVSgD+N2MrgyV1uNizAZMw4tx6i/RTc+/uCsKSCmg0xXx7arCP/OFcIwTsiQ== + +end-of-stream@^1.1.0, end-of-stream@^1.4.1: + version "1.4.4" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" + integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== + dependencies: + once "^1.4.0" + +error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== + +extract-zip@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.1.tgz#663dca56fe46df890d5f131ef4a06d22bb8ba13a" + integrity sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg== + dependencies: + debug "^4.1.1" + get-stream "^5.1.0" + yauzl "^2.10.0" + optionalDependencies: + "@types/yauzl" "^2.9.1" + +fd-slicer@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" + integrity sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g== + dependencies: + pend "~1.2.0" + +fs-constants@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" + integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +get-stream@^5.1.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" + integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== + dependencies: + pump "^3.0.0" + +glob@^9.2.0: + version "9.3.4" + resolved "https://registry.yarnpkg.com/glob/-/glob-9.3.4.tgz#e75dee24891a80c25cc7ee1dd327e126b98679af" + integrity sha512-qaSc49hojMOv1EPM4EuyITjDSgSKI0rthoHnvE81tcOi1SCVndHko7auqxdQ14eiQG2NDBJBE86+2xIrbIvrbA== + dependencies: + fs.realpath "^1.0.0" + minimatch "^8.0.2" + minipass "^4.2.4" + path-scurry "^1.6.1" + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== + +https-proxy-agent@5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" + integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== + dependencies: + agent-base "6" + debug "4" + +ieee754@^1.1.13: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + +import-fresh@^3.2.1: + version "3.3.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +inherits@^2.0.3, inherits@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== + +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +json-parse-even-better-errors@^2.3.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + +lines-and-columns@^1.1.6: + version "1.2.4" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + +lru-cache@^7.14.1: + version "7.18.3" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.18.3.tgz#f793896e0fd0e954a59dfdd82f0773808df6aa89" + integrity sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA== + +minimatch@^8.0.2: + version "8.0.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-8.0.2.tgz#ba35f8afeb255a4cbad4b6677b46132f3278c469" + integrity sha512-ikHGF67ODxj7vS5NKU2wvTsFLbExee+KXVCnBWh8Cg2hVJfBMQIrlo50qru/09E0EifjnU8dZhJ/iHhyXJM6Mw== + dependencies: + brace-expansion "^2.0.1" + +minipass@^4.0.2, minipass@^4.2.4: + version "4.2.5" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-4.2.5.tgz#9e0e5256f1e3513f8c34691dd68549e85b2c8ceb" + integrity sha512-+yQl7SX3bIT83Lhb4BVorMAHVuqsskxRdlmO9kTpyukp8vsm2Sn/fUOV9xlnG8/a5JsypJzap21lz/y3FBMJ8Q== + +mitt@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/mitt/-/mitt-3.0.0.tgz#69ef9bd5c80ff6f57473e8d89326d01c414be0bd" + integrity sha512-7dX2/10ITVyqh4aOSVI9gdape+t9l2/8QxHrFmUXu4EEUpdlxl6RudZUPZoc+zuY2hk1j7XxVroIVIan/pD/SQ== + +mkdirp-classic@^0.5.2: + version "0.5.3" + resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" + integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +node-fetch@2.6.7: + version "2.6.7" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" + integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== + dependencies: + whatwg-url "^5.0.0" + +once@^1.3.1, once@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +parse-json@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-even-better-errors "^2.3.0" + lines-and-columns "^1.1.6" + +path-scurry@^1.6.1: + version "1.6.3" + resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.6.3.tgz#4eba7183d64ef88b63c7d330bddc3ba279dc6c40" + integrity sha512-RAmB+n30SlN+HnNx6EbcpoDy9nwdpcGPnEKrJnu6GZoDWBdIjo1UQMVtW2ybtC7LC2oKLcMq8y5g8WnKLiod9g== + dependencies: + lru-cache "^7.14.1" + minipass "^4.0.2" + +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + +pend@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" + integrity sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg== + +progress@2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" + integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== + +proxy-from-env@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + +pump@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" + integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +puppeteer-core@19.7.5: + version "19.7.5" + resolved "https://registry.yarnpkg.com/puppeteer-core/-/puppeteer-core-19.7.5.tgz#cedc8eb7862fe7a8aa2a25ed167c0f1230de72b2" + integrity sha512-EJuNha+SxPfaYFbkoWU80H3Wb1SiQH5fFyb2xdbWda0ziax5mhV63UMlqNfPeTDIWarwtR4OIcq/9VqY8HPOsg== + dependencies: + chromium-bidi "0.4.5" + cross-fetch "3.1.5" + debug "4.3.4" + devtools-protocol "0.0.1094867" + extract-zip "2.0.1" + https-proxy-agent "5.0.1" + proxy-from-env "1.1.0" + rimraf "4.4.0" + tar-fs "2.1.1" + unbzip2-stream "1.4.3" + ws "8.12.1" + +puppeteer@~19.7.0: + version "19.7.5" + resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-19.7.5.tgz#d7db0dfcc80ca2cdf8eb0100bae1ce888a841389" + integrity sha512-UqD8K+yaZa6/hwzP54AATCiHrEYGGxzQcse9cZzrtsVGd8wT0llCdYhsBp8n+zvnb1ofY0YFgI3TYZ/MiX5uXQ== + dependencies: + cosmiconfig "8.1.0" + https-proxy-agent "5.0.1" + progress "2.0.3" + proxy-from-env "1.1.0" + puppeteer-core "19.7.5" + +readable-stream@^3.1.1, readable-stream@^3.4.0: + version "3.6.2" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" + integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +rimraf@4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-4.4.0.tgz#c7a9f45bb2ec058d2e60ef9aca5167974313d605" + integrity sha512-X36S+qpCUR0HjXlkDe4NAOhS//aHH0Z+h8Ckf2auGJk3PTnx5rLmrHkwNdbVQuCSUhOyFrlRvFEllZOYE+yZGQ== + dependencies: + glob "^9.2.0" + +safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +tar-fs@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784" + integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng== + dependencies: + chownr "^1.1.1" + mkdirp-classic "^0.5.2" + pump "^3.0.0" + tar-stream "^2.1.4" + +tar-stream@^2.1.4: + version "2.2.0" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" + 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" + +through@^2.3.8: + version "2.3.8" + resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== + +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== + +unbzip2-stream@1.4.3: + version "1.4.3" + resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz#b0da04c4371311df771cdc215e87f2130991ace7" + integrity sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg== + dependencies: + buffer "^5.2.1" + through "^2.3.8" + +util-deprecate@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== + +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== + +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" + integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +ws@8.12.1: + version "8.12.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.12.1.tgz#c51e583d79140b5e42e39be48c934131942d4a8f" + integrity sha512-1qo+M9Ba+xNhPB+YTWUlK6M17brTut5EXbcBaMRN5pH5dFrXz7lzz1ChFSUq3bOUl8yEvSenhHmYUNJxFzdJew== + +yauzl@^2.10.0: + version "2.10.0" + resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" + integrity sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g== + dependencies: + buffer-crc32 "~0.2.3" + fd-slicer "~1.1.0" diff --git a/src/technologies/_.json b/src/technologies/_.json index 475da8fa6..412177d09 100644 --- a/src/technologies/_.json +++ b/src/technologies/_.json @@ -106,10 +106,10 @@ 6 ], "description": "42stores is a French SaaS ecommerce solution that was established in 2008. It offers a range of features such as monitoring, customer support, and regular updates. The platform is known for its flexibility and modularity, making it possible to integrate with various ERP systems.", - "icon": "42stores.svg", "headers": { "Powered-By": "^42stores$" }, + "icon": "42stores.svg", "pricing": [ "poa", "recurring" diff --git a/src/technologies/a.json b/src/technologies/a.json index 51bc0e842..0691742f4 100644 --- a/src/technologies/a.json +++ b/src/technologies/a.json @@ -1543,8 +1543,8 @@ ], "cpe": "cpe:2.3:a:adobe:experience_manager:*:*:*:*:*:*:*:*", "description": "Adobe Experience Manager Franklin, also known as Project Helix or Composability, is a new way to publish AEM pages using Google Drive or Microsoft Office via Sharepoint. Instead of components, Franklin uses blocks to build pages. Blocks are pieces of a document that will be transformed into web page content.", - "icon": "Adobe Experience Manager Franklin.svg", "excludes": "Adobe Experience Manager", + "icon": "Adobe Experience Manager Franklin.svg", "scriptSrc": "^.+/scripts/lib-franklin\\.js$", "website": "https://www.hlx.live" }, @@ -4213,7 +4213,6 @@ ], "description": "Assertive Yield is a SaaS company that specialises in helping SSPs (Supply-Side Platforms), publishers, and ad networks optimise their advertising revenue through real-time attribution and yield optimisation strategies.", "icon": "Assertive Yield.svg", - "saas": true, "js": { "assertive.predict": "" }, @@ -4221,6 +4220,7 @@ "payg", "poa" ], + "saas": true, "website": "https://www.assertiveyield.com" }, "Astra": { @@ -4671,8 +4671,8 @@ 87 ], "description": "Automatic.css is a CSS framework for WordPress page builders.", - "icon": "Automatic.css.png", "dom": "link[href*='/wp-content/uploads/automatic-css/']", + "icon": "Automatic.css.png", "pricing": [ "low", "recurring" diff --git a/src/technologies/f.json b/src/technologies/f.json index c44d45cc4..04a685879 100644 --- a/src/technologies/f.json +++ b/src/technologies/f.json @@ -881,8 +881,8 @@ 18, 22 ], - "description": "Flask is a Python micro web framework ideal for rapidly constructing web applications, offering minimalism, flexibility, and modularity.", "cpe": "cpe:2.3:a:palletsprojects:flask:*:*:*:*:*:*:*:*", + "description": "Flask is a Python micro web framework ideal for rapidly constructing web applications, offering minimalism, flexibility, and modularity.", "headers": { "Server": "Werkzeug/?([\\d\\.]+)?\\;version:\\1" }, diff --git a/src/technologies/g.json b/src/technologies/g.json index e71548497..3f30fc626 100644 --- a/src/technologies/g.json +++ b/src/technologies/g.json @@ -1570,8 +1570,8 @@ 36 ], "description": "Google AdSense is a program run by Google through which website publishers serve advertisements that are targeted to the site content and audience.", - "icon": "Google AdSense.svg", "dom": "amp-ad[type='adsense']", + "icon": "Google AdSense.svg", "js": { "Goog_AdSense_": "", "Goog_AdSense_OsdAdapter": "", diff --git a/src/technologies/i.json b/src/technologies/i.json index 814380b9f..ee58e2c74 100644 --- a/src/technologies/i.json +++ b/src/technologies/i.json @@ -1208,6 +1208,18 @@ ], "website": "https://isotope.metafizzy.co" }, + "Isso": { + "cats": [ + 15 + ], + "description": "Isso is a lightweight commenting server written in Python and JavaScript, referred to as \"Ich schrei sonst\" in German.", + "implies": "Python", + "js": { + "Isso.fetchComments": "" + }, + "oss": true, + "website": "https://github.com/posativ/isso/" + }, "Issuu": { "cats": [ 19, @@ -1229,18 +1241,6 @@ "scriptSrc": "\\.issuu\\.com/", "website": "https://issuu.com" }, - "Isso": { - "cats": [ - 15 - ], - "description": "Isso is a lightweight commenting server written in Python and JavaScript, referred to as \"Ich schrei sonst\" in German.", - "js": { - "Isso.fetchComments": "" - }, - "implies": "Python", - "oss": true, - "website": "https://github.com/posativ/isso/" - }, "Iterable": { "cats": [ 32 diff --git a/src/technologies/k.json b/src/technologies/k.json index 1d2d7858f..03ffda0d3 100644 --- a/src/technologies/k.json +++ b/src/technologies/k.json @@ -644,12 +644,12 @@ "cats": [ 53 ], - "description": "Kicksite is a gym and martial arts member management software with attendance tracking, automated billing, free texting, lead capture forms and more.", - "icon": "Kicksite.png", "cookies": { "_kicksite_session": "" }, - "dom":"iframe[src*='.kicksite.net/']", + "description": "Kicksite is a gym and martial arts member management software with attendance tracking, automated billing, free texting, lead capture forms and more.", + "dom": "iframe[src*='.kicksite.net/']", + "icon": "Kicksite.png", "pricing": [ "mid", "recurring" diff --git a/src/technologies/l.json b/src/technologies/l.json index ab4209107..83735bb1e 100644 --- a/src/technologies/l.json +++ b/src/technologies/l.json @@ -456,7 +456,6 @@ 87 ], "description": "LearnDash is a WordPress plugin that enables the creation and management of online courses, quizzes, and educational content within a website.", - "icon": "LearnDash.svg", "dom": { "link[href*='/wp-content/plugins/sfwd-lms/']": { "attributes": { @@ -464,12 +463,13 @@ } } }, - "requires": "WordPress", + "icon": "LearnDash.svg", "pricing": [ "low", "onetime", "recurring" ], + "requires": "WordPress", "website": "https://www.learndash.com" }, "LearnWorlds": { @@ -1519,23 +1519,6 @@ "scriptSrc": "lodash.*\\.js", "website": "https://www.lodash.com" }, - "Loglib": { - "cats": [ - 10 - ], - "description": "Loglib is a Open Source and Privacy-First web analytics that aims to provide simple yet can be powerful based on your needs.", - "icon": "Loglib.svg", - "js": { - "lli": "", - "llc": "" - }, - "pricing": [ - "freemium" - ], - "oss": true, - "saas": true, - "website": "https://www.loglib.io" - }, "LogRocket": { "cats": [ 10 @@ -1627,6 +1610,23 @@ ], "website": "https://www.loginradius.com" }, + "Loglib": { + "cats": [ + 10 + ], + "description": "Loglib is a Open Source and Privacy-First web analytics that aims to provide simple yet can be powerful based on your needs.", + "icon": "Loglib.svg", + "js": { + "llc": "", + "lli": "" + }, + "oss": true, + "pricing": [ + "freemium" + ], + "saas": true, + "website": "https://www.loglib.io" + }, "LogoiX": { "cats": [ 99 @@ -2095,4 +2095,4 @@ "oss": true, "website": "https://github.com/paulirish/lite-youtube-embed" } -} +} \ No newline at end of file diff --git a/src/technologies/o.json b/src/technologies/o.json index 0d2ea6533..62406882b 100644 --- a/src/technologies/o.json +++ b/src/technologies/o.json @@ -1993,4 +1993,4 @@ }, "website": "https://owncloud.org" } -} +} \ No newline at end of file diff --git a/src/technologies/p.json b/src/technologies/p.json index ca90c3330..ded79c82d 100644 --- a/src/technologies/p.json +++ b/src/technologies/p.json @@ -3380,20 +3380,6 @@ "oss": true, "website": "https://github.com/mroderick/PubSubJS" }, - "Public CMS": { - "cats": [ - 1 - ], - "cookies": { - "PUBLICCMS_USER": "" - }, - "headers": { - "X-Powered-PublicCMS": "^(.+)$\\;version:\\1" - }, - "icon": "Public CMS.png", - "implies": "Java", - "website": "https://www.publiccms.com" - }, "PubTech": { "cats": [ 67 @@ -3408,6 +3394,20 @@ ], "website": "https://www.pubtech.ai/" }, + "Public CMS": { + "cats": [ + 1 + ], + "cookies": { + "PUBLICCMS_USER": "" + }, + "headers": { + "X-Powered-PublicCMS": "^(.+)$\\;version:\\1" + }, + "icon": "Public CMS.png", + "implies": "Java", + "website": "https://www.publiccms.com" + }, "Pulse Secure": { "cats": [ 46 @@ -3924,4 +3924,4 @@ }, "website": "https://punbb.informer.com" } -} +} \ No newline at end of file diff --git a/src/technologies/s.json b/src/technologies/s.json index 6b303a21c..0cdaef522 100644 --- a/src/technologies/s.json +++ b/src/technologies/s.json @@ -795,8 +795,8 @@ ], "description": "Salla is an ecommerce platform specifically tailored to serve businesses and customers in Saudi Arabia.", "headers": { - "x-powered-by": "^Salla$", - "X-Frame-Options": "\\.salla\\.sa" + "X-Frame-Options": "\\.salla\\.sa", + "x-powered-by": "^Salla$" }, "icon": "Salla.svg", "js": { @@ -7082,7 +7082,7 @@ ], "description": "Swiper is a JavaScript library that creates modern touch sliders with hardware-accelerated transitions.", "dom": [ - "div[data-swiper-slide-index]", + "div[data-swiper-slide-index]", "swiper-container", "swiper-slide", "div.swiper-wrapper", @@ -7430,4 +7430,4 @@ }, "website": "https://styled-components.com" } -} +} \ No newline at end of file From f54b8ba04e7f710f5877697b94c91ced24627573 Mon Sep 17 00:00:00 2001 From: Max Ostapenko <1611259+max-ostapenko@users.noreply.github.com> Date: Sun, 12 Jul 2026 15:25:00 +0200 Subject: [PATCH 3/6] Fix scripts/link.js and format NPM driver to pass linter --- scripts/link.js | 22 +- src/drivers/npm/cli.js | 105 +++-- src/drivers/npm/driver.js | 794 +++++++++++++++++++------------------- src/drivers/npm/yarn.lock | 160 ++++---- 4 files changed, 540 insertions(+), 541 deletions(-) diff --git a/scripts/link.js b/scripts/link.js index fd0b336d5..c7460333b 100644 --- a/scripts/link.js +++ b/scripts/link.js @@ -1,21 +1,27 @@ -const fs = require('fs') +const fs = require('fs'); +const path = require('path'); const link = (src, dest) => { + const dir = path.dirname(dest); + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }); + } + if (fs.existsSync(dest)) { - fs.unlinkSync(dest) + fs.unlinkSync(dest); } - fs.linkSync(src, dest) -} + fs.linkSync(src, dest); +}; -link('./src/js/wappalyzer.js', './src/drivers/npm/wappalyzer.js') -link('./src/categories.json', './src/drivers/npm/categories.json') +link('./src/js/wappalyzer.js', './src/drivers/npm/wappalyzer.js'); +link('./src/categories.json', './src/drivers/npm/categories.json'); for (const index of Array(27).keys()) { - const character = index ? String.fromCharCode(index + 96) : '_' + const character = index ? String.fromCharCode(index + 96) : '_'; link( `./src/technologies/${character}.json`, `./src/drivers/npm/technologies/${character}.json` - ) + ); } diff --git a/src/drivers/npm/cli.js b/src/drivers/npm/cli.js index ac2c377b0..dd6445a63 100644 --- a/src/drivers/npm/cli.js +++ b/src/drivers/npm/cli.js @@ -1,13 +1,13 @@ #!/usr/bin/env node -const Wappalyzer = require('./driver') +const Wappalyzer = require('./driver'); -const args = process.argv.slice(2) +const args = process.argv.slice(2); -const options = {} +const options = {}; -let url -let arg +let url; +let arg; const aliases = { a: 'userAgent', @@ -25,41 +25,40 @@ const aliases = { w: 'maxWait', n: 'noScripts', N: 'noRedirect', - e: 'extended', -} + e: 'extended' +}; while (true) { - // eslint-disable-line no-constant-condition - arg = args.shift() + arg = args.shift(); if (!arg) { - break + break; } - const matches = /^-?-([^=]+)(?:=(.+)?)?/.exec(arg) + const matches = /^-?-([^=]+)(?:=(.+)?)?/.exec(arg); if (matches) { const key = aliases[matches[1]] || - matches[1].replace(/-\w/g, (_matches) => _matches[1].toUpperCase()) - // eslint-disable-next-line no-nested-ternary + matches[1].replace(/-\w/g, (_matches) => _matches[1].toUpperCase()); + const value = matches[2] ? matches[2] : args[0] && !args[0].startsWith('-') - ? args.shift() - : true + ? args.shift() + : true; if (options[key]) { if (!Array.isArray(options[key])) { - options[key] = [options[key]] + options[key] = [options[key]]; } - options[key].push(value) + options[key].push(value); } else { - options[key] = value + options[key] = value; } } else { - url = arg + url = arg; } } @@ -95,81 +94,79 @@ Options: --local-storage=... JSON object to use as local storage --session-storage=... JSON object to use as session storage --defer=ms Defer scan for ms milliseconds after page load -`) - process.exit(options.help ? 0 : 1) +`); + process.exit(options.help ? 0 : 1); } try { - const { hostname } = new URL(url) + const { hostname } = new URL(url); if (!hostname) { - throw new Error('Invalid URL') + throw new Error('Invalid URL'); } } catch (error) { - // eslint-disable-next-line no-console - console.log(error.message || error.toString()) + console.log(error.message || error.toString()); - process.exit(1) + process.exit(1); } -const headers = {} +const headers = {}; if (options.header) { - ;(Array.isArray(options.header) ? options.header : [options.header]).forEach( + (Array.isArray(options.header) ? options.header : [options.header]).forEach( (header) => { - const [key, value] = header.split(':') + const [key, value] = header.split(':'); - headers[key.trim()] = (value || '').trim() + headers[key.trim()] = (value || '').trim(); } - ) + ); } const storage = { local: {}, - session: {}, -} + session: {} +}; for (const type of Object.keys(storage)) { if (options[`${type}Storage`]) { try { - storage[type] = JSON.parse(options[`${type}Storage`]) + storage[type] = JSON.parse(options[`${type}Storage`]); if ( !options[`${type}Storage`] || !Object.keys(options[`${type}Storage`]).length ) { - throw new Error('Object has no properties') + throw new Error('Object has no properties'); } } catch (error) { - // eslint-disable-next-line no-console - console.log(`${type}Storage error: ${error.message || error}`) + console.log(`${type}Storage error: ${error.message || error}`); - process.exit(1) + process.exit(1); } } } -;(async function () { - const wappalyzer = new Wappalyzer(options) +(async function () { + const wappalyzer = new Wappalyzer(options); try { - await wappalyzer.init() + await wappalyzer.init(); - const site = await wappalyzer.open(url, headers, storage) + const site = await wappalyzer.open(url, headers, storage); await new Promise((resolve) => setTimeout(resolve, parseInt(options.defer || 0, 10)) - ) + ); - const results = await site.analyze() + const results = await site.analyze(); process.stdout.write( `${JSON.stringify(results, null, options.pretty ? 2 : null)}\n` - ) + ); - await wappalyzer.destroy() + await wappalyzer.destroy(); - process.exit(0) + process.exit(0); } catch (error) { try { await Promise.race([ @@ -179,16 +176,14 @@ for (const type of Object.keys(storage)) { () => reject(new Error('Attempt to close the browser timed out')), 3000 ) - ), - ]) + ) + ]); } catch (error) { - // eslint-disable-next-line no-console - console.error(error.message || String(error)) + console.error(error.message || String(error)); } - // eslint-disable-next-line no-console - console.error(error.message || String(error)) + console.error(error.message || String(error)); - process.exit(1) + process.exit(1); } -})() +})(); diff --git a/src/drivers/npm/driver.js b/src/drivers/npm/driver.js index abfbcada2..84177d506 100644 --- a/src/drivers/npm/driver.js +++ b/src/drivers/npm/driver.js @@ -1,16 +1,16 @@ -const fs = require('fs') -const dns = require('dns').promises -const path = require('path') -const http = require('http') -const https = require('https') -const puppeteer = require('puppeteer') -const Wappalyzer = require('./wappalyzer') +const fs = require('fs'); +const dns = require('dns').promises; +const path = require('path'); +const http = require('http'); +const https = require('https'); +const puppeteer = require('puppeteer'); +const Wappalyzer = require('./wappalyzer'); const { setTechnologies, setCategories, analyze, analyzeManyToMany, resolve } = - Wappalyzer + Wappalyzer; const { CHROMIUM_BIN, CHROMIUM_DATA_DIR, CHROMIUM_WEBSOCKET, CHROMIUM_ARGS } = - process.env + process.env; const chromiumArgs = CHROMIUM_ARGS ? CHROMIUM_ARGS.split(' ') @@ -23,19 +23,19 @@ const chromiumArgs = CHROMIUM_ARGS '--ignore-certificate-errors', '--allow-running-insecure-content', '--disable-web-security', - `--user-data-dir=${CHROMIUM_DATA_DIR || '/tmp/chromium'}`, - ] + `--user-data-dir=${CHROMIUM_DATA_DIR || '/tmp/chromium'}` + ]; -const extensions = /^([^.]+$|\.(asp|aspx|cgi|htm|html|jsp|php)$)/ +const extensions = /^([^.]+$|\.(asp|aspx|cgi|htm|html|jsp|php)$)/; const categories = JSON.parse( fs.readFileSync(path.resolve(`${__dirname}/categories.json`)) -) +); -let technologies = {} +let technologies = {}; for (const index of Array(27).keys()) { - const character = index ? String.fromCharCode(index + 96) : '_' + const character = index ? String.fromCharCode(index + 96) : '_'; technologies = { ...technologies, @@ -43,17 +43,17 @@ for (const index of Array(27).keys()) { fs.readFileSync( path.resolve(`${__dirname}/technologies/${character}.json`) ) - ), - } + ) + }; } -setTechnologies(technologies) -setCategories(categories) +setTechnologies(technologies); +setCategories(categories); -const xhrDebounce = [] +const xhrDebounce = []; function sleep(ms) { - return new Promise((resolve) => setTimeout(resolve, ms)) + return new Promise((resolve) => setTimeout(resolve, ms)); } function getJs(page, technologies = Wappalyzer.technologies) { @@ -63,18 +63,17 @@ function getJs(page, technologies = Wappalyzer.technologies) { .map(({ name, js }) => ({ name, chains: Object.keys(js) })) .reduce((technologies, { name, chains }) => { chains.forEach((chain) => { - chain = chain.replace(/\[([^\]]+)\]/g, '.$1') + chain = chain.replace(/\[([^\]]+)\]/g, '.$1'); - const parts = chain.split('.') + const parts = chain.split('.'); const root = /^[a-z_$][a-z0-9_$]*$/i.test(parts[0]) - ? // eslint-disable-next-line no-new-func - new Function( + ? new Function( `return typeof ${ parts[0] } === 'undefined' ? undefined : ${parts.shift()}` )() - : window + : window; const value = parts.reduce( (value, method) => @@ -84,7 +83,7 @@ function getJs(page, technologies = Wappalyzer.technologies) { ? value[method] : '__UNDEFINED__', root || '__UNDEFINED__' - ) + ); if (value !== '__UNDEFINED__') { technologies.push({ @@ -93,14 +92,14 @@ function getJs(page, technologies = Wappalyzer.technologies) { value: typeof value === 'string' || typeof value === 'number' ? value - : !!value, - }) + : !!value + }); } - }) + }); - return technologies - }, []) - }, technologies) + return technologies; + }, []); + }, technologies); } function analyzeJs(js, technologies = Wappalyzer.technologies) { @@ -110,9 +109,9 @@ function analyzeJs(js, technologies = Wappalyzer.technologies) { technologies.find(({ name: _name }) => name === _name), 'js', { [chain]: [value] } - ) + ); }) - .flat() + .flat(); } function getDom(page, technologies = Wappalyzer.technologies) { @@ -123,19 +122,19 @@ function getDom(page, technologies = Wappalyzer.technologies) { const toScalar = (value) => typeof value === 'string' || typeof value === 'number' ? value - : !!value + : !!value; Object.keys(dom).forEach((selector) => { - let nodes = [] + let nodes = []; try { - nodes = document.querySelectorAll(selector) + nodes = document.querySelectorAll(selector); } catch (error) { // Continue } if (!nodes.length) { - return + return; } dom[selector].forEach(({ exists, text, properties, attributes }) => { @@ -144,7 +143,7 @@ function getDom(page, technologies = Wappalyzer.technologies) { technologies.filter(({ name: _name }) => _name === name) .length >= 50 ) { - return + return; } if ( @@ -157,15 +156,14 @@ function getDom(page, technologies = Wappalyzer.technologies) { technologies.push({ name, selector, - exists: '', - }) + exists: '' + }); } if (text) { - // eslint-disable-next-line unicorn/prefer-text-content const value = ( node.textContent ? node.textContent.trim() : '' - ).slice(0, 1000000) + ).slice(0, 1000000); if ( value && @@ -177,8 +175,8 @@ function getDom(page, technologies = Wappalyzer.technologies) { technologies.push({ name, selector, - text: value, - }) + text: value + }); } } @@ -191,7 +189,7 @@ function getDom(page, technologies = Wappalyzer.technologies) { name: _name, selector: _selector, property: _property, - value, + value }) => name === _name && selector === _selector && @@ -199,18 +197,18 @@ function getDom(page, technologies = Wappalyzer.technologies) { value === toScalar(value) ) === -1 ) { - const value = node[property] + const value = node[property]; if (typeof value !== 'undefined') { technologies.push({ name, selector, property, - value: toScalar(value), - }) + value: toScalar(value) + }); } } - }) + }); } if (attributes) { @@ -222,7 +220,7 @@ function getDom(page, technologies = Wappalyzer.technologies) { name: _name, selector: _selector, attribute: _atrribute, - value, + value }) => name === _name && selector === _selector && @@ -230,56 +228,56 @@ function getDom(page, technologies = Wappalyzer.technologies) { value === toScalar(value) ) === -1 ) { - const value = node.getAttribute(attribute) + const value = node.getAttribute(attribute); technologies.push({ name, selector, attribute, - value: toScalar(value), - }) + value: toScalar(value) + }); } - }) + }); } - }) - }) - }) + }); + }); + }); - return technologies - }, []) - }, technologies) + return technologies; + }, []); + }, technologies); } function analyzeDom(dom, technologies = Wappalyzer.technologies) { return dom .map(({ name, selector, exists, text, property, attribute, value }) => { - const technology = technologies.find(({ name: _name }) => name === _name) + const technology = technologies.find(({ name: _name }) => name === _name); if (typeof exists !== 'undefined') { return analyzeManyToMany(technology, 'dom.exists', { - [selector]: [''], - }) + [selector]: [''] + }); } if (typeof text !== 'undefined') { return analyzeManyToMany(technology, 'dom.text', { - [selector]: [text], - }) + [selector]: [text] + }); } if (typeof property !== 'undefined') { return analyzeManyToMany(technology, `dom.properties.${property}`, { - [selector]: [value], - }) + [selector]: [value] + }); } if (typeof attribute !== 'undefined') { return analyzeManyToMany(technology, `dom.attributes.${attribute}`, { - [selector]: [value], - }) + [selector]: [value] + }); } }) - .flat() + .flat(); } function get(url, options = {}) { @@ -287,10 +285,10 @@ function get(url, options = {}) { options.timeout || (this.options.fast ? this.Math.min(this.options.maxWait, 3000) - : this.options.maxWait) + : this.options.maxWait); if (['http:', 'https:'].includes(url.protocol)) { - const { get } = url.protocol === 'http:' ? http : https + const { get } = url.protocol === 'http:' ? http : https; return new Promise((resolve, reject) => get( @@ -298,32 +296,32 @@ function get(url, options = {}) { { rejectUnauthorized: false, headers: { - 'User-Agent': options.userAgent, - }, + 'User-Agent': options.userAgent + } }, (response) => { if (response.statusCode >= 300) { return reject( new Error(`${response.statusCode} ${response.statusMessage}`) - ) + ); } - response.setEncoding('utf8') + response.setEncoding('utf8'); - let body = '' + let body = ''; - response.on('data', (data) => (body += data)) - response.on('error', (error) => reject(new Error(error.message))) - response.on('end', () => resolve(body)) + response.on('data', (data) => (body += data)); + response.on('error', (error) => reject(new Error(error.message))); + response.on('end', () => resolve(body)); } ) .setTimeout(timeout, () => reject(new Error(`Timeout (${url}, ${timeout}ms)`)) ) .on('error', (error) => reject(new Error(error.message))) - ) + ); } else { - throw new Error(`Invalid protocol: ${url.protocol}`) + throw new Error(`Invalid protocol: ${url.protocol}`); } } @@ -345,45 +343,45 @@ class Driver { userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36', extended: false, - ...options, - } + ...options + }; - this.options.debug = Boolean(+this.options.debug) - this.options.fast = Boolean(+this.options.fast) - this.options.recursive = Boolean(+this.options.recursive) + this.options.debug = Boolean(+this.options.debug); + this.options.fast = Boolean(+this.options.fast); + this.options.recursive = Boolean(+this.options.recursive); this.options.probe = String(this.options.probe || '').toLowerCase() === 'basic' ? 'basic' : String(this.options.probe || '').toLowerCase() === 'full' - ? 'full' - : Boolean(+this.options.probe) && 'full' - this.options.delay = parseInt(this.options.delay, 10) - this.options.maxDepth = parseInt(this.options.maxDepth, 10) - this.options.maxUrls = parseInt(this.options.maxUrls, 10) - this.options.maxWait = parseInt(this.options.maxWait, 10) - this.options.htmlMaxCols = parseInt(this.options.htmlMaxCols, 10) - this.options.htmlMaxRows = parseInt(this.options.htmlMaxRows, 10) - this.options.noScripts = Boolean(+this.options.noScripts) - this.options.extended = Boolean(+this.options.extended) + ? 'full' + : Boolean(+this.options.probe) && 'full'; + this.options.delay = parseInt(this.options.delay, 10); + this.options.maxDepth = parseInt(this.options.maxDepth, 10); + this.options.maxUrls = parseInt(this.options.maxUrls, 10); + this.options.maxWait = parseInt(this.options.maxWait, 10); + this.options.htmlMaxCols = parseInt(this.options.htmlMaxCols, 10); + this.options.htmlMaxRows = parseInt(this.options.htmlMaxRows, 10); + this.options.noScripts = Boolean(+this.options.noScripts); + this.options.extended = Boolean(+this.options.extended); if (this.options.proxy) { - chromiumArgs.push(`--proxy-server=${this.options.proxy}`) + chromiumArgs.push(`--proxy-server=${this.options.proxy}`); } - this.destroyed = false + this.destroyed = false; } async init() { for (let attempt = 1; attempt <= 2; attempt++) { - this.log(`Launching browser (attempt ${attempt})...`) + this.log(`Launching browser (attempt ${attempt})...`); try { if (CHROMIUM_WEBSOCKET) { this.browser = await puppeteer.connect({ ignoreHTTPSErrors: true, acceptInsecureCerts: true, - browserWSEndpoint: CHROMIUM_WEBSOCKET, - }) + browserWSEndpoint: CHROMIUM_WEBSOCKET + }); } else { this.browser = await puppeteer.launch({ ignoreHTTPSErrors: true, @@ -392,148 +390,146 @@ class Driver { executablePath: CHROMIUM_BIN, timeout: this.options.fast ? Math.min(this.options.maxWait, 10000) - : this.options.maxWait, - }) + : this.options.maxWait + }); } - break + break; } catch (error) { - this.log(error) + this.log(error); if (attempt >= 2) { - throw new Error(error.message || error.toString()) + throw new Error(error.message || error.toString()); } } } this.browser.on('disconnected', () => { - this.browser = undefined + this.browser = undefined; - this.log('Browser disconnected') - }) + this.log('Browser disconnected'); + }); } async destroy() { if (this.browser) { try { - await sleep(1) + await sleep(1); - await this.browser.close() + await this.browser.close(); - this.log('Browser closed') + this.log('Browser closed'); } catch (error) { - throw new Error(error.toString()) + throw new Error(error.toString()); } } } async open(url, headers = {}, storage = {}) { - const site = new Site(url.split('#')[0], headers, this) + const site = new Site(url.split('#')[0], headers, this); if (storage.local || storage.session) { - this.log('Setting storage...') + this.log('Setting storage...'); - const page = await site.newPage(site.originalUrl) + const page = await site.newPage(site.originalUrl); - await page.setRequestInterception(true) + await page.setRequestInterception(true); page.on('request', (request) => request.respond({ status: 200, contentType: 'text/plain', - body: 'ok', + body: 'ok' }) - ) + ); - await page.goto(url) + await page.goto(url); await page.evaluate((storage) => { - ;['local', 'session'].forEach((type) => { + ['local', 'session'].forEach((type) => { Object.keys(storage[type] || {}).forEach((key) => { - window[`${type}Storage`].setItem(key, storage[type][key]) - }) - }) - }, storage) + window[`${type}Storage`].setItem(key, storage[type][key]); + }); + }); + }, storage); try { - await page.close() + await page.close(); } catch { // Continue } } - return site + return site; } log(message, source = 'driver') { if (this.options.debug) { - // eslint-disable-next-line no-console - console.log(`log | ${source} |`, message) + console.log(`log | ${source} |`, message); } } } class Site { constructor(url, headers = {}, driver) { - ;({ + ({ options: this.options, browser: this.browser, - init: this.initDriver, - } = driver) + init: this.initDriver + } = driver); this.options.headers = { ...this.options.headers, - ...headers, - } + ...headers + }; - this.driver = driver + this.driver = driver; try { - this.originalUrl = new URL(url) + this.originalUrl = new URL(url); } catch (error) { - throw new Error(error.toString()) + throw new Error(error.toString()); } - this.analyzedUrls = {} - this.analyzedXhr = {} - this.analyzedRequires = {} - this.detections = [] + this.analyzedUrls = {}; + this.analyzedXhr = {}; + this.analyzedRequires = {}; + this.detections = []; - this.listeners = {} + this.listeners = {}; - this.pages = [] + this.pages = []; - this.cache = {} + this.cache = {}; - this.probed = false + this.probed = false; } log(message, source = 'driver', type = 'log') { if (this.options.debug) { - // eslint-disable-next-line no-console - console[type](`${type} | ${source} |`, message) + console[type](`${type} | ${source} |`, message); } - this.emit(type, { message, source }) + this.emit(type, { message, source }); } error(error, source = 'driver') { - this.log(error, source, 'error') + this.log(error, source, 'error'); } on(event, callback) { if (!this.listeners[event]) { - this.listeners[event] = [] + this.listeners[event] = []; } - this.listeners[event].push(callback) + this.listeners[event].push(callback); } emit(event, params) { if (this.listeners[event]) { return Promise.allSettled( this.listeners[event].map((listener) => listener(params)) - ) + ); } } @@ -545,84 +541,84 @@ class Site { ? Math.min(this.options.maxWait, 2000) : this.options.maxWait ) { - let timeout = null + let timeout = null; if (!(promise instanceof Promise)) { - return Promise.resolve(promise) + return Promise.resolve(promise); } return Promise.race([ new Promise((resolve, reject) => { timeout = setTimeout(() => { - clearTimeout(timeout) + clearTimeout(timeout); - const error = new Error(errorMessage) + const error = new Error(errorMessage); - error.code = 'PROMISE_TIMEOUT_ERROR' + error.code = 'PROMISE_TIMEOUT_ERROR'; if (fallback !== undefined) { - this.error(error) + this.error(error); - resolve(fallback) + resolve(fallback); } else { - reject(error) + reject(error); } - }, maxWait) + }, maxWait); }), promise.then((value) => { - clearTimeout(timeout) + clearTimeout(timeout); - return value - }), - ]) + return value; + }) + ]); } async goto(url) { // Return when the URL is a duplicate or maxUrls has been reached if (this.analyzedUrls[url.href]) { - return [] + return []; } - this.log(`Navigate to ${url}`) + this.log(`Navigate to ${url}`); this.analyzedUrls[url.href] = { - status: 0, - } + status: 0 + }; - const page = await this.newPage(url) + const page = await this.newPage(url); - await page.setRequestInterception(true) + await page.setRequestInterception(true); - let responseReceived = false + let responseReceived = false; page.on('request', async (request) => { try { if (request.resourceType() === 'xhr') { - let hostname + let hostname; try { - ;({ hostname } = new URL(request.url())) + ({ hostname } = new URL(request.url())); } catch (error) { - request.abort('blockedbyclient') + request.abort('blockedbyclient'); - return + return; } if (!xhrDebounce.includes(hostname)) { - xhrDebounce.push(hostname) + xhrDebounce.push(hostname); setTimeout(async () => { - xhrDebounce.splice(xhrDebounce.indexOf(hostname), 1) + xhrDebounce.splice(xhrDebounce.indexOf(hostname), 1); this.analyzedXhr[url.hostname] = - this.analyzedXhr[url.hostname] || [] + this.analyzedXhr[url.hostname] || []; if (!this.analyzedXhr[url.hostname].includes(hostname)) { - this.analyzedXhr[url.hostname].push(hostname) + this.analyzedXhr[url.hostname].push(hostname); - await this.onDetect(url, analyze({ xhr: hostname })) + await this.onDetect(url, analyze({ xhr: hostname })); } - }, 1000) + }, 1000); } } @@ -633,31 +629,31 @@ class Site { request.resourceType() ) ) { - request.abort('blockedbyclient') + request.abort('blockedbyclient'); } else { - await this.emit('request', { page, request }) + await this.emit('request', { page, request }); if (Object.keys(this.options.headers).length) { const headers = { ...request.headers(), - ...this.options.headers, - } + ...this.options.headers + }; - request.continue({ headers }) + request.continue({ headers }); } else { - request.continue() + request.continue(); } } } catch (error) { - error.message += ` (${url})` + error.message += ` (${url})`; - this.error(error) + this.error(error); } - }) + }); page.on('response', async (response) => { if (!page || page.__closed || page.isClosed()) { - return + return; } try { @@ -666,121 +662,125 @@ class Site { response.frame().url() === url.href && response.request().resourceType() === 'script' ) { - const scripts = await response.text() + const scripts = await response.text(); - await this.onDetect(response.url(), analyze({ scripts })) + await this.onDetect(response.url(), analyze({ scripts })); } } catch (error) { if (error.constructor.name !== 'ProtocolError') { - error.message += ` (${url})` + error.message += ` (${url})`; - this.error(error) + this.error(error); } } try { if (response.url() === url.href) { this.analyzedUrls[url.href] = { - status: response.status(), - } + status: response.status() + }; - const rawHeaders = response.headers() - const headers = {} + const rawHeaders = response.headers(); + const headers = {}; Object.keys(rawHeaders).forEach((key) => { headers[key] = [ ...(headers[key] || []), ...(Array.isArray(rawHeaders[key]) ? rawHeaders[key] - : [rawHeaders[key]]), - ] - }) + : [rawHeaders[key]]) + ]; + }); // Prevent cross-domain redirects if (response.status() >= 300 && response.status() < 400) { if (headers.location) { - const _url = new URL(headers.location.slice(-1), url) + const _url = new URL(headers.location.slice(-1), url); - const redirects = Object.keys(this.analyzedUrls).length - 1 + const redirects = Object.keys(this.analyzedUrls).length - 1; if ( _url.hostname.replace(/^www\./, '') === this.originalUrl.hostname.replace(/^www\./, '') || (redirects < 3 && !this.options.noRedirect) ) { - url = _url + url = _url; - return + return; } } } - responseReceived = true + responseReceived = true; const certIssuer = response.securityDetails() ? response.securityDetails().issuer() - : '' + : ''; - await this.onDetect(url, analyze({ headers, certIssuer })) + await this.onDetect(url, analyze({ headers, certIssuer })); - await this.emit('response', { page, response, headers, certIssuer }) + await this.emit('response', { page, response, headers, certIssuer }); } } catch (error) { - error.message += ` (${url})` + error.message += ` (${url})`; - this.error(error) + this.error(error); } - }) + }); try { - await page.goto(url.href) + await page.goto(url.href); if (page.url() === 'about:blank') { - const error = new Error(`The page failed to load (${url})`) + const error = new Error(`The page failed to load (${url})`); - error.code = 'WAPPALYZER_PAGE_EMPTY' + error.code = 'WAPPALYZER_PAGE_EMPTY'; - throw error + throw error; } if (!this.options.noScripts) { - await sleep(this.options.fast ? 1000 : 3000) + await sleep(this.options.fast ? 1000 : 3000); } // page.on('console', (message) => this.log(message.text())) // Cookies - let cookies = [] + let cookies = []; try { cookies = (await page.cookies()).reduce( (cookies, { name, value }) => ({ ...cookies, - [name.toLowerCase()]: [value], + [name.toLowerCase()]: [value] }), {} - ) + ); // Change Google Analytics 4 cookie from _ga_XXXXXXXXXX to _ga_* Object.keys(cookies).forEach((name) => { if (/_ga_[A-Z0-9]+/.test(name)) { - cookies['_ga_*'] = cookies[name] + cookies['_ga_*'] = cookies[name]; - delete cookies[name] + delete cookies[name]; } - }) + }); } catch (error) { - error.message += ` (${url})` + error.message += ` (${url})`; - this.error(error) + this.error(error); } // HTML - let html = await this.promiseTimeout(page.content(), '', 'Timeout (html)') + let html = await this.promiseTimeout( + page.content(), + '', + 'Timeout (html)' + ); if (this.options.htmlMaxCols && this.options.htmlMaxRows) { - const batches = [] - const rows = html.length / this.options.htmlMaxCols + const batches = []; + const rows = html.length / this.options.htmlMaxCols; for (let i = 0; i < rows; i += 1) { if ( @@ -792,21 +792,21 @@ class Site { i * this.options.htmlMaxCols, (i + 1) * this.options.htmlMaxCols ) - ) + ); } } - html = batches.join('\n') + html = batches.join('\n'); } - let links = [] - let text = '' - let css = '' - let scriptSrc = [] - let scripts = [] - let meta = [] - let js = [] - let dom = [] + let links = []; + let text = ''; + let css = ''; + let scriptSrc = []; + let scripts = []; + let meta = []; + let js = []; + let dom = []; if (html) { await Promise.all([ @@ -825,14 +825,14 @@ class Site { href, pathname, protocol, - rel, + rel }) => ({ hash, hostname, href, pathname, protocol, - rel, + rel }) ) ), @@ -842,7 +842,7 @@ class Site { ).jsonValue(), [], 'Timeout (links)' - ) + ); })(), (async () => { // Text @@ -850,9 +850,7 @@ class Site { ( await this.promiseTimeout( page.evaluateHandle( - () => - // eslint-disable-next-line unicorn/prefer-text-content - document.body && document.body.innerText + () => document.body && document.body.innerText ), { jsonValue: () => '' }, 'Timeout (text)' @@ -860,7 +858,7 @@ class Site { ).jsonValue(), '', 'Timeout (text)' - ) + ); })(), (async () => { // CSS @@ -868,27 +866,27 @@ class Site { ( await this.promiseTimeout( page.evaluateHandle((maxRows) => { - const css = [] + const css = []; try { if (!document.styleSheets.length) { - return '' + return ''; } for (const sheet of Array.from(document.styleSheets)) { for (const rules of Array.from(sheet.cssRules)) { - css.push(rules.cssText) + css.push(rules.cssText); if (css.length >= maxRows) { - break + break; } } } } catch (error) { - return '' + return ''; } - return css.join('\n') + return css.join('\n'); }, this.options.htmlMaxRows), { jsonValue: () => '' }, 'Timeout (css)' @@ -896,17 +894,17 @@ class Site { ).jsonValue(), '', 'Timeout (css)' - ) + ); })(), (async () => { // Script tags - ;[scriptSrc, scripts] = await this.promiseTimeout( + [scriptSrc, scripts] = await this.promiseTimeout( ( await this.promiseTimeout( page.evaluateHandle(() => { const nodes = Array.from( document.getElementsByTagName('script') - ) + ); return [ nodes @@ -917,8 +915,8 @@ class Site { .map(({ src }) => src), nodes .map((node) => node.textContent) - .filter((script) => script), - ] + .filter((script) => script) + ]; }), { jsonValue: () => [] }, 'Timeout (scripts)' @@ -926,7 +924,7 @@ class Site { ).jsonValue(), [], 'Timeout (scripts)' - ) + ); })(), (async () => { // Meta tags @@ -938,18 +936,18 @@ class Site { (metas, meta) => { const key = meta.getAttribute('name') || - meta.getAttribute('property') + meta.getAttribute('property'); if (key) { metas[key.toLowerCase()] = - metas[key.toLowerCase()] || [] + metas[key.toLowerCase()] || []; metas[key.toLowerCase()].push( meta.getAttribute('content') - ) + ); } - return metas + return metas; }, {} ) @@ -960,19 +958,19 @@ class Site { ).jsonValue(), [], 'Timeout (meta)' - ) + ); })(), (async () => { // JavaScript js = this.options.noScripts ? [] - : await this.promiseTimeout(getJs(page), [], 'Timeout (js)') + : await this.promiseTimeout(getJs(page), [], 'Timeout (js)'); })(), (async () => { // DOM - dom = await this.promiseTimeout(getDom(page), [], 'Timeout (dom)') - })(), - ]) + dom = await this.promiseTimeout(getDom(page), [], 'Timeout (dom)'); + })() + ]); } this.cache[url.href] = { @@ -982,8 +980,8 @@ class Site { cookies, scripts, scriptSrc, - meta, - } + meta + }; await this.onDetect( url, @@ -998,10 +996,10 @@ class Site { css, scripts, scriptSrc, - meta, - }), + meta + }) ].flat() - ) + ); const reducedLinks = Array.prototype.reduce.call( links, @@ -1017,39 +1015,39 @@ class Site { link.hostname === url.hostname && extensions.test(link.pathname.slice(-5)) ) { - results.push(new URL(link.href.split('#')[0])) + results.push(new URL(link.href.split('#')[0])); } - return results + return results; }, [] - ) + ); await this.emit('goto', { page, url, links: reducedLinks, - ...this.cache[url.href], - }) + ...this.cache[url.href] + }); - page.__closed = true + page.__closed = true; try { - await page.close() + await page.close(); - this.log(`Page closed (${url})`) + this.log(`Page closed (${url})`); } catch (error) { // Continue } - return reducedLinks + return reducedLinks; } catch (error) { - page.__closed = true + page.__closed = true; try { - await page.close() + await page.close(); - this.log(`Page closed (${url})`) + this.log(`Page closed (${url})`); } catch (error) { // Continue } @@ -1057,75 +1055,75 @@ class Site { if (error.message.includes('net::ERR_NAME_NOT_RESOLVED')) { const newError = new Error( `Hostname could not be resolved (${url.hostname})` - ) + ); - newError.code = 'WAPPALYZER_DNS_ERROR' + newError.code = 'WAPPALYZER_DNS_ERROR'; - throw newError + throw newError; } if ( error.constructor.name === 'TimeoutError' || error.code === 'PROMISE_TIMEOUT_ERROR' ) { - error.code = 'WAPPALYZER_TIMEOUT_ERROR' + error.code = 'WAPPALYZER_TIMEOUT_ERROR'; } - error.message += ` (${url})` + error.message += ` (${url})`; - throw error + throw error; } } async newPage(url) { if (!this.browser) { - await this.initDriver() + await this.initDriver(); if (!this.browser) { - throw new Error('Browser closed') + throw new Error('Browser closed'); } } - let page + let page; try { - page = await this.browser.newPage() + page = await this.browser.newPage(); if (!page || page.isClosed()) { - throw new Error('Page did not open') + throw new Error('Page did not open'); } } catch (error) { - error.message += ` (${url})` + error.message += ` (${url})`; - this.error(error) + this.error(error); - await this.initDriver() + await this.initDriver(); - page = await this.browser.newPage() + page = await this.browser.newPage(); } - this.pages.push(page) + this.pages.push(page); - page.setJavaScriptEnabled(!this.options.noScripts) + page.setJavaScriptEnabled(!this.options.noScripts); - page.setDefaultTimeout(this.options.maxWait) + page.setDefaultTimeout(this.options.maxWait); - await page.setUserAgent(this.options.userAgent) + await page.setUserAgent(this.options.userAgent); - page.on('dialog', (dialog) => dialog.dismiss()) + page.on('dialog', (dialog) => dialog.dismiss()); page.on('error', (error) => { - error.message += ` (${url})` + error.message += ` (${url})`; - this.error(error) - }) + this.error(error); + }); - return page + return page; } async analyze(url = this.originalUrl, index = 1, depth = 1) { if (this.options.recursive) { - await sleep(this.options.delay * index) + await sleep(this.options.delay * index); } await Promise.allSettled([ @@ -1133,7 +1131,7 @@ class Site { try { const links = ((await this.goto(url)) || []).filter( ({ href }) => !this.analyzedUrls[href] - ) + ); if ( links.length && @@ -1147,27 +1145,27 @@ class Site { this.options.maxUrls - Object.keys(this.analyzedUrls).length ), depth + 1 - ) + ); } } catch (error) { this.analyzedUrls[url.href] = { status: this.analyzedUrls[url.href]?.status || 0, - error: error.message || error.toString(), - } + error: error.message || error.toString() + }; - error.message += ` (${url})` + error.message += ` (${url})`; - this.error(error) + this.error(error); } })(), (async () => { if (this.options.probe && !this.probed) { - this.probed = true + this.probed = true; - await this.probe(url) + await this.probe(url); } - })(), - ]) + })() + ]); const patterns = this.options.extended ? this.detections.reduce( @@ -1175,10 +1173,10 @@ class Site { patterns, { technology: { name, implies, excludes }, - pattern: { regex, value, match, confidence, type, version }, + pattern: { regex, value, match, confidence, type, version } } ) => { - patterns[name] = patterns[name] || [] + patterns[name] = patterns[name] || []; patterns[name].push({ type, @@ -1188,14 +1186,14 @@ class Site { confidence, version, implies: implies.map(({ name }) => name), - excludes: excludes.map(({ name }) => name), - }) + excludes: excludes.map(({ name }) => name) + }); - return patterns + return patterns; }, {} ) - : undefined + : undefined; const results = { urls: this.analyzedUrls, @@ -1210,7 +1208,7 @@ class Site { website, cpe, categories, - rootPath, + rootPath }) => ({ slug, name, @@ -1223,26 +1221,26 @@ class Site { categories: categories.map(({ id, slug, name }) => ({ id, slug, - name, + name })), - rootPath, + rootPath }) ), - patterns, - } + patterns + }; - await this.emit('analyze', results) + await this.emit('analyze', results); - return results + return results; } async probe(url) { const paths = [ { type: 'robots', - path: '/robots.txt', - }, - ] + path: '/robots.txt' + } + ]; if (this.options.probe === 'full') { Wappalyzer.technologies @@ -1252,77 +1250,77 @@ class Site { ...Object.keys(technology.probe).map((path) => ({ type: 'probe', path, - technology, + technology })) - ) - }) + ); + }); } // DNS - const records = {} + const records = {}; const resolveDns = (func, hostname) => { return this.promiseTimeout( func(hostname).catch((error) => { if (error.code !== 'ENODATA') { - error.message += ` (${url})` + error.message += ` (${url})`; - this.error(error) + this.error(error); } - return [] + return []; }), [], 'Timeout (dns)', this.options.fast ? Math.min(this.options.maxWait, 15000) : this.options.maxWait - ) - } + ); + }; - const domain = url.hostname.replace(/^www\./, '') + const domain = url.hostname.replace(/^www\./, ''); await Promise.allSettled([ // Static files ...paths.map(async ({ type, path, technology }, index) => { try { - await sleep(this.options.delay * index) + await sleep(this.options.delay * index); const body = await get(new URL(path, url.href), { userAgent: this.options.userAgent, - timeout: Math.min(this.options.maxWait, 3000), - }) + timeout: Math.min(this.options.maxWait, 3000) + }); - this.log(`Probe ok (${path})`) + this.log(`Probe ok (${path})`); - const text = body.slice(0, 100000) + const text = body.slice(0, 100000); await this.onDetect( url, analyze( { - [type]: path ? { [path]: [text] } : text, + [type]: path ? { [path]: [text] } : text }, technology && [technology] ) - ) + ); } catch (error) { - this.error(`Probe failed (${path}): ${error.message || error}`) + this.error(`Probe failed (${path}): ${error.message || error}`); } }), // DNS - // eslint-disable-next-line no-async-promise-executor + new Promise(async (resolve, reject) => { - ;[records.cname, records.ns, records.mx, records.txt, records.soa] = + [records.cname, records.ns, records.mx, records.txt, records.soa] = await Promise.all([ resolveDns(dns.resolveCname, url.hostname), resolveDns(dns.resolveNs, domain), resolveDns(dns.resolveMx, domain), resolveDns(dns.resolveTxt, domain), - resolveDns(dns.resolveSoa, domain), - ]) + resolveDns(dns.resolveSoa, domain) + ]); const dnsRecords = Object.keys(records).reduce((dns, type) => { - dns[type] = dns[type] || [] + dns[type] = dns[type] || []; Array.prototype.push.apply( dns[type], @@ -1330,37 +1328,37 @@ class Site { ? records[type].map((value) => { return typeof value === 'object' ? Object.values(value).join(' ') - : value + : value; }) : [Object.values(records[type]).join(' ')] - ) + ); - return dns - }, {}) + return dns; + }, {}); this.log( `Probe DNS ok: (${Object.values(dnsRecords).flat().length} records)` - ) + ); - await this.onDetect(url, analyze({ dns: dnsRecords })) + await this.onDetect(url, analyze({ dns: dnsRecords })); - resolve() - }), - ]) + resolve(); + }) + ]); } async batch(links, depth, batch = 0) { if (links.length === 0) { - return + return; } - const batched = links.splice(0, this.options.batchSize) + const batched = links.splice(0, this.options.batchSize); await Promise.allSettled( batched.map((link, index) => this.analyze(link, index, depth)) - ) + ); - await this.batch(links, depth, batch + 1) + await this.batch(links, depth, batch + 1); } async onDetect(url, detections = []) { @@ -1376,25 +1374,25 @@ class Site { ({ technology: { name: _name }, pattern: { regex: _regex }, - version: _version, + version: _version }) => name === _name && version === _version && (!regex || regex.toString() === _regex.toString()) ) === index - ) + ); // Track if technology was identified on website's root path detections.forEach(({ technology: { name } }) => { const detection = this.detections.find( ({ technology: { name: _name } }) => name === _name - ) + ); - detection.rootPath = detection.rootPath || url.pathname === '/' - }) + detection.rootPath = detection.rootPath || url.pathname === '/'; + }); if (this.cache[url.href]) { - const resolved = resolve(this.detections) + const resolved = resolve(this.detections); const requires = [ ...Wappalyzer.requires.filter(({ name }) => @@ -1404,34 +1402,34 @@ class Site { resolved.some(({ categories }) => categories.some(({ id }) => id === categoryId) ) - ), - ] + ) + ]; await Promise.allSettled( requires.map(async ({ name, categoryId, technologies }) => { const id = categoryId ? `category:${categoryId}` - : `technology:${name}` + : `technology:${name}`; this.analyzedRequires[url.href] = - this.analyzedRequires[url.href] || [] + this.analyzedRequires[url.href] || []; if (!this.analyzedRequires[url.href].includes(id)) { - this.analyzedRequires[url.href].push(id) + this.analyzedRequires[url.href].push(id); const { page, cookies, html, text, css, scripts, scriptSrc, meta } = - this.cache[url.href] + this.cache[url.href]; const js = await this.promiseTimeout( getJs(page, technologies), [], 'Timeout (js)' - ) + ); const dom = await this.promiseTimeout( getDom(page, technologies), [], 'Timeout (dom)' - ) + ); await this.onDetect( url, @@ -1447,15 +1445,15 @@ class Site { css, scripts, scriptSrc, - meta, + meta }, technologies - ), + ) ].flat() - ) + ); } }) - ) + ); } } @@ -1463,19 +1461,19 @@ class Site { await Promise.allSettled( this.pages.map(async (page) => { if (page) { - page.__closed = true + page.__closed = true; try { - await page.close() + await page.close(); } catch (error) { // Continue } } }) - ) + ); - this.log('Site closed') + this.log('Site closed'); } } -module.exports = Driver +module.exports = Driver; diff --git a/src/drivers/npm/yarn.lock b/src/drivers/npm/yarn.lock index 11397257c..331648635 100644 --- a/src/drivers/npm/yarn.lock +++ b/src/drivers/npm/yarn.lock @@ -4,19 +4,19 @@ "@babel/code-frame@^7.0.0": version "7.21.4" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.21.4.tgz#d0fa9e4413aca81f2b23b9442797bda1826edb39" + resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz" integrity sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g== dependencies: "@babel/highlight" "^7.18.6" "@babel/helper-validator-identifier@^7.18.6": version "7.19.1" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2" + resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz" integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w== "@babel/highlight@^7.18.6": version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf" + resolved "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz" integrity sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g== dependencies: "@babel/helper-validator-identifier" "^7.18.6" @@ -25,48 +25,48 @@ "@types/node@*": version "18.15.11" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.15.11.tgz#b3b790f09cb1696cffcec605de025b088fa4225f" + resolved "https://registry.npmjs.org/@types/node/-/node-18.15.11.tgz" integrity sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q== "@types/yauzl@^2.9.1": version "2.10.0" - resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.10.0.tgz#b3248295276cf8c6f153ebe6a9aba0c988cb2599" + resolved "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.0.tgz" integrity sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw== dependencies: "@types/node" "*" agent-base@6: version "6.0.2" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" + resolved "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz" integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== dependencies: debug "4" ansi-styles@^3.2.1: version "3.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + 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" argparse@^2.0.1: version "2.0.1" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== balanced-match@^1.0.0: version "1.0.2" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== base64-js@^1.3.1: version "1.5.1" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== bl@^4.0.3: version "4.1.0" - resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" + resolved "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz" integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== dependencies: buffer "^5.5.0" @@ -75,19 +75,19 @@ bl@^4.0.3: brace-expansion@^2.0.1: version "2.0.1" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz" integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== dependencies: balanced-match "^1.0.0" buffer-crc32@~0.2.3: version "0.2.13" - resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" + resolved "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz" integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ== buffer@^5.2.1, buffer@^5.5.0: version "5.7.1" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" + resolved "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz" integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== dependencies: base64-js "^1.3.1" @@ -95,12 +95,12 @@ buffer@^5.2.1, buffer@^5.5.0: callsites@^3.0.0: version "3.1.0" - resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + resolved "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz" integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== chalk@^2.0.0: version "2.4.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + resolved "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== dependencies: ansi-styles "^3.2.1" @@ -109,31 +109,31 @@ chalk@^2.0.0: chownr@^1.1.1: version "1.1.4" - resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" + resolved "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz" integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== chromium-bidi@0.4.5: version "0.4.5" - resolved "https://registry.yarnpkg.com/chromium-bidi/-/chromium-bidi-0.4.5.tgz#a352e755536dde609bd2c77e4b1f0906bff8784e" + resolved "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.5.tgz" integrity sha512-rkav9YzRfAshSTG3wNXF7P7yNiI29QAo1xBXElPoCoSQR5n20q3cOyVhDv6S7+GlF/CJ/emUxlQiR0xOPurkGg== dependencies: mitt "3.0.0" color-convert@^1.9.0: version "1.9.3" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + 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" color-name@1.1.3: version "1.1.3" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz" integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== cosmiconfig@8.1.0: version "8.1.0" - resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-8.1.0.tgz#947e174c796483ccf0a48476c24e4fefb7e1aea8" + resolved "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.1.0.tgz" integrity sha512-0tLZ9URlPGU7JsKq0DQOQ3FoRsYX8xDZ7xMiATQfaiGMz7EHowNkbU9u1coAOmnh9p/1ySpm0RB3JNWRXM5GCg== dependencies: import-fresh "^3.2.1" @@ -143,45 +143,45 @@ cosmiconfig@8.1.0: cross-fetch@3.1.5: version "3.1.5" - resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.5.tgz#e1389f44d9e7ba767907f7af8454787952ab534f" + resolved "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz" integrity sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw== dependencies: node-fetch "2.6.7" -debug@4, debug@4.3.4, debug@^4.1.1: +debug@^4.1.1, debug@4, debug@4.3.4: version "4.3.4" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + resolved "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== dependencies: ms "2.1.2" -devtools-protocol@0.0.1094867: +devtools-protocol@*, devtools-protocol@0.0.1094867: version "0.0.1094867" - resolved "https://registry.yarnpkg.com/devtools-protocol/-/devtools-protocol-0.0.1094867.tgz#2ab93908e9376bd85d4e0604aa2651258f13e374" + resolved "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1094867.tgz" integrity sha512-pmMDBKiRVjh0uKK6CT1WqZmM3hBVSgD+N2MrgyV1uNizAZMw4tx6i/RTc+/uCsKSCmg0xXx7arCP/OFcIwTsiQ== end-of-stream@^1.1.0, end-of-stream@^1.4.1: version "1.4.4" - resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" + resolved "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz" integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== dependencies: once "^1.4.0" error-ex@^1.3.1: version "1.3.2" - resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + resolved "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz" integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== dependencies: is-arrayish "^0.2.1" escape-string-regexp@^1.0.5: version "1.0.5" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz" integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== extract-zip@2.0.1: version "2.0.1" - resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.1.tgz#663dca56fe46df890d5f131ef4a06d22bb8ba13a" + resolved "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz" integrity sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg== dependencies: debug "^4.1.1" @@ -192,31 +192,31 @@ extract-zip@2.0.1: fd-slicer@~1.1.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" + 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" fs-constants@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" + resolved "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz" integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== fs.realpath@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== get-stream@^5.1.0: version "5.2.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" + resolved "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz" integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== dependencies: pump "^3.0.0" glob@^9.2.0: version "9.3.4" - resolved "https://registry.yarnpkg.com/glob/-/glob-9.3.4.tgz#e75dee24891a80c25cc7ee1dd327e126b98679af" + resolved "https://registry.npmjs.org/glob/-/glob-9.3.4.tgz" integrity sha512-qaSc49hojMOv1EPM4EuyITjDSgSKI0rthoHnvE81tcOi1SCVndHko7auqxdQ14eiQG2NDBJBE86+2xIrbIvrbA== dependencies: fs.realpath "^1.0.0" @@ -226,12 +226,12 @@ glob@^9.2.0: has-flag@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + resolved "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz" integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== https-proxy-agent@5.0.1: version "5.0.1" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" + 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" @@ -239,12 +239,12 @@ https-proxy-agent@5.0.1: ieee754@^1.1.13: version "1.2.1" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== import-fresh@^3.2.1: version "3.3.0" - resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + resolved "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz" integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== dependencies: parent-module "^1.0.0" @@ -252,92 +252,92 @@ import-fresh@^3.2.1: inherits@^2.0.3, inherits@^2.0.4: version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== is-arrayish@^0.2.1: version "0.2.1" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + resolved "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz" integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== js-tokens@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== js-yaml@^4.1.0: version "4.1.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz" integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== dependencies: argparse "^2.0.1" json-parse-even-better-errors@^2.3.0: version "2.3.1" - resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + resolved "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz" integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== lines-and-columns@^1.1.6: version "1.2.4" - resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + resolved "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz" integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== lru-cache@^7.14.1: version "7.18.3" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.18.3.tgz#f793896e0fd0e954a59dfdd82f0773808df6aa89" + resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz" integrity sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA== minimatch@^8.0.2: version "8.0.2" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-8.0.2.tgz#ba35f8afeb255a4cbad4b6677b46132f3278c469" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-8.0.2.tgz" integrity sha512-ikHGF67ODxj7vS5NKU2wvTsFLbExee+KXVCnBWh8Cg2hVJfBMQIrlo50qru/09E0EifjnU8dZhJ/iHhyXJM6Mw== dependencies: brace-expansion "^2.0.1" minipass@^4.0.2, minipass@^4.2.4: version "4.2.5" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-4.2.5.tgz#9e0e5256f1e3513f8c34691dd68549e85b2c8ceb" + resolved "https://registry.npmjs.org/minipass/-/minipass-4.2.5.tgz" integrity sha512-+yQl7SX3bIT83Lhb4BVorMAHVuqsskxRdlmO9kTpyukp8vsm2Sn/fUOV9xlnG8/a5JsypJzap21lz/y3FBMJ8Q== mitt@3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/mitt/-/mitt-3.0.0.tgz#69ef9bd5c80ff6f57473e8d89326d01c414be0bd" + resolved "https://registry.npmjs.org/mitt/-/mitt-3.0.0.tgz" integrity sha512-7dX2/10ITVyqh4aOSVI9gdape+t9l2/8QxHrFmUXu4EEUpdlxl6RudZUPZoc+zuY2hk1j7XxVroIVIan/pD/SQ== mkdirp-classic@^0.5.2: version "0.5.3" - resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" + resolved "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz" integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== ms@2.1.2: version "2.1.2" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== node-fetch@2.6.7: version "2.6.7" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" + resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz" integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== dependencies: whatwg-url "^5.0.0" once@^1.3.1, once@^1.4.0: version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz" integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== dependencies: wrappy "1" parent-module@^1.0.0: version "1.0.1" - resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + resolved "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz" integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== dependencies: callsites "^3.0.0" parse-json@^5.0.0: version "5.2.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + resolved "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz" integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== dependencies: "@babel/code-frame" "^7.0.0" @@ -347,7 +347,7 @@ parse-json@^5.0.0: path-scurry@^1.6.1: version "1.6.3" - resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.6.3.tgz#4eba7183d64ef88b63c7d330bddc3ba279dc6c40" + resolved "https://registry.npmjs.org/path-scurry/-/path-scurry-1.6.3.tgz" integrity sha512-RAmB+n30SlN+HnNx6EbcpoDy9nwdpcGPnEKrJnu6GZoDWBdIjo1UQMVtW2ybtC7LC2oKLcMq8y5g8WnKLiod9g== dependencies: lru-cache "^7.14.1" @@ -355,27 +355,27 @@ path-scurry@^1.6.1: path-type@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + resolved "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== pend@~1.2.0: version "1.2.0" - resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" + resolved "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz" integrity sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg== progress@2.0.3: version "2.0.3" - resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" + resolved "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz" integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== proxy-from-env@1.1.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + resolved "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz" integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== pump@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" + resolved "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz" integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== dependencies: end-of-stream "^1.1.0" @@ -383,7 +383,7 @@ pump@^3.0.0: puppeteer-core@19.7.5: version "19.7.5" - resolved "https://registry.yarnpkg.com/puppeteer-core/-/puppeteer-core-19.7.5.tgz#cedc8eb7862fe7a8aa2a25ed167c0f1230de72b2" + resolved "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-19.7.5.tgz" integrity sha512-EJuNha+SxPfaYFbkoWU80H3Wb1SiQH5fFyb2xdbWda0ziax5mhV63UMlqNfPeTDIWarwtR4OIcq/9VqY8HPOsg== dependencies: chromium-bidi "0.4.5" @@ -400,7 +400,7 @@ puppeteer-core@19.7.5: puppeteer@~19.7.0: version "19.7.5" - resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-19.7.5.tgz#d7db0dfcc80ca2cdf8eb0100bae1ce888a841389" + resolved "https://registry.npmjs.org/puppeteer/-/puppeteer-19.7.5.tgz" integrity sha512-UqD8K+yaZa6/hwzP54AATCiHrEYGGxzQcse9cZzrtsVGd8wT0llCdYhsBp8n+zvnb1ofY0YFgI3TYZ/MiX5uXQ== dependencies: cosmiconfig "8.1.0" @@ -411,7 +411,7 @@ puppeteer@~19.7.0: readable-stream@^3.1.1, readable-stream@^3.4.0: version "3.6.2" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" + resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz" integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== dependencies: inherits "^2.0.3" @@ -420,38 +420,38 @@ readable-stream@^3.1.1, readable-stream@^3.4.0: resolve-from@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz" integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== rimraf@4.4.0: version "4.4.0" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-4.4.0.tgz#c7a9f45bb2ec058d2e60ef9aca5167974313d605" + resolved "https://registry.npmjs.org/rimraf/-/rimraf-4.4.0.tgz" integrity sha512-X36S+qpCUR0HjXlkDe4NAOhS//aHH0Z+h8Ckf2auGJk3PTnx5rLmrHkwNdbVQuCSUhOyFrlRvFEllZOYE+yZGQ== dependencies: glob "^9.2.0" safe-buffer@~5.2.0: version "5.2.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== string_decoder@^1.1.1: version "1.3.0" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== dependencies: safe-buffer "~5.2.0" supports-color@^5.3.0: version "5.5.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz" integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== dependencies: has-flag "^3.0.0" tar-fs@2.1.1: version "2.1.1" - resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784" + resolved "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz" integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng== dependencies: chownr "^1.1.1" @@ -461,7 +461,7 @@ tar-fs@2.1.1: tar-stream@^2.1.4: version "2.2.0" - resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" + resolved "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz" integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== dependencies: bl "^4.0.3" @@ -472,17 +472,17 @@ tar-stream@^2.1.4: through@^2.3.8: version "2.3.8" - resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + resolved "https://registry.npmjs.org/through/-/through-2.3.8.tgz" integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== tr46@~0.0.3: version "0.0.3" - resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + resolved "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz" integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== unbzip2-stream@1.4.3: version "1.4.3" - resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz#b0da04c4371311df771cdc215e87f2130991ace7" + 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" @@ -490,17 +490,17 @@ unbzip2-stream@1.4.3: util-deprecate@^1.0.1: version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== webidl-conversions@^3.0.0: version "3.0.1" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz" integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== whatwg-url@^5.0.0: version "5.0.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" + resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz" integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== dependencies: tr46 "~0.0.3" @@ -508,17 +508,17 @@ whatwg-url@^5.0.0: wrappy@1: version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== ws@8.12.1: version "8.12.1" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.12.1.tgz#c51e583d79140b5e42e39be48c934131942d4a8f" + resolved "https://registry.npmjs.org/ws/-/ws-8.12.1.tgz" integrity sha512-1qo+M9Ba+xNhPB+YTWUlK6M17brTut5EXbcBaMRN5pH5dFrXz7lzz1ChFSUq3bOUl8yEvSenhHmYUNJxFzdJew== yauzl@^2.10.0: version "2.10.0" - resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" + resolved "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz" integrity sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g== dependencies: buffer-crc32 "~0.2.3" From c18848a8079202cfc4fe943fcb9993888af41c4c Mon Sep 17 00:00:00 2001 From: Max Ostapenko <1611259+max-ostapenko@users.noreply.github.com> Date: Tue, 14 Jul 2026 14:08:16 +0200 Subject: [PATCH 4/6] refactor: remove npm driver subdirectory and simplify package configuration Signed-off-by: Max Ostapenko <1611259+max-ostapenko@users.noreply.github.com> --- README.md | 2 +- src/drivers/npm/cli.js => cli.js | 2 +- package-lock.json | 496 ++++++++++++++++-- package.json | 8 +- scripts/link.js | 27 - src/drivers/{npm => }/README.md | 15 +- src/drivers/{npm => }/driver.js | 875 +++++++++---------------------- src/drivers/npm/Dockerfile | 31 -- src/drivers/npm/package.json | 46 -- src/drivers/npm/yarn.lock | 525 ------------------- 10 files changed, 723 insertions(+), 1304 deletions(-) rename src/drivers/npm/cli.js => cli.js (98%) delete mode 100644 scripts/link.js rename src/drivers/{npm => }/README.md (93%) rename src/drivers/{npm => }/driver.js (55%) delete mode 100644 src/drivers/npm/Dockerfile delete mode 100644 src/drivers/npm/package.json delete mode 100644 src/drivers/npm/yarn.lock diff --git a/README.md b/README.md index 8068a6456..ebab1fac4 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ npm install ### Command line ```sh -node src/drivers/npm/cli.js https://example.com +node cli.js https://example.com ``` ### Chrome extension diff --git a/src/drivers/npm/cli.js b/cli.js similarity index 98% rename from src/drivers/npm/cli.js rename to cli.js index dd6445a63..49d87dd8e 100644 --- a/src/drivers/npm/cli.js +++ b/cli.js @@ -1,6 +1,6 @@ #!/usr/bin/env node -const Wappalyzer = require('./driver'); +const Wappalyzer = require('./src/drivers/driver'); const args = process.argv.slice(2); diff --git a/package-lock.json b/package-lock.json index 2c3cf0c5c..880fcf737 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,6 +4,12 @@ "requires": true, "packages": { "": { + "dependencies": { + "puppeteer": "~19.7.0" + }, + "bin": { + "wappalyzer": "cli.js" + }, "devDependencies": { "@google-cloud/bigquery": "8.3.1", "@google-cloud/storage": "7.21.0", @@ -24,7 +30,6 @@ "version": "7.29.0", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", @@ -51,6 +56,7 @@ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -176,7 +182,6 @@ "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, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -1755,7 +1760,7 @@ "version": "25.2.2", "resolved": "https://registry.npmjs.org/@types/node/-/node-25.2.2.tgz", "integrity": "sha512-BkmoP5/FhRYek5izySdkOneRyXYN35I860MFAGupTdebyE66uZaR+bXLHq8k4DirE5DwQi3NuhvRU1jqTVwUrQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "undici-types": "~7.16.0" @@ -1805,6 +1810,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@ungap/structured-clone": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.1.tgz", @@ -2100,6 +2115,7 @@ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2133,6 +2149,7 @@ "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -2276,7 +2293,6 @@ "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, "license": "Python-2.0" }, "node_modules/arrify": { @@ -2419,7 +2435,6 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, "funding": [ { "type": "github", @@ -2486,6 +2501,17 @@ "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==", + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, "node_modules/brace-expansion": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", @@ -2536,6 +2562,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", @@ -2560,6 +2587,39 @@ "node-int64": "^0.4.0" } }, + "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" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", @@ -2592,7 +2652,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -2694,6 +2753,24 @@ "node": ">= 6" } }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "license": "ISC" + }, + "node_modules/chromium-bidi": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.5.tgz", + "integrity": "sha512-rkav9YzRfAshSTG3wNXF7P7yNiI29QAo1xBXElPoCoSQR5n20q3cOyVhDv6S7+GlF/CJ/emUxlQiR0xOPurkGg==", + "license": "Apache-2.0", + "dependencies": { + "mitt": "3.0.0" + }, + "peerDependencies": { + "devtools-protocol": "*" + } + }, "node_modules/ci-info": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", @@ -2824,6 +2901,35 @@ } } }, + "node_modules/cross-fetch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", + "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", + "license": "MIT", + "dependencies": { + "node-fetch": "2.6.7" + } + }, + "node_modules/cross-fetch/node_modules/node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -2880,7 +2986,6 @@ "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -2959,6 +3064,13 @@ "node": ">=8" } }, + "node_modules/devtools-protocol": { + "version": "0.0.1094867", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1094867.tgz", + "integrity": "sha512-pmMDBKiRVjh0uKK6CT1WqZmM3hBVSgD+N2MrgyV1uNizAZMw4tx6i/RTc+/uCsKSCmg0xXx7arCP/OFcIwTsiQ==", + "license": "BSD-3-Clause", + "peer": true + }, "node_modules/diff": { "version": "8.0.3", "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.3.tgz", @@ -3055,7 +3167,6 @@ "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==", - "dev": true, "license": "MIT", "dependencies": { "once": "^1.4.0" @@ -3088,7 +3199,6 @@ "version": "1.3.4", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", - "dev": true, "license": "MIT", "dependencies": { "is-arrayish": "^0.2.1" @@ -3172,6 +3282,7 @@ "integrity": "sha512-6lVbcqSodALYo+4ELD0heG6lFiFxnLMuLkiMi2qV8LMp54N8tE8FT1GMH+ev4Ti00nFjNze2+Su6DsV5OQW3Dg==", "dev": true, "license": "MIT", + "peer": true, "workspaces": [ "packages/*" ], @@ -3508,6 +3619,41 @@ "dev": true, "license": "MIT" }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "license": "BSD-2-Clause", + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/extract-zip/node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -3602,6 +3748,15 @@ "bser": "2.1.1" } }, + "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==", + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + } + }, "node_modules/fetch-blob": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", @@ -3731,6 +3886,12 @@ "node": ">=12.20.0" } }, + "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==", + "license": "MIT" + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -3933,7 +4094,6 @@ "version": "13.0.1", "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.1.tgz", "integrity": "sha512-B7U/vJpE3DkJ5WXTgTpTRN63uV42DseiXXKMwG14LQBXmsdeIoHAPbU/MEo6II0k5ED74uc2ZGTC6MwHFQhF6w==", - "dev": true, "license": "BlueOak-1.0.0", "dependencies": { "minimatch": "^10.1.2", @@ -4250,6 +4410,26 @@ "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" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -4264,7 +4444,6 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "dev": true, "license": "MIT", "dependencies": { "parent-module": "^1.0.0", @@ -4311,14 +4490,12 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, "license": "ISC" }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true, "license": "MIT" }, "node_modules/is-binary-path": { @@ -5150,14 +5327,12 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, "license": "MIT" }, "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, "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -5200,7 +5375,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true, "license": "MIT" }, "node_modules/json-schema-traverse": { @@ -5236,6 +5410,7 @@ "integrity": "sha512-75EA7EWZExL/j+MDKQrRbdzcRI2HOkRlmUw8fZJc1ioqFEOvBsq7Rt+A6yCxOt9w/TYNpkt52gC6nm/g5tFIng==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "acorn": "^8.5.0", "eslint-visitor-keys": "^5.0.0", @@ -5322,7 +5497,6 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true, "license": "MIT" }, "node_modules/locate-path": { @@ -5474,7 +5648,6 @@ "version": "10.2.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", - "dev": true, "license": "BlueOak-1.0.0", "dependencies": { "brace-expansion": "^5.0.5" @@ -5490,7 +5663,6 @@ "version": "4.0.4", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true, "license": "MIT", "engines": { "node": "18 || 20 || >=22" @@ -5500,7 +5672,6 @@ "version": "5.0.5", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", - "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^4.0.2" @@ -5513,12 +5684,23 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, "license": "ISC", "engines": { "node": ">=16 || 14 >=14.17" } }, + "node_modules/mitt": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.0.tgz", + "integrity": "sha512-7dX2/10ITVyqh4aOSVI9gdape+t9l2/8QxHrFmUXu4EEUpdlxl6RudZUPZoc+zuY2hk1j7XxVroIVIan/pD/SQ==", + "license": "MIT" + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "license": "MIT" + }, "node_modules/mocha": { "version": "10.8.2", "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.8.2.tgz", @@ -5636,7 +5818,6 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, "license": "MIT" }, "node_modules/napi-postinstall": { @@ -5768,7 +5949,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "license": "ISC", "dependencies": { "wrappy": "1" @@ -5854,14 +6034,12 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "dev": true, "license": "BlueOak-1.0.0" }, "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, "license": "MIT", "dependencies": { "callsites": "^3.0.0" @@ -5874,7 +6052,6 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.0.0", @@ -5929,7 +6106,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.1.tgz", "integrity": "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==", - "dev": true, "license": "BlueOak-1.0.0", "dependencies": { "lru-cache": "^11.0.0", @@ -5946,17 +6122,30 @@ "version": "11.2.5", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.5.tgz", "integrity": "sha512-vFrFJkWtJvJnD5hg+hJvVE8Lh/TcMzKnTgCWmtBipwI5yLX/iX+5UB2tfuyODF5E7k9xEzMdYgGqaSb1c0c5Yw==", - "dev": true, "license": "BlueOak-1.0.0", "engines": { "node": "20 || >=22" } }, + "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==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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==", + "license": "MIT" + }, "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, "license": "ISC" }, "node_modules/picomatch": { @@ -6067,6 +6256,7 @@ "integrity": "sha512-yWG/o/4oJfo036EKAfK6ACAoDOfHeRHx4tuxkfBZiauURiaSmYwlpOr5LQqKtIkRD2z1PLteme2WoxEnj4tHTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -6119,6 +6309,31 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -6129,6 +6344,142 @@ "node": ">=6" } }, + "node_modules/puppeteer": { + "version": "19.7.5", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-19.7.5.tgz", + "integrity": "sha512-UqD8K+yaZa6/hwzP54AATCiHrEYGGxzQcse9cZzrtsVGd8wT0llCdYhsBp8n+zvnb1ofY0YFgI3TYZ/MiX5uXQ==", + "deprecated": "< 24.15.0 is no longer supported", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "cosmiconfig": "8.1.0", + "https-proxy-agent": "5.0.1", + "progress": "2.0.3", + "proxy-from-env": "1.1.0", + "puppeteer-core": "19.7.5" + } + }, + "node_modules/puppeteer-core": { + "version": "19.7.5", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-19.7.5.tgz", + "integrity": "sha512-EJuNha+SxPfaYFbkoWU80H3Wb1SiQH5fFyb2xdbWda0ziax5mhV63UMlqNfPeTDIWarwtR4OIcq/9VqY8HPOsg==", + "license": "Apache-2.0", + "dependencies": { + "chromium-bidi": "0.4.5", + "cross-fetch": "3.1.5", + "debug": "4.3.4", + "devtools-protocol": "0.0.1094867", + "extract-zip": "2.0.1", + "https-proxy-agent": "5.0.1", + "proxy-from-env": "1.1.0", + "rimraf": "4.4.0", + "tar-fs": "2.1.1", + "unbzip2-stream": "1.4.3", + "ws": "8.12.1" + }, + "engines": { + "node": ">=14.14.0" + }, + "peerDependencies": { + "typescript": ">= 4.7.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/puppeteer-core/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==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/puppeteer-core/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/puppeteer-core/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==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/puppeteer-core/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "license": "MIT" + }, + "node_modules/puppeteer/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==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/puppeteer/node_modules/cosmiconfig": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.1.0.tgz", + "integrity": "sha512-0tLZ9URlPGU7JsKq0DQOQ3FoRsYX8xDZ7xMiATQfaiGMz7EHowNkbU9u1coAOmnh9p/1ySpm0RB3JNWRXM5GCg==", + "license": "MIT", + "dependencies": { + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "parse-json": "^5.0.0", + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + } + }, + "node_modules/puppeteer/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==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/pure-rand": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-7.0.1.tgz", @@ -6197,7 +6548,6 @@ "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, "license": "MIT", "dependencies": { "inherits": "^2.0.3", @@ -6268,7 +6618,6 @@ "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, "license": "MIT", "engines": { "node": ">=4" @@ -6312,7 +6661,6 @@ "version": "6.1.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.1.2.tgz", "integrity": "sha512-cFCkPslJv7BAXJsYlK1dZsbP8/ZNLkCAQ0bi1hf5EKX2QHegmDFEFA6QhuYJlk7UDdc+02JjO80YSOrWPpw06g==", - "dev": true, "license": "BlueOak-1.0.0", "dependencies": { "glob": "^13.0.0", @@ -6332,7 +6680,6 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, "funding": [ { "type": "github", @@ -6521,7 +6868,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, "license": "MIT", "dependencies": { "safe-buffer": "~5.2.0" @@ -6651,6 +6997,34 @@ "url": "https://opencollective.com/synckit" } }, + "node_modules/tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "license": "MIT", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "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==", + "license": "MIT", + "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/teeny-request": { "version": "10.1.2", "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-10.1.2.tgz", @@ -6711,6 +7085,12 @@ "node": ">=18" } }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "license": "MIT" + }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", @@ -6735,7 +7115,6 @@ "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "dev": true, "license": "MIT" }, "node_modules/tslib": { @@ -6782,11 +7161,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "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==", + "license": "MIT", + "dependencies": { + "buffer": "^5.2.1", + "through": "^2.3.8" + } + }, "node_modules/undici-types": { "version": "7.16.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/unrs-resolver": { @@ -6869,7 +7258,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true, "license": "MIT" }, "node_modules/v8-to-istanbul": { @@ -6911,7 +7299,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "dev": true, "license": "BSD-2-Clause" }, "node_modules/webpagetest": { @@ -6947,7 +7334,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dev": true, "license": "MIT", "dependencies": { "tr46": "~0.0.3", @@ -7009,7 +7395,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, "license": "ISC" }, "node_modules/write-file-atomic": { @@ -7039,6 +7424,27 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/ws": { + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.12.1.tgz", + "integrity": "sha512-1qo+M9Ba+xNhPB+YTWUlK6M17brTut5EXbcBaMRN5pH5dFrXz7lzz1ChFSUq3bOUl8yEvSenhHmYUNJxFzdJew==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/xml-naming": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/xml-naming/-/xml-naming-0.1.0.tgz", @@ -7164,6 +7570,16 @@ "node": ">=12" } }, + "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==", + "license": "MIT", + "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", diff --git a/package.json b/package.json index 21dc71683..94a915817 100644 --- a/package.json +++ b/package.json @@ -1,4 +1,11 @@ { + "main": "src/drivers/driver.js", + "bin": { + "wappalyzer": "./cli.js" + }, + "dependencies": { + "puppeteer": "~19.7.0" + }, "devDependencies": { "@google-cloud/bigquery": "8.3.1", "@google-cloud/storage": "7.21.0", @@ -15,7 +22,6 @@ "node": ">=24.0.0" }, "scripts": { - "link": "node ./scripts/link.js", "lint": "eslint --exit-on-fatal-error --max-warnings 0 && jsonlint -jksV ./schema.json --trim-trailing-commas --enforce-double-quotes ./src/technologies/ && jsonlint -js --trim-trailing-commas --enforce-double-quotes ./src/categories.json", "lint:fix": "eslint --exit-on-fatal-error --fix && jsonlint -isV ./schema.json --trim-trailing-commas --enforce-double-quotes ./src/technologies/ && jsonlint -is --trim-trailing-commas --enforce-double-quotes ./src/categories.json", "validate": "node ./scripts/validate.js", diff --git a/scripts/link.js b/scripts/link.js deleted file mode 100644 index c7460333b..000000000 --- a/scripts/link.js +++ /dev/null @@ -1,27 +0,0 @@ -const fs = require('fs'); -const path = require('path'); - -const link = (src, dest) => { - const dir = path.dirname(dest); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir, { recursive: true }); - } - - if (fs.existsSync(dest)) { - fs.unlinkSync(dest); - } - - fs.linkSync(src, dest); -}; - -link('./src/js/wappalyzer.js', './src/drivers/npm/wappalyzer.js'); -link('./src/categories.json', './src/drivers/npm/categories.json'); - -for (const index of Array(27).keys()) { - const character = index ? String.fromCharCode(index + 96) : '_'; - - link( - `./src/technologies/${character}.json`, - `./src/drivers/npm/technologies/${character}.json` - ); -} diff --git a/src/drivers/npm/README.md b/src/drivers/README.md similarity index 93% rename from src/drivers/npm/README.md rename to src/drivers/README.md index f856e4b9d..4940abdda 100644 --- a/src/drivers/npm/README.md +++ b/src/drivers/README.md @@ -1,15 +1,15 @@ # Wappalyzer -[Wappalyzer](https://www.wappalyzer.com/) indentifies technologies on websites. +[Wappalyzer](https://www.wappalyzer.com/) indentifies technologies on websites. *Note:* The [wappalyzer-core](https://www.npmjs.com/package/wappalyzer-core) package provides a low-level API without dependencies. ## Command line -### Installation +To run the CLI command `wappalyzer` globally, link it from the repository root: ```shell -$ npm i -g wappalyzer +$ npm link ``` ### Usage @@ -45,19 +45,18 @@ wappalyzer [options] ``` - ## Dependency -### Installation +To install this package in another local project, reference it by its local path: ```shell -$ npm i wappalyzer +$ npm install /path/to/wappalyzer ``` ### Usage ```javascript -const Wappalyzer = require('wappalyzer') +const Wappalyzer = require('./driver') const url = 'https://www.wappalyzer.com' @@ -112,7 +111,7 @@ const wappalyzer = new Wappalyzer(options) Multiple URLs can be processed in parallel: ```javascript -const Wappalyzer = require('wappalyzer'); +const Wappalyzer = require('./driver'); const urls = ['https://www.wappalyzer.com', 'https://www.example.com'] diff --git a/src/drivers/npm/driver.js b/src/drivers/driver.js similarity index 55% rename from src/drivers/npm/driver.js rename to src/drivers/driver.js index 84177d506..db06df98b 100644 --- a/src/drivers/npm/driver.js +++ b/src/drivers/driver.js @@ -4,7 +4,7 @@ const path = require('path'); const http = require('http'); const https = require('https'); const puppeteer = require('puppeteer'); -const Wappalyzer = require('./wappalyzer'); +const Wappalyzer = require('../js/wappalyzer'); const { setTechnologies, setCategories, analyze, analyzeManyToMany, resolve } = Wappalyzer; @@ -15,25 +15,29 @@ const { CHROMIUM_BIN, CHROMIUM_DATA_DIR, CHROMIUM_WEBSOCKET, CHROMIUM_ARGS } = const chromiumArgs = CHROMIUM_ARGS ? CHROMIUM_ARGS.split(' ') : [ - '--headless', - '--single-process', - '--no-sandbox', - '--no-zygote', - '--disable-gpu', - '--ignore-certificate-errors', - '--allow-running-insecure-content', - '--disable-web-security', - `--user-data-dir=${CHROMIUM_DATA_DIR || '/tmp/chromium'}` - ]; + '--headless', + '--single-process', + '--no-sandbox', + '--no-zygote', + '--disable-gpu', + '--ignore-certificate-errors', + '--allow-running-insecure-content', + '--disable-web-security', + `--user-data-dir=${CHROMIUM_DATA_DIR || '/tmp/chromium'}` + ]; const extensions = /^([^.]+$|\.(asp|aspx|cgi|htm|html|jsp|php)$)/; +const categoriesPath = path.resolve(`${__dirname}/../categories.json`); + const categories = JSON.parse( - fs.readFileSync(path.resolve(`${__dirname}/categories.json`)) + fs.readFileSync(categoriesPath) ); let technologies = {}; +const technologiesDir = path.resolve(`${__dirname}/../technologies`); + for (const index of Array(27).keys()) { const character = index ? String.fromCharCode(index + 96) : '_'; @@ -41,7 +45,7 @@ for (const index of Array(27).keys()) { ...technologies, ...JSON.parse( fs.readFileSync( - path.resolve(`${__dirname}/technologies/${character}.json`) + path.resolve(`${technologiesDir}/${character}.json`) ) ) }; @@ -69,17 +73,16 @@ function getJs(page, technologies = Wappalyzer.technologies) { const root = /^[a-z_$][a-z0-9_$]*$/i.test(parts[0]) ? new Function( - `return typeof ${ - parts[0] - } === 'undefined' ? undefined : ${parts.shift()}` - )() + `return typeof ${parts[0] + } === 'undefined' ? undefined : ${parts.shift()}` + )() : window; const value = parts.reduce( (value, method) => value && - value instanceof Object && - Object.prototype.hasOwnProperty.call(value, method) + value instanceof Object && + Object.prototype.hasOwnProperty.call(value, method) ? value[method] : '__UNDEFINED__', root || '__UNDEFINED__' @@ -701,7 +704,7 @@ class Site { if ( _url.hostname.replace(/^www\./, '') === - this.originalUrl.hostname.replace(/^www\./, '') || + this.originalUrl.hostname.replace(/^www\./, '') || (redirects < 3 && !this.options.noRedirect) ) { url = _url; @@ -783,696 +786,320 @@ class Site { const rows = html.length / this.options.htmlMaxCols; for (let i = 0; i < rows; i += 1) { - if ( - i < this.options.htmlMaxRows / 2 || - i > rows - this.options.htmlMaxRows / 2 - ) { - batches.push( - html.slice( - i * this.options.htmlMaxCols, - (i + 1) * this.options.htmlMaxCols - ) - ); + if (batches.length >= this.options.htmlMaxRows) { + break; } + + batches.push( + html.slice( + i * this.options.htmlMaxCols, + (i + 1) * this.options.htmlMaxCols + ) + ); } html = batches.join('\n'); } - let links = []; - let text = ''; - let css = ''; - let scriptSrc = []; - let scripts = []; - let meta = []; - let js = []; - let dom = []; - - if (html) { - await Promise.all([ - (async () => { - // Links - links = !this.options.recursive - ? [] - : await this.promiseTimeout( - ( - await this.promiseTimeout( - page.evaluateHandle(() => - Array.from(document.getElementsByTagName('a')).map( - ({ - hash, - hostname, - href, - pathname, - protocol, - rel - }) => ({ - hash, - hostname, - href, - pathname, - protocol, - rel - }) - ) - ), - { jsonValue: () => [] }, - 'Timeout (links)' - ) - ).jsonValue(), - [], - 'Timeout (links)' - ); - })(), - (async () => { - // Text - text = await this.promiseTimeout( - ( - await this.promiseTimeout( - page.evaluateHandle( - () => document.body && document.body.innerText - ), - { jsonValue: () => '' }, - 'Timeout (text)' - ) - ).jsonValue(), - '', - 'Timeout (text)' - ); - })(), - (async () => { - // CSS - css = await this.promiseTimeout( - ( - await this.promiseTimeout( - page.evaluateHandle((maxRows) => { - const css = []; - - try { - if (!document.styleSheets.length) { - return ''; - } - - for (const sheet of Array.from(document.styleSheets)) { - for (const rules of Array.from(sheet.cssRules)) { - css.push(rules.cssText); - - if (css.length >= maxRows) { - break; - } - } - } - } catch (error) { - return ''; - } - - return css.join('\n'); - }, this.options.htmlMaxRows), - { jsonValue: () => '' }, - 'Timeout (css)' - ) - ).jsonValue(), - '', - 'Timeout (css)' - ); - })(), - (async () => { - // Script tags - [scriptSrc, scripts] = await this.promiseTimeout( - ( - await this.promiseTimeout( - page.evaluateHandle(() => { - const nodes = Array.from( - document.getElementsByTagName('script') - ); - - return [ - nodes - .filter( - ({ src }) => - src && !src.startsWith('data:text/javascript;') - ) - .map(({ src }) => src), - nodes - .map((node) => node.textContent) - .filter((script) => script) - ]; - }), - { jsonValue: () => [] }, - 'Timeout (scripts)' - ) - ).jsonValue(), - [], - 'Timeout (scripts)' - ); - })(), - (async () => { - // Meta tags - meta = await this.promiseTimeout( - ( - await this.promiseTimeout( - page.evaluateHandle(() => - Array.from(document.querySelectorAll('meta')).reduce( - (metas, meta) => { - const key = - meta.getAttribute('name') || - meta.getAttribute('property'); - - if (key) { - metas[key.toLowerCase()] = - metas[key.toLowerCase()] || []; - - metas[key.toLowerCase()].push( - meta.getAttribute('content') - ); - } - - return metas; - }, - {} - ) - ), - { jsonValue: () => [] }, - 'Timeout (meta)' - ) - ).jsonValue(), - [], - 'Timeout (meta)' - ); - })(), - (async () => { - // JavaScript - js = this.options.noScripts - ? [] - : await this.promiseTimeout(getJs(page), [], 'Timeout (js)'); - })(), - (async () => { - // DOM - dom = await this.promiseTimeout(getDom(page), [], 'Timeout (dom)'); - })() - ]); - } - - this.cache[url.href] = { - page, - html, - text, - cookies, - scripts, - scriptSrc, - meta - }; - - await this.onDetect( - url, - [ - analyzeDom(dom), - analyzeJs(js), - analyze({ - url, - cookies, - html, - text, - css, - scripts, - scriptSrc, - meta - }) - ].flat() - ); - - const reducedLinks = Array.prototype.reduce.call( - links, - (results, link) => { - if ( - results && - Object.prototype.hasOwnProperty.call( - Object.getPrototypeOf(results), - 'push' - ) && - link.protocol && - link.protocol.match(/https?:/) && - link.hostname === url.hostname && - extensions.test(link.pathname.slice(-5)) - ) { - results.push(new URL(link.href.split('#')[0])); - } - - return results; - }, - [] - ); - - await this.emit('goto', { - page, - url, - links: reducedLinks, - ...this.cache[url.href] - }); - - page.__closed = true; + // CSS + let css = []; try { - await page.close(); + const styles = await page.evaluate(() => + Array.from(document.styleSheets) + .map((styleSheet) => { + try { + return Array.from(styleSheet.cssRules) + .map(({ cssText }) => cssText) + .join(' '); + } catch (error) { + // Continue + } + }) + .filter(Boolean) + ); - this.log(`Page closed (${url})`); + css = styles.map((style) => style.slice(0, 1000000)); } catch (error) { - // Continue + error.message += ` (${url})`; + + this.error(error); } - return reducedLinks; - } catch (error) { - page.__closed = true; + // Script tags + let scriptSrc = []; try { - await page.close(); - - this.log(`Page closed (${url})`); - } catch (error) { - // Continue - } - - if (error.message.includes('net::ERR_NAME_NOT_RESOLVED')) { - const newError = new Error( - `Hostname could not be resolved (${url.hostname})` + scriptSrc = await page.evaluate(() => + Array.from(document.scripts) + .map(({ src }) => src) + .filter(Boolean) ); + } catch (error) { + error.message += ` (${url})`; - newError.code = 'WAPPALYZER_DNS_ERROR'; - - throw newError; - } - - if ( - error.constructor.name === 'TimeoutError' || - error.code === 'PROMISE_TIMEOUT_ERROR' - ) { - error.code = 'WAPPALYZER_TIMEOUT_ERROR'; + this.error(error); } - error.message += ` (${url})`; + // Meta tags + let meta = {}; - throw error; - } - } + try { + meta = await page.evaluate(() => + Array.from(document.querySelectorAll('meta')).reduce((meta, node) => { + const name = + node.getAttribute('name') || + node.getAttribute('property') || + node.getAttribute('itemprop'); + const content = node.getAttribute('content'); + + if (name && content) { + meta[name.toLowerCase()] = meta[name.toLowerCase()] || []; + + meta[name.toLowerCase()].push(content.slice(0, 1000000)); + } - async newPage(url) { - if (!this.browser) { - await this.initDriver(); + return meta; + }, {}) + ); + } catch (error) { + error.message += ` (${url})`; - if (!this.browser) { - throw new Error('Browser closed'); + this.error(error); } - } - - let page; - - try { - page = await this.browser.newPage(); - if (!page || page.isClosed()) { - throw new Error('Page did not open'); - } - } catch (error) { - error.message += ` (${url})`; + const js = await this.promiseTimeout( + getJs(page), + [], + 'Timeout (js evaluation)' + ); - this.error(error); + const dom = await this.promiseTimeout( + getDom(page), + [], + 'Timeout (dom evaluation)' + ); - await this.initDriver(); + await this.onDetect(url, analyze({ html, css, scriptSrc, meta })); - page = await this.browser.newPage(); - } + await this.onDetect(url, analyzeJs(js)); - this.pages.push(page); + await this.onDetect(url, analyzeDom(dom)); - page.setJavaScriptEnabled(!this.options.noScripts); + await this.emit('goto', { page, url, html, cookies, js, dom }); - page.setDefaultTimeout(this.options.maxWait); + // Crawler + if (this.options.recursive) { + const links = await page.evaluate(() => + Array.from(document.querySelectorAll('a[href]')).map( + ({ href }) => href + ) + ); - await page.setUserAgent(this.options.userAgent); + await Promise.all( + links.map(async (link) => { + let _url; - page.on('dialog', (dialog) => dialog.dismiss()); + try { + _url = new URL(link); + } catch (error) { + return; + } - page.on('error', (error) => { - error.message += ` (${url})`; + // Must be on the same domain + if ( + _url.hostname.replace(/^www\./, '') === + url.hostname.replace(/^www\./, '') && + _url.protocol.startsWith('http') && + extensions.test(_url.pathname) + ) { + const redirects = Object.keys(this.analyzedUrls).length - 1; - this.error(error); - }); + if (redirects < this.options.maxUrls) { + const depth = url.pathname.split('/').length - 1; - return page; - } + if (depth < this.options.maxDepth) { + await this.goto(_url); + } + } + } + }) + ); + } + } catch (error) { + if (error.code !== 'WAPPALYZER_PAGE_EMPTY') { + error.message += ` (${url})`; - async analyze(url = this.originalUrl, index = 1, depth = 1) { - if (this.options.recursive) { - await sleep(this.options.delay * index); + this.error(error); + } + } finally { + try { + await page.close(); + } catch { + // Continue + } } + } - await Promise.allSettled([ - (async () => { - try { - const links = ((await this.goto(url)) || []).filter( - ({ href }) => !this.analyzedUrls[href] - ); - - if ( - links.length && - this.options.recursive && - Object.keys(this.analyzedUrls).length < this.options.maxUrls && - depth < this.options.maxDepth - ) { - await this.batch( - links.slice( - 0, - this.options.maxUrls - Object.keys(this.analyzedUrls).length - ), - depth + 1 - ); - } - } catch (error) { - this.analyzedUrls[url.href] = { - status: this.analyzedUrls[url.href]?.status || 0, - error: error.message || error.toString() - }; + async analyze() { + await this.goto(this.originalUrl); - error.message += ` (${url})`; + if (this.options.probe) { + await this.probe(); + } - this.error(error); - } - })(), - (async () => { - if (this.options.probe && !this.probed) { - this.probed = true; + const resolved = resolve(this.detections); - await this.probe(url); - } - })() - ]); + return { + urls: this.analyzedUrls, + technologies: resolved.map((resolved) => { + const technology = Wappalyzer.technologies.find( + ({ name }) => name === resolved.name + ); - const patterns = this.options.extended - ? this.detections.reduce( - ( - patterns, - { - technology: { name, implies, excludes }, - pattern: { regex, value, match, confidence, type, version } - } - ) => { - patterns[name] = patterns[name] || []; - - patterns[name].push({ - type, - regex: regex.source, - value: String(value).length <= 250 ? value : null, - match: match.length <= 250 ? match : null, - confidence, - version, - implies: implies.map(({ name }) => name), - excludes: excludes.map(({ name }) => name) - }); + const { name, slug, description, confidence, version, icon, website } = + technology; - return patterns; - }, - {} - ) - : undefined; + const categories = technology.categories.map((id) => + Wappalyzer.categories.find((category) => category.id === id) + ); - const results = { - urls: this.analyzedUrls, - technologies: resolve(this.detections).map( - ({ + return { slug, name, - description, + description: description || null, confidence, - version, + version: resolved.version || null, icon, website, - cpe, - categories, - rootPath - }) => ({ - slug, - name, - description, - confidence, - version: version || null, - icon, - website, - cpe, + cpe: technology.cpe || null, categories: categories.map(({ id, slug, name }) => ({ id, slug, name })), - rootPath - }) - ), - patterns + rootPath: resolved.rootPath || undefined + }; + }) }; - - await this.emit('analyze', results); - - return results; } - async probe(url) { - const paths = [ - { - type: 'robots', - path: '/robots.txt' - } - ]; - - if (this.options.probe === 'full') { - Wappalyzer.technologies - .filter(({ probe }) => Object.keys(probe).length) - .forEach((technology) => { - paths.push( - ...Object.keys(technology.probe).map((path) => ({ - type: 'probe', - path, - technology - })) - ); - }); - } + async probe() { + this.log(`Probe ${this.originalUrl}`); - // DNS - const records = {}; - const resolveDns = (func, hostname) => { - return this.promiseTimeout( - func(hostname).catch((error) => { - if (error.code !== 'ENODATA') { - error.message += ` (${url})`; - - this.error(error); - } + this.probed = true; - return []; - }), - [], - 'Timeout (dns)', - this.options.fast - ? Math.min(this.options.maxWait, 15000) - : this.options.maxWait - ); + // DNS + const dnsResolvers = { + txt: dns.resolveTxt(this.originalUrl.hostname), + mx: dns.resolveMx(this.originalUrl.hostname) }; - const domain = url.hostname.replace(/^www\./, ''); - - await Promise.allSettled([ - // Static files - ...paths.map(async ({ type, path, technology }, index) => { - try { - await sleep(this.options.delay * index); - - const body = await get(new URL(path, url.href), { - userAgent: this.options.userAgent, - timeout: Math.min(this.options.maxWait, 3000) - }); + const dnsResults = await Promise.allSettled( + Object.values(dnsResolvers) + ).then((results) => + results.reduce( + (results, result, index) => ({ + ...results, + [Object.keys(dnsResolvers)[index]]: + result.status === 'fulfilled' ? result.value : [] + }), + {} + ) + ); - this.log(`Probe ok (${path})`); + const dnsRecords = { + txt: dnsResults.txt.flat(), + mx: dnsResults.mx.map(({ exchange }) => exchange) + }; - const text = body.slice(0, 100000); + await this.onDetect(this.originalUrl, analyze({ dns: dnsRecords })); - await this.onDetect( - url, - analyze( - { - [type]: path ? { [path]: [text] } : text - }, - technology && [technology] - ) - ); - } catch (error) { - this.error(`Probe failed (${path}): ${error.message || error}`); - } - }), - // DNS - - new Promise(async (resolve, reject) => { - [records.cname, records.ns, records.mx, records.txt, records.soa] = - await Promise.all([ - resolveDns(dns.resolveCname, url.hostname), - resolveDns(dns.resolveNs, domain), - resolveDns(dns.resolveMx, domain), - resolveDns(dns.resolveTxt, domain), - resolveDns(dns.resolveSoa, domain) - ]); - - const dnsRecords = Object.keys(records).reduce((dns, type) => { - dns[type] = dns[type] || []; - - Array.prototype.push.apply( - dns[type], - Array.isArray(records[type]) - ? records[type].map((value) => { - return typeof value === 'object' - ? Object.values(value).join(' ') - : value; - }) - : [Object.values(records[type]).join(' ')] - ); + await this.emit('probe', { dns: dnsRecords }); - return dns; - }, {}); + // Deeper paths + const paths = Wappalyzer.technologies + .filter(({ probe }) => probe) + .reduce((paths, { probe }) => { + probe.paths.forEach((path) => { + path = path.replace(/^\//, ''); - this.log( - `Probe DNS ok: (${Object.values(dnsRecords).flat().length} records)` - ); + if (!paths.includes(path)) { + paths.push(path); + } + }); - await this.onDetect(url, analyze({ dns: dnsRecords })); + return paths; + }, []); - resolve(); - }) - ]); - } + const chunks = []; - async batch(links, depth, batch = 0) { - if (links.length === 0) { - return; + for (let i = 0; i < paths.length; i += this.options.batchSize) { + chunks.push(paths.slice(i, i + this.options.batchSize)); } - const batched = links.splice(0, this.options.batchSize); + for (const chunk of chunks) { + await Promise.all( + chunk.map(async (path) => { + const url = new URL(path, this.originalUrl); - await Promise.allSettled( - batched.map((link, index) => this.analyze(link, index, depth)) - ); + try { + const body = await get.call(this, url, { + userAgent: this.options.userAgent + }); - await this.batch(links, depth, batch + 1); + await this.onDetect(url, analyze({ probe: { [path]: body } })); + } catch (error) { + // Continue + } + }) + ); + } } - async onDetect(url, detections = []) { - this.detections = this.detections - .concat(detections) - .filter( - ( - { technology: { name }, pattern: { regex }, version }, - index, - detections - ) => - detections.findIndex( - ({ - technology: { name: _name }, - pattern: { regex: _regex }, - version: _version - }) => - name === _name && - version === _version && - (!regex || regex.toString() === _regex.toString()) - ) === index - ); + async onDetect(url, detections) { + if (detections.length) { + detections.forEach((detection) => { + detection.rootPath = + url.pathname === '/' || + url.pathname === + (this.analyzedUrls[this.originalUrl.href] || {}).pathname; - // Track if technology was identified on website's root path - detections.forEach(({ technology: { name } }) => { - const detection = this.detections.find( - ({ technology: { name: _name } }) => name === _name - ); + this.log( + `Detected ${detection.name}${detection.version ? ` ${detection.version}` : '' + } (confidence ${detection.confidence}%)`, + 'driver' + ); - detection.rootPath = detection.rootPath || url.pathname === '/'; - }); + this.emit('detected', { detection }); + }); - if (this.cache[url.href]) { - const resolved = resolve(this.detections); + this.detections = this.detections.concat(detections); + } + } - const requires = [ - ...Wappalyzer.requires.filter(({ name }) => - resolved.some(({ name: _name }) => _name === name) - ), - ...Wappalyzer.categoryRequires.filter(({ categoryId }) => - resolved.some(({ categories }) => - categories.some(({ id }) => id === categoryId) - ) - ) - ]; + async newPage(url) { + if (!this.browser) { + await this.initDriver(); + } - await Promise.allSettled( - requires.map(async ({ name, categoryId, technologies }) => { - const id = categoryId - ? `category:${categoryId}` - : `technology:${name}`; + const page = await this.browser.newPage(); - this.analyzedRequires[url.href] = - this.analyzedRequires[url.href] || []; + this.pages.push(page); - if (!this.analyzedRequires[url.href].includes(id)) { - this.analyzedRequires[url.href].push(id); + page.on('close', () => { + page.__closed = true; - const { page, cookies, html, text, css, scripts, scriptSrc, meta } = - this.cache[url.href]; + this.pages = this.pages.filter((_page) => _page !== page); + }); - const js = await this.promiseTimeout( - getJs(page, technologies), - [], - 'Timeout (js)' - ); - const dom = await this.promiseTimeout( - getDom(page, technologies), - [], - 'Timeout (dom)' - ); + await page.setUserAgent(this.options.userAgent); - await this.onDetect( - url, - [ - analyzeDom(dom, technologies), - analyzeJs(js, technologies), - await analyze( - { - url, - cookies, - html, - text, - css, - scripts, - scriptSrc, - meta - }, - technologies - ) - ].flat() - ); - } - }) - ); + if (this.options.noScripts) { + await page.setJavaScriptEnabled(false); } - } - - async destroy() { - await Promise.allSettled( - this.pages.map(async (page) => { - if (page) { - page.__closed = true; - - try { - await page.close(); - } catch (error) { - // Continue - } - } - }) - ); - this.log('Site closed'); + return page; } } diff --git a/src/drivers/npm/Dockerfile b/src/drivers/npm/Dockerfile deleted file mode 100644 index ebebe2237..000000000 --- a/src/drivers/npm/Dockerfile +++ /dev/null @@ -1,31 +0,0 @@ -FROM node:14-alpine - -MAINTAINER Wappalyzer - -ENV WAPPALYZER_ROOT /opt/wappalyzer -ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD true -ENV CHROMIUM_BIN /usr/bin/chromium-browser - -RUN apk update && apk add -u --no-cache \ - nodejs \ - udev \ - chromium \ - ttf-freefont \ - yarn - -RUN mkdir -p "$WAPPALYZER_ROOT/browsers" - -WORKDIR "$WAPPALYZER_ROOT" - -COPY technologies ./technologies -COPY \ - cli.js \ - categories.json \ - driver.js \ - package.json \ - wappalyzer.js \ - yarn.lock ./ - -RUN yarn install - -ENTRYPOINT ["node", "cli.js"] diff --git a/src/drivers/npm/package.json b/src/drivers/npm/package.json deleted file mode 100644 index f9721e5b3..000000000 --- a/src/drivers/npm/package.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "name": "wappalyzer", - "description": "Identify technology on websites", - "keywords": [ - "analyze", - "identify", - "detect", - "detector", - "technology", - "cms", - "framework", - "library", - "software" - ], - "homepage": "https://www.wappalyzer.com/", - "version": "6.10.66", - "author": "Wappalyzer", - "license": "GPL-3.0", - "repository": { - "type": "git", - "url": "https://github.com/wappalyzer/wappalyzer" - }, - "funding": [ - { - "url": "https://github.com/sponsors/aliasio" - } - ], - "main": "driver.js", - "files": [ - "cli.js", - "categories.json", - "driver.js", - "index.js", - "technologies/*", - "wappalyzer.js" - ], - "bin": { - "wappalyzer": "./cli.js" - }, - "dependencies": { - "puppeteer": "~19.7.0" - }, - "engines": { - "node": ">=16" - } -} \ No newline at end of file diff --git a/src/drivers/npm/yarn.lock b/src/drivers/npm/yarn.lock deleted file mode 100644 index 331648635..000000000 --- a/src/drivers/npm/yarn.lock +++ /dev/null @@ -1,525 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@babel/code-frame@^7.0.0": - version "7.21.4" - resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz" - integrity sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g== - dependencies: - "@babel/highlight" "^7.18.6" - -"@babel/helper-validator-identifier@^7.18.6": - version "7.19.1" - resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz" - integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w== - -"@babel/highlight@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz" - integrity sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g== - dependencies: - "@babel/helper-validator-identifier" "^7.18.6" - chalk "^2.0.0" - js-tokens "^4.0.0" - -"@types/node@*": - version "18.15.11" - resolved "https://registry.npmjs.org/@types/node/-/node-18.15.11.tgz" - integrity sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q== - -"@types/yauzl@^2.9.1": - version "2.10.0" - resolved "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.0.tgz" - integrity sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw== - dependencies: - "@types/node" "*" - -agent-base@6: - 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" - -ansi-styles@^3.2.1: - 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" - -argparse@^2.0.1: - version "2.0.1" - resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz" - integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== - -balanced-match@^1.0.0: - version "1.0.2" - resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz" - integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== - -base64-js@^1.3.1: - version "1.5.1" - resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz" - integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== - -bl@^4.0.3: - 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" - -brace-expansion@^2.0.1: - version "2.0.1" - resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz" - integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== - dependencies: - balanced-match "^1.0.0" - -buffer-crc32@~0.2.3: - version "0.2.13" - resolved "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz" - integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ== - -buffer@^5.2.1, buffer@^5.5.0: - version "5.7.1" - resolved "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz" - integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== - dependencies: - base64-js "^1.3.1" - ieee754 "^1.1.13" - -callsites@^3.0.0: - version "3.1.0" - resolved "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz" - integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== - -chalk@^2.0.0: - version "2.4.2" - resolved "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz" - integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== - dependencies: - ansi-styles "^3.2.1" - escape-string-regexp "^1.0.5" - supports-color "^5.3.0" - -chownr@^1.1.1: - version "1.1.4" - resolved "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz" - integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== - -chromium-bidi@0.4.5: - version "0.4.5" - resolved "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.5.tgz" - integrity sha512-rkav9YzRfAshSTG3wNXF7P7yNiI29QAo1xBXElPoCoSQR5n20q3cOyVhDv6S7+GlF/CJ/emUxlQiR0xOPurkGg== - dependencies: - mitt "3.0.0" - -color-convert@^1.9.0: - 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" - -color-name@1.1.3: - version "1.1.3" - resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz" - integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== - -cosmiconfig@8.1.0: - version "8.1.0" - resolved "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.1.0.tgz" - integrity sha512-0tLZ9URlPGU7JsKq0DQOQ3FoRsYX8xDZ7xMiATQfaiGMz7EHowNkbU9u1coAOmnh9p/1ySpm0RB3JNWRXM5GCg== - dependencies: - import-fresh "^3.2.1" - js-yaml "^4.1.0" - parse-json "^5.0.0" - path-type "^4.0.0" - -cross-fetch@3.1.5: - version "3.1.5" - resolved "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz" - integrity sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw== - dependencies: - node-fetch "2.6.7" - -debug@^4.1.1, debug@4, debug@4.3.4: - version "4.3.4" - resolved "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz" - integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== - dependencies: - ms "2.1.2" - -devtools-protocol@*, devtools-protocol@0.0.1094867: - version "0.0.1094867" - resolved "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1094867.tgz" - integrity sha512-pmMDBKiRVjh0uKK6CT1WqZmM3hBVSgD+N2MrgyV1uNizAZMw4tx6i/RTc+/uCsKSCmg0xXx7arCP/OFcIwTsiQ== - -end-of-stream@^1.1.0, end-of-stream@^1.4.1: - version "1.4.4" - resolved "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz" - integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== - dependencies: - once "^1.4.0" - -error-ex@^1.3.1: - version "1.3.2" - resolved "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz" - integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== - dependencies: - is-arrayish "^0.2.1" - -escape-string-regexp@^1.0.5: - version "1.0.5" - resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz" - integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== - -extract-zip@2.0.1: - version "2.0.1" - resolved "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz" - integrity sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg== - dependencies: - debug "^4.1.1" - get-stream "^5.1.0" - yauzl "^2.10.0" - optionalDependencies: - "@types/yauzl" "^2.9.1" - -fd-slicer@~1.1.0: - 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" - -fs-constants@^1.0.0: - 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== - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" - integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== - -get-stream@^5.1.0: - version "5.2.0" - resolved "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz" - integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== - dependencies: - pump "^3.0.0" - -glob@^9.2.0: - version "9.3.4" - resolved "https://registry.npmjs.org/glob/-/glob-9.3.4.tgz" - integrity sha512-qaSc49hojMOv1EPM4EuyITjDSgSKI0rthoHnvE81tcOi1SCVndHko7auqxdQ14eiQG2NDBJBE86+2xIrbIvrbA== - dependencies: - fs.realpath "^1.0.0" - minimatch "^8.0.2" - minipass "^4.2.4" - path-scurry "^1.6.1" - -has-flag@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz" - integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== - -https-proxy-agent@5.0.1: - 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" - -ieee754@^1.1.13: - version "1.2.1" - resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz" - integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== - -import-fresh@^3.2.1: - version "3.3.0" - resolved "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz" - integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== - dependencies: - parent-module "^1.0.0" - resolve-from "^4.0.0" - -inherits@^2.0.3, inherits@^2.0.4: - version "2.0.4" - resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -is-arrayish@^0.2.1: - version "0.2.1" - resolved "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz" - integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== - -js-tokens@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz" - integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== - -js-yaml@^4.1.0: - version "4.1.0" - resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz" - integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== - dependencies: - argparse "^2.0.1" - -json-parse-even-better-errors@^2.3.0: - version "2.3.1" - resolved "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz" - integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== - -lines-and-columns@^1.1.6: - version "1.2.4" - resolved "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz" - integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== - -lru-cache@^7.14.1: - version "7.18.3" - resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz" - integrity sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA== - -minimatch@^8.0.2: - version "8.0.2" - resolved "https://registry.npmjs.org/minimatch/-/minimatch-8.0.2.tgz" - integrity sha512-ikHGF67ODxj7vS5NKU2wvTsFLbExee+KXVCnBWh8Cg2hVJfBMQIrlo50qru/09E0EifjnU8dZhJ/iHhyXJM6Mw== - dependencies: - brace-expansion "^2.0.1" - -minipass@^4.0.2, minipass@^4.2.4: - version "4.2.5" - resolved "https://registry.npmjs.org/minipass/-/minipass-4.2.5.tgz" - integrity sha512-+yQl7SX3bIT83Lhb4BVorMAHVuqsskxRdlmO9kTpyukp8vsm2Sn/fUOV9xlnG8/a5JsypJzap21lz/y3FBMJ8Q== - -mitt@3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/mitt/-/mitt-3.0.0.tgz" - integrity sha512-7dX2/10ITVyqh4aOSVI9gdape+t9l2/8QxHrFmUXu4EEUpdlxl6RudZUPZoc+zuY2hk1j7XxVroIVIan/pD/SQ== - -mkdirp-classic@^0.5.2: - version "0.5.3" - resolved "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz" - integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== - -ms@2.1.2: - version "2.1.2" - resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz" - integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== - -node-fetch@2.6.7: - version "2.6.7" - resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz" - integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== - dependencies: - whatwg-url "^5.0.0" - -once@^1.3.1, once@^1.4.0: - 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" - -parent-module@^1.0.0: - version "1.0.1" - resolved "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz" - integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== - dependencies: - callsites "^3.0.0" - -parse-json@^5.0.0: - version "5.2.0" - resolved "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz" - integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== - dependencies: - "@babel/code-frame" "^7.0.0" - error-ex "^1.3.1" - json-parse-even-better-errors "^2.3.0" - lines-and-columns "^1.1.6" - -path-scurry@^1.6.1: - version "1.6.3" - resolved "https://registry.npmjs.org/path-scurry/-/path-scurry-1.6.3.tgz" - integrity sha512-RAmB+n30SlN+HnNx6EbcpoDy9nwdpcGPnEKrJnu6GZoDWBdIjo1UQMVtW2ybtC7LC2oKLcMq8y5g8WnKLiod9g== - dependencies: - lru-cache "^7.14.1" - minipass "^4.0.2" - -path-type@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz" - integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== - -pend@~1.2.0: - version "1.2.0" - resolved "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz" - integrity sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg== - -progress@2.0.3: - version "2.0.3" - resolved "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz" - integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== - -proxy-from-env@1.1.0: - version "1.1.0" - resolved "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz" - integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== - -pump@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz" - integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== - dependencies: - end-of-stream "^1.1.0" - once "^1.3.1" - -puppeteer-core@19.7.5: - version "19.7.5" - resolved "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-19.7.5.tgz" - integrity sha512-EJuNha+SxPfaYFbkoWU80H3Wb1SiQH5fFyb2xdbWda0ziax5mhV63UMlqNfPeTDIWarwtR4OIcq/9VqY8HPOsg== - dependencies: - chromium-bidi "0.4.5" - cross-fetch "3.1.5" - debug "4.3.4" - devtools-protocol "0.0.1094867" - extract-zip "2.0.1" - https-proxy-agent "5.0.1" - proxy-from-env "1.1.0" - rimraf "4.4.0" - tar-fs "2.1.1" - unbzip2-stream "1.4.3" - ws "8.12.1" - -puppeteer@~19.7.0: - version "19.7.5" - resolved "https://registry.npmjs.org/puppeteer/-/puppeteer-19.7.5.tgz" - integrity sha512-UqD8K+yaZa6/hwzP54AATCiHrEYGGxzQcse9cZzrtsVGd8wT0llCdYhsBp8n+zvnb1ofY0YFgI3TYZ/MiX5uXQ== - dependencies: - cosmiconfig "8.1.0" - https-proxy-agent "5.0.1" - progress "2.0.3" - proxy-from-env "1.1.0" - puppeteer-core "19.7.5" - -readable-stream@^3.1.1, readable-stream@^3.4.0: - 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" - -resolve-from@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz" - integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== - -rimraf@4.4.0: - version "4.4.0" - resolved "https://registry.npmjs.org/rimraf/-/rimraf-4.4.0.tgz" - integrity sha512-X36S+qpCUR0HjXlkDe4NAOhS//aHH0Z+h8Ckf2auGJk3PTnx5rLmrHkwNdbVQuCSUhOyFrlRvFEllZOYE+yZGQ== - dependencies: - glob "^9.2.0" - -safe-buffer@~5.2.0: - version "5.2.1" - resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" - integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== - -string_decoder@^1.1.1: - version "1.3.0" - resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - -supports-color@^5.3.0: - version "5.5.0" - resolved "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz" - integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== - dependencies: - has-flag "^3.0.0" - -tar-fs@2.1.1: - version "2.1.1" - resolved "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz" - integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng== - dependencies: - chownr "^1.1.1" - mkdirp-classic "^0.5.2" - pump "^3.0.0" - tar-stream "^2.1.4" - -tar-stream@^2.1.4: - 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" - -through@^2.3.8: - version "2.3.8" - resolved "https://registry.npmjs.org/through/-/through-2.3.8.tgz" - integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== - -tr46@~0.0.3: - version "0.0.3" - resolved "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz" - integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== - -unbzip2-stream@1.4.3: - 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" - -util-deprecate@^1.0.1: - version "1.0.2" - resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" - integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== - -webidl-conversions@^3.0.0: - version "3.0.1" - resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz" - integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== - -whatwg-url@^5.0.0: - version "5.0.0" - resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz" - integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== - dependencies: - tr46 "~0.0.3" - webidl-conversions "^3.0.0" - -wrappy@1: - version "1.0.2" - resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" - integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== - -ws@8.12.1: - version "8.12.1" - resolved "https://registry.npmjs.org/ws/-/ws-8.12.1.tgz" - integrity sha512-1qo+M9Ba+xNhPB+YTWUlK6M17brTut5EXbcBaMRN5pH5dFrXz7lzz1ChFSUq3bOUl8yEvSenhHmYUNJxFzdJew== - -yauzl@^2.10.0: - 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" From 4dbe2d7a2043fb16023a84bfd0c0dd9630a5c276 Mon Sep 17 00:00:00 2001 From: Max Ostapenko <1611259+max-ostapenko@users.noreply.github.com> Date: Tue, 14 Jul 2026 14:15:46 +0200 Subject: [PATCH 5/6] Potential fix for pull request finding 'Unused variable, import, function or class' Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> --- src/drivers/driver.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/drivers/driver.js b/src/drivers/driver.js index db06df98b..3a9fc51c8 100644 --- a/src/drivers/driver.js +++ b/src/drivers/driver.js @@ -957,7 +957,7 @@ class Site { ({ name }) => name === resolved.name ); - const { name, slug, description, confidence, version, icon, website } = + const { name, slug, description, confidence, icon, website } = technology; const categories = technology.categories.map((id) => From d05dab0a0d505b6197a728acb38a919ad4def293 Mon Sep 17 00:00:00 2001 From: Max Ostapenko <1611259+max-ostapenko@users.noreply.github.com> Date: Tue, 14 Jul 2026 14:20:21 +0200 Subject: [PATCH 6/6] lint Signed-off-by: Max Ostapenko <1611259+max-ostapenko@users.noreply.github.com> --- src/drivers/driver.js | 48 +++++++++++++++++++++---------------------- 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/src/drivers/driver.js b/src/drivers/driver.js index 3a9fc51c8..7346d78ae 100644 --- a/src/drivers/driver.js +++ b/src/drivers/driver.js @@ -15,24 +15,22 @@ const { CHROMIUM_BIN, CHROMIUM_DATA_DIR, CHROMIUM_WEBSOCKET, CHROMIUM_ARGS } = const chromiumArgs = CHROMIUM_ARGS ? CHROMIUM_ARGS.split(' ') : [ - '--headless', - '--single-process', - '--no-sandbox', - '--no-zygote', - '--disable-gpu', - '--ignore-certificate-errors', - '--allow-running-insecure-content', - '--disable-web-security', - `--user-data-dir=${CHROMIUM_DATA_DIR || '/tmp/chromium'}` - ]; + '--headless', + '--single-process', + '--no-sandbox', + '--no-zygote', + '--disable-gpu', + '--ignore-certificate-errors', + '--allow-running-insecure-content', + '--disable-web-security', + `--user-data-dir=${CHROMIUM_DATA_DIR || '/tmp/chromium'}` + ]; const extensions = /^([^.]+$|\.(asp|aspx|cgi|htm|html|jsp|php)$)/; const categoriesPath = path.resolve(`${__dirname}/../categories.json`); -const categories = JSON.parse( - fs.readFileSync(categoriesPath) -); +const categories = JSON.parse(fs.readFileSync(categoriesPath)); let technologies = {}; @@ -44,9 +42,7 @@ for (const index of Array(27).keys()) { technologies = { ...technologies, ...JSON.parse( - fs.readFileSync( - path.resolve(`${technologiesDir}/${character}.json`) - ) + fs.readFileSync(path.resolve(`${technologiesDir}/${character}.json`)) ) }; } @@ -73,16 +69,17 @@ function getJs(page, technologies = Wappalyzer.technologies) { const root = /^[a-z_$][a-z0-9_$]*$/i.test(parts[0]) ? new Function( - `return typeof ${parts[0] - } === 'undefined' ? undefined : ${parts.shift()}` - )() + `return typeof ${ + parts[0] + } === 'undefined' ? undefined : ${parts.shift()}` + )() : window; const value = parts.reduce( (value, method) => value && - value instanceof Object && - Object.prototype.hasOwnProperty.call(value, method) + value instanceof Object && + Object.prototype.hasOwnProperty.call(value, method) ? value[method] : '__UNDEFINED__', root || '__UNDEFINED__' @@ -704,7 +701,7 @@ class Site { if ( _url.hostname.replace(/^www\./, '') === - this.originalUrl.hostname.replace(/^www\./, '') || + this.originalUrl.hostname.replace(/^www\./, '') || (redirects < 3 && !this.options.noRedirect) ) { url = _url; @@ -909,7 +906,7 @@ class Site { // Must be on the same domain if ( _url.hostname.replace(/^www\./, '') === - url.hostname.replace(/^www\./, '') && + url.hostname.replace(/^www\./, '') && _url.protocol.startsWith('http') && extensions.test(_url.pathname) ) { @@ -1063,10 +1060,11 @@ class Site { detection.rootPath = url.pathname === '/' || url.pathname === - (this.analyzedUrls[this.originalUrl.href] || {}).pathname; + (this.analyzedUrls[this.originalUrl.href] || {}).pathname; this.log( - `Detected ${detection.name}${detection.version ? ` ${detection.version}` : '' + `Detected ${detection.name}${ + detection.version ? ` ${detection.version}` : '' } (confidence ${detection.confidence}%)`, 'driver' );