Repository: lirantal/lockfile-lint (js)

Issue:
Mark results as OK, how?
So I'm running the lockfile-linter and I get for example these results:

> detected resolved URL for package with a different name: string-width-cjs
>     expected: string-width-cjs
>     actual: string-width
> 
> detected resolved URL for package with a different name: strip-ansi-cjs
>     expected: strip-ansi-cjs
>     actual: strip-ansi
> 
> detected resolved URL for package with a different name: wrap-ansi-cjs
>     expected: wrap-ansi-cjs
>     actual: wrap-ansi
> 
>  ✖ Error: security issues detected!

I research this and I find it because of this package: https://github.com/isaacs/cliui/blob/aa397fedbd0550c9925af6b62f970de663285641/package.json#L52-L57

I don't like it but it seems to be OK.

What should I do to continue enabling the `validate-package-names` rule and not failed on those packages?

Pull request:
feat: allowed package name aliases
<!--- Provide a general summary of your changes in the Title above -->

## Description

Fix #175 

<!--- Describe your changes in detail -->

## Types of changes

Package name aliases such as depicted in #175 would cause the `--validate-package-names` flag to trigger an issue. This PR creates a new trusted policy allow-list to match package names with their alias as pairs.

<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->

- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)


Patch:
diff --git a/.changeset/hot-spiders-taste.md b/.changeset/hot-spiders-taste.md
new file mode 100644
index 0000000..5450018
--- /dev/null
+++ b/.changeset/hot-spiders-taste.md
@@ -0,0 +1,5 @@
+---
+'lockfile-lint-api': minor
+---
+
+Add verbose information in debug
diff --git a/packages/lockfile-lint-api/CHANGELOG.md b/packages/lockfile-lint-api/CHANGELOG.md
index 397753f..4c4c5aa 100644
--- a/packages/lockfile-lint-api/CHANGELOG.md
+++ b/packages/lockfile-lint-api/CHANGELOG.md
@@ -1,11 +1,5 @@
 # Change Log
 
