diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..8c10e4b --- /dev/null +++ b/.eslintignore @@ -0,0 +1,5 @@ +dist +node_modules +coverage +**/*.js + diff --git a/.eslintrc.cjs b/.eslintrc.cjs new file mode 100644 index 0000000..d0973c9 --- /dev/null +++ b/.eslintrc.cjs @@ -0,0 +1,35 @@ +module.exports = { + root: true, + parser: '@typescript-eslint/parser', + plugins: ['@typescript-eslint'], + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + ], + env: { + node: true, + es2021: true, + jest: true, + }, + parserOptions: { + ecmaVersion: 2021, + sourceType: 'module', + }, + rules: { + '@typescript-eslint/no-explicit-any': 'warn', + '@typescript-eslint/explicit-function-return-type': 'off', + '@typescript-eslint/consistent-type-imports': 'warn', + 'no-console': 'off', + 'no-var': 'error', + preferConst: 'error', + }, + overrides: [ + { + files: ['**/*.js'], + rules: { + '@typescript-eslint/no-var-requires': 'off', + }, + }, + ], +}; + diff --git a/.gitignore b/.gitignore index bd888cd..c43ae43 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,8 @@ /node_modules /.pnp .pnp.js - +contracts/ +test.ts # testing /coverage @@ -16,6 +17,7 @@ .env.development.local .env.test.local .env.production.local +.npmrc npm-debug.log* yarn-debug.log* diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..ef722db --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 CredShields + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 2cccd3d..4a6a2a9 100644 --- a/README.md +++ b/README.md @@ -1 +1,151 @@ -# solidityscan-npm-package \ No newline at end of file +# SolidityScan + +Secure your Solidity smart contracts straight from your terminal or JavaScript code! **SolidityScan** is a lightweight CLI and Node.js library that connects to the [CredShields SolidityScan](https://solidityscan.com) API to identify vulnerabilities, gas optimisations, and other issues in your smart-contract projects. + +--- + +## Table of Contents + +1. [Features](#features) +2. [Installation](#installation) +3. [Getting an API Key](#getting-an-api-key) +4. [CLI Usage](#cli-usage) + * [Scan a local directory](#scan-a-local-directory) + * [Run a local file server](#run-a-local-file-server) +5. [Programmatic Usage](#programmatic-usage) +6. [Examples](#examples) +7. [Contributing](#contributing) +8. [License](#license) + +--- + +## Features + +β€’ πŸ“¦ **Local Directory Scanning** – Zip and upload your local Solidity source code and get instant feedback in the terminal.
+β€’ ⚑ **Real-time Progress** – Live WebSocket updates with an elegant spinner so you always know the scan status.
+β€’ πŸ“‹ **Readable Reports** – Vulnerabilities and severities are displayed in coloured, column-aligned tables, followed by a concise scan summary. +β€’ 🌐 **Local WebSocket File Server** – Spin up a file server for quick web-UI integrations and demos. + +--- + +## Installation + +```bash +# Install globally to use the `solidityscan` CLI +yarn global add solidityscan # or npm install -g solidityscan + +# Add to a project for programmatic use +npm install solidityscan --save # or yarn add solidityscan +``` + +> **Requirement**: Node.js >= 14 + +--- + +## Getting an API Key + +1. Sign up or log in at [solidityscan.com](https://solidityscan.com). +2. Navigate to **API Keys** and generate a new key. +3. Either export it as an environment variable: + +```bash +export SOLIDITYSCAN_API_KEY="YOUR_API_KEY" +``` + +…or pass it as the last argument in each CLI command (see below). + +--- + +## CLI Usage + +After installing globally you will have a `solidityscan` binary in your PATH. +Run `solidityscan --help` to view the brief usage guide. + +--- + +### Scan a Local Directory + +Analyse a local folder containing `.sol` files. The tool packages the Solidity source, uploads it, waits for the scan to finish and prints the results. + +If using with API key in terminal command. + +```bash +solidityscan local /path/to/my/contracts [api-key] [project-name] +``` + +If using with API key in environment variable. + +```bash +solidityscan local /path/to/my/contracts [project-name] +``` + +If no project name is provided, it will default to "LocalScan". + +--- + +### Run a Local File Server + +Start a WebSocket file server to expose your local directory to the SolidityScan web-UI +A unique **identifier** (\`--id\`) is mandatory – this becomes the sub-domain of the temporary public URL. + +```bash +# Serve current directory on default port 9462 (or 9463-9466 if 9462 is busy) +solidityscan -l --id + +# Serve a specific directory +solidityscan -l -p /my/contracts --id + +# Bind to an explicit port instead of the automatic range +solidityscan -l -p ./src --id --port 9000 +``` + +Behaviour notes: + +* If **--port** is omitted the CLI tries to bind to 9462 and, if in use, increments the port up to 5 times (9462-9466). +* The **identifier** should be exactly the string shown on SolidityScan's **Connect to Localhost** page – copy it from the web-UI and use it with `--id`. + +--- + +## Programmatic Usage + +You can also integrate SolidityScan directly into your Node.js scripts or CI pipelines: + +```js +const solidityscan = require("solidityscan"); + +(async () => { + const apiToken = process.env.SOLIDITYSCAN_API_KEY; + + // Scan a local directory (same behaviour as CLI `test`) + await solidityscan.runTests("./contracts", apiToken); +})(); +``` + +Available exported helpers: + +* `runTests(directoryPath, apiToken)` +* `scan()` – executes the CLI with current `process.argv` (internally used by the binary). + +--- + +## Examples + +You can find full working examples inside the [`examples/`](https://github.com/Credshields/solidityscan-npm-package/tree/main/examples) directory (coming soon). + +--- + +## Contributing + +1. Fork the repo and create your feature branch: `git checkout -b feat/awesome-feature`. +2. Install dependencies: `npm install`. +3. Run the tests: `npm test`. +4. Commit your changes and push: `git push origin feat/awesome-feature`. +5. Open a pull request – we love to review! + +Please adhere to the existing code style and add unit tests for any new logic. + +--- + +## License + +This project is licensed under the **ISC** license – see the [LICENSE](LICENSE) file for details. \ No newline at end of file diff --git a/bin/solidityscan.js b/bin/solidityscan.js old mode 100644 new mode 100755 index 78251c3..6aa5fd9 --- a/bin/solidityscan.js +++ b/bin/solidityscan.js @@ -1,4 +1,4 @@ -#!/usr/bin/env node - -// Simple forwarder to the real CLI implementation -require("../src/cli").scan(); \ No newline at end of file +#!/usr/bin/env node + +// Forwarder to compiled CLI implementation +require("../dist/src/cli").scan(); \ No newline at end of file diff --git a/dist/index.d.ts b/dist/index.d.ts new file mode 100644 index 0000000..0f5b5f5 --- /dev/null +++ b/dist/index.d.ts @@ -0,0 +1,10 @@ +import * as api from "./src/api"; +import * as cli from "./src/cli"; +export declare const projectScan: typeof api.projectScan; +export declare const generateReport: typeof api.generateReport; +export declare const quickScanProject: typeof api.quickScanProject; +export declare const quickScanContract: typeof api.quickScanContract; +export declare const contractScan: typeof api.contractScan; +export declare const analyseProject: typeof api.analyzeProject; +export declare const runTests: typeof api.runTests; +export declare const scan: typeof cli.scan; diff --git a/dist/index.js b/dist/index.js new file mode 100644 index 0000000..1596a74 --- /dev/null +++ b/dist/index.js @@ -0,0 +1,47 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || (function () { + var ownKeys = function(o) { + ownKeys = Object.getOwnPropertyNames || function (o) { + var ar = []; + for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; + return ar; + }; + return ownKeys(o); + }; + return function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); + __setModuleDefault(result, mod); + return result; + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +exports.scan = exports.runTests = exports.analyseProject = exports.contractScan = exports.quickScanContract = exports.quickScanProject = exports.generateReport = exports.projectScan = void 0; +const api = __importStar(require("./src/api")); +const cli = __importStar(require("./src/cli")); +exports.projectScan = api.projectScan; +exports.generateReport = api.generateReport; +exports.quickScanProject = api.quickScanProject; +exports.quickScanContract = api.quickScanContract; +exports.contractScan = api.contractScan; +exports.analyseProject = api.analyzeProject; +exports.runTests = api.runTests; +exports.scan = cli.scan; +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/index.js.map b/dist/index.js.map new file mode 100644 index 0000000..90f441b --- /dev/null +++ b/dist/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,+CAAiC;AACjC,+CAAiC;AAEpB,QAAA,WAAW,GAAG,GAAG,CAAC,WAAW,CAAC;AAC9B,QAAA,cAAc,GAAG,GAAG,CAAC,cAAc,CAAC;AACpC,QAAA,gBAAgB,GAAG,GAAG,CAAC,gBAAgB,CAAC;AACxC,QAAA,iBAAiB,GAAG,GAAG,CAAC,iBAAiB,CAAC;AAC1C,QAAA,YAAY,GAAG,GAAG,CAAC,YAAY,CAAC;AAChC,QAAA,cAAc,GAAG,GAAG,CAAC,cAAc,CAAC;AACpC,QAAA,QAAQ,GAAG,GAAG,CAAC,QAAQ,CAAC;AACxB,QAAA,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC"} \ No newline at end of file diff --git a/dist/src/api.d.ts b/dist/src/api.d.ts new file mode 100644 index 0000000..cdcef1b --- /dev/null +++ b/dist/src/api.d.ts @@ -0,0 +1,31 @@ +export interface ProjectScanPayload { + provider: string; + project_url: string; + project_name: string; + project_branch: string; + recur_scans?: boolean; + skip_file_paths?: string[]; +} +export interface ContractScanPayload { + contract_address: string; + contract_platform: string; + contract_chain: string; +} +export interface GenerateReportPayload { + project_id: string; + scan_id: string; + scan_type: string; +} +declare function projectScan(projectPayload: ProjectScanPayload, apiToken?: string, spinner?: boolean): Promise; +declare function quickScanProject(projectPayload: ProjectScanPayload, apiToken?: string, spinner?: boolean): Promise; +declare function quickScanContract(contractPayload: ContractScanPayload, apiToken?: string, spinner?: boolean): Promise; +declare function generateReport(generateReportPayload: GenerateReportPayload, apiToken?: string, spinner?: boolean): Promise; +declare function contractScan(contractPayload: ContractScanPayload, apiToken?: string, spinner?: boolean): Promise; +declare function analyzeProject(projectDirectory: string, apiToken: string | undefined, projectName: string, isRunningTest?: boolean): Promise; +type RunTestsReturn = { + metadata: unknown; + scanDetails: unknown; + resultFile: string; +}; +declare function runTests(projectDirectory: string, apiToken: string | undefined, projectName: string): Promise; +export { projectScan, generateReport, contractScan, analyzeProject, runTests, quickScanProject, quickScanContract, }; diff --git a/dist/src/api.js b/dist/src/api.js new file mode 100644 index 0000000..3f26b45 --- /dev/null +++ b/dist/src/api.js @@ -0,0 +1,166 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || (function () { + var ownKeys = function(o) { + ownKeys = Object.getOwnPropertyNames || function (o) { + var ar = []; + for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; + return ar; + }; + return ownKeys(o); + }; + return function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); + __setModuleDefault(result, mod); + return result; + }; +})(); +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.projectScan = projectScan; +exports.generateReport = generateReport; +exports.contractScan = contractScan; +exports.analyzeProject = analyzeProject; +exports.runTests = runTests; +exports.quickScanProject = quickScanProject; +exports.quickScanContract = quickScanContract; +const utils = __importStar(require("./utils")); +const fs_1 = __importDefault(require("fs")); +const cli_spinners_1 = __importDefault(require("cli-spinners")); +const axios_1 = __importDefault(require("axios")); +const https_1 = __importDefault(require("https")); +const spinner = cli_spinners_1.default.dots; +async function projectScan(projectPayload, apiToken, spinner = true) { + const request_payload = { + action: "message", + payload: { + type: "private_project_scan_initiate", + body: projectPayload, + }, + }; + return utils.initializeWebSocket(apiToken, request_payload, spinner); +} +async function quickScanProject(projectPayload, apiToken, spinner = true) { + const request_payload = { + action: "message", + payload: { + type: "private_quick_scan_initiate", + body: projectPayload, + }, + }; + return utils.initializeWebSocket(apiToken, request_payload, spinner); +} +async function quickScanContract(contractPayload, apiToken, spinner = true) { + const request_payload = { + action: "message", + payload: { + type: "private_quick_scan_initiate", + body: contractPayload, + }, + }; + return utils.initializeWebSocket(apiToken, request_payload, spinner); +} +async function generateReport(generateReportPayload, apiToken, spinner = true) { + const request_payload = { + action: "message", + payload: { + type: "generate_report", + body: generateReportPayload, + }, + }; + return utils.initializeWebSocket(apiToken, request_payload, spinner); +} +async function contractScan(contractPayload, apiToken, spinner = true) { + const request_payload = { + action: "message", + payload: { + type: "private_threat_scan_initiate", + body: contractPayload, + }, + }; + return utils.initializeWebSocket(apiToken, request_payload, spinner); +} +async function analyzeProject(projectDirectory, apiToken, projectName, isRunningTest = false) { + try { + const initializingSpinner = await utils.showSpinnerWithStatus("Initializing Scan", spinner.frames); + const projectZipPath = await utils.createProjectZip(projectDirectory); + const uploadUrl = await utils.getUploadPresignedUrl(projectZipPath, apiToken); + if (uploadUrl) { + const fileData = fs_1.default.readFileSync(projectZipPath); + const uploadSuccessful = await utils.uploadToS3(fileData, uploadUrl); + if (uploadSuccessful) { + utils.stopSpinner(initializingSpinner, "Initializing Scan"); + const scanningSpinner = await utils.showSpinnerWithStatus("Scan in progress", spinner.frames); + const request_payload = { + type: "private_project_scan_initiate", + body: { + file_urls: [uploadUrl], + project_name: projectName, + project_visibility: "public", + project_type: "new", + }, + }; + const result = utils.initializeWebSocket(apiToken, request_payload); + return result; + } + } + else { + throw new Error(`Error analyzing project`); + } + } + catch (error) { + throw new Error(`Error analyzing project from directory: ${error.message}`); + } +} +async function runTests(projectDirectory, apiToken, projectName) { + try { + const results = await analyzeProject(projectDirectory, apiToken, projectName, true); + const maybeResults = results; + if (maybeResults && maybeResults.scan_details && maybeResults.scan_details.link) { + const response = await axios_1.default.get(maybeResults.scan_details.link, { + httpsAgent: new https_1.default.Agent({ rejectUnauthorized: false }) + }); + const scanData = response.data; + const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); + const filename = `scan-result-${maybeResults.scan_id}-${timestamp}.json`; + fs_1.default.writeFileSync(filename, JSON.stringify(scanData, null, 2)); + console.log(`\nScan results saved to: ${filename}`); + return { + metadata: maybeResults, + scanDetails: scanData, + resultFile: filename + }; + } + else { + console.error("Error: Scan results link not found in the response"); + return results; + } + } + catch (error) { + console.error("Error during scan:", error?.message || error); + if (error?.response) { + console.error("API response error:", error.response.data); + } + throw error; + } +} +//# sourceMappingURL=api.js.map \ No newline at end of file diff --git a/dist/src/api.js.map b/dist/src/api.js.map new file mode 100644 index 0000000..e466b55 --- /dev/null +++ b/dist/src/api.js.map @@ -0,0 +1 @@ +{"version":3,"file":"api.js","sourceRoot":"","sources":["../../src/api.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsLE,kCAAW;AACX,wCAAc;AACd,oCAAY;AACZ,wCAAc;AACd,4BAAQ;AACR,4CAAgB;AAChB,8CAAiB;AA5LnB,+CAAiC;AACjC,4CAAoB;AACpB,gEAAuC;AACvC,kDAA0B;AAC1B,kDAA0B;AAC1B,MAAM,OAAO,GAAG,sBAAW,CAAC,IAAI,CAAC;AAuBjC,KAAK,UAAU,WAAW,CAAC,cAAkC,EAAE,QAAiB,EAAE,UAAmB,IAAI;IACvG,MAAM,eAAe,GAAG;QACtB,MAAM,EAAE,SAAS;QACjB,OAAO,EAAE;YACP,IAAI,EAAE,+BAA+B;YACrC,IAAI,EAAE,cAAc;SACrB;KACF,CAAC;IACF,OAAO,KAAK,CAAC,mBAAmB,CAAC,QAAQ,EAAE,eAAe,EAAE,OAAO,CAAC,CAAC;AACvE,CAAC;AAED,KAAK,UAAU,gBAAgB,CAAC,cAAkC,EAAE,QAAiB,EAAE,UAAmB,IAAI;IAC5G,MAAM,eAAe,GAAG;QACtB,MAAM,EAAE,SAAS;QACjB,OAAO,EAAE;YACP,IAAI,EAAE,6BAA6B;YACnC,IAAI,EAAE,cAAc;SACrB;KACF,CAAC;IACF,OAAO,KAAK,CAAC,mBAAmB,CAAC,QAAQ,EAAE,eAAe,EAAE,OAAO,CAAC,CAAC;AACvE,CAAC;AAED,KAAK,UAAU,iBAAiB,CAAC,eAAoC,EAAE,QAAiB,EAAE,UAAmB,IAAI;IAC/G,MAAM,eAAe,GAAG;QACtB,MAAM,EAAE,SAAS;QACjB,OAAO,EAAE;YACP,IAAI,EAAE,6BAA6B;YACnC,IAAI,EAAE,eAAe;SACtB;KACF,CAAC;IACF,OAAO,KAAK,CAAC,mBAAmB,CAAC,QAAQ,EAAE,eAAe,EAAE,OAAO,CAAC,CAAC;AACvE,CAAC;AAGD,KAAK,UAAU,cAAc,CAC3B,qBAA4C,EAC5C,QAAiB,EACjB,UAAmB,IAAI;IAEvB,MAAM,eAAe,GAAG;QACtB,MAAM,EAAE,SAAS;QACjB,OAAO,EAAE;YACP,IAAI,EAAE,iBAAiB;YACvB,IAAI,EAAE,qBAAqB;SAC5B;KACF,CAAC;IACF,OAAO,KAAK,CAAC,mBAAmB,CAAC,QAAQ,EAAE,eAAe,EAAE,OAAO,CAAC,CAAC;AACvE,CAAC;AAED,KAAK,UAAU,YAAY,CAAC,eAAoC,EAAE,QAAiB,EAAE,UAAmB,IAAI;IAC1G,MAAM,eAAe,GAAG;QACtB,MAAM,EAAE,SAAS;QACjB,OAAO,EAAE;YACP,IAAI,EAAE,8BAA8B;YACpC,IAAI,EAAE,eAAe;SACtB;KACF,CAAC;IACF,OAAO,KAAK,CAAC,mBAAmB,CAAC,QAAQ,EAAE,eAAe,EAAE,OAAO,CAAC,CAAC;AACvE,CAAC;AAED,KAAK,UAAU,cAAc,CAC3B,gBAAwB,EACxB,QAA4B,EAC5B,WAAmB,EACnB,aAAa,GAAG,KAAK;IAErB,IAAI,CAAC;QACH,MAAM,mBAAmB,GAAG,MAAM,KAAK,CAAC,qBAAqB,CAC3D,mBAAmB,EACnB,OAAO,CAAC,MAAM,CACf,CAAC;QACF,MAAM,cAAc,GAAG,MAAM,KAAK,CAAC,gBAAgB,CAAC,gBAAgB,CAAC,CAAC;QACtE,MAAM,SAAS,GAAG,MAAM,KAAK,CAAC,qBAAqB,CACjD,cAAc,EACd,QAAQ,CACT,CAAC;QACF,IAAI,SAAS,EAAE,CAAC;YACd,MAAM,QAAQ,GAAG,YAAE,CAAC,YAAY,CAAC,cAAc,CAAC,CAAC;YACjD,MAAM,gBAAgB,GAAG,MAAM,KAAK,CAAC,UAAU,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;YACrE,IAAI,gBAAgB,EAAE,CAAC;gBACrB,KAAK,CAAC,WAAW,CAAC,mBAAmB,EAAE,mBAAmB,CAAC,CAAC;gBAC5D,MAAM,eAAe,GAAG,MAAM,KAAK,CAAC,qBAAqB,CACvD,kBAAkB,EAClB,OAAO,CAAC,MAAM,CACf,CAAC;gBACF,MAAM,eAAe,GAAG;oBACtB,IAAI,EAAE,+BAA+B;oBACrC,IAAI,EAAE;wBACJ,SAAS,EAAE,CAAC,SAAS,CAAC;wBACtB,YAAY,EAAE,WAAW;wBACzB,kBAAkB,EAAE,QAAQ;wBAC5B,YAAY,EAAE,KAAK;qBACpB;iBACF,CAAC;gBACF,MAAM,MAAM,GAAG,KAAK,CAAC,mBAAmB,CACtC,QAAQ,EACR,eAAe,CAChB,CAAC;gBACF,OAAO,MAAM,CAAC;YAChB,CAAC;QACH,CAAC;aAAM,CAAC;YACN,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC;QAC7C,CAAC;IACH,CAAC;IAAC,OAAO,KAAU,EAAE,CAAC;QACpB,MAAM,IAAI,KAAK,CAAC,2CAA2C,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;IAC9E,CAAC;AACH,CAAC;AAQD,KAAK,UAAU,QAAQ,CACrB,gBAAwB,EACxB,QAA4B,EAC5B,WAAmB;IAEnB,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,cAAc,CAAC,gBAAgB,EAAE,QAAQ,EAAE,WAAW,EAAE,IAAI,CAAC,CAAC;QAEpF,MAAM,YAAY,GAAQ,OAAc,CAAC;QACzC,IAAI,YAAY,IAAI,YAAY,CAAC,YAAY,IAAI,YAAY,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC;YAChF,MAAM,QAAQ,GAAG,MAAM,eAAK,CAAC,GAAG,CAAC,YAAY,CAAC,YAAY,CAAC,IAAI,EAAE;gBAC/D,UAAU,EAAE,IAAI,eAAK,CAAC,KAAK,CAAC,EAAE,kBAAkB,EAAE,KAAK,EAAE,CAAC;aAC3D,CAAC,CAAC;YAEH,MAAM,QAAQ,GAAG,QAAQ,CAAC,IAAI,CAAC;YAC/B,MAAM,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;YACjE,MAAM,QAAQ,GAAG,eAAe,YAAY,CAAC,OAAO,IAAI,SAAS,OAAO,CAAC;YAEzE,YAAE,CAAC,aAAa,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;YAC9D,OAAO,CAAC,GAAG,CAAC,4BAA4B,QAAQ,EAAE,CAAC,CAAC;YAEpD,OAAO;gBACL,QAAQ,EAAE,YAAY;gBACtB,WAAW,EAAE,QAAQ;gBACrB,UAAU,EAAE,QAAQ;aACrB,CAAC;QACJ,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,KAAK,CAAC,oDAAoD,CAAC,CAAC;YACpE,OAAO,OAAO,CAAC;QACjB,CAAC;IACH,CAAC;IAAC,OAAO,KAAU,EAAE,CAAC;QACpB,OAAO,CAAC,KAAK,CAAC,oBAAoB,EAAE,KAAK,EAAE,OAAO,IAAI,KAAK,CAAC,CAAC;QAC7D,IAAI,KAAK,EAAE,QAAQ,EAAE,CAAC;YACpB,OAAO,CAAC,KAAK,CAAC,qBAAqB,EAAE,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QAC5D,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC"} \ No newline at end of file diff --git a/dist/src/cli.d.ts b/dist/src/cli.d.ts new file mode 100644 index 0000000..97e2d51 --- /dev/null +++ b/dist/src/cli.d.ts @@ -0,0 +1,2 @@ +declare function scan(): void; +export { scan, }; diff --git a/dist/src/cli.js b/dist/src/cli.js new file mode 100644 index 0000000..4a62b19 --- /dev/null +++ b/dist/src/cli.js @@ -0,0 +1,210 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || (function () { + var ownKeys = function(o) { + ownKeys = Object.getOwnPropertyNames || function (o) { + var ar = []; + for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; + return ar; + }; + return ownKeys(o); + }; + return function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); + __setModuleDefault(result, mod); + return result; + }; +})(); +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.scan = scan; +const axios_1 = __importDefault(require("axios")); +const api_1 = require("./api"); +const utils = __importStar(require("./utils")); +function scan() { + const [, , ...args] = process.argv; + if (args[0] === "scan") { + const scanType = args[1]; + if (scanType === "project") { + const provider = args[2]; + const projectUrl = args[3]; + const projectBranch = args[4]; + const projecName = args[5]; + const recurScan = args[7] ? args[7] === 'true' : false; + const apiKey = process.env.SOLIDITYSCAN_API_KEY || args[6]; + if (!provider || !projectUrl || !projectBranch || !projecName) { + console.error("Usage: solisityscan scan "); + process.exit(1); + } + const payload = { + provider: provider, + project_url: projectUrl, + project_name: projecName, + project_branch: projectBranch, + recur_scans: recurScan || false, + skip_file_paths: [], + }; + (0, api_1.projectScan)(payload, apiKey) + .then((results) => { + console.log("Scan results:", results); + }) + .catch((error) => { + console.error("Error during scan:", error); + }); + } + else if (scanType === "contract") { + const contractAddress = args[2]; + const contractChain = args[3]; + const contractPlatform = args[4]; + const cliProvidedToken = args[5]; // CLI provided token + if (!contractAddress || !contractChain || !contractPlatform) { + console.error("Usage: solisityscan scan contract
[apiToken]"); + process.exit(1); + } + try { + // Get API token from environment or command line + const apiToken = process.env.SOLIDITYSCAN_API_KEY || cliProvidedToken; + if (!apiToken) { + console.error("No API token provided. Please set SOLIDITYSCAN_API_KEY environment variable or provide token as argument."); + process.exit(1); + } + const payload = { + contract_address: contractAddress, + contract_platform: contractPlatform, + contract_chain: contractChain, + }; + (0, api_1.contractScan)(payload, apiToken) + .then((results) => { + console.log("Scan results:", JSON.stringify(results, null, 2)); + }) + .catch((error) => { + console.error("\nError during scan:", error); + }); + } + catch (error) { + console.error("\nError with API token:", error?.message || error); + process.exit(1); + } + } + } + else if (args[0] === "local") { + const projectPath = args[1]; + const apiKeyFromArgs = args[2] && !process.env.SOLIDITYSCAN_API_KEY; + const apiKey = process.env.SOLIDITYSCAN_API_KEY || args[2]; + let projectName = apiKeyFromArgs ? args[3] : args[2]; + if (!projectName) { + projectName = "LocalScan"; + } + if (!projectPath) { + console.error("Usage: solisityscan run-tests "); + process.exit(1); + } + (0, api_1.analyzeProject)(projectPath, apiKey, projectName) + .then((results) => { + const r = results; + if (r?.scan_details?.link) { + axios_1.default.get(r.scan_details.link) + .then((response) => { + utils.displayScanResults(response.data.scan_report); + }) + .catch((error) => { + console.error("Error during scan:", error); + }); + } + }) + .catch((error) => { + console.error("Error during scan:", error); + }); + } + else if (args.includes("-l")) { + const dirFlagIndex = args.indexOf("-p"); + const serveDirectory = dirFlagIndex !== -1 && args[dirFlagIndex + 1] + ? args[dirFlagIndex + 1] + : process.cwd(); + const portFlagIndex = args.indexOf("--port"); + const userSpecifiedPort = portFlagIndex !== -1 && args[portFlagIndex + 1] + ? parseInt(args[portFlagIndex + 1], 10) + : null; + const idFlagIndex = args.indexOf("--id"); + const tunnelId = idFlagIndex !== -1 && args[idFlagIndex + 1] + ? args[idFlagIndex + 1] + : null; + if (!tunnelId) { + console.error("Missing --id argument"); + process.exit(1); + } + let portAttempts = 0; + let port = userSpecifiedPort || 9462; + let wss; + while (portAttempts < 5) { + try { + wss = utils.startLocalFileServer(serveDirectory, port); + break; // success + } + catch (err) { + if (userSpecifiedPort || err?.code !== "EADDRINUSE") { + console.error(err?.message || "Failed to start local server"); + process.exit(1); + } + port += 1; + portAttempts += 1; + } + } + if (!wss) { + console.error("Could not bind to any port between 9462 and 9466"); + process.exit(1); + } + (async () => { + try { + const localtunnel = require("localtunnel"); + const tunnel = await localtunnel({ port, subdomain: tunnelId }); + const clean = () => { + try { + tunnel.close(); + } + catch (_) { } + try { + if (wss) + wss.close(); + } + catch (_) { } + process.exit(0); + }; + process.on("SIGINT", clean); + process.on("SIGTERM", clean); + } + catch (e) { + console.error("Error during tunnel:", e); + } + })(); + return; + } + else { + console.error("Unknown command. Usage: solidityscan scan "); + process.exit(1); + } +} +if (require.main === module) { + // executed via `node src/cli.js ...` – run the CLI + scan(); +} +//# sourceMappingURL=cli.js.map \ No newline at end of file diff --git a/dist/src/cli.js.map b/dist/src/cli.js.map new file mode 100644 index 0000000..8f02677 --- /dev/null +++ b/dist/src/cli.js.map @@ -0,0 +1 @@ +{"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/cli.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/src/utils.d.ts b/dist/src/utils.d.ts new file mode 100644 index 0000000..07d0644 --- /dev/null +++ b/dist/src/utils.d.ts @@ -0,0 +1,10 @@ +declare const initializeWebSocket: (apiToken: string | undefined, payload: any, spinner?: boolean) => Promise; +declare const createProjectZip: (projectDirectory: string) => Promise; +declare const getUploadPresignedUrl: (fileName: string, apiToken?: string) => Promise; +declare const uploadToS3: (fileData: Buffer | Uint8Array, uploadUrl: string) => Promise; +declare const displayScanResults: (scan: any) => void; +declare const displayScanSummary: (scan: any) => void; +declare function showSpinnerWithStatus(statusMessage: string, spinnerFrames: string[]): Promise>; +declare function stopSpinner(interval: ReturnType, statusMessage: string): void; +declare function startLocalFileServer(rootDirectory: string, port?: number): any; +export { initializeWebSocket, createProjectZip, getUploadPresignedUrl, uploadToS3, displayScanResults, displayScanSummary, showSpinnerWithStatus, stopSpinner, startLocalFileServer, }; diff --git a/dist/src/utils.js b/dist/src/utils.js new file mode 100644 index 0000000..53df0c0 --- /dev/null +++ b/dist/src/utils.js @@ -0,0 +1,531 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.displayScanSummary = exports.displayScanResults = exports.uploadToS3 = exports.getUploadPresignedUrl = exports.createProjectZip = exports.initializeWebSocket = void 0; +exports.showSpinnerWithStatus = showSpinnerWithStatus; +exports.stopSpinner = stopSpinner; +exports.startLocalFileServer = startLocalFileServer; +const request_1 = __importDefault(require("request")); +const ws_1 = __importDefault(require("ws")); +const fs_1 = __importDefault(require("fs")); +const path_1 = __importDefault(require("path")); +const archiver_1 = __importDefault(require("archiver")); +const axios_1 = __importDefault(require("axios")); +const cli_table3_1 = __importDefault(require("cli-table3")); +const cli_spinners_1 = __importDefault(require("cli-spinners")); +const spinner = cli_spinners_1.default.dots; +const getApi = (apiToken) => { + const apiBaseUrl = "https://api.solidityscan.com/"; + if (apiToken) { + const instance = axios_1.default.create({ + baseURL: apiBaseUrl, + headers: { + "Content-Type": "application/json", + "accept": "application/json, text/plain, */*", + "Authorization": `Bearer ${apiToken}`, + "cache-control": "no-cache", + } + }); + return instance; + } + else { + const instance = axios_1.default.create({ + baseURL: apiBaseUrl, + headers: { + "Content-Type": "application/json", + "CF-Access-Client-Secret": "", + "CF-Access-Client-Id": "", + } + }); + return instance; + } +}; +const initializeWebSocket = (apiToken, payload, spinner = true) => { + const wsUrl = 'wss://api-ws.solidityscan.com/'; + const ws = new ws_1.default(wsUrl, { + rejectUnauthorized: false + }); + const emitMessage = (messagePayload) => { + ws.send(JSON.stringify({ + action: "message", + payload: messagePayload, + })); + }; + return new Promise((resolve, reject) => { + const connectionTimeout = setTimeout(() => { + ws.close(); + reject(new Error("WebSocket connection timed out waiting for scan results")); + }, 60000); // 60 second timeout + ws.on("open", () => { + if (apiToken) { + emitMessage({ + type: "auth_token_register", + body: { + auth_token: apiToken, + }, + }); + } + else { + console.log("No authentication token provided, sending payload directly"); + emitMessage(payload); + } + }); + ws.on("message", (data) => { + try { + const receivedMessage = JSON.parse(data.toString()); + clearTimeout(connectionTimeout); + if (receivedMessage.type === "auth_token_register") { + if (payload.payload) { + emitMessage(payload.payload); + } + else { + emitMessage(payload); + } + } + else if (receivedMessage.type === "scan_status") { + if (receivedMessage.payload?.scan_status === "scan_done") { + resolve(receivedMessage.payload); + ws.close(); + } + } + else if (receivedMessage.type === "quick_scan_status") { + if (receivedMessage.payload?.scan_status === "scan_done") { + resolve(receivedMessage.payload); + ws.close(); + } + else { + console.log(`\n[WebSocket] Waiting for scan to complete. Current status: ${receivedMessage.payload?.scan_status || receivedMessage.payload?.quick_scan_status || 'processing'}`); + } + } + else if (receivedMessage.type === "report_generation_status") { + if (receivedMessage.payload?.report_status === "report_generated") { + resolve(receivedMessage.payload); + ws.close(); + } + else { + console.log(`\n[WebSocket] Report generation payload: ${receivedMessage.payload}`); + } + } + else if (receivedMessage.type === "quick_scan_result") { + if (receivedMessage.payload?.scan_details?.link) { + request_1.default.get(receivedMessage.payload.scan_details.link, (error, response, body) => { + if (error) { + resolve(error); + } + else if (response.statusCode !== 200) { + resolve(error); + } + else { + try { + const scan_result = JSON.parse(body); + resolve(scan_result.scan_report || scan_result); + } + catch (e) { + resolve(body); + } + } + ws.close(); + }); + } + } + else if (receivedMessage.type === "error") { + console.log("\n Error received from server:", receivedMessage.payload?.payload?.error_message || receivedMessage.payload?.error_message || "Unknown error"); + ws.close(); + reject(receivedMessage.payload?.payload?.error_message || receivedMessage.payload?.error_message || "Unknown error from server"); + } + else { + if (spinner) { + process.stdout.write("."); + } + } + } + catch (error) { + console.error("\nError processing message:", error); + console.error("\nRaw message data:", data.toString()); + } + }); + ws.on("error", (error) => { + console.log(error); + reject(error); + }); + ws.on("close", () => { }); + }); +}; +exports.initializeWebSocket = initializeWebSocket; +const createProjectZip = async (projectDirectory) => { + try { + const zipFileName = "project.zip"; + const output = fs_1.default.createWriteStream(zipFileName); + const archive = (0, archiver_1.default)("zip", { zlib: { level: 9 } }); + archive.pipe(output); + const gatherSolFiles = (directory) => { + const files = fs_1.default.readdirSync(directory); + files.forEach((file) => { + const filePath = path_1.default.join(directory, file); + if (fs_1.default.statSync(filePath).isDirectory() && file !== "node_modules") { + gatherSolFiles(filePath); + } + else if (path_1.default.extname(file) === ".sol") { + const relativePath = path_1.default.relative(projectDirectory, filePath); + const fileContent = fs_1.default.readFileSync(filePath); + archive.append(fileContent, { name: relativePath }); + } + }); + }; + gatherSolFiles(projectDirectory); + await archive.finalize(); + return zipFileName; + } + catch (error) { + throw new Error(`Error creating project ZIP: ${error.message}`); + } +}; +exports.createProjectZip = createProjectZip; +const getUploadPresignedUrl = async (fileName, apiToken) => { + try { + const apiUrl = `private/api-get-presigned-url/?file_name=${fileName}`; + const API = getApi(apiToken); + const response = await API.get(apiUrl); + if (response.status === 200 && response.data && response.data.result) { + return response.data.result.url; + } + else { + return null; + } + } + catch (error) { + throw new Error(`Failed to get presigned URL: ${error.message}`); + } +}; +exports.getUploadPresignedUrl = getUploadPresignedUrl; +const uploadToS3 = async (fileData, uploadUrl) => { + try { + const response = await axios_1.default.put(uploadUrl, fileData, { + headers: { + "Content-Type": "application/octet-stream", + } + }); + if (response.status === 200 || response.status === 204) { + return true; + } + else { + return false; + } + } + catch (error) { + return false; + } +}; +exports.uploadToS3 = uploadToS3; +function capitalizeFirstLetter(str) { + return str.charAt(0).toUpperCase() + str.slice(1); +} +function formatHtmlForTerminal(htmlContent) { + if (!htmlContent) + return ''; + let text = htmlContent.replace(//gi, '\n'); + text = text.replace(/(.*?)<\/code>/gi, '`$1`'); + text = text.replace(/<\/?[^>]+(>|$)/g, ''); + text = text.replace(/\s+/g, ' ').trim(); + return text; +} +const displayScanResults = (scan) => { + const table = new cli_table3_1.default({ + head: ["#", "NAME", "SEVERITY", "CONFIDENCE", "DESCRIPTION", "REMEDIATION"], + chars: { + 'top': '─', 'top-mid': '┬', 'top-left': 'β”Œ', 'top-right': '┐', + 'bottom': '─', 'bottom-mid': 'β”΄', 'bottom-left': 'β””', 'bottom-right': 'β”˜', + 'left': 'β”‚', 'left-mid': 'β”œ', 'mid': '─', 'mid-mid': 'β”Ό', + 'right': 'β”‚', 'right-mid': '─', 'middle': 'β”‚' + }, + style: { + head: ['bold'] + }, + // Configure optimal column widths + colWidths: [5, 20, 12, 12, 35, 35], + wordWrap: true + }); + let issueCount = 0; + scan.multi_file_scan_details.forEach((detail) => { + const { template_details } = detail; + if (detail.metric_wise_aggregated_findings) { + detail.metric_wise_aggregated_findings.forEach((bug) => { + issueCount++; + const filePath = bug.findings[0].file_path; + const location = `${filePath.replace("/project", "")}\nL${bug.findings[0].line_nos_start} - L${bug.findings[0].line_nos_end}`; + const description = formatHtmlForTerminal(template_details.issue_description); + const fullDescription = `${description}\n\nLocation:\n${location}`; + const row = [ + `${issueCount}.`, + template_details.issue_name, + capitalizeFirstLetter(template_details.issue_severity), + template_details.issue_confidence, + fullDescription, + formatHtmlForTerminal(template_details.issue_remediation) + ]; + table.push(row); + }); + } + }); + if (issueCount === 0) { + console.log('No security issues found!'); + } + else { + console.log('SECURITY SCAN RESULTS:'); + console.log(table.toString()); + console.log(`Found ${issueCount} security ${issueCount === 1 ? 'issue' : 'issues'}.`); + } +}; +exports.displayScanResults = displayScanResults; +const displayScanSummary = (scan) => { + const table = new cli_table3_1.default(); + const issues_count = scan.multi_file_scan_summary.issue_severity_distribution.critical + + scan.multi_file_scan_summary.issue_severity_distribution.high + + scan.multi_file_scan_summary.issue_severity_distribution.medium + + scan.multi_file_scan_summary.issue_severity_distribution.low + + scan.multi_file_scan_summary.issue_severity_distribution.informational + + scan.multi_file_scan_summary.issue_severity_distribution.gas; + table.push({ + Critical: scan.multi_file_scan_summary.issue_severity_distribution.critical, + }, { High: scan.multi_file_scan_summary.issue_severity_distribution.high }, { + Medium: scan.multi_file_scan_summary.issue_severity_distribution.medium, + }, { Low: scan.multi_file_scan_summary.issue_severity_distribution.low }, { + Informational: scan.multi_file_scan_summary.issue_severity_distribution.informational, + }, { Gas: scan.multi_file_scan_summary.issue_severity_distribution.gas }, { "Security Score": `${scan.multi_file_scan_summary.score_v2} / 100` }); + console.log(table.toString()); + console.log(`Scan successful! ${issues_count} issues found. To view detailed results and generate a report navigate to solidityscan.com.`); +}; +exports.displayScanSummary = displayScanSummary; +// Function to display a spinner with dynamic status +async function showSpinnerWithStatus(statusMessage, spinnerFrames) { + process.stdout.write(`${statusMessage}... `); + let frameIndex = 0; + const interval = setInterval(() => { + process.stdout.write(spinnerFrames[frameIndex]); + process.stdout.write("\b"); + frameIndex = (frameIndex + 1) % spinnerFrames.length; + }, 100); + return interval; +} +// Function to stop the spinner +function stopSpinner(interval, statusMessage) { + clearInterval(interval); + process.stdout.write("\r"); + console.log(`${statusMessage}... Done`); +} +// New helper to serve local directory over WebSocket +function startLocalFileServer(rootDirectory, port = 8080) { + if (!fs_1.default.existsSync(rootDirectory)) { + throw new Error(`Directory not found: ${rootDirectory}`); + } + const absoluteRoot = path_1.default.resolve(rootDirectory); + const wss = new ws_1.default.Server({ port, verifyClient: (info, done) => { + if (!originIsAllowed(info.origin)) { + done(false); + console.log(`Connection from origin ${info.origin} is not allowed`); + return; + } + done(true); + } }); + console.log(`SolidityScan local file server started\nServing directory: ${absoluteRoot}`); + wss.on("connection", (socket) => { + socket.on("message", async (raw) => { + let message; + try { + message = JSON.parse(raw); + } + catch (err) { + socket.send(JSON.stringify({ type: "error", error: "Invalid JSON message" })); + return; + } + const { action, payload } = message; + if (action === "listFiles") { + // Return hierarchical folder tree with metadata + const buildTree = (dir, relPath = "") => { + const name = path_1.default.basename(dir); + const stat = fs_1.default.statSync(dir); + if (stat.isDirectory()) { + const dirs = []; + const files = []; + fs_1.default.readdirSync(dir).forEach((entry) => { + if (entry === "node_modules") + return; + const abs = path_1.default.join(dir, entry); + const rootName = path_1.default.basename(absoluteRoot); + // Build a raw relative path using native separators + let childRelRaw; + if (relPath === "") { + childRelRaw = path_1.default.join(rootName, entry); + } + else { + childRelRaw = path_1.default.join(relPath, entry); + } + // Convert to POSIX style with a single "/" separator for JSON responses + const childRel = childRelRaw + .split(path_1.default.sep) + .join("/") + .replace(/\/+/g, "/"); + if (fs_1.default.statSync(abs).isDirectory()) { + // Recurse with the raw path to preserve correct joining behaviour + dirs.push(buildTree(abs, childRelRaw + path_1.default.sep)); + } + else { + const fStat = fs_1.default.statSync(abs); + files.push({ + path: childRel, + name: entry, + size: fStat.size, + mtimeMs: fStat.mtimeMs, + checked: entry.endsWith(".sol"), + }); + } + }); + // Determine checked / isChildCheck flags + const numSol = files.filter((f) => f.checked).length; + const numNonSol = files.length - numSol; + let checkedDir = false; + let isChildCheck = false; + if (numSol === 0) { + checkedDir = false; + isChildCheck = false; + } + else if (numNonSol === 0) { + checkedDir = true; + isChildCheck = true; + } + else { + checkedDir = false; + isChildCheck = true; + } + // Build directory path (raw) and then normalise to POSIX + let dirPathRaw; + if (dir === absoluteRoot) { + dirPathRaw = path_1.default.basename(absoluteRoot) + path_1.default.sep; + } + else { + dirPathRaw = relPath + (relPath && !relPath.endsWith(path_1.default.sep) ? path_1.default.sep : ""); + } + let dirPath = dirPathRaw + .split(path_1.default.sep) + .join("/") + .replace(/\/+/g, "/"); + // Ensure directory paths end with a single trailing slash + if (!dirPath.endsWith("/")) { + dirPath += "/"; + } + return { + name, + path: dirPath, + tree: dirs, + isChildCheck, + checked: checkedDir, + blobs: files, + size: 0, + mtimeMs: stat.mtimeMs, + }; + } + // Should not reach here for files as we handle in parent + return undefined; + }; + const rootTreeInternal = buildTree(absoluteRoot, ""); + const responseTree = { + name: "", + path: "", + tree: [rootTreeInternal], + isChildCheck: rootTreeInternal.isChildCheck, + checked: rootTreeInternal.checked, + blobs: [], + size: 0, + mtimeMs: rootTreeInternal.mtimeMs, + }; + socket.send(JSON.stringify({ type: "folderStructure", tree: responseTree })); + } + else if (action === "zipAndSendFiles") { + const presignedUrl = payload.presigned_url; + if (!originIsAllowed(payload.origin)) { + socket.send(JSON.stringify({ type: "error", error: "origin not allowed" })); + return; + } + const skip = new Set(payload.skip_file_paths || []); + if (!presignedUrl) { + socket.send(JSON.stringify({ type: "error", error: "presigned_url missing" })); + return; + } + const archive = (0, archiver_1.default)("zip", { zlib: { level: 9 } }); + const chunks = []; + archive.on("data", (chunk) => chunks.push(chunk)); + archive.on("warning", (err) => { + if (err.code !== "ENOENT") { + socket.send(JSON.stringify({ type: "error", error: err.message })); + } + }); + archive.on("error", (err) => { + socket.send(JSON.stringify({ type: "error", error: err.message })); + }); + archive.on("end", async () => { + const buffer = Buffer.concat(chunks); + let success = false; + try { + if (presignedUrl.startsWith("memory://")) { + // test stub + success = true; + } + else { + success = await uploadToS3(buffer, presignedUrl); + } + } + catch (e) { + console.log("error uploading file", e); + success = false; + } + socket.send(JSON.stringify({ + type: "uploadStatus", + success, + })); + }); + // recursively walk and add files not skipped + const walkAdd = (dir, rel = "") => { + fs_1.default.readdirSync(dir).forEach((entry) => { + if (entry === "node_modules") + return; + const abs = path_1.default.join(dir, entry); + const relPath = path_1.default.join(rel, entry); + const relPathPosix = relPath + .split(path_1.default.sep) + .join("/") + .replace(/\/+/g, "/"); + const stat = fs_1.default.statSync(abs); + if (stat.isDirectory()) { + walkAdd(abs, relPath); + } + else { + if (!skip.has(relPathPosix)) { + // Use POSIX-style path inside the archive to avoid platform-specific separators + archive.file(abs, { name: relPathPosix }); + } + } + }); + }; + walkAdd(absoluteRoot, ""); + archive.finalize(); + } + else { + socket.send(JSON.stringify({ type: "error", error: "Unknown action" })); + } + }); + }); + return wss; +} +function getDomain(url) { + const domainMatch = url.match(/^(?:https?:\/\/)?(?:[^@\n]+@)?(?:www\.)?([^:\/\n?]+)/img); + return domainMatch ? domainMatch[0] : null; +} +function originIsAllowed(origin) { + return true; + const DOMAIN = getDomain(origin) || ""; + const allowedOrigins = ["https://solidityscan.com", "https://develop.solidityscan.com", "https://credshields-prod.s3.amazonaws.com", "https://credshields-dev.s3.amazonaws.com/"]; + return allowedOrigins.includes(DOMAIN); +} +//# sourceMappingURL=utils.js.map \ No newline at end of file diff --git a/dist/src/utils.js.map b/dist/src/utils.js.map new file mode 100644 index 0000000..d24af64 --- /dev/null +++ b/dist/src/utils.js.map @@ -0,0 +1 @@ +{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/utils.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/index.js b/index.js deleted file mode 100755 index 5679590..0000000 --- a/index.js +++ /dev/null @@ -1,10 +0,0 @@ -const api = require("./src/api"); -const cli = require("./src/cli"); - -module.exports = { - projectScan: api.projectScan, - contractScan: api.contractScan, - analyseProject: api.analyzeProject, - runTests: api.runTests, - scan: cli.scan, -}; diff --git a/index.ts b/index.ts new file mode 100755 index 0000000..1abe7af --- /dev/null +++ b/index.ts @@ -0,0 +1,11 @@ +import * as api from "./src/api"; +import * as cli from "./src/cli"; + +export const projectScan = api.projectScan; +export const generateReport = api.generateReport; +export const quickScanProject = api.quickScanProject; +export const quickScanContract = api.quickScanContract; +export const contractScan = api.contractScan; +export const analyseProject = api.analyzeProject; +export const runTests = api.runTests; +export const scan = cli.scan; diff --git a/originalReadme.md b/originalReadme.md new file mode 100644 index 0000000..85b488b --- /dev/null +++ b/originalReadme.md @@ -0,0 +1,190 @@ +# SolidityScan + +Secure your Solidity smart contracts straight from your terminal or JavaScript code! **SolidityScan** is a lightweight CLI and Node.js library that connects to the [CredShields SolidityScan](https://solidityscan.com) API to identify vulnerabilities, gas optimisations, and other issues in your smart-contract projects. + +--- + +## Table of Contents + +1. [Features](#features) +2. [Installation](#installation) +3. [Getting an API Key](#getting-an-api-key) +4. [CLI Usage](#cli-usage) + * [Scan a remote project](#scan-a-remote-project) + * [Scan a contract address](#scan-a-contract-address) + * [Scan a local directory](#scan-a-local-directory) + * [Run a local file server](#run-a-local-file-server) +5. [Programmatic Usage](#programmatic-usage) +6. [Examples](#examples) +7. [Contributing](#contributing) +8. [License](#license) + +--- + +## Features + +β€’ πŸ” **Project & Contract Scanning** – Analyse entire Git repositories or individual contract addresses for known vulnerabilities.
+β€’ πŸ“¦ **Local Directory Scanning** – Zip and upload your local Solidity source code and get instant feedback in the terminal.
+β€’ ⚑ **Real-time Progress** – Live WebSocket updates with an elegant spinner so you always know the scan status.
+β€’ πŸ“‹ **Readable Reports** – Vulnerabilities and severities are displayed in coloured, column-aligned tables, followed by a concise scan summary. +β€’ 🌐 **Local WebSocket File Server** – Spin up a file server for quick web-UI integrations and demos. + +--- + +## Installation + +```bash +# Install globally to use the `solidityscan` CLI +yarn global add solidityscan # or npm install -g solidityscan + +# Add to a project for programmatic use +npm install solidityscan --save # or yarn add solidityscan +``` + +> **Requirement**: Node.js >= 14 + +--- + +## Getting an API Key + +1. Sign up or log in at [solidityscan.com](https://solidityscan.com). +2. Navigate to **API Keys** and generate a new key. +3. Either export it as an environment variable: + +```bash +export SOLIDITYSCAN_API_KEY="YOUR_API_KEY" +``` + +…or pass it as the last argument in each CLI command (see below). + +--- + +## CLI Usage + +After installing globally you will have a `solidityscan` binary in your PATH. +Run `solidityscan --help` to view the brief usage guide. + +### Scan a Remote Project + +```bash +solidityscan scan project [recurScan] +# Example +solidityscan scan project github https://github.com/Credshields/solidityscan-npm-package main DemoProject $SOLIDITYSCAN_API_KEY +``` + +Arguments: + +1. `provider` – Currently supported: `github` (more coming soon). +2. `repo-url` – HTTPS or SSH URL of the repository. +3. `branch` – Branch to scan (e.g. `main`). +4. `project-name` – Friendly name that will appear in the dashboard. +5. `api-key` *(optional)* – Falls back to `SOLIDITYSCAN_API_KEY` env var. +6. `recurScan` *(optional)* – `true` to enable recurring scans. + +--- + +### Scan a Contract Address + +```bash +solidityscan scan contract
+# Example +solidityscan scan contract 0x1234... ethereum evm $SOLIDITYSCAN_API_KEY +``` + +* `address` – Deployed contract address. +* `chain` – Network/chain identifier, e.g. `ethereum`, `polygon`. +* `platform` – Platform indicator such as `evm`. + +--- + +### Scan a Local Directory + +Analyse a local folder containing `.sol` files. The tool packages the Solidity source, uploads it, waits for the scan to finish and prints the results. + +```bash +solidityscan test /path/to/my/contracts [api-key] +``` + +--- + +### Run a Local File Server + +Start a WebSocket file server to expose your local directory to the SolidityScan web-UI (handy for visually picking files). + +```bash +# Serve current directory on default port 8080 +solidityscan -l + +# Serve a custom directory on port 9090 +solidityscan -l -p /my/contracts --port 9090 +``` + +--- + +## Programmatic Usage + +You can also integrate SolidityScan directly into your Node.js scripts or CI pipelines: + +```js +const solidityscan = require("solidityscan"); + +(async () => { + const apiToken = process.env.SOLIDITYSCAN_API_KEY; + + // 1. Scan a Git repository + const projectPayload = { + provider: "github", + project_url: "https://github.com/Credshields/awesome-contracts", + project_name: "AwesomeContracts", + project_branch: "main", + recur_scans: false, + skip_file_paths: [], + }; + const repoScanResult = await solidityscan.projectScan(projectPayload, apiToken); + console.log(repoScanResult); + + // 2. Scan a contract address + const contractPayload = { + contract_address: "0x1234...", + contract_chain: "ethereum", + contract_platform: "evm", + }; + const contractScanResult = await solidityscan.contractScan(contractPayload, apiToken); + console.log(contractScanResult); + + // 3. Scan a local directory (same behaviour as CLI `test`) + await solidityscan.runTests("./contracts", apiToken); +})(); +``` + +Available exported helpers: + +* `projectScan(payload, apiToken)` +* `contractScan(payload, apiToken)` +* `analyseProject(directoryPath, apiToken)` +* `runTests(directoryPath, apiToken)` +* `scan()` – executes the CLI with current `process.argv` (internally used by the binary). + +--- + +## Examples + +You can find full working examples inside the [`examples/`](https://github.com/Credshields/solidityscan-npm-package/tree/main/examples) directory (coming soon). + +--- + +## Contributing + +1. Fork the repo and create your feature branch: `git checkout -b feat/awesome-feature`. +2. Install dependencies: `npm install`. +3. Run the tests: `npm test`. +4. Commit your changes and push: `git push origin feat/awesome-feature`. +5. Open a pull request – we love to review! + +Please adhere to the existing code style and add unit tests for any new logic. + +--- + +## License + +This project is licensed under the **ISC** license – see the [LICENSE](LICENSE) file for details. \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index c101082..970b4b1 100755 --- a/package-lock.json +++ b/package-lock.json @@ -1,25 +1,37 @@ { - "name": "solidityscan-npm-test", - "version": "1.0.0", + "name": "solidityscan", + "version": "0.1.6", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "solidityscan-npm-test", - "version": "1.0.0", + "name": "solidityscan", + "version": "0.1.6", "license": "ISC", "dependencies": { "archiver": "^6.0.1", "axios": "^1.6.7", "cli-spinners": "^2.9.2", "cli-table3": "^0.6.3", + "dotenv": "^16.5.0", "fs": "^0.0.1-security", + "localtunnel": "^2.0.2", "path": "^0.12.7", "request": "^2.88.2", "ws": "^8.16.0" }, + "bin": { + "solidityscan": "dist/bin/solidityscan.js" + }, "devDependencies": { - "jest": "^29.7.0" + "@types/jest": "^29.5.12", + "@types/node": "^20.14.11", + "@types/ws": "^8.5.10", + "@typescript-eslint/eslint-plugin": "^7.17.0", + "@typescript-eslint/parser": "^7.17.0", + "eslint": "^8.57.0", + "jest": "^29.7.0", + "typescript": "^5.5.4" } }, "node_modules/@ampproject/remapping": { @@ -496,6 +508,204 @@ "node": ">=0.1.90" } }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", + "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/eslintrc/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true, + "license": "BSD-3-Clause" + }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -890,6 +1100,44 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", @@ -988,13 +1236,25 @@ "@types/istanbul-lib-report": "*" } }, + "node_modules/@types/jest": { + "version": "29.5.14", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.14.tgz", + "integrity": "sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, "node_modules/@types/node": { - "version": "24.0.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.1.tgz", - "integrity": "sha512-MX4Zioh39chHlDJbKmEgydJDS3tspMP/lnQC67G3SWsTnb9NeYVWOjkxpOSy4oMfPs4StcWHwBrvUb4ybfnuaw==", + "version": "20.19.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.10.tgz", + "integrity": "sha512-iAFpG6DokED3roLSP0K+ybeDdIX6Bc0Vd3mLW5uDqThPWtNos3E+EqOM11mPQHKzfWHqEBuLjIlsBQQ8CsISmQ==", "dev": true, + "license": "MIT", "dependencies": { - "undici-types": "~7.8.0" + "undici-types": "~6.21.0" } }, "node_modules/@types/stack-utils": { @@ -1003,6 +1263,16 @@ "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", "dev": true }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/yargs": { "version": "17.0.33", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", @@ -1018,114 +1288,375 @@ "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", "dev": true }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.18.0.tgz", + "integrity": "sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==", + "dev": true, + "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/type-utils": "7.18.0", + "@typescript-eslint/utils": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^7.0.0", + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "node_modules/@typescript-eslint/parser": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.18.0.tgz", + "integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "type-fest": "^0.21.3" + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/typescript-estree": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", + "debug": "^4.3.4" }, "engines": { - "node": ">=8" + "node": "^18.18.0 || >=20.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "engines": { - "node": ">=8" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/@typescript-eslint/scope-manager": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz", + "integrity": "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==", "dev": true, + "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0" }, "engines": { - "node": ">=8" + "node": "^18.18.0 || >=20.0.0" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "node_modules/@typescript-eslint/type-utils": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.18.0.tgz", + "integrity": "sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==", "dev": true, + "license": "MIT", "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" + "@typescript-eslint/typescript-estree": "7.18.0", + "@typescript-eslint/utils": "7.18.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.3.0" }, "engines": { - "node": ">= 8" + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/archiver": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-6.0.1.tgz", - "integrity": "sha512-CXGy4poOLBKptiZH//VlWdFuUC1RESbdZjGjILwBuZ73P7WkAUN0htfSfBq/7k6FRFlpu7bg4JOkj1vU9G6jcQ==", - "dependencies": { - "archiver-utils": "^4.0.1", - "async": "^3.2.4", - "buffer-crc32": "^0.2.1", - "readable-stream": "^3.6.0", - "readdir-glob": "^1.1.2", - "tar-stream": "^3.0.0", - "zip-stream": "^5.0.1" - }, + "node_modules/@typescript-eslint/types": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz", + "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==", + "dev": true, + "license": "MIT", "engines": { - "node": ">= 12.0.0" + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/archiver-utils": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-4.0.1.tgz", - "integrity": "sha512-Q4Q99idbvzmgCTEAAhi32BkOyq8iVI5EwdO0PmBDSGIzzjYNdcFn7Q7k3OzbLy4kLUPXfJtG6fO2RjftXbobBg==", + "node_modules/@typescript-eslint/typescript-estree": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz", + "integrity": "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==", + "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "glob": "^8.0.0", - "graceful-fs": "^4.2.0", - "lazystream": "^1.0.0", - "lodash": "^4.17.15", - "normalize-path": "^3.0.0", - "readable-stream": "^3.6.0" + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" }, "engines": { - "node": ">= 12.0.0" + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.18.0.tgz", + "integrity": "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/typescript-estree": "7.18.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz", + "integrity": "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "7.18.0", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "dev": true, + "license": "ISC" + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/archiver": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-6.0.1.tgz", + "integrity": "sha512-CXGy4poOLBKptiZH//VlWdFuUC1RESbdZjGjILwBuZ73P7WkAUN0htfSfBq/7k6FRFlpu7bg4JOkj1vU9G6jcQ==", + "dependencies": { + "archiver-utils": "^4.0.1", + "async": "^3.2.4", + "buffer-crc32": "^0.2.1", + "readable-stream": "^3.6.0", + "readdir-glob": "^1.1.2", + "tar-stream": "^3.0.0", + "zip-stream": "^5.0.1" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/archiver-utils": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-4.0.1.tgz", + "integrity": "sha512-Q4Q99idbvzmgCTEAAhi32BkOyq8iVI5EwdO0PmBDSGIzzjYNdcFn7Q7k3OzbLy4kLUPXfJtG6fO2RjftXbobBg==", + "dependencies": { + "glob": "^8.0.0", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash": "^4.17.15", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, "dependencies": { "sprintf-js": "~1.0.2" } }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/asn1": { "version": "0.2.6", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", @@ -1539,7 +2070,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "dependencies": { "color-name": "~1.1.4" }, @@ -1550,8 +2080,7 @@ "node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "node_modules/combined-stream": { "version": "1.0.8", @@ -1695,6 +2224,13 @@ } } }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, "node_modules/deepmerge": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", @@ -1730,6 +2266,44 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dotenv": { + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz", + "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", @@ -1775,7 +2349,6 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, "engines": { "node": ">=6" } @@ -1789,6 +2362,246 @@ "node": ">=8" } }, + "node_modules/eslint": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/eslint/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", @@ -1802,6 +2615,52 @@ "node": ">=4" } }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", @@ -1873,11 +2732,58 @@ "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==" }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, "node_modules/fb-watchman": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", @@ -1887,6 +2793,19 @@ "bser": "2.1.1" } }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -1912,6 +2831,28 @@ "node": ">=8" } }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, "node_modules/follow-redirects": { "version": "1.15.9", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", @@ -1998,7 +2939,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, "engines": { "node": "6.* || 8.* || >= 10.*" } @@ -2050,6 +2990,19 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", @@ -2059,11 +3012,39 @@ "node": ">=4" } }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, "node_modules/har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", @@ -2126,13 +3107,50 @@ "npm": ">=1.3.7" } }, - "node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true, + "license": "MIT", "engines": { - "node": ">=10.17.0" + "node": ">=4" } }, "node_modules/import-local": { @@ -2198,6 +3216,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -2215,6 +3243,19 @@ "node": ">=6" } }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -2224,6 +3265,16 @@ "node": ">=0.12.0" } }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -3024,6 +4075,13 @@ "node": ">=6" } }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", @@ -3040,6 +4098,13 @@ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, "node_modules/json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", @@ -3071,6 +4136,16 @@ "node": ">=0.6.0" } }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, "node_modules/kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", @@ -3127,12 +4202,114 @@ "node": ">=6" } }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/lines-and-columns": { "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 }, + "node_modules/localtunnel": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/localtunnel/-/localtunnel-2.0.2.tgz", + "integrity": "sha512-n418Cn5ynvJd7m/N1d9WVJISLJF/ellZnfsLnx8WBWGzxv/ntNcFkJ1o6se5quUhCplfLGBNL5tYHiq5WF3Nug==", + "license": "MIT", + "dependencies": { + "axios": "0.21.4", + "debug": "4.3.2", + "openurl": "1.1.1", + "yargs": "17.1.1" + }, + "bin": { + "lt": "bin/lt.js" + }, + "engines": { + "node": ">=8.3.0" + } + }, + "node_modules/localtunnel/node_modules/axios": { + "version": "0.21.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", + "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.14.0" + } + }, + "node_modules/localtunnel/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/localtunnel/node_modules/debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/localtunnel/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/localtunnel/node_modules/yargs": { + "version": "17.1.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.1.1.tgz", + "integrity": "sha512-c2k48R0PwKIqKhPMWjeiF6y2xY/gPMUlro0sgxqXpbOIohWiLNXWslsootttv7E1e73QPAMQSg5FeySbVcpsPQ==", + "license": "MIT", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/localtunnel/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, "node_modules/locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", @@ -3150,6 +4327,13 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -3201,6 +4385,16 @@ "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", "dev": true }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, "node_modules/micromatch": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", @@ -3328,6 +4522,30 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/openurl": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/openurl/-/openurl-1.1.1.tgz", + "integrity": "sha512-d/gTkTb1i1GKz5k3XE3XFV/PxQ1k45zDqGP2OA7YhgsaLoqm6qRvARAZOFer1fcXritWlGBRCu/UgeS4HAnXAA==", + "license": "MIT" + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -3379,6 +4597,19 @@ "node": ">=6" } }, + "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" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -3439,6 +4670,16 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", @@ -3483,6 +4724,16 @@ "node": ">=8" } }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/pretty-format": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", @@ -3577,6 +4828,27 @@ "node": ">=0.6" } }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/queue-tick": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", @@ -3644,7 +4916,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -3699,6 +4970,104 @@ "node": ">=10" } }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -4010,6 +5379,13 @@ "node": "*" } }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true, + "license": "MIT" + }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", @@ -4040,6 +5416,19 @@ "node": ">=0.8" } }, + "node_modules/ts-api-utils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", + "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, "node_modules/tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -4056,6 +5445,19 @@ "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==" }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/type-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", @@ -4077,11 +5479,26 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/typescript": { + "version": "5.9.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", + "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/undici-types": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", - "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", - "dev": true + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" }, "node_modules/update-browserslist-db": { "version": "1.1.3", @@ -4199,11 +5616,20 @@ "node": ">= 8" } }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -4258,7 +5684,6 @@ "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, "engines": { "node": ">=10" } diff --git a/package.json b/package.json index 73eac82..b08839b 100755 --- a/package.json +++ b/package.json @@ -1,9 +1,14 @@ { - "name": "solidityscan-npm-test", - "version": "1.0.0", - "description": "Test SolidityScan", - "main": "index.js", + "name": "solidityscan", + "version": "0.2.3", + "description": "SolidityScan is a tool for scanning Solidity smart contracts for vulnerabilities.", + "main": "dist/index.js", + "types": "dist/index.d.ts", "scripts": { + "build": "tsc -p tsconfig.json", + "typecheck": "tsc --noEmit -p tsconfig.json", + "lint": "eslint . --ext .ts", + "prepublishOnly": "npm run build", "test": "jest" }, "keywords": [ @@ -22,15 +27,32 @@ "axios": "^1.6.7", "cli-spinners": "^2.9.2", "cli-table3": "^0.6.3", + "dotenv": "^16.5.0", "fs": "^0.0.1-security", + "localtunnel": "^2.0.2", "path": "^0.12.7", "request": "^2.88.2", "ws": "^8.16.0" }, "devDependencies": { - "jest": "^29.7.0" + "@types/jest": "^29.5.12", + "@types/node": "^20.14.11", + "@types/ws": "^8.5.10", + "@typescript-eslint/eslint-plugin": "^7.17.0", + "@typescript-eslint/parser": "^7.17.0", + "eslint": "^8.57.0", + "jest": "^29.7.0", + "typescript": "^5.5.4" }, "bin": { "solidityscan": "bin/solidityscan.js" - } + }, + "repository": { + "type": "git", + "url": "git+https://github.com/Credshields/solidityscan-npm-package.git" + }, + "bugs": { + "url": "https://github.com/Credshields/solidityscan-npm-package/issues" + }, + "homepage": "https://github.com/Credshields/solidityscan-npm-package#readme" } diff --git a/src/api.js b/src/api.js deleted file mode 100755 index 4632338..0000000 --- a/src/api.js +++ /dev/null @@ -1,90 +0,0 @@ -const utils = require("./utils"); -const fs = require("fs"); -const cliSpinners = require("cli-spinners"); -const spinner = cliSpinners.dots; - -async function projectScan(projectPayload, apiToken) { - const request_payload = { - action: "message", - payload: { - type: "private_project_scan_initiate", - body: projectPayload, - }, - }; - return utils.initializeWebSocket(apiToken, request_payload); -} - -async function contractScan(contractPayload, apiToken) { - const request_payload = { - action: "message", - payload: { - type: "private_block_scan_initiate", - body: contractPayload, - }, - }; - return utils.initializeWebSocket(apiToken, request_payload); -} - -async function analyzeProject(projectDirectory, apiToken) { - try { - const initializingSpinner = await utils.showSpinnerWithStatus( - "Initializing Scan", - spinner.frames - ); - const projectZipPath = await utils.createProjectZip(projectDirectory); - const uploadUrl = await utils.getUploadPresignedUrl( - projectZipPath, - apiToken - ); - if (uploadUrl) { - const fileData = fs.readFileSync(projectZipPath); - const uploadSuccessful = await utils.uploadToS3(fileData, uploadUrl); - if (uploadSuccessful) { - utils.stopSpinner(initializingSpinner, "Initializing Scan"); - const scanningSpinner = await utils.showSpinnerWithStatus( - "Scan in progress", - spinner.frames - ); - const request_payload = { - type: "private_project_scan_initiate", - body: { - file_urls: [uploadUrl], - project_name: "fileName", - project_visibility: "public", - project_type: "new", - }, - }; - const result = utils.initializeWebSocket( - apiToken, - request_payload, - scanningSpinner - ); - return result; - } - } else { - throw new Error(`Error analyzing project`); - } - } catch (error) { - throw new Error(`Error analyzing project from directory: ${error.message}`); - } -} - -async function runTests(projectDirectory, apiToken) { - analyzeProject(projectDirectory, apiToken) - .then((results) => { - utils.displayScanResults(results); - console.log(""); - console.log("Scan Summary"); - utils.displayScanSummary(results); - }) - .catch((error) => { - console.error("Error during scan:", error); - }); -} - -module.exports = { - projectScan, - contractScan, - analyzeProject, - runTests, -}; diff --git a/src/api.ts b/src/api.ts new file mode 100755 index 0000000..192debe --- /dev/null +++ b/src/api.ts @@ -0,0 +1,190 @@ +import * as utils from "./utils"; +import fs from "fs"; +import cliSpinners from "cli-spinners"; +import axios from "axios"; +import https from "https"; +const spinner = cliSpinners.dots; + +export interface ProjectScanPayload { + provider: string; + project_url: string; + project_name: string; + project_branch: string; + recur_scans?: boolean; + skip_file_paths?: string[]; +} + +export interface ContractScanPayload { + contract_address: string; + contract_platform: string; + contract_chain: string; +} + +export interface GenerateReportPayload { + project_id: string; + scan_id: string; + scan_type: string; +} + +async function projectScan(projectPayload: ProjectScanPayload, apiToken?: string, spinner: boolean = true) { + const request_payload = { + action: "message", + payload: { + type: "private_project_scan_initiate", + body: projectPayload, + }, + }; + return utils.initializeWebSocket(apiToken, request_payload, spinner); +} + +async function quickScanProject(projectPayload: ProjectScanPayload, apiToken?: string, spinner: boolean = true) { + const request_payload = { + action: "message", + payload: { + type: "private_quick_scan_initiate", + body: projectPayload, + }, + }; + return utils.initializeWebSocket(apiToken, request_payload, spinner); +} + +async function quickScanContract(contractPayload: ContractScanPayload, apiToken?: string, spinner: boolean = true) { + const request_payload = { + action: "message", + payload: { + type: "private_quick_scan_initiate", + body: contractPayload, + }, + }; + return utils.initializeWebSocket(apiToken, request_payload, spinner); +} + + +async function generateReport( + generateReportPayload: GenerateReportPayload, + apiToken?: string, + spinner: boolean = true +) { + const request_payload = { + action: "message", + payload: { + type: "generate_report", + body: generateReportPayload, + }, + }; + return utils.initializeWebSocket(apiToken, request_payload, spinner); +} + +async function contractScan(contractPayload: ContractScanPayload, apiToken?: string, spinner: boolean = true) { + const request_payload = { + action: "message", + payload: { + type: "private_threat_scan_initiate", + body: contractPayload, + }, + }; + return utils.initializeWebSocket(apiToken, request_payload, spinner); +} + +async function analyzeProject( + projectDirectory: string, + apiToken: string | undefined, + projectName: string, + isRunningTest = false +) { + try { + const initializingSpinner = await utils.showSpinnerWithStatus( + "Initializing Scan", + spinner.frames + ); + const projectZipPath = await utils.createProjectZip(projectDirectory); + const uploadUrl = await utils.getUploadPresignedUrl( + projectZipPath, + apiToken + ); + if (uploadUrl) { + const fileData = fs.readFileSync(projectZipPath); + const uploadSuccessful = await utils.uploadToS3(fileData, uploadUrl); + if (uploadSuccessful) { + utils.stopSpinner(initializingSpinner, "Initializing Scan"); + const scanningSpinner = await utils.showSpinnerWithStatus( + "Scan in progress", + spinner.frames + ); + const request_payload = { + type: "private_project_scan_initiate", + body: { + file_urls: [uploadUrl], + project_name: projectName, + project_visibility: "public", + project_type: "new", + }, + }; + const result = utils.initializeWebSocket( + apiToken, + request_payload + ); + return result; + } + } else { + throw new Error(`Error analyzing project`); + } + } catch (error: any) { + throw new Error(`Error analyzing project from directory: ${error.message}`); + } +} + +type RunTestsReturn = { + metadata: unknown; + scanDetails: unknown; + resultFile: string; +}; + +async function runTests( + projectDirectory: string, + apiToken: string | undefined, + projectName: string +): Promise { + try { + const results = await analyzeProject(projectDirectory, apiToken, projectName, true); + + const maybeResults: any = results as any; + if (maybeResults && maybeResults.scan_details && maybeResults.scan_details.link) { + const response = await axios.get(maybeResults.scan_details.link, { + httpsAgent: new https.Agent({ rejectUnauthorized: false }) + }); + + const scanData = response.data; + const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); + const filename = `scan-result-${maybeResults.scan_id}-${timestamp}.json`; + + fs.writeFileSync(filename, JSON.stringify(scanData, null, 2)); + console.log(`\nScan results saved to: ${filename}`); + + return { + metadata: maybeResults, + scanDetails: scanData, + resultFile: filename + }; + } else { + console.error("Error: Scan results link not found in the response"); + return results; + } + } catch (error: any) { + console.error("Error during scan:", error?.message || error); + if (error?.response) { + console.error("API response error:", error.response.data); + } + throw error; + } +} + +export { + projectScan, + generateReport, + contractScan, + analyzeProject, + runTests, + quickScanProject, + quickScanContract, +}; diff --git a/src/cli.js b/src/cli.js deleted file mode 100755 index 62a83ef..0000000 --- a/src/cli.js +++ /dev/null @@ -1,119 +0,0 @@ -const { projectScan, contractScan, analyzeProject } = require("./api"); -const utils = require("./utils"); - -function scan() { - const [, , ...args] = process.argv; - - if (args[0] === "scan") { - const scanType = args[1]; - if (scanType === "project") { - const provider = args[2]; - const projectUrl = args[3]; - const projectBranch = args[4]; - const projecName = args[5]; - const recurScan = args[7]; - const apiKey = process.env.SOLIDITYSCAN_API_KEY || args[6]; - - if (!provider || !projectUrl || !projectBranch || !projecName) { - console.error("Usage: solisityscan scan "); - process.exit(1); - } - - const payload = { - provider: provider, - project_url: projectUrl, - project_name: projecName, - project_branch: projectBranch, - recur_scans: recurScan || false, - skip_file_paths: [], - }; - - projectScan(payload, apiKey) - .then((results) => { - console.log("Scan results:", results); - }) - .catch((error) => { - console.error("Error during scan:", error); - }); - } else if (scanType === "contract") { - const contractAddress = args[2]; - const contractChain = args[3]; - const contractPlatform = args[4]; - const apiKey = process.env.SOLIDITYSCAN_API_KEY || args[5]; - - if (!contractAddress || !contractChain || !contractPlatform) { - console.error("Usage: solisityscan scan "); - process.exit(1); - } - - const payload = { - contract_address: contractAddress, - contract_platform: contractPlatform, - contract_chain: contractPlatform, - }; - - contractScan(payload, apiKey) - .then((results) => { - console.log("Scan results:", results); - }) - .catch((error) => { - console.error("Error during scan:", error); - }); - } - } else if (args[0] === "test") { - const projectPath = args[1]; - const apiKey = process.env.SOLIDITYSCAN_API_KEY || args[2]; - - if (!projectPath) { - console.error("Usage: solisityscan run-tests "); - process.exit(1); - } - - analyzeProject(projectPath, apiKey) - .then((results) => { - utils.displayScanResults(results); - console.log(""); - console.log("Scan Summary"); - utils.displayScanSummary(results); - }) - .catch((error) => { - console.error("Error during scan:", error); - }); - } else if (args.includes("-l")) { - // Start local WebSocket file server to expose file system - const dirFlagIndex = args.indexOf("-p"); - const serveDirectory = - dirFlagIndex !== -1 && args[dirFlagIndex + 1] - ? args[dirFlagIndex + 1] - : process.cwd(); - - const portFlagIndex = args.indexOf("--port"); - const port = - portFlagIndex !== -1 && args[portFlagIndex + 1] - ? parseInt(args[portFlagIndex + 1], 10) - : 8080; - - try { - utils.startLocalFileServer(serveDirectory, port); - } catch (error) { - console.error(error.message); - process.exit(1); - } - // keep the process alive when in local server mode - return; - } else { - console.error( - "Unknown command. Usage: solidityscan scan " - ); - process.exit(1); - } -} - -if (require.main === module) { - // executed via `node src/cli.js ...` – run the CLI - scan(); -} - -module.exports = { - scan, -}; diff --git a/src/cli.ts b/src/cli.ts new file mode 100755 index 0000000..a09ed39 --- /dev/null +++ b/src/cli.ts @@ -0,0 +1,193 @@ +import { default as axios } from "axios"; +import { projectScan, contractScan, analyzeProject, ProjectScanPayload, ContractScanPayload } from "./api"; +import * as utils from "./utils"; + +function scan(): void { + const [, , ...args] = process.argv; + + if (args[0] === "scan") { + const scanType = args[1]; + if (scanType === "project") { + const provider = args[2]; + const projectUrl = args[3]; + const projectBranch = args[4]; + const projecName = args[5]; + const recurScan = args[7] ? args[7] === 'true' : false; + const apiKey = process.env.SOLIDITYSCAN_API_KEY || args[6]; + + if (!provider || !projectUrl || !projectBranch || !projecName) { + console.error("Usage: solisityscan scan "); + process.exit(1); + } + + const payload: ProjectScanPayload = { + provider: provider, + project_url: projectUrl, + project_name: projecName, + project_branch: projectBranch, + recur_scans: recurScan || false, + skip_file_paths: [], + }; + + projectScan(payload, apiKey) + .then((results) => { + console.log("Scan results:", results); + }) + .catch((error) => { + console.error("Error during scan:", error); + }); + } else if (scanType === "contract") { + const contractAddress: string | undefined = args[2]; + const contractChain: string | undefined = args[3]; + const contractPlatform: string | undefined = args[4]; + const cliProvidedToken = args[5]; // CLI provided token + + if (!contractAddress || !contractChain || !contractPlatform) { + console.error("Usage: solisityscan scan contract
[apiToken]"); + process.exit(1); + } + + try { + // Get API token from environment or command line + const apiToken = process.env.SOLIDITYSCAN_API_KEY || cliProvidedToken; + if (!apiToken) { + console.error("No API token provided. Please set SOLIDITYSCAN_API_KEY environment variable or provide token as argument."); + process.exit(1); + } + + const payload: ContractScanPayload = { + contract_address: contractAddress, + contract_platform: contractPlatform, + contract_chain: contractChain, + }; + + contractScan(payload, apiToken) + .then((results) => { + console.log("Scan results:",JSON.stringify(results, null, 2)); + }) + .catch((error) => { + console.error("\nError during scan:", error); + }); + } catch (error: any) { + console.error("\nError with API token:", error?.message || error); + process.exit(1); + } + } + } else if (args[0] === "local") { + const projectPath: string | undefined = args[1]; + const apiKeyFromArgs = args[2] && !process.env.SOLIDITYSCAN_API_KEY; + const apiKey: string | undefined = process.env.SOLIDITYSCAN_API_KEY || args[2]; + let projectName: string | undefined = apiKeyFromArgs ? args[3] : args[2]; + + if(!projectName){ + projectName = "LocalScan"; + } + + if (!projectPath) { + console.error("Usage: solisityscan run-tests "); + process.exit(1); + } + + analyzeProject(projectPath, apiKey, projectName) + .then((results) => { + const r: any = results as any; + if(r?.scan_details?.link){ + axios.get(r.scan_details.link) + .then((response) => { + utils.displayScanResults(response.data.scan_report); + }) + .catch((error) => { + console.error("Error during scan:", error); + }); + } + }) + .catch((error) => { + console.error("Error during scan:", error); + }); + } else if (args.includes("-l")) { + + const dirFlagIndex = args.indexOf("-p"); + const serveDirectory = + dirFlagIndex !== -1 && args[dirFlagIndex + 1] + ? args[dirFlagIndex + 1] + : process.cwd(); + + + const portFlagIndex = args.indexOf("--port"); + const userSpecifiedPort = + portFlagIndex !== -1 && args[portFlagIndex + 1] + ? parseInt(args[portFlagIndex + 1], 10) + : null; + + const idFlagIndex = args.indexOf("--id"); + const tunnelId = + idFlagIndex !== -1 && args[idFlagIndex + 1] + ? args[idFlagIndex + 1] + : null; + + if (!tunnelId) { + console.error("Missing --id argument"); + process.exit(1); + } + + let portAttempts = 0; + let port = userSpecifiedPort || 9462; + let wss; + + while (portAttempts < 5) { + try { + wss = utils.startLocalFileServer(serveDirectory, port); + break; // success + } catch (err: any) { + if (userSpecifiedPort || err?.code !== "EADDRINUSE") { + console.error(err?.message || "Failed to start local server"); + process.exit(1); + } + port += 1; + portAttempts += 1; + } + } + + if (!wss) { + console.error("Could not bind to any port between 9462 and 9466"); + process.exit(1); + } + + (async () => { + try { + const localtunnel = require("localtunnel"); + const tunnel = await localtunnel({ port, subdomain: tunnelId }); + + const clean = () => { + try { + tunnel.close(); + } catch (_) {} + try { + if (wss) wss.close(); + } catch (_) {} + process.exit(0); + }; + process.on("SIGINT", clean); + process.on("SIGTERM", clean); + } catch (e) { + console.error("Error during tunnel:", e); + } + })(); + + return; + } else { + console.error( + "Unknown command. Usage: solidityscan scan " + ); + process.exit(1); + } +} + +if (require.main === module) { + // executed via `node src/cli.js ...` – run the CLI + scan(); +} + +export { + scan, +}; diff --git a/src/utils.js b/src/utils.js deleted file mode 100755 index 782fcb6..0000000 --- a/src/utils.js +++ /dev/null @@ -1,436 +0,0 @@ -const request = require("request"); -const WebSocket = require("ws"); -const fs = require("fs"); -const path = require("path"); -const archiver = require("archiver"); -const axios = require("axios"); -const Table = require("cli-table3"); - -const cliSpinners = require("cli-spinners"); -const spinner = cliSpinners.dots; - -const getApi = (apiToken) => { - if (apiToken) { - return axios.create({ - baseURL: "https://api-develop.solidityscan.com/", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${apiToken}`, - "CF-Access-Client-Secret": "", - "CF-Access-Client-Id": "", - }, - }); - } else { - return axios.create({ - baseURL: "https://api-develop.solidityscan.com/", - headers: { - "Content-Type": "application/json", - "CF-Access-Client-Secret": "", - "CF-Access-Client-Id": "", - }, - }); - } -}; - -const initializeWebSocket = (apiToken, payload, spinner) => { - const wsUrl = `${"wss://api-ws-stage.solidityscan.com/stage"}${ - apiToken ? `?auth_token=${apiToken}` : "" - }`; - const ws = new WebSocket(wsUrl); - - return new Promise((resolve, reject) => { - ws.on("open", () => { - ws.send( - JSON.stringify({ - action: "message", - payload: payload, - }) - ); - }); - - ws.on("message", (data) => { - const receivedMessage = JSON.parse(data); - if (receivedMessage.type === "error") { - ws.close(); - reject(receivedMessage.payload.payload.error_message); - } else if (receivedMessage.type === "scan_status") { - if (receivedMessage.payload.scan_status === "scan_done") { - request.get( - receivedMessage.payload.scan_details.link, - (error, response, body) => { - if (error) { - resolve(error); - } else if (response.statusCode !== 200) { - resolve(error); - } else { - const scan_result = JSON.parse(body); - resolve(scan_result.scan_report); - } - stopSpinner(spinner, "Scan in progress"); - ws.close(); - } - ); - } - } - console.log(receivedMessage); - }); - - ws.on("error", (error) => { - console.log(error); - reject(error); - }); - - ws.on("close", () => {}); - }); -}; - -const createProjectZip = async (projectDirectory) => { - try { - const zipFileName = "project.zip"; - const output = fs.createWriteStream(zipFileName); - const archive = archiver("zip", { zlib: { level: 9 } }); - - archive.pipe(output); - - const gatherSolFiles = (directory) => { - const files = fs.readdirSync(directory); - - files.forEach((file) => { - const filePath = path.join(directory, file); - - if (fs.statSync(filePath).isDirectory() && file !== "node_modules") { - gatherSolFiles(filePath); - } else if (path.extname(file) === ".sol") { - const relativePath = path.relative(projectDirectory, filePath); - const fileContent = fs.readFileSync(filePath); - archive.append(fileContent, { name: relativePath }); - } - }); - }; - - gatherSolFiles(projectDirectory); - - await archive.finalize(); - - return zipFileName; - } catch (error) { - throw new Error(`Error creating project ZIP: ${error.message}`); - } -}; - -const getUploadPresignedUrl = async (fileName, apiToken) => { - const apiUrl = `private/api-get-presigned-url/?file_name=${fileName}`; - const API = getApi(apiToken); - const response = await API.get(apiUrl); - if (response.status === 200 && response.data && response.data.result) { - return response.data.result.url; - } else { - return null; - } -}; - -const uploadToS3 = async (fileData, uploadUrl) => { - try { - const response = await axios.put(uploadUrl, fileData, { - headers: { - "Content-Type": "application/octet-stream", - }, - // httpsAgent: new https.Agent({ - // rejectUnauthorized: false // Only for local - // }) - }); - if (response.status === 200) { - return true; - } else { - return false; - } - } catch (error) { - console.log("error uploading file>",error) - return false; - } -}; - -function capitalizeFirstLetter(str) { - return str.charAt(0).toUpperCase() + str.slice(1); -} - -const displayScanResults = (scan) => { - const table = new Table({ - head: ["Issue Name", "Issue Severity", "File Path", "Line No."], - }); - - scan.multi_file_scan_details.forEach((detail) => { - const { template_details } = detail; - if (detail.metric_wise_aggregated_findings) { - detail.metric_wise_aggregated_findings.forEach((bug) => { - const filePath = bug.findings[0].file_path; - const line = `L${bug.findings[0].line_nos_start} - L${bug.findings[0].line_nos_end}`; - const row = [ - template_details.issue_name, - capitalizeFirstLetter(template_details.issue_severity), - filePath.replace("/project", ""), - line, - ]; - table.push(row); - }); - } - }); - - console.log(table.toString()); -}; - -const displayScanSummary = (scan) => { - const table = new Table(); - - const issues_count = - scan.multi_file_scan_summary.issue_severity_distribution.critical + - scan.multi_file_scan_summary.issue_severity_distribution.high + - scan.multi_file_scan_summary.issue_severity_distribution.medium + - scan.multi_file_scan_summary.issue_severity_distribution.low + - scan.multi_file_scan_summary.issue_severity_distribution.informational + - scan.multi_file_scan_summary.issue_severity_distribution.gas; - - table.push( - { - Critical: - scan.multi_file_scan_summary.issue_severity_distribution.critical, - }, - { High: scan.multi_file_scan_summary.issue_severity_distribution.high }, - { - Medium: scan.multi_file_scan_summary.issue_severity_distribution.medium, - }, - { Low: scan.multi_file_scan_summary.issue_severity_distribution.low }, - { - Informational: - scan.multi_file_scan_summary.issue_severity_distribution.informational, - }, - { Gas: scan.multi_file_scan_summary.issue_severity_distribution.gas }, - { "Security Score": `${scan.multi_file_scan_summary.score_v2} / 100` } - ); - console.log(table.toString()); - console.log( - `Scan successful! ${issues_count} issues found. To view detailed results and generate a report navigate to solidityscan.com.` - ); -}; - -// Function to display a spinner with dynamic status -async function showSpinnerWithStatus(statusMessage, spinnerFrames) { - process.stdout.write(`${statusMessage}... `); - - let frameIndex = 0; - - const interval = setInterval(() => { - process.stdout.write(spinnerFrames[frameIndex]); - process.stdout.write("\b"); - frameIndex = (frameIndex + 1) % spinnerFrames.length; - }, 100); - - return interval; -} - -// Function to stop the spinner -function stopSpinner(interval, statusMessage) { - clearInterval(interval); - process.stdout.write("\r"); - console.log(`${statusMessage}... Done`); -} - -// New helper to serve local directory over WebSocket -function startLocalFileServer(rootDirectory, port = 8080) { - if (!fs.existsSync(rootDirectory)) { - throw new Error(`Directory not found: ${rootDirectory}`); - } - - const absoluteRoot = path.resolve(rootDirectory); - const wss = new WebSocket.Server({ port }); - - // eslint-disable-next-line no-console - console.log(`SolidityScan local file server started on ws://localhost:${wss.options.port}\nServing directory: ${absoluteRoot}`); - - wss.on("connection", (socket) => { - socket.on("message", async (raw) => { - let message; - try { - message = JSON.parse(raw); - } catch (err) { - socket.send( - JSON.stringify({ type: "error", error: "Invalid JSON message" }) - ); - return; - } - - const { action, payload } = message; - - if (action === "listFiles") { - // Return hierarchical folder tree with metadata - const buildTree = (dir, relPath = "") => { - const name = path.basename(dir); - const stat = fs.statSync(dir); - if (stat.isDirectory()) { - const dirs = []; - const files = []; - fs.readdirSync(dir).forEach((entry) => { - if (entry === "node_modules") return; - const abs = path.join(dir, entry); - const rootName = path.basename(absoluteRoot); - - let childRel; - if (relPath === "") { - childRel = path.join(rootName, entry); - } else { - childRel = path.join(relPath, entry); - } - - if (fs.statSync(abs).isDirectory()) { - dirs.push(buildTree(abs, childRel + path.sep)); - } else { - const fStat = fs.statSync(abs); - files.push({ - path: childRel, - name: entry, - size: fStat.size, - mtimeMs: fStat.mtimeMs, - checked: entry.endsWith(".sol"), - }); - } - }); - - // Determine checked / isChildCheck flags - const numSol = files.filter((f) => f.checked).length; - const numNonSol = files.length - numSol; - let checkedDir = false; - let isChildCheck = false; - if (numSol === 0) { - checkedDir = false; - isChildCheck = false; - } else if (numNonSol === 0) { - checkedDir = true; - isChildCheck = true; - } else { - checkedDir = false; - isChildCheck = true; - } - - let dirPath; - if (dir === absoluteRoot) { - dirPath = path.basename(absoluteRoot) + "/"; - } else { - dirPath = relPath + (relPath && !relPath.endsWith(path.sep) ? path.sep : ""); - } - - return { - name, - path: dirPath, - tree: dirs, - isChildCheck, - checked: checkedDir, - blobs: files, - size: 0, - mtimeMs: stat.mtimeMs, - }; - } - // Should not reach here for files as we handle in parent - }; - - const rootTreeInternal = buildTree(absoluteRoot, ""); - const responseTree = { - name: "", - path: "", - tree: [rootTreeInternal], - isChildCheck: rootTreeInternal.isChildCheck, - checked: rootTreeInternal.checked, - blobs: [], - size: 0, - mtimeMs: rootTreeInternal.mtimeMs, - }; - - socket.send( - JSON.stringify({ type: "folderStructure", tree: responseTree }) - ); - } else if (action === "zipAndSendFiles") { - const presignedUrl = payload.presigned_url; - const skip = new Set(payload.skip_file_paths || []); - if (!presignedUrl) { - socket.send( - JSON.stringify({ type: "error", error: "presigned_url missing" }) - ); - return; - } - - const archive = archiver("zip", { zlib: { level: 9 } }); - const chunks = []; - - archive.on("data", (chunk) => chunks.push(chunk)); - archive.on("warning", (err) => { - if (err.code !== "ENOENT") { - socket.send(JSON.stringify({ type: "error", error: err.message })); - } - }); - archive.on("error", (err) => { - socket.send(JSON.stringify({ type: "error", error: err.message })); - }); - archive.on("end", async () => { - const buffer = Buffer.concat(chunks); - - let success = false; - try { - if (presignedUrl.startsWith("memory://")) { - // test stub - success = true; - } else { - success = await uploadToS3(buffer, presignedUrl); - } - } catch (e) { - console.log("error uploading file",e) - success = false; - } - - socket.send( - JSON.stringify({ - type: "uploadStatus", - success, - }) - ); - }); - - // recursively walk and add files not skipped - const walkAdd = (dir, rel = "") => { - fs.readdirSync(dir).forEach((entry) => { - if (entry === "node_modules") return; - const abs = path.join(dir, entry); - const relPath = path.join(rel, entry); - const relPathPosix = relPath.split(path.sep).join("/"); - const stat = fs.statSync(abs); - if (stat.isDirectory()) { - walkAdd(abs, relPath); - } else { - if (!skip.has(relPathPosix)) { - archive.file(abs, { name: relPath }); - } - } - }); - }; - walkAdd(absoluteRoot, ""); - archive.finalize(); - } else { - socket.send( - JSON.stringify({ type: "error", error: "Unknown action" }) - ); - } - }); - }); - - return wss; -} - -module.exports = { - initializeWebSocket, - createProjectZip, - getUploadPresignedUrl, - uploadToS3, - displayScanResults, - displayScanSummary, - showSpinnerWithStatus, - stopSpinner, - startLocalFileServer, -}; diff --git a/src/utils.ts b/src/utils.ts new file mode 100755 index 0000000..a8b613e --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,585 @@ +import request from "request"; +import WebSocket from "ws"; +import fs from "fs"; +import path from "path"; +import archiver from "archiver"; +import axios, { type AxiosInstance } from "axios"; +import Table from "cli-table3"; + +import cliSpinners from "cli-spinners"; +const spinner = cliSpinners.dots; + +const getApi = (apiToken?: string): AxiosInstance => { + const apiBaseUrl = "https://api.solidityscan.com/"; + if (apiToken) { + const instance = axios.create({ + baseURL: apiBaseUrl, + headers: { + "Content-Type": "application/json", + "accept": "application/json, text/plain, */*", + "Authorization": `Bearer ${apiToken}`, + "cache-control": "no-cache", + } + }); + return instance; + } else { + const instance = axios.create({ + baseURL: apiBaseUrl, + headers: { + "Content-Type": "application/json", + "CF-Access-Client-Secret": "", + "CF-Access-Client-Id": "", + } + }); + return instance; + } +}; + +const initializeWebSocket = (apiToken: string | undefined, payload: any, spinner: boolean = true, ): Promise => { + const wsUrl = 'wss://api-ws.solidityscan.com/'; + const ws = new WebSocket(wsUrl, { + rejectUnauthorized: false + }); + + const emitMessage = (messagePayload: any) => { + ws.send( + JSON.stringify({ + action: "message", + payload: messagePayload, + }) + ); + }; + + return new Promise((resolve, reject) => { + const connectionTimeout: NodeJS.Timeout = setTimeout(() => { + ws.close(); + reject(new Error("WebSocket connection timed out waiting for scan results")); + }, 60000); // 60 second timeout + ws.on("open", () => { + if (apiToken) { + emitMessage({ + type: "auth_token_register", + body: { + auth_token: apiToken, + }, + }); + } else { + console.log("No authentication token provided, sending payload directly"); + emitMessage(payload); + } + }); + + ws.on("message", (data: any) => { + try { + const receivedMessage = JSON.parse(data.toString()); + clearTimeout(connectionTimeout); + if (receivedMessage.type === "auth_token_register") { + + if (payload.payload) { + emitMessage(payload.payload); + } else { + emitMessage(payload); + } + } + else if (receivedMessage.type === "scan_status") { + if (receivedMessage.payload?.scan_status === "scan_done") { + resolve(receivedMessage.payload); + ws.close(); + } + } + else if (receivedMessage.type === "quick_scan_status") { + if (receivedMessage.payload?.scan_status === "scan_done") { + resolve(receivedMessage.payload); + ws.close(); + } else { + console.log(`\n[WebSocket] Waiting for scan to complete. Current status: ${receivedMessage.payload?.scan_status || receivedMessage.payload?.quick_scan_status || 'processing'}`); + } + } + else if (receivedMessage.type === "report_generation_status") { + if (receivedMessage.payload?.report_status === "report_generated") { + resolve(receivedMessage.payload); + ws.close(); + } else { + console.log(`\n[WebSocket] Report generation payload: ${receivedMessage.payload}`); + } + } + else if (receivedMessage.type === "quick_scan_result") { + if (receivedMessage.payload?.scan_details?.link) { + request.get( + receivedMessage.payload.scan_details.link, + (error: any, response: any, body: any) => { + if (error) { + resolve(error); + } else if (response.statusCode !== 200) { + resolve(error); + } else { + try { + const scan_result = JSON.parse(body); + resolve(scan_result.scan_report || scan_result); + } catch (e) { + resolve(body); + } + } + + ws.close(); + } + ); + } + } + else if (receivedMessage.type === "error") { + console.log("\n Error received from server:", receivedMessage.payload?.payload?.error_message || receivedMessage.payload?.error_message || "Unknown error"); + ws.close(); + reject(receivedMessage.payload?.payload?.error_message || receivedMessage.payload?.error_message || "Unknown error from server"); + } + else { + if (spinner) { + process.stdout.write("."); + } + } + } catch (error) { + console.error("\nError processing message:", error); + console.error("\nRaw message data:", data.toString()); + } + }); + + ws.on("error", (error: unknown) => { + console.log(error); + reject(error); + }); + + ws.on("close", () => {}); + }); +}; + +const createProjectZip = async (projectDirectory: string): Promise => { + try { + const zipFileName = "project.zip"; + const output = fs.createWriteStream(zipFileName); + const archive = archiver("zip", { zlib: { level: 9 } }); + + archive.pipe(output); + + const gatherSolFiles = (directory: string) => { + const files = fs.readdirSync(directory); + + files.forEach((file) => { + const filePath = path.join(directory, file); + + if (fs.statSync(filePath).isDirectory() && file !== "node_modules") { + gatherSolFiles(filePath); + } else if (path.extname(file) === ".sol") { + const relativePath = path.relative(projectDirectory, filePath); + const fileContent = fs.readFileSync(filePath); + archive.append(fileContent, { name: relativePath }); + } + }); + }; + + gatherSolFiles(projectDirectory); + + await archive.finalize(); + + return zipFileName; + } catch (error: any) { + throw new Error(`Error creating project ZIP: ${error.message}`); + } +}; + +const getUploadPresignedUrl = async (fileName: string, apiToken?: string) => { + try { + const apiUrl = `private/api-get-presigned-url/?file_name=${fileName}`; + const API = getApi(apiToken); + const response = await API.get(apiUrl); + if (response.status === 200 && response.data && response.data.result) { + return response.data.result.url; + } else { + return null; + } + } catch (error: any) { + throw new Error(`Failed to get presigned URL: ${error.message}`); + } +}; + +const uploadToS3 = async (fileData: Buffer | Uint8Array, uploadUrl: string): Promise => { + try { + const response = await axios.put(uploadUrl, fileData, { + headers: { + "Content-Type": "application/octet-stream", + } + }); + + if (response.status === 200 || response.status === 204) { + return true; + } else { + return false; + } + } catch (error) { + return false; + } +}; + +function capitalizeFirstLetter(str: string) { + return str.charAt(0).toUpperCase() + str.slice(1); +} + +function formatHtmlForTerminal(htmlContent: string) { + if (!htmlContent) return ''; + let text = htmlContent.replace(//gi, '\n'); + text = text.replace(/(.*?)<\/code>/gi, '`$1`'); + text = text.replace(/<\/?[^>]+(>|$)/g, ''); + text = text.replace(/\s+/g, ' ').trim(); + + return text; +} + +const displayScanResults = (scan: any): void => { + const table = new Table({ + head: ["#", "NAME", "SEVERITY", "CONFIDENCE", "DESCRIPTION", "REMEDIATION"], + chars: { + 'top': '─', 'top-mid': '┬', 'top-left': 'β”Œ', 'top-right': '┐', + 'bottom': '─', 'bottom-mid': 'β”΄', 'bottom-left': 'β””', 'bottom-right': 'β”˜', + 'left': 'β”‚', 'left-mid': 'β”œ', 'mid': '─', 'mid-mid': 'β”Ό', + 'right': 'β”‚', 'right-mid': '─', 'middle': 'β”‚' + }, + style: { + head: ['bold'] + }, + // Configure optimal column widths + colWidths: [5, 20, 12, 12, 35, 35], + wordWrap: true + }); + + let issueCount = 0; + scan.multi_file_scan_details.forEach((detail: any) => { + const { template_details } = detail; + if (detail.metric_wise_aggregated_findings) { + detail.metric_wise_aggregated_findings.forEach((bug: any) => { + issueCount++; + const filePath = bug.findings[0].file_path; + const location = `${filePath.replace("/project", "")}\nL${bug.findings[0].line_nos_start} - L${bug.findings[0].line_nos_end}`; + const description = formatHtmlForTerminal(template_details.issue_description); + const fullDescription = `${description}\n\nLocation:\n${location}`; + + const row = [ + `${issueCount}.`, + template_details.issue_name, + capitalizeFirstLetter(template_details.issue_severity), + template_details.issue_confidence, + fullDescription, + formatHtmlForTerminal(template_details.issue_remediation) + ]; + table.push(row); + }); + } + }); + + if (issueCount === 0) { + console.log('No security issues found!'); + } else { + console.log('SECURITY SCAN RESULTS:'); + console.log(table.toString()); + console.log(`Found ${issueCount} security ${issueCount === 1 ? 'issue' : 'issues'}.`); + } +}; + +const displayScanSummary = (scan: any): void => { + const table = new Table(); + + const issues_count = + scan.multi_file_scan_summary.issue_severity_distribution.critical + + scan.multi_file_scan_summary.issue_severity_distribution.high + + scan.multi_file_scan_summary.issue_severity_distribution.medium + + scan.multi_file_scan_summary.issue_severity_distribution.low + + scan.multi_file_scan_summary.issue_severity_distribution.informational + + scan.multi_file_scan_summary.issue_severity_distribution.gas; + + table.push( + { + Critical: + scan.multi_file_scan_summary.issue_severity_distribution.critical, + }, + { High: scan.multi_file_scan_summary.issue_severity_distribution.high }, + { + Medium: scan.multi_file_scan_summary.issue_severity_distribution.medium, + }, + { Low: scan.multi_file_scan_summary.issue_severity_distribution.low }, + { + Informational: + scan.multi_file_scan_summary.issue_severity_distribution.informational, + }, + { Gas: scan.multi_file_scan_summary.issue_severity_distribution.gas }, + { "Security Score": `${scan.multi_file_scan_summary.score_v2} / 100` } + ); + console.log(table.toString()); + console.log( + `Scan successful! ${issues_count} issues found. To view detailed results and generate a report navigate to solidityscan.com.` + ); +}; + +// Function to display a spinner with dynamic status +async function showSpinnerWithStatus(statusMessage: string, spinnerFrames: string[]): Promise> { + process.stdout.write(`${statusMessage}... `); + + let frameIndex = 0; + + const interval = setInterval(() => { + process.stdout.write(spinnerFrames[frameIndex]); + process.stdout.write("\b"); + frameIndex = (frameIndex + 1) % spinnerFrames.length; + }, 100); + + return interval as ReturnType; +} + +// Function to stop the spinner +function stopSpinner(interval: ReturnType, statusMessage: string) { + clearInterval(interval as any); + process.stdout.write("\r"); + console.log(`${statusMessage}... Done`); +} + +// New helper to serve local directory over WebSocket +function startLocalFileServer(rootDirectory: string, port: number = 8080) { + if (!fs.existsSync(rootDirectory)) { + throw new Error(`Directory not found: ${rootDirectory}`); + } + + const absoluteRoot = path.resolve(rootDirectory); + const wss = new WebSocket.Server({ port, verifyClient: (info: any, done: any) => { + if (!originIsAllowed(info.origin)) { + done(false) + console.log(`Connection from origin ${info.origin} is not allowed`) + return + } + done(true) + } }); + + console.log(`SolidityScan local file server started\nServing directory: ${absoluteRoot}`); + + wss.on("connection", (socket: any) => { + socket.on("message", async (raw: any) => { + let message; + try { + message = JSON.parse(raw); + } catch (err) { + socket.send( + JSON.stringify({ type: "error", error: "Invalid JSON message" }) + ); + return; + } + + const { action, payload } = message; + + if (action === "listFiles") { + // Return hierarchical folder tree with metadata + const buildTree = (dir: string, relPath: string = ""): any => { + const name = path.basename(dir); + const stat = fs.statSync(dir); + if (stat.isDirectory()) { + const dirs: any[] = []; + const files: any[] = []; + fs.readdirSync(dir).forEach((entry) => { + if (entry === "node_modules") return; + const abs = path.join(dir, entry); + const rootName = path.basename(absoluteRoot); + + // Build a raw relative path using native separators + let childRelRaw: string; + if (relPath === "") { + childRelRaw = path.join(rootName, entry); + } else { + childRelRaw = path.join(relPath, entry); + } + + // Convert to POSIX style with a single "/" separator for JSON responses + const childRel = childRelRaw + .split(path.sep) + .join("/") + .replace(/\/+/g, "/"); + + if (fs.statSync(abs).isDirectory()) { + // Recurse with the raw path to preserve correct joining behaviour + dirs.push(buildTree(abs, childRelRaw + path.sep)); + } else { + const fStat = fs.statSync(abs); + files.push({ + path: childRel, + name: entry, + size: fStat.size, + mtimeMs: fStat.mtimeMs, + checked: entry.endsWith(".sol"), + }); + } + }); + + // Determine checked / isChildCheck flags + const numSol = files.filter((f) => f.checked).length; + const numNonSol = files.length - numSol; + let checkedDir = false; + let isChildCheck = false; + if (numSol === 0) { + checkedDir = false; + isChildCheck = false; + } else if (numNonSol === 0) { + checkedDir = true; + isChildCheck = true; + } else { + checkedDir = false; + isChildCheck = true; + } + + // Build directory path (raw) and then normalise to POSIX + let dirPathRaw: string; + if (dir === absoluteRoot) { + dirPathRaw = path.basename(absoluteRoot) + path.sep; + } else { + dirPathRaw = relPath + (relPath && !relPath.endsWith(path.sep) ? path.sep : ""); + } + + let dirPath = dirPathRaw + .split(path.sep) + .join("/") + .replace(/\/+/g, "/"); + // Ensure directory paths end with a single trailing slash + if (!dirPath.endsWith("/")) { + dirPath += "/"; + } + + return { + name, + path: dirPath, + tree: dirs, + isChildCheck, + checked: checkedDir, + blobs: files, + size: 0, + mtimeMs: stat.mtimeMs, + }; + } + // Should not reach here for files as we handle in parent + return undefined as any; + }; + + const rootTreeInternal: any = buildTree(absoluteRoot, "") as any; + const responseTree = { + name: "", + path: "", + tree: [rootTreeInternal], + isChildCheck: rootTreeInternal.isChildCheck, + checked: rootTreeInternal.checked, + blobs: [], + size: 0, + mtimeMs: rootTreeInternal.mtimeMs, + }; + + socket.send( + JSON.stringify({ type: "folderStructure", tree: responseTree }) + ); + } else if (action === "zipAndSendFiles") { + const presignedUrl = payload.presigned_url; + if (!originIsAllowed(payload.origin)) { + socket.send( + JSON.stringify({ type: "error", error: "origin not allowed" }) + ); + return; + } + const skip = new Set(payload.skip_file_paths || []); + if (!presignedUrl) { + socket.send( + JSON.stringify({ type: "error", error: "presigned_url missing" }) + ); + return; + } + + const archive = archiver("zip", { zlib: { level: 9 } }); + const chunks: Buffer[] = []; + + archive.on("data", (chunk: Buffer) => chunks.push(chunk)); + archive.on("warning", (err: any) => { + if (err.code !== "ENOENT") { + socket.send(JSON.stringify({ type: "error", error: err.message })); + } + }); + archive.on("error", (err: any) => { + socket.send(JSON.stringify({ type: "error", error: err.message })); + }); + archive.on("end", async () => { + const buffer = Buffer.concat(chunks); + + let success = false; + try { + if (presignedUrl.startsWith("memory://")) { + // test stub + success = true; + } else { + success = await uploadToS3(buffer, presignedUrl); + } + } catch (e) { + console.log("error uploading file",e) + success = false; + } + + socket.send( + JSON.stringify({ + type: "uploadStatus", + success, + }) + ); + }); + + // recursively walk and add files not skipped + const walkAdd = (dir: string, rel: string = "") => { + fs.readdirSync(dir).forEach((entry) => { + if (entry === "node_modules") return; + const abs = path.join(dir, entry); + const relPath = path.join(rel, entry); + const relPathPosix = relPath + .split(path.sep) + .join("/") + .replace(/\/+/g, "/"); + const stat = fs.statSync(abs); + if (stat.isDirectory()) { + walkAdd(abs, relPath); + } else { + if (!skip.has(relPathPosix)) { + // Use POSIX-style path inside the archive to avoid platform-specific separators + archive.file(abs, { name: relPathPosix }); + } + } + }); + }; + walkAdd(absoluteRoot, ""); + archive.finalize(); + } else { + socket.send( + JSON.stringify({ type: "error", error: "Unknown action" }) + ); + } + }); + }); + + return wss; +} + +function getDomain (url: string) { + const domainMatch = url.match(/^(?:https?:\/\/)?(?:[^@\n]+@)?(?:www\.)?([^:\/\n?]+)/img) + return domainMatch ? domainMatch[0] : null +} +function originIsAllowed (origin: string) { + return true; + const DOMAIN = getDomain(origin) || ""; + const allowedOrigins = ["https://solidityscan.com","https://develop.solidityscan.com", "https://credshields-prod.s3.amazonaws.com", "https://credshields-dev.s3.amazonaws.com/"] + return allowedOrigins.includes(DOMAIN) +} + +export { + initializeWebSocket, + createProjectZip, + getUploadPresignedUrl, + uploadToS3, + displayScanResults, + displayScanSummary, + showSpinnerWithStatus, + stopSpinner, + startLocalFileServer, +}; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..ae4c449 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,32 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "CommonJS", + "moduleResolution": "Node", + "outDir": "dist", + "rootDir": ".", + "declaration": true, + "sourceMap": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "types": ["node"], + "allowJs": true + }, + "include": [ + "index.ts", + "src/**/*.ts", + "bin/**/*.ts", + "types.d.ts" + ], + "exclude": [ + "dist", + "node_modules", + "__tests__" + ] +} + diff --git a/types.d.ts b/types.d.ts new file mode 100644 index 0000000..b91a8dd --- /dev/null +++ b/types.d.ts @@ -0,0 +1,18 @@ +declare module 'request' { + const request: any; + export default request; +} +declare module 'archiver' { + const archiver: any; + export default archiver; +} +declare module 'ws' { + export default class WebSocket { + static Server: any; + constructor(url: string, options?: any); + on(event: string, cb: (...args: any[]) => void): void; + send(data: any): void; + close(): void; + } +} +