-## 5.7.0
-
-### Minor Changes
-
-- [#172](https://github.com/lirantal/lockfile-lint/pull/172) [`ebe0637`](https://github.com/lirantal/lockfile-lint/commit/ebe06370b52d7eac0b297097e7c093719b9bace1) Thanks [@lirantal](https://github.com/lirantal)! - Add verbose information in debug
-
 ## 5.6.0
 
 ### Minor Changes
diff --git a/packages/lockfile-lint-api/package.json b/packages/lockfile-lint-api/package.json
index 36c2230..223ae05 100644
--- a/packages/lockfile-lint-api/package.json
+++ b/packages/lockfile-lint-api/package.json
@@ -1,6 +1,6 @@
 {
   "name": "lockfile-lint-api",
-  "version": "5.7.0",
+  "version": "5.6.0",
   "description": "Lint an npm or yarn lockfile to analyze and detect issues",
   "main": "index.js",
   "engines": {
diff --git a/packages/lockfile-lint-api/src/validators/ValidatePackageNames.js b/packages/lockfile-lint-api/src/validators/ValidatePackageNames.js
index 3ea5691..e8cf767 100644
--- a/packages/lockfile-lint-api/src/validators/ValidatePackageNames.js
+++ b/packages/lockfile-lint-api/src/validators/ValidatePackageNames.js
@@ -12,17 +12,28 @@ module.exports = class ValidatePackageNames {
     this.packages = packages
   }
 
-  validate () {
+  validate (packageNameAliases) {
     let validationResult = {
       type: 'success',
       errors: []
     }
 
+    const packageNameAliasPairs = this._getPackageNameAliasPairs(packageNameAliases)
+
     for (const [packageName, packageMetadata] of Object.entries(this.packages)) {
       if (!('resolved' in packageMetadata)) {
         continue
       }
 
+      if (Object.hasOwn(packageNameAliasPairs, this._getPackageNameOnly(packageName))) {
+        debug(
+          `skipping package name validation for aliased package name: ${packageName} resolving to: ${
+            packageNameAliasPairs[packageName]
+          }}`
+        )
+        continue
+      }
+
       try {
         const packageResolvedURL = new URL(packageMetadata.resolved)
 
@@ -40,19 +51,15 @@ module.exports = class ValidatePackageNames {
         const path = packageResolvedURL.pathname
         const packageNameFromResolved = path.split('/-/')[0].slice(1)
 
-        // Remove versioning info from packageName. The @ sign is the delimiter, but could also be the
-        // first character of a scoped package name. We handle this edge-case here.
-        const nameOnly = packageName.startsWith('@')
-          ? `@${packageName.slice(1).split('@')[0]}`
-          : packageName.split('@')[0]
+        const packageNameOnly = this._getPackageNameOnly(packageName)
 
-        const expectedURLBeginning = `${packageResolvedURL.origin}/${nameOnly}`
+        const expectedURLBeginning = `${packageResolvedURL.origin}/${packageNameOnly}`
 
         const isPassing = packageMetadata.resolved.startsWith(expectedURLBeginning)
         if (!isPassing) {
           validationResult.errors.push({
-            message: `detected resolved URL for package with a different name: ${nameOnly}\n    expected: ${nameOnly}\n    actual: ${packageNameFromResolved}\n`,
-            package: nameOnly
+            message: `detected resolved URL for package with a different name: ${packageNameOnly}\n    expected: ${packageNameOnly}\n    actual: ${packageNameFromResolved}\n`,
+            package: packageNameOnly
           })
         }
       } catch (error) {
@@ -66,4 +73,28 @@ module.exports = class ValidatePackageNames {
 
     return validationResult
   }
+
+  _getPackageNameOnly (packageName) {
+    // Remove versioning info from packageName. The @ sign is the delimiter, but could also be the
+    // first character of a scoped package name. We handle this edge-case here.
+    const packageNameOnly = packageName.startsWith('@')
+      ? `@${packageName.slice(1).split('@')[0]}`
+      : packageName.split('@')[0]
+
+    return packageNameOnly
+  }
+
+  _getPackageNameAliasPairs (packageNameAliases) {
+    if (!packageNameAliases || !Array.isArray(packageNameAliases)) {
+      return {}
+    }
+
+    const packageNameAliasPairs = {}
+    for (const packageNameAlias of packageNameAliases) {
+      const [packageName, aliasedPackageName] = packageNameAlias.split(':')
+      packageNameAliasPairs[packageName] = aliasedPackageName
+    }
+
+    return packageNameAliasPairs
+  }
 }
diff --git a/packages/lockfile-lint/bin/lockfile-lint.js b/packages/lockfile-lint/bin/lockfile-lint.js
index 74fed7d..a7b450e 100755
--- a/packages/lockfile-lint/bin/lockfile-lint.js
+++ b/packages/lockfile-lint/bin/lockfile-lint.js
@@ -69,7 +69,8 @@ for (const [commandArgument, commandValue] of Object.entries(config)) {
       options: {
         emptyHostname: config['empty-hostname'],
         allowedHosts: config['allowed-hosts'],
-        allowedUrls: config['allowed-urls']
+        allowedUrls: config['allowed-urls'],
+        allowedPackageNameAliases: config['allowed-package-name-aliases']
       }
     })
   }
diff --git a/packages/lockfile-lint/src/config.js b/packages/lockfile-lint/src/config.js
index 9d1e149..ccadc0e 100644
--- a/packages/lockfile-lint/src/config.js
+++ b/packages/lockfile-lint/src/config.js
@@ -80,6 +80,11 @@ module.exports = (argv, exitProcess = false, searchFrom = process.cwd()) => {
         type: 'array',
         describe: 'validates a whitelist of allowed URLs to be used for resources in the lockfile'
       },
+      'allowed-package-name-aliases': {
+        alias: ['l'],
+        type: 'array',
+        describe: 'validates an alias of package names to be used for resources in the lockfile'
+      },
       format: {
         alias: ['f'],
         type: 'string',
diff --git a/packages/lockfile-lint/src/validators/index.js b/packages/lockfile-lint/src/validators/index.js
index 197628a..2d5f7e6 100644
--- a/packages/lockfile-lint/src/validators/index.js
+++ b/packages/lockfile-lint/src/validators/index.js
@@ -47,7 +47,10 @@ function ValidateHostManager ({path, type, validatorValues, validatorOptions}) {
 
   const parser = new ParseLockfile(options)
   const lockfile = parser.parseSync()
-  const validator = new ValidateHost({packages: lockfile.object, debug: require('debug')('lockfile-lint')})
+  const validator = new ValidateHost({
+    packages: lockfile.object,
+    debug: require('debug')('lockfile-lint')
+  })
   const validationResult = validator.validate(validatorValues, validatorOptions)
 
   // Check if some of the errors are for allowed URLs and filter those out
@@ -98,7 +101,8 @@ function ValidatePackageNamesManager ({path, type, validatorValues, validatorOpt
   const lockfile = parser.parseSync()
   const validator = new ValidatePackageNames({packages: lockfile.object})
 
-  return validator.validate()
+  const packageNameAliases = (validatorOptions && validatorOptions.allowedPackageNameAliases) || []
+  return validator.validate(packageNameAliases)
 }
 
 function ValidateUrlManager ({path, type, validatorValues, validatorOptions}) {


Test patch:
diff --git a/packages/lockfile-lint/__tests__/cli.test.js b/packages/lockfile-lint/__tests__/cli.test.js
index 613cb0c..d4936e2 100644
--- a/packages/lockfile-lint/__tests__/cli.test.js
+++ b/packages/lockfile-lint/__tests__/cli.test.js
@@ -7,7 +7,8 @@ const cliExecPath = path.join(__dirname, '../bin/lockfile-lint.js')
 const {
   ValidateHostManager,
   ValidateUrlManager,
-  ValidateIntegrityManager
+  ValidateIntegrityManager,
+  ValidatePackageNamesManager
 } = require('../src/validators/index')
 
 describe('CLI tests', () => {
@@ -305,19 +306,69 @@ describe('CLI tests', () => {
     })
   })
 
+  test('Package lock which has aliases to other packages should detect that and return errors', done => {
+    const process = childProcess.spawn('node', [
+      cliExecPath,
+      '--type',
+      'npm',
+      '--path',
+      '__tests__/fixtures/package-lock-v3-package-name-aliases.json',
+      '--validate-package-names',
+      '--allowed-hosts',
+      'npm'
+    ])
+
+    let output = ''
+    process.stderr.on('data', chunk => {
+      output += chunk
+    })
+
+    process.on('close', exitCode => {
+      expect(output).toMatch(
+        /detected resolved URL for package with a different name: string-width-cjs/
+      )
+      expect(output).toMatch(
+        /detected resolved URL for package with a different name: strip-ansi-cjs/
+      )
+      expect(output).toMatch(
+        /detected resolved URL for package with a different name: wrap-ansi-cjs/
+      )
+      expect(output).toMatch(/Error: security issues detected!/)
+      expect(exitCode).toBe(1)
+      done()
+    })
+  })
+
+  test('Package lock which has aliases to other packages should allow passing when aliases are provided', done => {
+    const process = childProcess.spawn('node', [
+      cliExecPath,
+      '--type',
+      'npm',
+      '--path',
+      '__tests__/fixtures/package-lock-v3-package-name-aliases.json',
+      '--validate-package-names',
+      '--allowed-package-name-aliases',
+      'string-width-cjs:string-width',
+      'wrap-ansi-cjs:wrap-ansi',
+      'strip-ansi-cjs:strip-ansi',
+      '--allowed-hosts',
+      'npm'
+    ])
+
+    process.on('close', exitCode => {
+      // No error should be thrown
+      expect(exitCode).toBe(0)
+      done()
+    })
+  })
+
   describe('cosmiconfig integration', () => {
     it('options are loaded from cosmiconfig files', done => {
       const lintProcess = childProcess.spawn('node', [cliExecPath], {
         cwd: path.join(__dirname, 'fixtures/valid-config')
       })
 
-      let output = ''
-      lintProcess.stderr.on('data', chunk => {
-        output += chunk
-      })
-
       lintProcess.on('close', exitCode => {
-        console.log(output)
         expect(exitCode).toBe(0)
         done()
       })
@@ -435,4 +486,33 @@ describe('Validator managers:', () => {
       ]
     })
   })
+
+  it('Package name validator should error if aliases are used and not added as trusted policy', () => {
+    const result = ValidatePackageNamesManager({
+      path: '__tests__/fixtures/package-lock-v3-package-name-aliases.json',
+      type: 'npm',
+      validatorValues: []
+    })
+
+    expect(result).toEqual({
+      type: 'error',
+      errors: [
+        {
+          message:
+            'detected resolved URL for package with a different name: string-width-cjs\n    expected: string-width-cjs\n    actual: string-width\n',
+          package: 'string-width-cjs'
+        },
+        {
+          message:
+            'detected resolved URL for package with a different name: strip-ansi-cjs\n    expected: strip-ansi-cjs\n    actual: strip-ansi\n',
+          package: 'strip-ansi-cjs'
+        },
+        {
+          message:
+            'detected resolved URL for package with a different name: wrap-ansi-cjs\n    expected: wrap-ansi-cjs\n    actual: wrap-ansi\n',
+          package: 'wrap-ansi-cjs'
+        }
+      ]
+    })
+  })
 })
diff --git a/packages/lockfile-lint/__tests__/fixtures/package-lock-v3-package-name-aliases.json b/packages/lockfile-lint/__tests__/fixtures/package-lock-v3-package-name-aliases.json
new file mode 100644
index 0000000..8c8af83
--- /dev/null
+++ b/packages/lockfile-lint/__tests__/fixtures/package-lock-v3-package-name-aliases.json
@@ -0,0 +1,270 @@
+{
+    "name": "lokytest",
+    "version": "1.0.0",
+    "lockfileVersion": 3,
+    "requires": true,
+    "packages": {
+      "": {
+        "name": "lokytest",
+        "version": "1.0.0",
+        "license": "ISC",
+        "dependencies": {
+          "@isaacs/cliui": "^8.0.2",
+          "moment": "^2.29.4"
+        }
+      },
+      "node_modules/@isaacs/cliui": {
+        "version": "8.0.2",
+        "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
+        "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
+        "dependencies": {
+          "string-width": "^5.1.2",
+          "string-width-cjs": "npm:string-width@^4.2.0",
+          "strip-ansi": "^7.0.1",
+          "strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
+          "wrap-ansi": "^8.1.0",
+          "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
+        },
+        "engines": {
+          "node": ">=12"
+        }
+      },
+      "node_modules/ansi-regex": {
+        "version": "6.0.1",
+        "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
+        "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
+        "engines": {
+          "node": ">=12"
+        },
+        "funding": {
+          "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+        }
+      },
+      "node_modules/ansi-styles": {
+        "version": "6.2.1",
+        "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
+        "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
+        "engines": {
+          "node": ">=12"
+        },
+        "funding": {
+          "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+        }
+      },
+      "node_modules/color-convert": {
+        "version": "2.0.1",
+        "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+        "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+        "dependencies": {
+          "color-name": "~1.1.4"
+        },
+        "engines": {
+          "node": ">=7.0.0"
+        }
+      },
+      "node_modules/color-name": {
+        "version": "1.1.4",
+        "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+        "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
+      },
+      "node_modules/eastasianwidth": {
+        "version": "0.2.0",
+        "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
+        "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="
+      },
+      "node_modules/emoji-regex": {
+        "version": "9.2.2",
+        "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
+        "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="
+      },
+      "node_modules/is-fullwidth-code-point": {
+        "version": "3.0.0",
+        "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+        "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+        "engines": {
+          "node": ">=8"
+        }
+      },
+      "node_modules/moment": {
+        "version": "2.29.4",
+        "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
+        "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==",
+        "engines": {
+          "node": "*"
+        }
+      },
+      "node_modules/string-width": {
+        "version": "5.1.2",
+        "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
+        "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
+        "dependencies": {
+          "eastasianwidth": "^0.2.0",
+          "emoji-regex": "^9.2.2",
+          "strip-ansi": "^7.0.1"
+        },
+        "engines": {
+          "node": ">=12"
+        },
+        "funding": {
+          "url": "https://github.com/sponsors/sindresorhus"
+        }
+      },
+      "node_modules/string-width-cjs": {
+        "name": "string-width",
+        "version": "4.2.3",
+        "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+        "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+        "dependencies": {
+          "emoji-regex": "^8.0.0",
+          "is-fullwidth-code-point": "^3.0.0",
+          "strip-ansi": "^6.0.1"
+        },
+        "engines": {
+          "node": ">=8"
+        }
+      },
+      "node_modules/string-width-cjs/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/string-width-cjs/node_modules/emoji-regex": {
+        "version": "8.0.0",
+        "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+        "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
+      },
+      "node_modules/string-width-cjs/node_modules/strip-ansi": {
+        "version": "6.0.1",
+        "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+        "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+        "dependencies": {
+          "ansi-regex": "^5.0.1"
+        },
+        "engines": {
+          "node": ">=8"
+        }
+      },
+      "node_modules/strip-ansi": {
+        "version": "7.1.0",
+        "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
+        "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
+        "dependencies": {
+          "ansi-regex": "^6.0.1"
+        },
+        "engines": {
+          "node": ">=12"
+        },
+        "funding": {
+          "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+        }
+      },
+      "node_modules/strip-ansi-cjs": {
+        "name": "strip-ansi",
+        "version": "6.0.1",
+        "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+        "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+        "dependencies": {
+          "ansi-regex": "^5.0.1"
+        },
+        "engines": {
+          "node": ">=8"
+        }
+      },
+      "node_modules/strip-ansi-cjs/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/wrap-ansi": {
+        "version": "8.1.0",
+        "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
+        "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
+        "dependencies": {
+          "ansi-styles": "^6.1.0",
+          "string-width": "^5.0.1",
+          "strip-ansi": "^7.0.1"
+        },
+        "engines": {
+          "node": ">=12"
+        },
+        "funding": {
+          "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+        }
+      },
+      "node_modules/wrap-ansi-cjs": {
+        "name": "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==",
+        "dependencies": {
+          "ansi-styles": "^4.0.0",
+          "string-width": "^4.1.0",
+          "strip-ansi": "^6.0.0"
+        },
+        "engines": {
+          "node": ">=10"
+        },
+        "funding": {
+          "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+        }
+      },
+      "node_modules/wrap-ansi-cjs/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/wrap-ansi-cjs/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/wrap-ansi-cjs/node_modules/emoji-regex": {
+        "version": "8.0.0",
+        "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+        "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
+      },
+      "node_modules/wrap-ansi-cjs/node_modules/string-width": {
+        "version": "4.2.3",
+        "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+        "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+        "dependencies": {
+          "emoji-regex": "^8.0.0",
+          "is-fullwidth-code-point": "^3.0.0",
+          "strip-ansi": "^6.0.1"
+        },
+        "engines": {
+          "node": ">=8"
+        }
+      },
+      "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": {
+        "version": "6.0.1",
+        "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+        "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+        "dependencies": {
+          "ansi-regex": "^5.0.1"
+        },
+        "engines": {
+          "node": ">=8"
+        }
+      }
+    }
+  }
+  
\ No newline at end of file