diff --git a/README.md b/README.md index 82aef2c7..6c64c861 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ The HeroDevs CLI 1. Install node v20 or higher: [Download Node](https://nodejs.org/en/download) 1. Install the CLI using one of the following methods: - Globally: Refer to the [Usage](#usage) instructions on installing the CLI globally - - Npx:`npx @herodevs/cli@beta ` + - npx: `npx @herodevs/cli@beta` 1. Refer to the [Commands](#commands) section for a list of commands ## TERMS @@ -24,10 +24,10 @@ Use of this CLI is governed by the [HeroDevs End of Life Dataset Terms of Servic ## Scanning Behavior -The CLI's scanning commands (`hd scan eol` and `hd scan sbom`) are designed to be non-invasive: +The CLI is designed to be non-invasive: -* They do not install dependencies or modify package manager files (package-lock.json, yarn.lock, etc.) -* They analyze the project in its current state +* It does not install dependencies or modify package manager files (package-lock.json, yarn.lock, etc.) +* It analyzes the project in its current state * If you need dependencies installed for accurate scanning, please install them manually before running the scan @@ -38,7 +38,7 @@ $ npm install -g @herodevs/cli $ hd COMMAND running command... $ hd (--version) -@herodevs/cli/2.0.0-beta.4 darwin-arm64 node-v22.15.1 +@herodevs/cli/2.0.0-beta.4 darwin-arm64 node-v22.18.0 $ hd --help [COMMAND] USAGE $ hd COMMAND @@ -48,10 +48,7 @@ USAGE ## Commands * [`hd help [COMMAND]`](#hd-help-command) -* [`hd report committers`](#hd-report-committers) -* [`hd report purls`](#hd-report-purls) * [`hd scan eol`](#hd-scan-eol) -* [`hd scan sbom`](#hd-scan-sbom) * [`hd update [CHANNEL]`](#hd-update-channel) ## `hd help [COMMAND]` @@ -74,130 +71,49 @@ DESCRIPTION _See code: [@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/v6.2.29/src/commands/help.ts)_ -## `hd report committers` - -Generate report of committers to a git repository - -``` -USAGE - $ hd report committers [--json] [-m ] [-c] [-s] - -FLAGS - -c, --csv Output in CSV format - -m, --months= [default: 12] The number of months of git history to review - -s, --save Save the committers report as herodevs.committers. - -GLOBAL FLAGS - --json Format output as json. - -DESCRIPTION - Generate report of committers to a git repository - -EXAMPLES - $ hd report committers - - $ hd report committers --csv -s - - $ hd report committers --json - - $ hd report committers --csv -``` - -_See code: [src/commands/report/committers.ts](https://github.com/herodevs/cli/blob/v2.0.0-beta.4/src/commands/report/committers.ts)_ - -## `hd report purls` - -Generate a list of purls from a sbom - -``` -USAGE - $ hd report purls [--json] [-f ] [-d ] [-s] [-c] - -FLAGS - -c, --csv Save output in CSV format (only applies when using --save) - -d, --dir= The directory to scan in order to create a cyclonedx sbom - -f, --file= The file path of an existing cyclonedx sbom to scan for EOL - -s, --save Save the list of purls as herodevs.purls. - -GLOBAL FLAGS - --json Format output as json. - -DESCRIPTION - Generate a list of purls from a sbom - -EXAMPLES - $ hd report purls --json -s - - $ hd report purls --dir=./my-project - - $ hd report purls --file=path/to/sbom.json - - $ hd report purls --dir=./my-project --save - - $ hd report purls --save --csv -``` - -_See code: [src/commands/report/purls.ts](https://github.com/herodevs/cli/blob/v2.0.0-beta.4/src/commands/report/purls.ts)_ - ## `hd scan eol` -Scan a given sbom for EOL data +Scan a given SBOM for EOL data ``` USAGE - $ hd scan eol [--json] [-f ] [-p ] [-d ] [-s] + $ hd scan eol [--json] [-f | -d ] [-s] [--saveSbom] FLAGS - -d, --dir= The directory to scan in order to create a cyclonedx sbom - -f, --file= The file path of an existing cyclonedx sbom to scan for EOL - -p, --purls= The file path of a list of purls to scan for EOL - -s, --save Save the generated report as herodevs.report.json in the scanned directory + -d, --dir= [default: ] The directory to scan in order to create a cyclonedx SBOM + -f, --file= The file path of an existing cyclonedx SBOM to scan for EOL + -s, --save Save the generated report as herodevs.report.json in the scanned directory + --saveSbom Save the generated SBOM as herodevs.sbom.json in the scanned directory GLOBAL FLAGS --json Format output as json. DESCRIPTION - Scan a given sbom for EOL data + Scan a given SBOM for EOL data EXAMPLES - $ hd scan eol --dir=./my-project - - $ hd scan eol --file=path/to/sbom.json + Default behavior (no command or flags specified) - $ hd scan eol --purls=path/to/purls.json + $ hd - $ hd scan eol -a --dir=./my-project -``` + Equivalent to -_See code: [src/commands/scan/eol.ts](https://github.com/herodevs/cli/blob/v2.0.0-beta.4/src/commands/scan/eol.ts)_ + $ hd scan eol --dir . -## `hd scan sbom` + Skip SBOM generation and specify an existing file -Scan a SBOM for purls + $ hd scan eol --file /path/to/sbom.json -``` -USAGE - $ hd scan sbom [--json] [-f ] [-d ] [-s] [-b] + Save the report or SBOM to a file -FLAGS - -b, --background Run the scan in the background - -d, --dir= The directory to scan in order to create a cyclonedx sbom - -f, --file= The file path of an existing cyclonedx sbom to scan for EOL - -s, --save Save the generated SBOM as herodevs.sbom.json in the scanned directory + $ hd scan eol --save --saveSbom -GLOBAL FLAGS - --json Format output as json. + Output the report in JSON format (for APIs, CI, etc.) -DESCRIPTION - Scan a SBOM for purls - -EXAMPLES - $ hd scan sbom --dir=./my-project - - $ hd scan sbom --file=path/to/sbom.json + $ hd scan eol --json ``` -_See code: [src/commands/scan/sbom.ts](https://github.com/herodevs/cli/blob/v2.0.0-beta.4/src/commands/scan/sbom.ts)_ +_See code: [src/commands/scan/eol.ts](https://github.com/herodevs/cli/blob/v2.0.0-beta.4/src/commands/scan/eol.ts)_ ## `hd update [CHANNEL]` @@ -250,7 +166,7 @@ it contains build tools for most project types and will provide best results whe #### GitHub Actions ```yaml -# .github/workflows/herodevs-eol-scan.yml +## .github/workflows/herodevs-eol-scan.yml name: HeroDevs EOL Scan on: @@ -295,7 +211,7 @@ all requirements before the scan step. #### GitHub Actions ```yaml -# .github/workflows/herodevs-eol-scan.yml +## .github/workflows/herodevs-eol-scan.yml name: HeroDevs EOL Scan on: @@ -328,4 +244,4 @@ eol-scan: script: - echo # Prepare environment, install tooling, perform setup, etc. - npx @herodevs/cli@beta -``` \ No newline at end of file +``` diff --git a/e2e/fixtures/npm/angular-17.purls.json b/e2e/fixtures/npm/angular-17.sbom.json similarity index 100% rename from e2e/fixtures/npm/angular-17.purls.json rename to e2e/fixtures/npm/angular-17.sbom.json diff --git a/e2e/fixtures/npm/empty.purls.json b/e2e/fixtures/npm/empty.purls.json deleted file mode 100644 index 87223da0..00000000 --- a/e2e/fixtures/npm/empty.purls.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "purls": [] -} diff --git a/e2e/fixtures/npm/extra-large.purls.json b/e2e/fixtures/npm/extra-large.purls.json deleted file mode 100644 index ea09720a..00000000 --- a/e2e/fixtures/npm/extra-large.purls.json +++ /dev/null @@ -1,4049 +0,0 @@ -{ - "purls": [ - "pkg:npm/%40angular/common@12.2.2", - "pkg:npm/tslib@2.5.0", - "pkg:npm/%40angular/core@12.2.2", - "pkg:npm/%40angular/forms@12.2.2", - "pkg:npm/%40angular/localize@12.2.2", - "pkg:npm/%40babel/core@7.8.3", - "pkg:npm/%40babel/code-frame@7.18.6", - "pkg:npm/%40babel/highlight@7.18.6", - "pkg:npm/%40babel/helper-validator-identifier@7.19.1", - "pkg:npm/chalk@2.4.2", - "pkg:npm/ansi-styles@3.2.1", - "pkg:npm/color-convert@1.9.3", - "pkg:npm/color-name@1.1.3", - "pkg:npm/escape-string-regexp@1.0.5", - "pkg:npm/supports-color@5.5.0", - "pkg:npm/has-flag@3.0.0", - "pkg:npm/js-tokens@4.0.0", - "pkg:npm/%40babel/generator@7.20.14", - "pkg:npm/%40babel/types@7.20.7", - "pkg:npm/%40babel/helper-string-parser@7.19.4", - "pkg:npm/to-fast-properties@2.0.0", - "pkg:npm/%40jridgewell/gen-mapping@0.3.2", - "pkg:npm/%40jridgewell/set-array@1.1.2", - "pkg:npm/%40jridgewell/sourcemap-codec@1.4.14", - "pkg:npm/%40jridgewell/trace-mapping@0.3.17", - "pkg:npm/%40jridgewell/resolve-uri@3.1.0", - "pkg:npm/jsesc@2.5.2", - "pkg:npm/%40babel/helpers@7.20.13", - "pkg:npm/%40babel/template@7.20.7", - "pkg:npm/%40babel/parser@7.20.13", - "pkg:npm/%40babel/traverse@7.20.13", - "pkg:npm/%40babel/helper-environment-visitor@7.18.9", - "pkg:npm/%40babel/helper-function-name@7.19.0", - "pkg:npm/%40babel/helper-hoist-variables@7.18.6", - "pkg:npm/%40babel/helper-split-export-declaration@7.18.6", - "pkg:npm/debug@4.3.2", - "pkg:npm/ms@2.1.2", - "pkg:npm/globals@11.12.0", - "pkg:npm/convert-source-map@1.9.0", - "pkg:npm/gensync@1.0.0-beta.2", - "pkg:npm/json5@2.2.3", - "pkg:npm/lodash@4.17.21", - "pkg:npm/resolve@1.20.0", - "pkg:npm/is-core-module@2.11.0", - "pkg:npm/has@1.0.3", - "pkg:npm/function-bind@1.1.1", - "pkg:npm/path-parse@1.0.7", - "pkg:npm/semver@5.7.1", - "pkg:npm/source-map@0.5.7", - "pkg:npm/glob@7.1.7", - "pkg:npm/fs.realpath@1.0.0", - "pkg:npm/inflight@1.0.6", - "pkg:npm/once@1.4.0", - "pkg:npm/wrappy@1.0.2", - "pkg:npm/inherits@2.0.4", - "pkg:npm/minimatch@3.1.2", - "pkg:npm/brace-expansion@1.1.11", - "pkg:npm/balanced-match@1.0.2", - "pkg:npm/concat-map@0.0.1", - "pkg:npm/path-is-absolute@1.0.1", - "pkg:npm/yargs@17.6.2", - "pkg:npm/cliui@8.0.1", - "pkg:npm/string-width@4.2.3", - "pkg:npm/emoji-regex@8.0.0", - "pkg:npm/is-fullwidth-code-point@3.0.0", - "pkg:npm/strip-ansi@6.0.1", - "pkg:npm/ansi-regex@5.0.1", - "pkg:npm/wrap-ansi@7.0.0", - "pkg:npm/ansi-styles@4.3.0", - "pkg:npm/color-convert@2.0.1", - "pkg:npm/color-name@1.1.4", - "pkg:npm/escalade@3.1.1", - "pkg:npm/get-caller-file@2.0.5", - "pkg:npm/require-directory@2.1.1", - "pkg:npm/y18n@5.0.8", - "pkg:npm/yargs-parser@21.1.1", - "pkg:npm/%40angular/platform-browser@12.2.2", - "pkg:npm/%40angular/platform-browser-dynamic@12.2.2", - "pkg:npm/%40angular/router@12.2.2", - "pkg:npm/%40angular/upgrade@12.2.2", - "pkg:npm/gulp-util@3.0.8", - "pkg:npm/array-differ@1.0.0", - "pkg:npm/array-uniq@1.0.3", - "pkg:npm/beeper@1.1.1", - "pkg:npm/chalk@1.1.3", - "pkg:npm/ansi-styles@2.2.1", - "pkg:npm/has-ansi@2.0.0", - "pkg:npm/ansi-regex@2.1.1", - "pkg:npm/strip-ansi@3.0.1", - "pkg:npm/supports-color@2.0.0", - "pkg:npm/dateformat@2.2.0", - "pkg:npm/fancy-log@1.3.3", - "pkg:npm/ansi-gray@0.1.1", - "pkg:npm/ansi-wrap@0.1.0", - "pkg:npm/color-support@1.1.3", - "pkg:npm/parse-node-version@1.0.1", - "pkg:npm/time-stamp@1.1.0", - "pkg:npm/gulplog@1.0.0", - "pkg:npm/glogg@1.0.2", - "pkg:npm/sparkles@1.0.1", - "pkg:npm/has-gulplog@0.1.0", - "pkg:npm/lodash._reescape@3.0.0", - "pkg:npm/lodash._reevaluate@3.0.0", - "pkg:npm/lodash._reinterpolate@3.0.0", - "pkg:npm/lodash.template@3.6.2", - "pkg:npm/lodash._basecopy@3.0.1", - "pkg:npm/lodash._basetostring@3.0.1", - "pkg:npm/lodash._basevalues@3.0.0", - "pkg:npm/lodash._isiterateecall@3.0.9", - "pkg:npm/lodash.escape@3.2.0", - "pkg:npm/lodash._root@3.0.1", - "pkg:npm/lodash.keys@3.1.2", - "pkg:npm/lodash._getnative@3.9.1", - "pkg:npm/lodash.isarguments@3.1.0", - "pkg:npm/lodash.isarray@3.0.4", - "pkg:npm/lodash.restparam@3.6.1", - "pkg:npm/lodash.templatesettings@3.1.1", - "pkg:npm/minimist@1.2.7", - "pkg:npm/multipipe@0.1.2", - "pkg:npm/duplexer2@0.0.2", - "pkg:npm/readable-stream@1.1.14", - "pkg:npm/core-util-is@1.0.3", - "pkg:npm/isarray@0.0.1", - "pkg:npm/string_decoder@0.10.31", - "pkg:npm/object-assign@3.0.0", - "pkg:npm/replace-ext@0.0.1", - "pkg:npm/through2@2.0.5", - "pkg:npm/readable-stream@2.3.7", - "pkg:npm/isarray@1.0.0", - "pkg:npm/process-nextick-args@2.0.1", - "pkg:npm/safe-buffer@5.1.2", - "pkg:npm/string_decoder@1.1.1", - "pkg:npm/util-deprecate@1.0.2", - "pkg:npm/xtend@4.0.2", - "pkg:npm/vinyl@0.5.3", - "pkg:npm/clone@1.0.4", - "pkg:npm/clone-stats@0.0.1", - "pkg:npm/map-stream@0.0.5", - "pkg:npm/temp-write@1.1.2", - "pkg:npm/graceful-fs@4.2.10", - "pkg:npm/mkdirp@0.5.6", - "pkg:npm/os-tmpdir@1.0.2", - "pkg:npm/uuid@2.0.3", - "pkg:npm/util@0.10.4", - "pkg:npm/inherits@2.0.3", - "pkg:npm/%40sentry/tracing@7.30.0", - "pkg:npm/%40sentry/core@7.30.0", - "pkg:npm/%40sentry/types@7.30.0", - "pkg:npm/%40sentry/utils@7.30.0", - "pkg:npm/tslib@1.14.1", - "pkg:npm/async@3.2.6", - "pkg:npm/aws-sdk@2.1691.0", - "pkg:npm/buffer@4.9.2", - "pkg:npm/base64-js@1.5.1", - "pkg:npm/ieee754@1.1.13", - "pkg:npm/events@1.1.1", - "pkg:npm/jmespath@0.16.0", - "pkg:npm/querystring@0.2.0", - "pkg:npm/sax@1.2.1", - "pkg:npm/url@0.10.3", - "pkg:npm/punycode@1.3.2", - "pkg:npm/util@0.12.5", - "pkg:npm/is-arguments@1.1.1", - "pkg:npm/call-bind@1.0.2", - "pkg:npm/get-intrinsic@1.2.0", - "pkg:npm/has-symbols@1.0.3", - "pkg:npm/has-tostringtag@1.0.0", - "pkg:npm/is-generator-function@1.0.10", - "pkg:npm/is-typed-array@1.1.10", - "pkg:npm/available-typed-arrays@1.0.5", - "pkg:npm/for-each@0.3.3", - "pkg:npm/is-callable@1.2.7", - "pkg:npm/gopd@1.0.1", - "pkg:npm/which-typed-array@1.1.9", - "pkg:npm/uuid@8.0.0", - "pkg:npm/xml2js@0.6.2", - "pkg:npm/xmlbuilder@11.0.1", - "pkg:npm/console-stamp@3.0.4", - "pkg:npm/chalk@4.1.2", - "pkg:npm/supports-color@7.2.0", - "pkg:npm/has-flag@4.0.0", - "pkg:npm/dateformat@4.6.3", - "pkg:npm/event-emitter@0.3.5", - "pkg:npm/d@1.0.1", - "pkg:npm/es5-ext@0.10.62", - "pkg:npm/es6-iterator@2.0.3", - "pkg:npm/es6-symbol@3.1.3", - "pkg:npm/ext@1.7.0", - "pkg:npm/type@2.7.2", - "pkg:npm/next-tick@1.1.0", - "pkg:npm/type@1.2.0", - "pkg:npm/jsonwebtoken@5.7.0", - "pkg:npm/jws@3.2.2", - "pkg:npm/jwa@1.4.1", - "pkg:npm/buffer-equal-constant-time@1.0.1", - "pkg:npm/ecdsa-sig-formatter@1.0.11", - "pkg:npm/safe-buffer@5.2.1", - "pkg:npm/ms@0.7.3", - "pkg:npm/newrelic@8.7.1", - "pkg:npm/%40grpc/grpc-js@1.12.2", - "pkg:npm/%40grpc/proto-loader@0.7.13", - "pkg:npm/lodash.camelcase@4.3.0", - "pkg:npm/long@5.2.3", - "pkg:npm/protobufjs@7.3.2", - "pkg:npm/%40protobufjs/aspromise@1.1.2", - "pkg:npm/%40protobufjs/base64@1.1.2", - "pkg:npm/%40protobufjs/codegen@2.0.4", - "pkg:npm/%40protobufjs/eventemitter@1.1.0", - "pkg:npm/%40protobufjs/fetch@1.1.0", - "pkg:npm/%40protobufjs/inquire@1.1.0", - "pkg:npm/%40protobufjs/float@1.0.2", - "pkg:npm/%40protobufjs/path@1.1.2", - "pkg:npm/%40protobufjs/pool@1.1.0", - "pkg:npm/%40protobufjs/utf8@1.1.0", - "pkg:npm/%40types/node@15.14.9", - "pkg:npm/yargs@17.7.2", - "pkg:npm/%40js-sdsl/ordered-map@4.4.2", - "pkg:npm/%40grpc/proto-loader@0.5.6", - "pkg:npm/protobufjs@6.11.4", - "pkg:npm/%40types/long@4.0.2", - "pkg:npm/long@4.0.0", - "pkg:npm/%40newrelic/aws-sdk@4.1.2", - "pkg:npm/semver@7.3.5", - "pkg:npm/lru-cache@6.0.0", - "pkg:npm/yallist@4.0.0", - "pkg:npm/%40newrelic/koa@6.1.2", - "pkg:npm/%40newrelic/superagent@5.1.1", - "pkg:npm/%40tyriar/fibonacci-heap@2.0.9", - "pkg:npm/concat-stream@2.0.0", - "pkg:npm/buffer-from@1.1.2", - "pkg:npm/readable-stream@3.6.0", - "pkg:npm/string_decoder@1.3.0", - "pkg:npm/typedarray@0.0.6", - "pkg:npm/https-proxy-agent@5.0.1", - "pkg:npm/agent-base@6.0.2", - "pkg:npm/json-stringify-safe@5.0.1", - "pkg:npm/semver@5.7.2", - "pkg:npm/%40newrelic/native-metrics@7.1.2", - "pkg:npm/nan@2.17.0", - "pkg:npm/request@2.88.2", - "pkg:npm/aws-sign2@0.7.0", - "pkg:npm/aws4@1.12.0", - "pkg:npm/caseless@0.12.0", - "pkg:npm/combined-stream@1.0.8", - "pkg:npm/delayed-stream@1.0.0", - "pkg:npm/extend@3.0.2", - "pkg:npm/forever-agent@0.6.1", - "pkg:npm/form-data@2.3.3", - "pkg:npm/asynckit@0.4.0", - "pkg:npm/mime-types@2.1.35", - "pkg:npm/mime-db@1.52.0", - "pkg:npm/har-validator@5.1.5", - "pkg:npm/ajv@6.12.6", - "pkg:npm/fast-deep-equal@3.1.3", - "pkg:npm/fast-json-stable-stringify@2.1.0", - "pkg:npm/json-schema-traverse@0.4.1", - "pkg:npm/uri-js@4.4.1", - "pkg:npm/punycode@2.3.0", - "pkg:npm/har-schema@2.0.0", - "pkg:npm/http-signature@1.2.0", - "pkg:npm/assert-plus@1.0.0", - "pkg:npm/jsprim@1.4.2", - "pkg:npm/extsprintf@1.3.0", - "pkg:npm/json-schema@0.4.0", - "pkg:npm/verror@1.10.0", - "pkg:npm/core-util-is@1.0.2", - "pkg:npm/sshpk@1.17.0", - "pkg:npm/asn1@0.2.6", - "pkg:npm/safer-buffer@2.1.2", - "pkg:npm/bcrypt-pbkdf@1.0.2", - "pkg:npm/tweetnacl@0.14.5", - "pkg:npm/dashdash@1.14.1", - "pkg:npm/ecc-jsbn@0.1.2", - "pkg:npm/jsbn@0.1.1", - "pkg:npm/getpass@0.1.7", - "pkg:npm/is-typedarray@1.0.0", - "pkg:npm/isstream@0.1.2", - "pkg:npm/oauth-sign@0.9.0", - "pkg:npm/performance-now@2.1.0", - "pkg:npm/qs@6.5.3", - "pkg:npm/tough-cookie@2.5.0", - "pkg:npm/psl@1.9.0", - "pkg:npm/tunnel-agent@0.6.0", - "pkg:npm/uuid@3.4.0", - "pkg:npm/source-map-support@0.5.21", - "pkg:npm/source-map@0.6.1", - "pkg:npm/%40angular/http@7.2.16", - "pkg:npm/%40angular/localize@12.2.17", - "pkg:npm/%40babel/core@7.25.9", - "pkg:npm/%40ampproject/remapping@2.2.0", - "pkg:npm/%40jridgewell/gen-mapping@0.1.1", - "pkg:npm/%40babel/code-frame@7.25.9", - "pkg:npm/%40babel/highlight@7.25.9", - "pkg:npm/%40babel/helper-validator-identifier@7.25.9", - "pkg:npm/picocolors@1.0.0", - "pkg:npm/%40babel/generator@7.25.9", - "pkg:npm/%40babel/types@7.25.9", - "pkg:npm/%40babel/helper-string-parser@7.25.9", - "pkg:npm/%40jridgewell/gen-mapping@0.3.5", - "pkg:npm/%40jridgewell/set-array@1.2.1", - "pkg:npm/%40jridgewell/trace-mapping@0.3.25", - "pkg:npm/jsesc@3.0.2", - "pkg:npm/%40babel/helper-compilation-targets@7.25.9", - "pkg:npm/%40babel/compat-data@7.25.9", - "pkg:npm/%40babel/helper-validator-option@7.25.9", - "pkg:npm/browserslist@4.24.2", - "pkg:npm/caniuse-lite@1.0.30001669", - "pkg:npm/electron-to-chromium@1.5.45", - "pkg:npm/node-releases@2.0.18", - "pkg:npm/update-browserslist-db@1.1.1", - "pkg:npm/escalade@3.2.0", - "pkg:npm/picocolors@1.1.1", - "pkg:npm/lru-cache@5.1.1", - "pkg:npm/yallist@3.1.1", - "pkg:npm/semver@6.3.1", - "pkg:npm/%40babel/helper-module-transforms@7.25.9", - "pkg:npm/%40babel/helper-module-imports@7.25.9", - "pkg:npm/%40babel/traverse@7.25.9", - "pkg:npm/%40babel/parser@7.25.9", - "pkg:npm/%40babel/template@7.25.9", - "pkg:npm/%40babel/helper-simple-access@7.25.9", - "pkg:npm/%40babel/helpers@7.25.9", - "pkg:npm/convert-source-map@2.0.0", - "pkg:npm/%40babel/plugin-transform-modules-commonjs@7.25.9", - "pkg:npm/%40babel/helper-plugin-utils@7.25.9", - "pkg:npm/isomorphic-fetch@3.0.0", - "pkg:npm/node-fetch@2.6.9", - "pkg:npm/whatwg-url@5.0.0", - "pkg:npm/tr46@0.0.3", - "pkg:npm/webidl-conversions@3.0.1", - "pkg:npm/whatwg-fetch@3.6.2", - "pkg:npm/p-queue@6.6.2", - "pkg:npm/eventemitter3@4.0.7", - "pkg:npm/p-timeout@3.2.0", - "pkg:npm/p-finally@1.0.0", - "pkg:npm/p-retry@4.6.2", - "pkg:npm/%40types/retry@0.12.0", - "pkg:npm/retry@0.13.1", - "pkg:npm/debug@3.2.7", - "pkg:npm/eventemitter3@2.0.3", - "pkg:npm/mongodb@3.6.7", - "pkg:npm/bl@2.2.1", - "pkg:npm/readable-stream@2.3.8", - "pkg:npm/bson@1.1.6", - "pkg:npm/denque@1.5.1", - "pkg:npm/optional-require@1.1.8", - "pkg:npm/require-at@1.0.6", - "pkg:npm/saslprep@1.0.3", - "pkg:npm/sparse-bitfield@3.0.3", - "pkg:npm/memory-pager@1.5.0", - "pkg:npm/babel-runtime@6.26.0", - "pkg:npm/core-js@2.6.12", - "pkg:npm/regenerator-runtime@0.11.1", - "pkg:npm/express-session@1.17.2", - "pkg:npm/cookie@0.4.1", - "pkg:npm/cookie-signature@1.0.6", - "pkg:npm/debug@2.6.9", - "pkg:npm/ms@2.0.0", - "pkg:npm/depd@2.0.0", - "pkg:npm/on-headers@1.0.2", - "pkg:npm/parseurl@1.3.3", - "pkg:npm/uid-safe@2.1.5", - "pkg:npm/random-bytes@1.0.0", - "pkg:npm/express-throttle@2.0.0", - "pkg:npm/lru-cache@4.1.5", - "pkg:npm/pseudomap@1.0.2", - "pkg:npm/yallist@2.1.2", - "pkg:npm/socket.io@4.1.3", - "pkg:npm/%40types/cookie@0.4.1", - "pkg:npm/%40types/cors@2.8.17", - "pkg:npm/accepts@1.3.8", - "pkg:npm/negotiator@0.6.3", - "pkg:npm/base64id@2.0.0", - "pkg:npm/engine.io@5.1.1", - "pkg:npm/cors@2.8.5", - "pkg:npm/object-assign@4.1.1", - "pkg:npm/vary@1.1.2", - "pkg:npm/engine.io-parser@4.0.3", - "pkg:npm/base64-arraybuffer@0.1.4", - "pkg:npm/ws@7.4.6", - "pkg:npm/socket.io-adapter@2.3.3", - "pkg:npm/socket.io-parser@4.0.5", - "pkg:npm/%40types/component-emitter@1.2.11", - "pkg:npm/component-emitter@1.3.0", - "pkg:npm/socket.io-client@4.1.3", - "pkg:npm/backo2@1.0.2", - "pkg:npm/engine.io-client@5.1.2", - "pkg:npm/has-cors@1.1.0", - "pkg:npm/parseqs@0.0.6", - "pkg:npm/parseuri@0.0.6", - "pkg:npm/yeast@0.1.2", - "pkg:npm/%40ng-bootstrap/ng-bootstrap@9.1.3", - "pkg:npm/%40ng-idle/core@10.0.0", - "pkg:npm/%40opentok/client@2.24.6", - "pkg:npm/%40sentry/browser@7.30.0", - "pkg:npm/%40sentry/replay@7.30.0", - "pkg:npm/angular@1.5.11", - "pkg:npm/angular-animate@1.5.11", - "pkg:npm/angular-aria@1.5.11", - "pkg:npm/angular-bootstrap@0.12.2", - "pkg:npm/angular-bootstrap-colorpicker@3.0.32", - "pkg:npm/angular-bootstrap-datetimepicker@1.1.4", - "pkg:npm/angular@1.8.3", - "pkg:npm/moment@2.30.1", - "pkg:npm/angular-cookies@1.5.11", - "pkg:npm/angular-datatables@0.5.7", - "pkg:npm/datatables.net@1.13.11", - "pkg:npm/jquery@3.7.1", - "pkg:npm/datatables.net-dt@1.13.11", - "pkg:npm/angular-dialog-service@5.3.0", - "pkg:npm/angular-drag-and-drop-lists@2.1.0", - "pkg:npm/angular-elastic-input@2.4.0", - "pkg:npm/angular-in-memory-web-api@0.11.0", - "pkg:npm/angular-messages@1.5.11", - "pkg:npm/angular-mocks@1.5.11", - "pkg:npm/angular-route@1.5.11", - "pkg:npm/angular-sanitize@1.5.11", - "pkg:npm/angular-theme-spinner@1.2.3", - "pkg:npm/angular-translate@2.18.3", - "pkg:npm/angular-translate-loader-partial@2.18.3", - "pkg:npm/angular-translate-loader-url@2.18.3", - "pkg:npm/angular-translate-storage-cookie@2.18.3", - "pkg:npm/angular-translate-storage-local@2.18.3", - "pkg:npm/angular-ui-bootstrap@1.3.3", - "pkg:npm/angular-ui-indeterminate@1.0.0", - "pkg:npm/angular-ui-mask@1.8.7", - "pkg:npm/angular-ui-scrollpoint@2.1.1", - "pkg:npm/angular-ui-select@0.12.100", - "pkg:npm/angular-ui-sortable@0.13.4", - "pkg:npm/angular-ui-tree@2.22.6", - "pkg:npm/announcekit-angular@1.1.0", - "pkg:npm/babel-polyfill@6.26.0", - "pkg:npm/regenerator-runtime@0.10.5", - "pkg:npm/blob-polyfill@4.0.20200601", - "pkg:npm/bootstrap@3.4.1", - "pkg:npm/bowser@2.11.0", - "pkg:npm/chosen-js@1.8.7", - "pkg:npm/ckeditor@4.12.1", - "pkg:npm/console-patch@1.0.0", - "pkg:npm/datatables@1.10.12", - "pkg:npm/datatables.net-bs@1.13.11", - "pkg:npm/datatables.net-buttons@1.3.1", - "pkg:npm/datatables.net-buttons-dt@1.7.1", - "pkg:npm/datatables.net-buttons@1.7.1", - "pkg:npm/datatables.net-colreorder@1.7.2", - "pkg:npm/datatables.net-colreorder-dt@1.7.2", - "pkg:npm/datatables.net-fixedcolumns@3.3.3", - "pkg:npm/datatables.net-fixedcolumns-dt@3.3.3", - "pkg:npm/datatables.net-responsive@2.1.1", - "pkg:npm/datatables.net-rowreorder@1.5.0", - "pkg:npm/datatables.net-select@1.7.1", - "pkg:npm/datatables.net-select-dt@1.7.1", - "pkg:npm/daterangepicker@3.1.0", - "pkg:npm/deamdify@0.3.0", - "pkg:npm/escodegen@1.14.3", - "pkg:npm/esprima@4.0.1", - "pkg:npm/estraverse@4.3.0", - "pkg:npm/esutils@2.0.3", - "pkg:npm/optionator@0.8.3", - "pkg:npm/deep-is@0.1.4", - "pkg:npm/fast-levenshtein@2.0.6", - "pkg:npm/levn@0.3.0", - "pkg:npm/prelude-ls@1.1.2", - "pkg:npm/type-check@0.3.2", - "pkg:npm/word-wrap@1.2.3", - "pkg:npm/esprima@2.7.3", - "pkg:npm/through@2.3.8", - "pkg:npm/debounce@1.2.1", - "pkg:npm/decomponentify@0.1.0", - "pkg:npm/falafel@0.3.1", - "pkg:npm/esprima@1.1.0-dev", - "pkg:npm/deglobalify@0.2.0", - "pkg:npm/escodegen@0.0.28", - "pkg:npm/esprima@1.0.4", - "pkg:npm/estraverse@1.3.2", - "pkg:npm/source-map@0.7.3", - "pkg:npm/fast-json-patch@0.5.7", - "pkg:npm/global-jsdom@8.6.0", - "pkg:npm/icheck@1.0.2", - "pkg:npm/intl-tel-input@13.0.4", - "pkg:npm/ipaddr.js@2.2.0", - "pkg:npm/jquery-slimscroll@1.3.8", - "pkg:npm/jquery-timepicker@1.3.3", - "pkg:npm/jquery-ui@1.14.0", - "pkg:npm/jsdom@20.0.2", - "pkg:npm/abab@2.0.6", - "pkg:npm/acorn@8.8.2", - "pkg:npm/acorn-globals@7.0.1", - "pkg:npm/acorn-walk@8.3.2", - "pkg:npm/cssom@0.5.0", - "pkg:npm/cssstyle@2.3.0", - "pkg:npm/cssom@0.3.8", - "pkg:npm/data-urls@3.0.2", - "pkg:npm/whatwg-mimetype@3.0.0", - "pkg:npm/whatwg-url@11.0.0", - "pkg:npm/tr46@3.0.0", - "pkg:npm/webidl-conversions@7.0.0", - "pkg:npm/decimal.js@10.4.3", - "pkg:npm/domexception@4.0.0", - "pkg:npm/escodegen@2.1.0", - "pkg:npm/estraverse@5.3.0", - "pkg:npm/form-data@4.0.1", - "pkg:npm/html-encoding-sniffer@3.0.0", - "pkg:npm/whatwg-encoding@2.0.0", - "pkg:npm/iconv-lite@0.6.3", - "pkg:npm/http-proxy-agent@5.0.0", - "pkg:npm/%40tootallnate/once@2.0.0", - "pkg:npm/is-potential-custom-element-name@1.0.1", - "pkg:npm/nwsapi@2.2.2", - "pkg:npm/parse5@7.2.0", - "pkg:npm/entities@4.5.0", - "pkg:npm/saxes@6.0.0", - "pkg:npm/xmlchars@2.2.0", - "pkg:npm/symbol-tree@3.2.4", - "pkg:npm/tough-cookie@4.1.4", - "pkg:npm/universalify@0.2.0", - "pkg:npm/url-parse@1.5.10", - "pkg:npm/querystringify@2.2.0", - "pkg:npm/requires-port@1.0.0", - "pkg:npm/w3c-xmlserializer@3.0.0", - "pkg:npm/xml-name-validator@4.0.0", - "pkg:npm/ws@8.18.0", - "pkg:npm/json8-patch@1.0.6", - "pkg:npm/json8@1.0.5", - "pkg:npm/json8-pointer@1.0.6", - "pkg:npm/jstz@2.1.1", - "pkg:npm/matchmedia-ng@1.0.8", - "pkg:npm/metismenu@2.7.9", - "pkg:npm/mock-socket@9.1.5", - "pkg:npm/moment-timezone@0.5.46", - "pkg:npm/nexmo-client@8.7.3", - "pkg:npm/%40bugsnag/js@6.5.2", - "pkg:npm/%40bugsnag/browser@6.5.2", - "pkg:npm/%40bugsnag/node@6.5.2", - "pkg:npm/byline@5.0.0", - "pkg:npm/error-stack-parser@2.1.4", - "pkg:npm/stackframe@1.3.4", - "pkg:npm/iserror@0.0.2", - "pkg:npm/pump@3.0.0", - "pkg:npm/end-of-stream@1.4.4", - "pkg:npm/stack-generator@2.0.10", - "pkg:npm/%40types/socket.io-client@1.4.36", - "pkg:npm/detect-browser@4.8.0", - "pkg:npm/loglevel@1.8.1", - "pkg:npm/loglevel-plugin-prefix@0.8.4", - "pkg:npm/public-ip@4.0.4", - "pkg:npm/dns-socket@4.2.2", - "pkg:npm/dns-packet@5.4.0", - "pkg:npm/%40leichtgewicht/ip-codec@2.0.4", - "pkg:npm/got@9.6.0", - "pkg:npm/%40sindresorhus/is@0.14.0", - "pkg:npm/%40szmarczak/http-timer@1.1.2", - "pkg:npm/defer-to-connect@1.1.3", - "pkg:npm/cacheable-request@6.1.0", - "pkg:npm/clone-response@1.0.3", - "pkg:npm/mimic-response@1.0.1", - "pkg:npm/get-stream@5.2.0", - "pkg:npm/http-cache-semantics@4.1.1", - "pkg:npm/keyv@3.1.0", - "pkg:npm/json-buffer@3.0.0", - "pkg:npm/lowercase-keys@2.0.0", - "pkg:npm/normalize-url@4.5.1", - "pkg:npm/responselike@1.0.2", - "pkg:npm/lowercase-keys@1.0.1", - "pkg:npm/decompress-response@3.3.0", - "pkg:npm/duplexer3@0.1.5", - "pkg:npm/get-stream@4.1.0", - "pkg:npm/p-cancelable@1.1.0", - "pkg:npm/to-readable-stream@1.0.0", - "pkg:npm/url-parse-lax@3.0.0", - "pkg:npm/prepend-http@2.0.0", - "pkg:npm/is-ip@3.1.0", - "pkg:npm/ip-regex@4.3.0", - "pkg:npm/rtc-stats-adapter@1.2.0", - "pkg:npm/joi@17.7.0", - "pkg:npm/%40hapi/hoek@9.3.0", - "pkg:npm/%40hapi/topo@5.1.0", - "pkg:npm/%40sideway/address@4.1.4", - "pkg:npm/%40sideway/formula@3.0.1", - "pkg:npm/%40sideway/pinpoint@2.0.0", - "pkg:npm/sdp-transform@2.14.1", - "pkg:npm/socket.io-client@2.5.0", - "pkg:npm/component-bind@1.0.0", - "pkg:npm/debug@3.1.0", - "pkg:npm/engine.io-client@3.5.3", - "pkg:npm/component-inherit@0.0.3", - "pkg:npm/engine.io-parser@2.2.1", - "pkg:npm/after@0.8.2", - "pkg:npm/arraybuffer.slice@0.0.7", - "pkg:npm/blob@0.0.5", - "pkg:npm/has-binary2@1.0.3", - "pkg:npm/isarray@2.0.1", - "pkg:npm/indexof@0.0.1", - "pkg:npm/xmlhttprequest-ssl@1.6.3", - "pkg:npm/socket.io-parser@3.3.3", - "pkg:npm/to-array@0.1.4", - "pkg:npm/webrtc-adapter@7.7.1", - "pkg:npm/rtcpeerconnection-shim@1.2.15", - "pkg:npm/sdp@2.12.0", - "pkg:npm/wildemitter@1.2.1", - "pkg:npm/ng-dialog@1.4.0", - "pkg:npm/ng-draggable@1.0.0", - "pkg:npm/ng-file-upload@12.2.13", - "pkg:npm/ng-idle@1.3.2", - "pkg:npm/ng-img-crop-full-extended@0.6.2", - "pkg:npm/ngstorage@0.3.11", - "pkg:npm/ngx-cookie-service@12.0.3", - "pkg:npm/opentok-layout-js@3.9.2", - "pkg:npm/pdfjs-dist@2.0.489", - "pkg:npm/node-ensure@0.0.0", - "pkg:npm/worker-loader@1.1.1", - "pkg:npm/loader-utils@1.4.2", - "pkg:npm/big.js@5.2.2", - "pkg:npm/emojis-list@3.0.0", - "pkg:npm/json5@1.0.2", - "pkg:npm/schema-utils@0.4.7", - "pkg:npm/ajv-keywords@3.5.2", - "pkg:npm/pwstrength-bootstrap@3.1.3", - "pkg:npm/rxjs@6.6.7", - "pkg:npm/rxjs-compat@6.6.7", - "pkg:npm/select2@4.0.13", - "pkg:npm/slimscroll@0.9.1", - "pkg:npm/browserify@17.0.0", - "pkg:npm/JSONStream@1.3.5", - "pkg:npm/jsonparse@1.3.1", - "pkg:npm/assert@1.5.0", - "pkg:npm/util@0.10.3", - "pkg:npm/inherits@2.0.1", - "pkg:npm/browser-pack@6.1.0", - "pkg:npm/combine-source-map@0.8.0", - "pkg:npm/convert-source-map@1.1.3", - "pkg:npm/inline-source-map@0.6.2", - "pkg:npm/lodash.memoize@3.0.4", - "pkg:npm/defined@1.0.1", - "pkg:npm/umd@3.0.3", - "pkg:npm/browser-resolve@2.0.0", - "pkg:npm/browserify-zlib@0.2.0", - "pkg:npm/pako@1.0.11", - "pkg:npm/buffer@5.2.1", - "pkg:npm/ieee754@1.2.1", - "pkg:npm/cached-path-relative@1.1.0", - "pkg:npm/concat-stream@1.6.2", - "pkg:npm/console-browserify@1.2.0", - "pkg:npm/constants-browserify@1.0.0", - "pkg:npm/crypto-browserify@3.12.0", - "pkg:npm/browserify-cipher@1.0.1", - "pkg:npm/browserify-aes@1.2.0", - "pkg:npm/buffer-xor@1.0.3", - "pkg:npm/cipher-base@1.0.4", - "pkg:npm/create-hash@1.2.0", - "pkg:npm/md5.js@1.3.5", - "pkg:npm/hash-base@3.1.0", - "pkg:npm/ripemd160@2.0.2", - "pkg:npm/sha.js@2.4.11", - "pkg:npm/evp_bytestokey@1.0.3", - "pkg:npm/browserify-des@1.0.2", - "pkg:npm/des.js@1.0.1", - "pkg:npm/minimalistic-assert@1.0.1", - "pkg:npm/browserify-sign@4.2.1", - "pkg:npm/bn.js@5.2.1", - "pkg:npm/browserify-rsa@4.1.0", - "pkg:npm/randombytes@2.1.0", - "pkg:npm/create-hmac@1.1.7", - "pkg:npm/elliptic@6.5.7", - "pkg:npm/bn.js@4.12.0", - "pkg:npm/brorand@1.1.0", - "pkg:npm/hash.js@1.1.7", - "pkg:npm/hmac-drbg@1.0.1", - "pkg:npm/minimalistic-crypto-utils@1.0.1", - "pkg:npm/parse-asn1@5.1.6", - "pkg:npm/asn1.js@5.4.1", - "pkg:npm/pbkdf2@3.1.2", - "pkg:npm/create-ecdh@4.0.4", - "pkg:npm/diffie-hellman@5.0.3", - "pkg:npm/miller-rabin@4.0.1", - "pkg:npm/public-encrypt@4.0.3", - "pkg:npm/randomfill@1.0.4", - "pkg:npm/deps-sort@2.0.1", - "pkg:npm/shasum-object@1.0.0", - "pkg:npm/fast-safe-stringify@2.1.1", - "pkg:npm/subarg@1.0.0", - "pkg:npm/domain-browser@1.2.0", - "pkg:npm/duplexer2@0.1.4", - "pkg:npm/events@3.3.0", - "pkg:npm/glob@7.2.3", - "pkg:npm/htmlescape@1.1.1", - "pkg:npm/https-browserify@1.0.0", - "pkg:npm/insert-module-globals@7.2.1", - "pkg:npm/acorn-node@1.8.2", - "pkg:npm/acorn@7.4.1", - "pkg:npm/acorn-walk@7.2.0", - "pkg:npm/is-buffer@1.1.6", - "pkg:npm/process@0.11.10", - "pkg:npm/undeclared-identifiers@1.1.3", - "pkg:npm/dash-ast@1.0.0", - "pkg:npm/get-assigned-identifiers@1.2.0", - "pkg:npm/simple-concat@1.0.1", - "pkg:npm/labeled-stream-splicer@2.0.2", - "pkg:npm/stream-splicer@2.0.1", - "pkg:npm/mkdirp-classic@0.5.3", - "pkg:npm/module-deps@6.2.3", - "pkg:npm/detective@5.2.1", - "pkg:npm/parents@1.0.1", - "pkg:npm/path-platform@0.11.15", - "pkg:npm/stream-combiner2@1.1.1", - "pkg:npm/os-browserify@0.3.0", - "pkg:npm/path-browserify@1.0.1", - "pkg:npm/punycode@1.4.1", - "pkg:npm/querystring-es3@0.2.1", - "pkg:npm/read-only-stream@2.0.0", - "pkg:npm/shell-quote@1.8.0", - "pkg:npm/stream-browserify@3.0.0", - "pkg:npm/stream-http@3.2.0", - "pkg:npm/builtin-status-codes@3.0.0", - "pkg:npm/syntax-error@1.4.0", - "pkg:npm/timers-browserify@1.4.2", - "pkg:npm/tty-browserify@0.0.1", - "pkg:npm/url@0.11.0", - "pkg:npm/vm-browserify@1.1.2", - "pkg:npm/classie@1.0.0", - "pkg:npm/domhelper@0.9.1", - "pkg:npm/util-extend@1.0.3", - "pkg:npm/socketgd@1.0.12", - "pkg:npm/sweetalert@1.1.3", - "pkg:npm/systemjs@6.15.1", - "pkg:npm/toastr@2.1.4", - "pkg:npm/tsify@5.0.4", - "pkg:npm/semver@6.3.0", - "pkg:npm/tsconfig@5.0.3", - "pkg:npm/any-promise@1.3.0", - "pkg:npm/parse-json@2.2.0", - "pkg:npm/error-ex@1.3.2", - "pkg:npm/is-arrayish@0.2.1", - "pkg:npm/strip-bom@2.0.0", - "pkg:npm/is-utf8@0.2.1", - "pkg:npm/strip-json-comments@2.0.1", - "pkg:npm/ui-select@0.19.8", - "pkg:npm/uninstall@0.0.0", - "pkg:npm/uuid-random@1.3.2", - "pkg:npm/v-accordion@1.6.0", - "pkg:npm/vinyl@2.2.1", - "pkg:npm/clone@2.1.2", - "pkg:npm/clone-buffer@1.0.0", - "pkg:npm/clone-stats@1.0.0", - "pkg:npm/cloneable-readable@1.1.3", - "pkg:npm/remove-trailing-separator@1.1.0", - "pkg:npm/replace-ext@1.0.1", - "pkg:npm/zone.js@0.11.8", - "pkg:npm/%40sentry/angular@7.64.0", - "pkg:npm/%40sentry/browser@7.64.0", - "pkg:npm/%40sentry-internal/tracing@7.64.0", - "pkg:npm/%40sentry/core@7.64.0", - "pkg:npm/%40sentry/types@7.64.0", - "pkg:npm/%40sentry/utils@7.64.0", - "pkg:npm/%40sentry/replay@7.64.0", - "pkg:npm/bootstrap@4.6.2", - "pkg:npm/browserify-shim@3.8.16", - "pkg:npm/exposify@0.5.0", - "pkg:npm/globo@1.1.0", - "pkg:npm/accessory@1.1.0", - "pkg:npm/ap@0.2.0", - "pkg:npm/balanced-match@0.2.1", - "pkg:npm/dot-parts@1.0.1", - "pkg:npm/is-defined@1.0.0", - "pkg:npm/ternary@1.0.0", - "pkg:npm/map-obj@1.0.1", - "pkg:npm/replace-requires@1.0.4", - "pkg:npm/detective@4.7.1", - "pkg:npm/acorn@5.7.4", - "pkg:npm/has-require@1.2.2", - "pkg:npm/patch-text@1.0.2", - "pkg:npm/through2@0.4.2", - "pkg:npm/readable-stream@1.0.34", - "pkg:npm/xtend@2.1.2", - "pkg:npm/object-keys@0.4.0", - "pkg:npm/transformify@0.1.2", - "pkg:npm/mothership@0.3.0", - "pkg:npm/find-parent-dir@0.3.1", - "pkg:npm/rename-function-calls@0.1.1", - "pkg:npm/detective@3.1.0", - "pkg:npm/escodegen@1.1.0", - "pkg:npm/estraverse@1.5.1", - "pkg:npm/esutils@1.0.0", - "pkg:npm/source-map@0.1.43", - "pkg:npm/amdefine@1.0.1", - "pkg:npm/esprima-fb@3001.1.0-dev-harmony-fb", - "pkg:npm/resolve@0.6.3", - "pkg:npm/canvas-to-blob@1.0.0", - "pkg:npm/ckeditor4-angular@2.4.1", - "pkg:npm/ckeditor4-integrations-common@1.0.0", - "pkg:npm/file-saver@2.0.5", - "pkg:npm/global-jsdom@7.0.0", - "pkg:npm/gulp-typescript@6.0.0-alpha.1", - "pkg:npm/ansi-colors@4.1.1", - "pkg:npm/plugin-error@1.0.1", - "pkg:npm/ansi-colors@1.1.0", - "pkg:npm/arr-diff@4.0.0", - "pkg:npm/arr-union@3.1.0", - "pkg:npm/extend-shallow@3.0.2", - "pkg:npm/assign-symbols@1.0.0", - "pkg:npm/is-extendable@1.0.1", - "pkg:npm/is-plain-object@2.0.4", - "pkg:npm/isobject@3.0.1", - "pkg:npm/through2@3.0.2", - "pkg:npm/vinyl-fs@3.0.3", - "pkg:npm/fs-mkdirp-stream@1.0.0", - "pkg:npm/glob-stream@6.1.0", - "pkg:npm/glob-parent@3.1.0", - "pkg:npm/is-glob@3.1.0", - "pkg:npm/is-extglob@2.1.1", - "pkg:npm/path-dirname@1.0.2", - "pkg:npm/is-negated-glob@1.0.0", - "pkg:npm/ordered-read-streams@1.0.1", - "pkg:npm/pumpify@1.5.1", - "pkg:npm/duplexify@3.7.1", - "pkg:npm/stream-shift@1.0.1", - "pkg:npm/pump@2.0.1", - "pkg:npm/to-absolute-glob@2.0.2", - "pkg:npm/is-absolute@1.0.0", - "pkg:npm/is-relative@1.0.0", - "pkg:npm/is-unc-path@1.0.0", - "pkg:npm/unc-path-regex@0.1.2", - "pkg:npm/is-windows@1.0.2", - "pkg:npm/unique-stream@2.3.1", - "pkg:npm/json-stable-stringify-without-jsonify@1.0.1", - "pkg:npm/through2-filter@3.0.0", - "pkg:npm/is-valid-glob@1.0.0", - "pkg:npm/lazystream@1.0.1", - "pkg:npm/lead@1.0.0", - "pkg:npm/flush-write-stream@1.1.1", - "pkg:npm/object.assign@4.1.4", - "pkg:npm/define-properties@1.1.4", - "pkg:npm/has-property-descriptors@1.0.0", - "pkg:npm/object-keys@1.1.1", - "pkg:npm/remove-bom-buffer@3.0.0", - "pkg:npm/remove-bom-stream@1.2.0", - "pkg:npm/resolve-options@1.1.0", - "pkg:npm/value-or-function@3.0.0", - "pkg:npm/to-through@2.0.0", - "pkg:npm/vinyl-sourcemap@1.1.0", - "pkg:npm/append-buffer@1.0.2", - "pkg:npm/buffer-equal@1.0.1", - "pkg:npm/normalize-path@2.1.1", - "pkg:npm/now-and-later@2.0.1", - "pkg:npm/husky@8.0.3", - "pkg:npm/i18n-2@0.7.3", - "pkg:npm/sprintf-js@1.1.2", - "pkg:npm/jsdom@16.7.0", - "pkg:npm/acorn-globals@6.0.0", - "pkg:npm/cssom@0.4.4", - "pkg:npm/data-urls@2.0.0", - "pkg:npm/whatwg-mimetype@2.3.0", - "pkg:npm/whatwg-url@8.7.0", - "pkg:npm/tr46@2.1.0", - "pkg:npm/webidl-conversions@6.1.0", - "pkg:npm/domexception@2.0.1", - "pkg:npm/webidl-conversions@5.0.0", - "pkg:npm/escodegen@2.0.0", - "pkg:npm/form-data@3.0.1", - "pkg:npm/html-encoding-sniffer@2.0.1", - "pkg:npm/whatwg-encoding@1.0.5", - "pkg:npm/iconv-lite@0.4.24", - "pkg:npm/http-proxy-agent@4.0.1", - "pkg:npm/%40tootallnate/once@1.1.2", - "pkg:npm/parse5@6.0.1", - "pkg:npm/saxes@5.0.1", - "pkg:npm/tough-cookie@4.1.2", - "pkg:npm/w3c-hr-time@1.0.2", - "pkg:npm/browser-process-hrtime@1.0.0", - "pkg:npm/w3c-xmlserializer@2.0.0", - "pkg:npm/xml-name-validator@3.0.0", - "pkg:npm/logstash@4.1.1", - "pkg:npm/ngx-cookie-service@11.0.2", - "pkg:npm/pdfjs-dist@2.16.105", - "pkg:npm/dommatrix@1.0.3", - "pkg:npm/web-streams-polyfill@3.2.1", - "pkg:npm/raven@2.6.4", - "pkg:npm/cookie@0.3.1", - "pkg:npm/md5@2.3.0", - "pkg:npm/charenc@0.0.2", - "pkg:npm/crypt@0.0.2", - "pkg:npm/stack-trace@0.0.10", - "pkg:npm/timed-out@4.0.1", - "pkg:npm/uuid@3.3.2", - "pkg:npm/raven-js@3.27.2", - "pkg:npm/reflect-metadata@0.1.13", - "pkg:npm/uuid@8.3.2", - "pkg:npm/%40angular/cli@12.2.2", - "pkg:npm/%40angular-devkit/architect@0.1202.2", - "pkg:npm/%40angular-devkit/core@12.2.2", - "pkg:npm/ajv@8.6.2", - "pkg:npm/json-schema-traverse@1.0.0", - "pkg:npm/require-from-string@2.0.2", - "pkg:npm/ajv-formats@2.1.0", - "pkg:npm/magic-string@0.25.7", - "pkg:npm/sourcemap-codec@1.4.8", - "pkg:npm/%40angular-devkit/schematics@12.2.2", - "pkg:npm/ora@5.4.1", - "pkg:npm/bl@4.1.0", - "pkg:npm/buffer@5.7.1", - "pkg:npm/cli-cursor@3.1.0", - "pkg:npm/restore-cursor@3.1.0", - "pkg:npm/onetime@5.1.2", - "pkg:npm/mimic-fn@2.1.0", - "pkg:npm/signal-exit@3.0.7", - "pkg:npm/cli-spinners@2.7.0", - "pkg:npm/is-interactive@1.0.0", - "pkg:npm/is-unicode-supported@0.1.0", - "pkg:npm/log-symbols@4.1.0", - "pkg:npm/wcwidth@1.0.1", - "pkg:npm/defaults@1.0.4", - "pkg:npm/%40schematics/angular@12.2.2", - "pkg:npm/jsonc-parser@3.0.0", - "pkg:npm/%40yarnpkg/lockfile@1.1.0", - "pkg:npm/ini@2.0.0", - "pkg:npm/inquirer@8.1.2", - "pkg:npm/ansi-escapes@4.3.2", - "pkg:npm/type-fest@0.21.3", - "pkg:npm/cli-width@3.0.0", - "pkg:npm/external-editor@3.1.0", - "pkg:npm/chardet@0.7.0", - "pkg:npm/tmp@0.0.33", - "pkg:npm/figures@3.2.0", - "pkg:npm/mute-stream@0.0.8", - "pkg:npm/run-async@2.4.1", - "pkg:npm/rxjs@7.8.0", - "pkg:npm/npm-package-arg@8.1.5", - "pkg:npm/hosted-git-info@4.1.0", - "pkg:npm/validate-npm-package-name@3.0.0", - "pkg:npm/builtins@1.0.3", - "pkg:npm/npm-pick-manifest@6.1.1", - "pkg:npm/npm-install-checks@4.0.0", - "pkg:npm/npm-normalize-package-bin@1.0.1", - "pkg:npm/open@8.2.1", - "pkg:npm/define-lazy-prop@2.0.0", - "pkg:npm/is-docker@2.2.1", - "pkg:npm/is-wsl@2.2.0", - "pkg:npm/pacote@11.3.5", - "pkg:npm/%40npmcli/git@2.1.0", - "pkg:npm/%40npmcli/promise-spawn@1.3.2", - "pkg:npm/infer-owner@1.0.4", - "pkg:npm/mkdirp@1.0.4", - "pkg:npm/promise-inflight@1.0.1", - "pkg:npm/promise-retry@2.0.1", - "pkg:npm/err-code@2.0.3", - "pkg:npm/retry@0.12.0", - "pkg:npm/which@2.0.2", - "pkg:npm/isexe@2.0.0", - "pkg:npm/%40npmcli/installed-package-contents@1.0.7", - "pkg:npm/npm-bundled@1.1.2", - "pkg:npm/%40npmcli/run-script@1.8.6", - "pkg:npm/%40npmcli/node-gyp@1.0.3", - "pkg:npm/node-gyp@7.1.2", - "pkg:npm/env-paths@2.2.1", - "pkg:npm/nopt@5.0.0", - "pkg:npm/abbrev@1.1.1", - "pkg:npm/npmlog@4.1.2", - "pkg:npm/are-we-there-yet@1.1.7", - "pkg:npm/delegates@1.0.0", - "pkg:npm/console-control-strings@1.1.0", - "pkg:npm/gauge@2.7.4", - "pkg:npm/aproba@1.2.0", - "pkg:npm/has-unicode@2.0.1", - "pkg:npm/string-width@1.0.2", - "pkg:npm/code-point-at@1.1.0", - "pkg:npm/is-fullwidth-code-point@1.0.0", - "pkg:npm/number-is-nan@1.0.1", - "pkg:npm/wide-align@1.1.5", - "pkg:npm/set-blocking@2.0.0", - "pkg:npm/rimraf@3.0.2", - "pkg:npm/tar@6.1.13", - "pkg:npm/chownr@2.0.0", - "pkg:npm/fs-minipass@2.1.0", - "pkg:npm/minipass@3.3.6", - "pkg:npm/minipass@4.0.1", - "pkg:npm/minizlib@2.1.2", - "pkg:npm/read-package-json-fast@2.0.3", - "pkg:npm/json-parse-even-better-errors@2.3.1", - "pkg:npm/cacache@15.3.0", - "pkg:npm/%40npmcli/fs@1.1.1", - "pkg:npm/%40gar/promisify@1.1.3", - "pkg:npm/%40npmcli/move-file@1.1.2", - "pkg:npm/minipass-collect@1.0.2", - "pkg:npm/minipass-flush@1.0.5", - "pkg:npm/minipass-pipeline@1.2.4", - "pkg:npm/p-map@4.0.0", - "pkg:npm/aggregate-error@3.1.0", - "pkg:npm/clean-stack@2.2.0", - "pkg:npm/indent-string@4.0.0", - "pkg:npm/ssri@8.0.1", - "pkg:npm/unique-filename@1.1.1", - "pkg:npm/unique-slug@2.0.2", - "pkg:npm/imurmurhash@0.1.4", - "pkg:npm/npm-packlist@2.2.2", - "pkg:npm/ignore-walk@3.0.4", - "pkg:npm/npm-registry-fetch@11.0.0", - "pkg:npm/make-fetch-happen@9.1.0", - "pkg:npm/agentkeepalive@4.2.1", - "pkg:npm/depd@1.1.2", - "pkg:npm/humanize-ms@1.2.1", - "pkg:npm/is-lambda@1.0.1", - "pkg:npm/minipass-fetch@1.4.1", - "pkg:npm/minipass-sized@1.0.3", - "pkg:npm/encoding@0.1.13", - "pkg:npm/socks-proxy-agent@6.2.1", - "pkg:npm/debug@4.3.4", - "pkg:npm/socks@2.7.1", - "pkg:npm/ip@2.0.0", - "pkg:npm/smart-buffer@4.2.0", - "pkg:npm/minipass-json-stream@1.0.1", - "pkg:npm/symbol-observable@4.0.0", - "pkg:npm/%40angular/compiler@12.2.2", - "pkg:npm/%40angular/compiler-cli@12.2.2", - "pkg:npm/%40babel/core@7.20.12", - "pkg:npm/%40babel/helper-compilation-targets@7.20.7", - "pkg:npm/%40babel/compat-data@7.20.14", - "pkg:npm/%40babel/helper-validator-option@7.18.6", - "pkg:npm/browserslist@4.21.5", - "pkg:npm/caniuse-lite@1.0.30001449", - "pkg:npm/electron-to-chromium@1.4.284", - "pkg:npm/node-releases@2.0.9", - "pkg:npm/update-browserslist-db@1.0.10", - "pkg:npm/%40babel/helper-module-transforms@7.20.11", - "pkg:npm/%40babel/helper-module-imports@7.18.6", - "pkg:npm/%40babel/helper-simple-access@7.20.2", - "pkg:npm/canonical-path@1.0.0", - "pkg:npm/chokidar@3.5.3", - "pkg:npm/anymatch@3.1.3", - "pkg:npm/normalize-path@3.0.0", - "pkg:npm/picomatch@2.3.1", - "pkg:npm/braces@3.0.2", - "pkg:npm/fill-range@7.0.1", - "pkg:npm/to-regex-range@5.0.1", - "pkg:npm/is-number@7.0.0", - "pkg:npm/glob-parent@5.1.2", - "pkg:npm/is-glob@4.0.3", - "pkg:npm/is-binary-path@2.1.0", - "pkg:npm/binary-extensions@2.2.0", - "pkg:npm/readdirp@3.6.0", - "pkg:npm/fsevents@2.3.3", - "pkg:npm/dependency-graph@0.11.0", - "pkg:npm/%40rollup/plugin-node-resolve@7.1.3", - "pkg:npm/%40rollup/pluginutils@3.1.0", - "pkg:npm/%40types/estree@0.0.39", - "pkg:npm/estree-walker@1.0.1", - "pkg:npm/%40types/resolve@0.0.8", - "pkg:npm/builtin-modules@3.3.0", - "pkg:npm/is-module@1.0.0", - "pkg:npm/%40types/angular@1.8.4", - "pkg:npm/%40types/angular-animate@1.5.11", - "pkg:npm/%40types/angular-cookies@1.8.1", - "pkg:npm/%40types/angular-mocks@1.7.1", - "pkg:npm/%40types/angular-resource@1.5.17", - "pkg:npm/%40types/angular-route@1.7.2", - "pkg:npm/%40types/angular-sanitize@1.8.1", - "pkg:npm/%40types/angular-translate@2.19.0", - "pkg:npm/%40types/jasmine@4.3.1", - "pkg:npm/%40types/jquery@3.5.16", - "pkg:npm/%40types/sizzle@2.3.3", - "pkg:npm/%40typescript-eslint/eslint-plugin@5.29.0", - "pkg:npm/%40typescript-eslint/scope-manager@5.29.0", - "pkg:npm/%40typescript-eslint/types@5.29.0", - "pkg:npm/%40typescript-eslint/visitor-keys@5.29.0", - "pkg:npm/eslint-visitor-keys@3.3.0", - "pkg:npm/%40typescript-eslint/type-utils@5.29.0", - "pkg:npm/%40typescript-eslint/utils@5.29.0", - "pkg:npm/%40types/json-schema@7.0.11", - "pkg:npm/%40typescript-eslint/typescript-estree@5.29.0", - "pkg:npm/globby@11.1.0", - "pkg:npm/array-union@2.1.0", - "pkg:npm/dir-glob@3.0.1", - "pkg:npm/path-type@4.0.0", - "pkg:npm/fast-glob@3.2.12", - "pkg:npm/%40nodelib/fs.stat@2.0.5", - "pkg:npm/%40nodelib/fs.walk@1.2.8", - "pkg:npm/%40nodelib/fs.scandir@2.1.5", - "pkg:npm/run-parallel@1.2.0", - "pkg:npm/queue-microtask@1.2.3", - "pkg:npm/fastq@1.15.0", - "pkg:npm/reusify@1.0.4", - "pkg:npm/merge2@1.4.1", - "pkg:npm/micromatch@4.0.5", - "pkg:npm/ignore@5.2.4", - "pkg:npm/slash@3.0.0", - "pkg:npm/semver@7.3.8", - "pkg:npm/tsutils@3.21.0", - "pkg:npm/eslint-scope@5.1.1", - "pkg:npm/esrecurse@4.3.0", - "pkg:npm/eslint-utils@3.0.0", - "pkg:npm/eslint-visitor-keys@2.1.0", - "pkg:npm/functional-red-black-tree@1.0.1", - "pkg:npm/regexpp@3.2.0", - "pkg:npm/%40typescript-eslint/parser@5.29.0", - "pkg:npm/acorn-globals@3.1.0", - "pkg:npm/acorn@4.0.13", - "pkg:npm/acorn-jsx@3.0.1", - "pkg:npm/acorn@3.3.0", - "pkg:npm/assertion-error@1.1.0", - "pkg:npm/bower@1.8.14", - "pkg:npm/brfs@1.6.1", - "pkg:npm/quote-stream@1.0.2", - "pkg:npm/buffer-equal@0.0.1", - "pkg:npm/static-module@2.2.5", - "pkg:npm/escodegen@1.9.1", - "pkg:npm/esprima@3.1.3", - "pkg:npm/falafel@2.2.5", - "pkg:npm/isarray@2.0.5", - "pkg:npm/magic-string@0.22.5", - "pkg:npm/merge-source-map@1.0.4", - "pkg:npm/object-inspect@1.4.1", - "pkg:npm/shallow-copy@0.0.1", - "pkg:npm/static-eval@2.1.0", - "pkg:npm/canonical-path@0.0.2", - "pkg:npm/chai@4.3.7", - "pkg:npm/check-error@1.0.2", - "pkg:npm/deep-eql@4.1.3", - "pkg:npm/type-detect@4.0.8", - "pkg:npm/get-func-name@2.0.0", - "pkg:npm/loupe@2.3.6", - "pkg:npm/pathval@1.1.1", - "pkg:npm/del@3.0.0", - "pkg:npm/globby@6.1.0", - "pkg:npm/array-union@1.0.2", - "pkg:npm/pify@2.3.0", - "pkg:npm/pinkie-promise@2.0.1", - "pkg:npm/pinkie@2.0.4", - "pkg:npm/is-path-cwd@1.0.0", - "pkg:npm/is-path-in-cwd@1.0.1", - "pkg:npm/is-path-inside@1.0.1", - "pkg:npm/path-is-inside@1.0.2", - "pkg:npm/p-map@1.2.0", - "pkg:npm/pify@3.0.0", - "pkg:npm/rimraf@2.7.1", - "pkg:npm/escape-string-regexp@4.0.0", - "pkg:npm/eslint@8.18.0", - "pkg:npm/%40eslint/eslintrc@1.4.1", - "pkg:npm/espree@9.4.1", - "pkg:npm/acorn-jsx@5.3.2", - "pkg:npm/globals@13.20.0", - "pkg:npm/type-fest@0.20.2", - "pkg:npm/import-fresh@3.3.0", - "pkg:npm/parent-module@1.0.1", - "pkg:npm/callsites@3.1.0", - "pkg:npm/resolve-from@4.0.0", - "pkg:npm/js-yaml@4.1.0", - "pkg:npm/argparse@2.0.1", - "pkg:npm/strip-json-comments@3.1.1", - "pkg:npm/%40humanwhocodes/config-array@0.9.5", - "pkg:npm/%40humanwhocodes/object-schema@1.2.1", - "pkg:npm/cross-spawn@7.0.3", - "pkg:npm/path-key@3.1.1", - "pkg:npm/shebang-command@2.0.0", - "pkg:npm/shebang-regex@3.0.0", - "pkg:npm/doctrine@3.0.0", - "pkg:npm/eslint-scope@7.1.1", - "pkg:npm/esquery@1.4.0", - "pkg:npm/file-entry-cache@6.0.1", - "pkg:npm/flat-cache@3.0.4", - "pkg:npm/flatted@3.2.7", - "pkg:npm/glob-parent@6.0.2", - "pkg:npm/levn@0.4.1", - "pkg:npm/prelude-ls@1.2.1", - "pkg:npm/type-check@0.4.0", - "pkg:npm/lodash.merge@4.6.2", - "pkg:npm/natural-compare@1.4.0", - "pkg:npm/optionator@0.9.1", - "pkg:npm/text-table@0.2.0", - "pkg:npm/v8-compile-cache@2.3.0", - "pkg:npm/eslint-plugin-angular@4.1.0", - "pkg:npm/gulp@4.0.2", - "pkg:npm/glob-watcher@5.0.5", - "pkg:npm/anymatch@2.0.0", - "pkg:npm/micromatch@3.1.10", - "pkg:npm/array-unique@0.3.2", - "pkg:npm/braces@2.3.2", - "pkg:npm/arr-flatten@1.1.0", - "pkg:npm/extend-shallow@2.0.1", - "pkg:npm/is-extendable@0.1.1", - "pkg:npm/fill-range@4.0.0", - "pkg:npm/is-number@3.0.0", - "pkg:npm/kind-of@3.2.2", - "pkg:npm/repeat-string@1.6.1", - "pkg:npm/to-regex-range@2.1.1", - "pkg:npm/repeat-element@1.1.4", - "pkg:npm/snapdragon@0.8.2", - "pkg:npm/base@0.11.2", - "pkg:npm/cache-base@1.0.1", - "pkg:npm/collection-visit@1.0.0", - "pkg:npm/map-visit@1.0.0", - "pkg:npm/object-visit@1.0.1", - "pkg:npm/get-value@2.0.6", - "pkg:npm/has-value@1.0.0", - "pkg:npm/has-values@1.0.0", - "pkg:npm/kind-of@4.0.0", - "pkg:npm/set-value@2.0.1", - "pkg:npm/split-string@3.1.0", - "pkg:npm/to-object-path@0.3.0", - "pkg:npm/union-value@1.0.1", - "pkg:npm/unset-value@1.0.0", - "pkg:npm/has-value@0.3.1", - "pkg:npm/has-values@0.1.4", - "pkg:npm/isobject@2.1.0", - "pkg:npm/class-utils@0.3.6", - "pkg:npm/define-property@0.2.5", - "pkg:npm/is-descriptor@0.1.6", - "pkg:npm/is-accessor-descriptor@0.1.6", - "pkg:npm/is-data-descriptor@0.1.4", - "pkg:npm/kind-of@5.1.0", - "pkg:npm/static-extend@0.1.2", - "pkg:npm/object-copy@0.1.0", - "pkg:npm/copy-descriptor@0.1.1", - "pkg:npm/define-property@1.0.0", - "pkg:npm/is-descriptor@1.0.2", - "pkg:npm/is-accessor-descriptor@1.0.0", - "pkg:npm/kind-of@6.0.3", - "pkg:npm/is-data-descriptor@1.0.0", - "pkg:npm/mixin-deep@1.3.2", - "pkg:npm/for-in@1.0.2", - "pkg:npm/pascalcase@0.1.1", - "pkg:npm/map-cache@0.2.2", - "pkg:npm/source-map-resolve@0.5.3", - "pkg:npm/atob@2.1.2", - "pkg:npm/decode-uri-component@0.2.2", - "pkg:npm/resolve-url@0.2.1", - "pkg:npm/source-map-url@0.4.1", - "pkg:npm/urix@0.1.0", - "pkg:npm/use@3.1.1", - "pkg:npm/snapdragon-node@2.1.1", - "pkg:npm/snapdragon-util@3.0.1", - "pkg:npm/to-regex@3.0.2", - "pkg:npm/define-property@2.0.2", - "pkg:npm/regex-not@1.0.2", - "pkg:npm/safe-regex@1.1.0", - "pkg:npm/ret@0.1.15", - "pkg:npm/extglob@2.0.4", - "pkg:npm/expand-brackets@2.1.4", - "pkg:npm/posix-character-classes@0.1.1", - "pkg:npm/fragment-cache@0.2.1", - "pkg:npm/nanomatch@1.2.13", - "pkg:npm/object.pick@1.3.0", - "pkg:npm/async-done@1.3.2", - "pkg:npm/stream-exhaust@1.0.2", - "pkg:npm/chokidar@2.1.8", - "pkg:npm/async-each@1.0.5", - "pkg:npm/is-binary-path@1.0.1", - "pkg:npm/binary-extensions@1.13.1", - "pkg:npm/readdirp@2.2.1", - "pkg:npm/upath@1.2.0", - "pkg:npm/fsevents@1.2.13", - "pkg:npm/bindings@1.5.0", - "pkg:npm/file-uri-to-path@1.0.0", - "pkg:npm/just-debounce@1.1.0", - "pkg:npm/object.defaults@1.1.0", - "pkg:npm/array-each@1.0.1", - "pkg:npm/array-slice@1.1.0", - "pkg:npm/for-own@1.0.0", - "pkg:npm/gulp-cli@2.3.0", - "pkg:npm/archy@1.0.0", - "pkg:npm/array-sort@1.0.0", - "pkg:npm/default-compare@1.0.0", - "pkg:npm/copy-props@2.0.5", - "pkg:npm/each-props@1.3.2", - "pkg:npm/is-plain-object@5.0.0", - "pkg:npm/interpret@1.4.0", - "pkg:npm/liftoff@3.1.0", - "pkg:npm/findup-sync@3.0.0", - "pkg:npm/detect-file@1.0.0", - "pkg:npm/resolve-dir@1.0.1", - "pkg:npm/expand-tilde@2.0.2", - "pkg:npm/homedir-polyfill@1.0.3", - "pkg:npm/parse-passwd@1.0.0", - "pkg:npm/global-modules@1.0.0", - "pkg:npm/global-prefix@1.0.2", - "pkg:npm/ini@1.3.8", - "pkg:npm/which@1.3.1", - "pkg:npm/fined@1.2.0", - "pkg:npm/parse-filepath@1.0.2", - "pkg:npm/path-root@0.1.1", - "pkg:npm/path-root-regex@0.1.2", - "pkg:npm/flagged-respawn@1.0.1", - "pkg:npm/object.map@1.0.1", - "pkg:npm/make-iterator@1.0.1", - "pkg:npm/rechoir@0.6.2", - "pkg:npm/matchdep@2.0.0", - "pkg:npm/findup-sync@2.0.0", - "pkg:npm/mute-stdout@1.0.1", - "pkg:npm/pretty-hrtime@1.0.3", - "pkg:npm/replace-homedir@1.0.0", - "pkg:npm/semver-greatest-satisfied-range@1.1.0", - "pkg:npm/sver-compat@1.5.0", - "pkg:npm/v8flags@3.2.0", - "pkg:npm/yargs@7.1.2", - "pkg:npm/camelcase@3.0.0", - "pkg:npm/cliui@3.2.0", - "pkg:npm/wrap-ansi@2.1.0", - "pkg:npm/decamelize@1.2.0", - "pkg:npm/get-caller-file@1.0.3", - "pkg:npm/os-locale@1.4.0", - "pkg:npm/lcid@1.0.0", - "pkg:npm/invert-kv@1.0.0", - "pkg:npm/read-pkg-up@1.0.1", - "pkg:npm/find-up@1.1.2", - "pkg:npm/path-exists@2.1.0", - "pkg:npm/read-pkg@1.1.0", - "pkg:npm/load-json-file@1.1.0", - "pkg:npm/normalize-package-data@2.5.0", - "pkg:npm/hosted-git-info@2.8.9", - "pkg:npm/validate-npm-package-license@3.0.4", - "pkg:npm/spdx-correct@3.1.1", - "pkg:npm/spdx-expression-parse@3.0.1", - "pkg:npm/spdx-exceptions@2.3.0", - "pkg:npm/spdx-license-ids@3.0.12", - "pkg:npm/path-type@1.1.0", - "pkg:npm/require-main-filename@1.0.1", - "pkg:npm/which-module@1.0.0", - "pkg:npm/y18n@3.2.2", - "pkg:npm/yargs-parser@5.0.1", - "pkg:npm/undertaker@1.3.0", - "pkg:npm/arr-map@2.0.2", - "pkg:npm/bach@1.2.0", - "pkg:npm/arr-filter@1.1.2", - "pkg:npm/array-initial@1.1.0", - "pkg:npm/is-number@4.0.0", - "pkg:npm/array-last@1.3.0", - "pkg:npm/async-settle@1.0.0", - "pkg:npm/collection-map@1.0.0", - "pkg:npm/es6-weak-map@2.0.3", - "pkg:npm/fast-levenshtein@1.1.4", - "pkg:npm/last-run@1.1.1", - "pkg:npm/default-resolution@2.0.0", - "pkg:npm/object.reduce@1.0.1", - "pkg:npm/undertaker-registry@1.0.1", - "pkg:npm/gulp-angular-templatecache@2.2.7", - "pkg:npm/gulp-concat@2.6.1", - "pkg:npm/concat-with-sourcemaps@1.1.0", - "pkg:npm/gulp-footer@2.0.2", - "pkg:npm/map-stream@0.0.7", - "pkg:npm/gulp-header@2.0.7", - "pkg:npm/lodash.template@4.5.0", - "pkg:npm/lodash.templatesettings@4.2.0", - "pkg:npm/jsesc@2.5.1", - "pkg:npm/stream-combiner@0.2.2", - "pkg:npm/duplexer@0.1.2", - "pkg:npm/gulp-clean-css@3.10.0", - "pkg:npm/clean-css@4.2.1", - "pkg:npm/through2@2.0.3", - "pkg:npm/vinyl-sourcemaps-apply@0.2.1", - "pkg:npm/gulp-develop-server@0.5.2", - "pkg:npm/gulp-util@3.0.7", - "pkg:npm/dateformat@1.0.12", - "pkg:npm/get-stdin@4.0.1", - "pkg:npm/meow@3.7.0", - "pkg:npm/camelcase-keys@2.1.0", - "pkg:npm/camelcase@2.1.1", - "pkg:npm/loud-rejection@1.6.0", - "pkg:npm/currently-unhandled@0.4.1", - "pkg:npm/array-find-index@1.0.2", - "pkg:npm/redent@1.0.0", - "pkg:npm/indent-string@2.1.0", - "pkg:npm/repeating@2.0.1", - "pkg:npm/is-finite@1.1.0", - "pkg:npm/strip-indent@1.0.1", - "pkg:npm/trim-newlines@1.0.0", - "pkg:npm/gulp-faster-browserify@0.2.1", - "pkg:npm/browserify@3.46.1", - "pkg:npm/JSONStream@0.7.4", - "pkg:npm/jsonparse@0.0.5", - "pkg:npm/assert@1.1.2", - "pkg:npm/browser-pack@2.0.1", - "pkg:npm/JSONStream@0.6.4", - "pkg:npm/through@2.2.7", - "pkg:npm/combine-source-map@0.3.0", - "pkg:npm/convert-source-map@0.3.5", - "pkg:npm/inline-source-map@0.3.1", - "pkg:npm/source-map@0.3.0", - "pkg:npm/browser-resolve@1.2.4", - "pkg:npm/browserify-zlib@0.1.4", - "pkg:npm/pako@0.2.9", - "pkg:npm/buffer@2.1.13", - "pkg:npm/base64-js@0.0.8", - "pkg:npm/builtins@0.0.7", - "pkg:npm/commondir@0.0.1", - "pkg:npm/concat-stream@1.4.11", - "pkg:npm/console-browserify@1.0.3", - "pkg:npm/constants-browserify@0.0.1", - "pkg:npm/crypto-browserify@1.0.9", - "pkg:npm/deep-equal@0.1.2", - "pkg:npm/defined@0.0.0", - "pkg:npm/deps-sort@0.1.2", - "pkg:npm/minimist@0.0.10", - "pkg:npm/derequire@0.8.0", - "pkg:npm/esrefactor@0.1.0", - "pkg:npm/escope@0.0.16", - "pkg:npm/estraverse@0.0.4", - "pkg:npm/domain-browser@1.1.7", - "pkg:npm/events@1.0.2", - "pkg:npm/glob@3.2.11", - "pkg:npm/minimatch@0.3.0", - "pkg:npm/lru-cache@2.7.3", - "pkg:npm/sigmund@1.0.1", - "pkg:npm/http-browserify@1.3.2", - "pkg:npm/Base64@0.2.1", - "pkg:npm/https-browserify@0.0.1", - "pkg:npm/insert-module-globals@6.0.0", - "pkg:npm/lexical-scope@1.1.1", - "pkg:npm/astw@2.2.0", - "pkg:npm/process@0.6.0", - "pkg:npm/xtend@3.0.0", - "pkg:npm/module-deps@2.0.6", - "pkg:npm/parents@0.0.2", - "pkg:npm/stream-combiner@0.1.0", - "pkg:npm/os-browserify@0.1.2", - "pkg:npm/parents@0.0.3", - "pkg:npm/path-platform@0.0.1", - "pkg:npm/path-browserify@0.0.1", - "pkg:npm/process@0.7.0", - "pkg:npm/punycode@1.2.4", - "pkg:npm/querystring-es3@0.2.0", - "pkg:npm/shell-quote@0.0.1", - "pkg:npm/stream-browserify@0.1.3", - "pkg:npm/process@0.5.2", - "pkg:npm/stream-combiner@0.0.4", - "pkg:npm/string_decoder@0.0.1", - "pkg:npm/subarg@0.0.1", - "pkg:npm/syntax-error@1.1.6", - "pkg:npm/acorn@2.7.0", - "pkg:npm/timers-browserify@1.0.3", - "pkg:npm/umd@2.0.0", - "pkg:npm/rfile@1.0.0", - "pkg:npm/callsite@1.0.0", - "pkg:npm/resolve@0.3.1", - "pkg:npm/ruglify@1.0.0", - "pkg:npm/uglify-js@2.2.5", - "pkg:npm/optimist@0.3.7", - "pkg:npm/wordwrap@0.0.3", - "pkg:npm/uglify-js@2.4.24", - "pkg:npm/async@0.2.10", - "pkg:npm/source-map@0.1.34", - "pkg:npm/uglify-to-browserify@1.0.2", - "pkg:npm/yargs@3.5.4", - "pkg:npm/camelcase@1.2.1", - "pkg:npm/window-size@0.1.0", - "pkg:npm/wordwrap@0.0.2", - "pkg:npm/vm-browserify@0.0.4", - "pkg:npm/browserify-shim@2.0.10", - "pkg:npm/gulp-util@2.2.20", - "pkg:npm/chalk@0.5.1", - "pkg:npm/ansi-styles@1.1.0", - "pkg:npm/has-ansi@0.1.0", - "pkg:npm/ansi-regex@0.2.1", - "pkg:npm/strip-ansi@0.3.0", - "pkg:npm/supports-color@0.2.0", - "pkg:npm/lodash._reinterpolate@2.4.1", - "pkg:npm/lodash.template@2.4.1", - "pkg:npm/lodash._escapestringchar@2.4.1", - "pkg:npm/lodash.defaults@2.4.1", - "pkg:npm/lodash._objecttypes@2.4.1", - "pkg:npm/lodash.keys@2.4.1", - "pkg:npm/lodash._isnative@2.4.1", - "pkg:npm/lodash._shimkeys@2.4.1", - "pkg:npm/lodash.isobject@2.4.1", - "pkg:npm/lodash.escape@2.4.1", - "pkg:npm/lodash._escapehtmlchar@2.4.1", - "pkg:npm/lodash._htmlescapes@2.4.1", - "pkg:npm/lodash._reunescapedhtml@2.4.1", - "pkg:npm/lodash.templatesettings@2.4.1", - "pkg:npm/lodash.values@2.4.1", - "pkg:npm/minimist@0.2.2", - "pkg:npm/through2@0.5.1", - "pkg:npm/vinyl@0.2.3", - "pkg:npm/gulp-html2jade@1.1.2", - "pkg:npm/event-stream@3.3.5", - "pkg:npm/from@0.1.7", - "pkg:npm/pause-stream@0.0.11", - "pkg:npm/split@1.0.1", - "pkg:npm/html2jade@0.8.6", - "pkg:npm/commander@2.20.3", - "pkg:npm/he@0.4.1", - "pkg:npm/jsdom-little@0.10.6", - "pkg:npm/cssstyle@0.2.37", - "pkg:npm/htmlparser2@3.10.1", - "pkg:npm/domelementtype@1.3.1", - "pkg:npm/domhandler@2.4.2", - "pkg:npm/domutils@1.7.0", - "pkg:npm/dom-serializer@0.2.2", - "pkg:npm/domelementtype@2.3.0", - "pkg:npm/entities@2.2.0", - "pkg:npm/entities@1.1.2", - "pkg:npm/nwmatcher@1.3.9", - "pkg:npm/map-stream@0.0.6", - "pkg:npm/gulp-rename@1.4.0", - "pkg:npm/gulp-sequence@1.0.0", - "pkg:npm/thunks@4.9.6", - "pkg:npm/gulp-sourcemaps@2.6.5", - "pkg:npm/%40gulp-sourcemaps/identity-map@1.0.2", - "pkg:npm/css@2.2.4", - "pkg:npm/%40gulp-sourcemaps/map-sources@1.0.0", - "pkg:npm/debug-fabulous@1.1.0", - "pkg:npm/memoizee@0.4.15", - "pkg:npm/is-promise@2.2.2", - "pkg:npm/lru-queue@0.1.0", - "pkg:npm/timers-ext@0.1.7", - "pkg:npm/detect-newline@2.1.0", - "pkg:npm/strip-bom-string@1.0.0", - "pkg:npm/gulp-sym@1.0.2", - "pkg:npm/%40soyuka/exists-sync@1.0.1", - "pkg:npm/gulp-uglify@3.0.2", - "pkg:npm/make-error-cause@1.2.2", - "pkg:npm/make-error@1.3.6", - "pkg:npm/uglify-js@3.17.4", - "pkg:npm/jasmine@4.6.0", - "pkg:npm/jasmine-core@4.6.0", - "pkg:npm/jasmine-core@2.4.1", - "pkg:npm/jscpd@3.5.10", - "pkg:npm/%40jscpd/core@3.5.4", - "pkg:npm/%40jscpd/finder@3.5.10", - "pkg:npm/%40jscpd/tokenizer@3.5.4", - "pkg:npm/reprism@0.0.11", - "pkg:npm/spark-md5@3.0.2", - "pkg:npm/blamer@1.0.6", - "pkg:npm/execa@4.1.0", - "pkg:npm/human-signals@1.1.1", - "pkg:npm/is-stream@2.0.1", - "pkg:npm/merge-stream@2.0.0", - "pkg:npm/npm-run-path@4.0.1", - "pkg:npm/strip-final-newline@2.0.0", - "pkg:npm/bytes@3.1.2", - "pkg:npm/cli-table3@0.6.3", - "pkg:npm/%40colors/colors@1.5.0", - "pkg:npm/colors@1.4.0", - "pkg:npm/fs-extra@9.1.0", - "pkg:npm/at-least-node@1.0.0", - "pkg:npm/jsonfile@6.1.0", - "pkg:npm/universalify@2.0.1", - "pkg:npm/markdown-table@2.0.0", - "pkg:npm/pug@3.0.2", - "pkg:npm/pug-code-gen@3.0.2", - "pkg:npm/constantinople@4.0.1", - "pkg:npm/doctypes@1.1.0", - "pkg:npm/js-stringify@1.0.2", - "pkg:npm/pug-attrs@3.0.0", - "pkg:npm/pug-runtime@3.0.1", - "pkg:npm/pug-error@2.0.0", - "pkg:npm/void-elements@3.1.0", - "pkg:npm/with@7.0.2", - "pkg:npm/assert-never@1.2.1", - "pkg:npm/babel-walk@3.0.0-canary-5", - "pkg:npm/pug-filters@4.0.0", - "pkg:npm/jstransformer@1.0.0", - "pkg:npm/promise@7.3.1", - "pkg:npm/asap@2.0.6", - "pkg:npm/pug-walk@2.0.0", - "pkg:npm/pug-lexer@5.0.1", - "pkg:npm/character-parser@2.2.0", - "pkg:npm/is-regex@1.1.4", - "pkg:npm/is-expression@4.0.0", - "pkg:npm/pug-linker@4.0.0", - "pkg:npm/pug-load@3.0.0", - "pkg:npm/pug-parser@6.0.0", - "pkg:npm/token-stream@1.0.0", - "pkg:npm/pug-strip-comments@2.0.0", - "pkg:npm/%40jscpd/html-reporter@3.5.10", - "pkg:npm/commander@5.1.0", - "pkg:npm/gitignore-to-glob@0.3.0", - "pkg:npm/karma-chrome-launcher@2.2.0", - "pkg:npm/fs-access@1.0.1", - "pkg:npm/null-check@1.0.0", - "pkg:npm/karma-cli@1.0.1", - "pkg:npm/karma-jasmine@1.1.2", - "pkg:npm/karma-jasmine-html-reporter@0.2.2", - "pkg:npm/ngma@1.0.6", - "pkg:npm/node-sloc@0.1.12", - "pkg:npm/promisify-node@0.5.0", - "pkg:npm/nodegit-promise@4.0.0", - "pkg:npm/parse-gitignore@0.5.1", - "pkg:npm/ts-node@7.0.1", - "pkg:npm/arrify@1.0.1", - "pkg:npm/diff@3.5.0", - "pkg:npm/yn@2.0.0", - "pkg:npm/ts-node-dev@1.1.8", - "pkg:npm/dynamic-dedupe@0.3.0", - "pkg:npm/tree-kill@1.2.2", - "pkg:npm/ts-node@9.1.1", - "pkg:npm/arg@4.1.3", - "pkg:npm/create-require@1.1.1", - "pkg:npm/diff@4.0.2", - "pkg:npm/yn@3.1.1", - "pkg:npm/tsconfig@7.0.0", - "pkg:npm/%40types/strip-bom@3.0.0", - "pkg:npm/%40types/strip-json-comments@0.0.30", - "pkg:npm/strip-bom@3.0.0", - "pkg:npm/tslint@5.20.1", - "pkg:npm/builtin-modules@1.1.1", - "pkg:npm/js-yaml@3.14.1", - "pkg:npm/argparse@1.0.10", - "pkg:npm/sprintf-js@1.0.3", - "pkg:npm/tsutils@2.29.0", - "pkg:npm/typescript@2.9.2", - "pkg:npm/nodemon@3.1.0", - "pkg:npm/ignore-by-default@1.0.1", - "pkg:npm/pstree.remy@1.1.8", - "pkg:npm/semver@7.6.2", - "pkg:npm/simple-update-notifier@2.0.0", - "pkg:npm/touch@3.1.1", - "pkg:npm/undefsafe@2.0.5", - "pkg:npm/nyc@15.1.0", - "pkg:npm/%40istanbuljs/load-nyc-config@1.1.0", - "pkg:npm/camelcase@5.3.1", - "pkg:npm/find-up@4.1.0", - "pkg:npm/locate-path@5.0.0", - "pkg:npm/p-locate@4.1.0", - "pkg:npm/p-limit@2.3.0", - "pkg:npm/p-try@2.2.0", - "pkg:npm/path-exists@4.0.0", - "pkg:npm/get-package-type@0.1.0", - "pkg:npm/resolve-from@5.0.0", - "pkg:npm/%40istanbuljs/schema@0.1.3", - "pkg:npm/caching-transform@4.0.0", - "pkg:npm/hasha@5.2.2", - "pkg:npm/type-fest@0.8.1", - "pkg:npm/make-dir@3.1.0", - "pkg:npm/package-hash@4.0.0", - "pkg:npm/lodash.flattendeep@4.4.0", - "pkg:npm/release-zalgo@1.0.0", - "pkg:npm/es6-error@4.1.1", - "pkg:npm/write-file-atomic@3.0.3", - "pkg:npm/typedarray-to-buffer@3.1.5", - "pkg:npm/find-cache-dir@3.3.2", - "pkg:npm/commondir@1.0.1", - "pkg:npm/pkg-dir@4.2.0", - "pkg:npm/foreground-child@2.0.0", - "pkg:npm/istanbul-lib-coverage@3.2.2", - "pkg:npm/istanbul-lib-hook@3.0.0", - "pkg:npm/append-transform@2.0.0", - "pkg:npm/default-require-extensions@3.0.1", - "pkg:npm/strip-bom@4.0.0", - "pkg:npm/istanbul-lib-instrument@4.0.3", - "pkg:npm/istanbul-lib-processinfo@2.0.3", - "pkg:npm/p-map@3.0.0", - "pkg:npm/istanbul-lib-report@3.0.1", - "pkg:npm/make-dir@4.0.0", - "pkg:npm/istanbul-lib-source-maps@4.0.1", - "pkg:npm/istanbul-reports@3.1.7", - "pkg:npm/html-escaper@2.0.2", - "pkg:npm/node-preload@0.2.1", - "pkg:npm/process-on-spawn@1.0.0", - "pkg:npm/fromentries@1.3.2", - "pkg:npm/spawn-wrap@2.0.0", - "pkg:npm/test-exclude@6.0.0", - "pkg:npm/yargs@15.4.1", - "pkg:npm/cliui@6.0.0", - "pkg:npm/wrap-ansi@6.2.0", - "pkg:npm/require-main-filename@2.0.0", - "pkg:npm/which-module@2.0.1", - "pkg:npm/y18n@4.0.3", - "pkg:npm/yargs-parser@18.1.3", - "pkg:npm/protractor@4.0.14", - "pkg:npm/%40types/jasmine@2.8.19", - "pkg:npm/%40types/node@6.14.13", - "pkg:npm/%40types/q@0.0.32", - "pkg:npm/%40types/selenium-webdriver@2.53.37", - "pkg:npm/adm-zip@0.4.7", - "pkg:npm/jasmine@2.4.1", - "pkg:npm/exit@0.1.2", - "pkg:npm/jasminewd2@0.0.10", - "pkg:npm/optimist@0.6.1", - "pkg:npm/q@1.4.1", - "pkg:npm/saucelabs@1.3.0", - "pkg:npm/https-proxy-agent@1.0.0", - "pkg:npm/agent-base@2.1.1", - "pkg:npm/semver@5.0.3", - "pkg:npm/selenium-webdriver@2.53.3", - "pkg:npm/adm-zip@0.4.4", - "pkg:npm/tmp@0.0.24", - "pkg:npm/ws@1.1.5", - "pkg:npm/options@0.0.6", - "pkg:npm/ultron@1.0.2", - "pkg:npm/xml2js@0.4.4", - "pkg:npm/sax@0.6.1", - "pkg:npm/xmlbuilder@9.0.7", - "pkg:npm/source-map-support@0.4.18", - "pkg:npm/webdriver-manager@10.3.0", - "pkg:npm/del@2.2.2", - "pkg:npm/globby@5.0.0", - "pkg:npm/rollup@1.32.1", - "pkg:npm/sinon@9.2.4", - "pkg:npm/%40sinonjs/commons@1.8.6", - "pkg:npm/%40sinonjs/fake-timers@6.0.1", - "pkg:npm/%40sinonjs/samsam@5.3.1", - "pkg:npm/lodash.get@4.4.2", - "pkg:npm/nise@4.1.0", - "pkg:npm/%40sinonjs/text-encoding@0.7.2", - "pkg:npm/just-extend@4.2.1", - "pkg:npm/path-to-regexp@1.8.0", - "pkg:npm/systemjs@6.1.4", - "pkg:npm/ts-node@10.9.2", - "pkg:npm/%40cspotcode/source-map-support@0.8.1", - "pkg:npm/%40jridgewell/trace-mapping@0.3.9", - "pkg:npm/%40tsconfig/node10@1.0.11", - "pkg:npm/%40tsconfig/node12@1.0.11", - "pkg:npm/%40tsconfig/node14@1.0.3", - "pkg:npm/%40tsconfig/node16@1.0.4", - "pkg:npm/v8-compile-cache-lib@3.0.1", - "pkg:npm/tslint@3.15.1", - "pkg:npm/diff@2.2.3", - "pkg:npm/findup-sync@0.3.0", - "pkg:npm/glob@5.0.15", - "pkg:npm/underscore.string@3.3.6", - "pkg:npm/typescript@3.9.10", - "pkg:npm/vinyl-buffer@1.0.1", - "pkg:npm/bl@1.2.3", - "pkg:npm/vinyl-source-stream@2.0.0", - "pkg:npm/vorpal@1.12.0", - "pkg:npm/in-publish@2.0.1", - "pkg:npm/inquirer@0.11.0", - "pkg:npm/ansi-escapes@1.4.0", - "pkg:npm/cli-cursor@1.0.2", - "pkg:npm/restore-cursor@1.0.1", - "pkg:npm/exit-hook@1.1.1", - "pkg:npm/onetime@1.1.0", - "pkg:npm/cli-width@1.1.1", - "pkg:npm/figures@1.7.0", - "pkg:npm/lodash@3.10.1", - "pkg:npm/readline2@1.0.1", - "pkg:npm/mute-stream@0.0.5", - "pkg:npm/run-async@0.1.0", - "pkg:npm/rx-lite@3.1.2", - "pkg:npm/log-update@1.0.2", - "pkg:npm/node-localstorage@0.6.0", - "pkg:npm/vorpal-autocomplete-fs@0.0.3", - "pkg:npm/watchify@3.11.1", - "pkg:npm/browserify@16.5.2", - "pkg:npm/events@2.1.0", - "pkg:npm/shasum@1.0.2", - "pkg:npm/json-stable-stringify@0.0.1", - "pkg:npm/jsonify@0.0.1", - "pkg:npm/stream-browserify@2.0.2", - "pkg:npm/outpipe@1.1.1", - "pkg:npm/webpack@5.74.0", - "pkg:npm/%40types/eslint-scope@3.7.4", - "pkg:npm/%40types/eslint@8.4.10", - "pkg:npm/%40types/estree@0.0.51", - "pkg:npm/%40webassemblyjs/ast@1.11.1", - "pkg:npm/%40webassemblyjs/helper-numbers@1.11.1", - "pkg:npm/%40webassemblyjs/floating-point-hex-parser@1.11.1", - "pkg:npm/%40webassemblyjs/helper-api-error@1.11.1", - "pkg:npm/%40xtuc/long@4.2.2", - "pkg:npm/%40webassemblyjs/helper-wasm-bytecode@1.11.1", - "pkg:npm/%40webassemblyjs/wasm-edit@1.11.1", - "pkg:npm/%40webassemblyjs/helper-buffer@1.11.1", - "pkg:npm/%40webassemblyjs/helper-wasm-section@1.11.1", - "pkg:npm/%40webassemblyjs/wasm-gen@1.11.1", - "pkg:npm/%40webassemblyjs/ieee754@1.11.1", - "pkg:npm/%40xtuc/ieee754@1.2.0", - "pkg:npm/%40webassemblyjs/leb128@1.11.1", - "pkg:npm/%40webassemblyjs/utf8@1.11.1", - "pkg:npm/%40webassemblyjs/wasm-opt@1.11.1", - "pkg:npm/%40webassemblyjs/wasm-parser@1.11.1", - "pkg:npm/%40webassemblyjs/wast-printer@1.11.1", - "pkg:npm/acorn-import-assertions@1.8.0", - "pkg:npm/chrome-trace-event@1.0.3", - "pkg:npm/enhanced-resolve@5.12.0", - "pkg:npm/tapable@2.2.1", - "pkg:npm/es-module-lexer@0.9.3", - "pkg:npm/glob-to-regexp@0.4.1", - "pkg:npm/loader-runner@4.3.0", - "pkg:npm/neo-async@2.6.2", - "pkg:npm/schema-utils@3.1.1", - "pkg:npm/terser-webpack-plugin@5.3.6", - "pkg:npm/jest-worker@27.5.1", - "pkg:npm/supports-color@8.1.1", - "pkg:npm/serialize-javascript@6.0.0", - "pkg:npm/terser@5.16.2", - "pkg:npm/%40jridgewell/source-map@0.3.2", - "pkg:npm/watchpack@2.4.0", - "pkg:npm/webpack-sources@3.2.3", - "pkg:npm/webpack-cli@4.10.0", - "pkg:npm/%40discoveryjs/json-ext@0.5.7", - "pkg:npm/%40webpack-cli/configtest@1.2.0", - "pkg:npm/%40webpack-cli/info@1.5.0", - "pkg:npm/envinfo@7.8.1", - "pkg:npm/%40webpack-cli/serve@1.7.0", - "pkg:npm/colorette@2.0.19", - "pkg:npm/commander@7.2.0", - "pkg:npm/fastest-levenshtein@1.0.16", - "pkg:npm/import-local@3.1.0", - "pkg:npm/resolve-cwd@3.0.0", - "pkg:npm/interpret@2.2.0", - "pkg:npm/rechoir@0.7.1", - "pkg:npm/webpack-merge@5.8.0", - "pkg:npm/clone-deep@4.0.1", - "pkg:npm/shallow-clone@3.0.1", - "pkg:npm/wildcard@2.0.0", - "pkg:npm/yargs@3.10.0", - "pkg:npm/cliui@2.1.0", - "pkg:npm/center-align@0.1.3", - "pkg:npm/align-text@0.1.4", - "pkg:npm/longest@1.0.1", - "pkg:npm/lazy-cache@1.0.4", - "pkg:npm/right-align@0.1.3", - "pkg:npm/punycode@2.1.1", - "pkg:npm/cli-spinners@2.6.1", - "pkg:npm/defaults@1.0.3", - "pkg:npm/rxjs@7.5.5", - "pkg:npm/tslib@2.3.1", - "pkg:npm/glob@7.2.0", - "pkg:npm/aws4@1.11.0", - "pkg:npm/psl@1.8.0", - "pkg:npm/tar@6.1.11", - "pkg:npm/minipass@3.1.6", - "pkg:npm/http-cache-semantics@4.1.0", - "pkg:npm/https-proxy-agent@5.0.0", - "pkg:npm/socks-proxy-agent@6.1.1", - "pkg:npm/socks@2.6.2", - "pkg:npm/ip@1.1.5", - "pkg:npm/is-core-module@2.8.1", - "pkg:npm/%40angular/compiler-cli@12.2.17", - "pkg:npm/%40babel/core@7.17.9", - "pkg:npm/%40ampproject/remapping@2.1.2", - "pkg:npm/%40jridgewell/trace-mapping@0.3.4", - "pkg:npm/%40jridgewell/resolve-uri@3.0.5", - "pkg:npm/%40jridgewell/sourcemap-codec@1.4.11", - "pkg:npm/%40babel/code-frame@7.16.7", - "pkg:npm/%40babel/highlight@7.17.9", - "pkg:npm/%40babel/helper-validator-identifier@7.16.7", - "pkg:npm/%40babel/generator@7.17.9", - "pkg:npm/%40babel/types@7.17.0", - "pkg:npm/%40babel/helper-compilation-targets@7.17.7", - "pkg:npm/%40babel/compat-data@7.17.7", - "pkg:npm/%40babel/helper-validator-option@7.16.7", - "pkg:npm/browserslist@4.20.2", - "pkg:npm/caniuse-lite@1.0.30001331", - "pkg:npm/electron-to-chromium@1.4.107", - "pkg:npm/node-releases@2.0.3", - "pkg:npm/%40babel/helper-module-transforms@7.17.7", - "pkg:npm/%40babel/helper-environment-visitor@7.16.7", - "pkg:npm/%40babel/helper-module-imports@7.16.7", - "pkg:npm/%40babel/helper-simple-access@7.17.7", - "pkg:npm/%40babel/helper-split-export-declaration@7.16.7", - "pkg:npm/%40babel/template@7.16.7", - "pkg:npm/%40babel/parser@7.17.9", - "pkg:npm/%40babel/traverse@7.25.3", - "pkg:npm/%40babel/code-frame@7.24.7", - "pkg:npm/%40babel/highlight@7.24.7", - "pkg:npm/%40babel/helper-validator-identifier@7.24.7", - "pkg:npm/%40babel/generator@7.25.0", - "pkg:npm/%40babel/types@7.25.2", - "pkg:npm/%40babel/helper-string-parser@7.24.8", - "pkg:npm/%40jridgewell/resolve-uri@3.1.2", - "pkg:npm/%40jridgewell/sourcemap-codec@1.5.0", - "pkg:npm/%40babel/parser@7.25.3", - "pkg:npm/%40babel/template@7.25.0", - "pkg:npm/%40babel/helpers@7.17.9", - "pkg:npm/convert-source-map@1.8.0", - "pkg:npm/json5@2.2.1", - "pkg:npm/anymatch@3.1.2", - "pkg:npm/fsevents@2.3.2", - "pkg:npm/minimist@1.2.6", - "pkg:npm/%40babel/plugin-proposal-object-rest-spread@7.17.3", - "pkg:npm/%40babel/helper-plugin-utils@7.16.7", - "pkg:npm/%40babel/plugin-syntax-object-rest-spread@7.8.3", - "pkg:npm/%40babel/plugin-transform-parameters@7.16.7", - "pkg:npm/graceful-fs@3.0.12", - "pkg:npm/natives@1.1.6", - "pkg:npm/%40sentry/tracing@7.120.0", - "pkg:npm/%40sentry-internal/tracing@7.120.0", - "pkg:npm/%40sentry/core@7.120.0", - "pkg:npm/%40sentry/types@7.120.0", - "pkg:npm/%40sentry/utils@7.120.0", - "pkg:npm/async@3.2.3", - "pkg:npm/aws-sdk@2.1692.0", - "pkg:npm/get-intrinsic@1.1.1", - "pkg:npm/is-typed-array@1.1.8", - "pkg:npm/es-abstract@1.19.4", - "pkg:npm/es-to-primitive@1.2.1", - "pkg:npm/is-callable@1.2.4", - "pkg:npm/is-date-object@1.0.5", - "pkg:npm/is-symbol@1.0.4", - "pkg:npm/get-symbol-description@1.0.0", - "pkg:npm/internal-slot@1.0.3", - "pkg:npm/side-channel@1.0.4", - "pkg:npm/object-inspect@1.12.0", - "pkg:npm/is-negative-zero@2.0.2", - "pkg:npm/is-shared-array-buffer@1.0.2", - "pkg:npm/is-string@1.0.7", - "pkg:npm/is-weakref@1.0.2", - "pkg:npm/object.assign@4.1.2", - "pkg:npm/define-properties@1.1.3", - "pkg:npm/string.prototype.trimend@1.0.4", - "pkg:npm/string.prototype.trimstart@1.0.4", - "pkg:npm/unbox-primitive@1.0.1", - "pkg:npm/has-bigints@1.0.1", - "pkg:npm/which-boxed-primitive@1.0.2", - "pkg:npm/is-bigint@1.0.4", - "pkg:npm/is-boolean-object@1.1.2", - "pkg:npm/is-number-object@1.0.7", - "pkg:npm/foreach@2.0.5", - "pkg:npm/which-typed-array@1.1.7", - "pkg:npm/es5-ext@0.10.60", - "pkg:npm/ext@1.6.0", - "pkg:npm/type@2.6.0", - "pkg:npm/protobufjs@7.4.0", - "pkg:npm/nan@2.15.0", - "pkg:npm/%40babel/core@7.26.0", - "pkg:npm/%40ampproject/remapping@2.3.0", - "pkg:npm/%40babel/code-frame@7.26.2", - "pkg:npm/%40babel/generator@7.26.2", - "pkg:npm/%40babel/parser@7.26.2", - "pkg:npm/%40babel/types@7.26.0", - "pkg:npm/%40babel/compat-data@7.26.2", - "pkg:npm/caniuse-lite@1.0.30001684", - "pkg:npm/electron-to-chromium@1.5.67", - "pkg:npm/%40babel/helper-module-transforms@7.26.0", - "pkg:npm/%40babel/helpers@7.26.0", - "pkg:npm/node-fetch@2.6.7", - "pkg:npm/whatwg-fetch@3.6.20", - "pkg:npm/%40opentok/client@2.28.4", - "pkg:npm/%40sentry/browser@7.120.0", - "pkg:npm/%40sentry-internal/feedback@7.120.0", - "pkg:npm/%40sentry-internal/replay-canvas@7.120.0", - "pkg:npm/%40sentry/replay@7.120.0", - "pkg:npm/%40sentry/integrations@7.120.0", - "pkg:npm/localforage@1.10.0", - "pkg:npm/lie@3.1.1", - "pkg:npm/immediate@3.0.6", - "pkg:npm/datatables.net@1.11.5", - "pkg:npm/datatables.net-dt@1.11.5", - "pkg:npm/datatables.net-bs@1.11.5", - "pkg:npm/datatables.net-colreorder@1.5.5", - "pkg:npm/datatables.net-colreorder-dt@1.5.5", - "pkg:npm/datatables.net-rowreorder@1.2.8", - "pkg:npm/datatables.net-select@1.3.4", - "pkg:npm/datatables.net-select-dt@1.3.4", - "pkg:npm/jquery-ui@1.14.1", - "pkg:npm/acorn@8.14.0", - "pkg:npm/acorn-walk@8.3.4", - "pkg:npm/nwsapi@2.2.14", - "pkg:npm/parse5@7.2.1", - "pkg:npm/mock-socket@9.1.2", - "pkg:npm/loglevel@1.9.2", - "pkg:npm/dns-packet@5.6.1", - "pkg:npm/%40leichtgewicht/ip-codec@2.0.5", - "pkg:npm/joi@17.13.3", - "pkg:npm/%40sideway/address@4.1.5", - "pkg:npm/sdp-transform@2.15.0", - "pkg:npm/engine.io-client@3.5.4", - "pkg:npm/ws@7.5.10", - "pkg:npm/socket.io-parser@3.3.4", - "pkg:npm/loader-utils@1.4.0", - "pkg:npm/json5@1.0.1", - "pkg:npm/defined@1.0.0", - "pkg:npm/bn.js@5.2.0", - "pkg:npm/detective@5.2.0", - "pkg:npm/shell-quote@1.7.3", - "pkg:npm/util@0.12.4", - "pkg:npm/socket.io-client@4.8.1", - "pkg:npm/%40socket.io/component-emitter@3.1.2", - "pkg:npm/engine.io-client@6.6.2", - "pkg:npm/engine.io-parser@5.2.3", - "pkg:npm/ws@8.17.1", - "pkg:npm/xmlhttprequest-ssl@2.1.2", - "pkg:npm/socket.io-parser@4.2.4", - "pkg:npm/grunt-autoprefixer@3.0.4", - "pkg:npm/autoprefixer-core@5.2.1", - "pkg:npm/browserslist@0.4.0", - "pkg:npm/caniuse-db@1.0.30001664", - "pkg:npm/num2fraction@1.2.2", - "pkg:npm/postcss@4.1.16", - "pkg:npm/es6-promise@2.3.0", - "pkg:npm/js-base64@2.1.9", - "pkg:npm/source-map@0.4.4", - "pkg:npm/chalk@1.0.0", - "pkg:npm/has-ansi@1.0.3", - "pkg:npm/ansi-regex@1.1.1", - "pkg:npm/strip-ansi@2.0.1", - "pkg:npm/supports-color@1.3.1", - "pkg:npm/diff@1.3.2", - "pkg:npm/grunt-contrib-copy@1.0.0", - "pkg:npm/file-sync-cmp@0.1.1", - "pkg:npm/grunt-contrib-cssmin@5.0.0", - "pkg:npm/clean-css@5.3.3", - "pkg:npm/maxmin@3.0.0", - "pkg:npm/gzip-size@5.1.1", - "pkg:npm/pify@4.0.1", - "pkg:npm/pretty-bytes@5.6.0", - "pkg:npm/grunt-contrib-less@2.1.0", - "pkg:npm/less@3.13.1", - "pkg:npm/copy-anything@2.0.6", - "pkg:npm/is-what@3.14.1", - "pkg:npm/errno@0.1.8", - "pkg:npm/prr@1.0.1", - "pkg:npm/image-size@0.5.5", - "pkg:npm/make-dir@2.1.0", - "pkg:npm/mime@1.6.0", - "pkg:npm/native-request@1.1.2", - "pkg:npm/%40sentry/angular@7.31.1", - "pkg:npm/%40sentry/browser@7.31.1", - "pkg:npm/%40sentry/core@7.31.1", - "pkg:npm/%40sentry/types@7.31.1", - "pkg:npm/%40sentry/utils@7.31.1", - "pkg:npm/%40sentry/replay@7.31.1", - "pkg:npm/%40sentry/browser@6.16.1", - "pkg:npm/%40sentry/core@6.16.1", - "pkg:npm/%40sentry/hub@6.16.1", - "pkg:npm/%40sentry/types@6.16.1", - "pkg:npm/%40sentry/utils@6.16.1", - "pkg:npm/%40sentry/minimal@6.16.1", - "pkg:npm/%40sentry/tracing@7.31.1", - "pkg:npm/%40types/socket.io-client@3.0.0", - "pkg:npm/crypto-js@4.1.1", - "pkg:npm/exponential-backoff@3.1.1", - "pkg:npm/buffer-equal@1.0.0", - "pkg:npm/highcharts@9.3.3", - "pkg:npm/highcharts-ng@0.0.11", - "pkg:npm/intl-tel-input@17.0.21", - "pkg:npm/abab@2.0.5", - "pkg:npm/acorn@8.7.0", - "pkg:npm/decimal.js@10.3.1", - "pkg:npm/nwsapi@2.2.0", - "pkg:npm/tough-cookie@4.0.0", - "pkg:npm/universalify@0.1.2", - "pkg:npm/ng-img-crop-full-extended@0.5.4", - "pkg:npm/node-forge@1.3.1", - "pkg:npm/subnet-cidr-calculator@1.0.12", - "pkg:npm/underscore@1.13.2", - "pkg:npm/ts-mocha@8.0.0", - "pkg:npm/tsconfig-paths@3.14.1", - "pkg:npm/%40types/json5@0.0.29", - "pkg:npm/array-find@1.0.0", - "pkg:npm/remove-value@1.0.0", - "pkg:npm/builtin-modules@3.2.0", - "pkg:npm/%40types/jquery@3.5.14", - "pkg:npm/fast-glob@3.2.11", - "pkg:npm/fastq@1.13.0", - "pkg:npm/ignore@5.2.0", - "pkg:npm/semver@7.3.7", - "pkg:npm/babelify@8.0.0", - "pkg:npm/falafel@2.2.4", - "pkg:npm/browserify-shim@3.8.14", - "pkg:npm/mothership@0.2.0", - "pkg:npm/canvas@2.9.1", - "pkg:npm/%40mapbox/node-pre-gyp@1.0.9", - "pkg:npm/detect-libc@2.0.1", - "pkg:npm/npmlog@5.0.1", - "pkg:npm/are-we-there-yet@2.0.0", - "pkg:npm/gauge@3.0.2", - "pkg:npm/simple-get@3.1.1", - "pkg:npm/decompress-response@4.2.1", - "pkg:npm/mimic-response@2.1.0", - "pkg:npm/chai@4.3.6", - "pkg:npm/deep-eql@3.0.1", - "pkg:npm/loupe@2.3.4", - "pkg:npm/cypress@6.3.0", - "pkg:npm/%40cypress/listr-verbose-renderer@0.4.1", - "pkg:npm/date-fns@1.30.1", - "pkg:npm/%40cypress/request@2.88.12", - "pkg:npm/http-signature@1.3.6", - "pkg:npm/jsprim@2.0.2", - "pkg:npm/qs@6.10.4", - "pkg:npm/%40cypress/xvfb@1.2.4", - "pkg:npm/lodash.once@4.1.1", - "pkg:npm/%40types/sinonjs__fake-timers@6.0.4", - "pkg:npm/arch@2.2.0", - "pkg:npm/blob-util@2.0.2", - "pkg:npm/bluebird@3.7.2", - "pkg:npm/cachedir@2.4.0", - "pkg:npm/check-more-types@2.24.0", - "pkg:npm/cli-table3@0.6.1", - "pkg:npm/common-tags@1.8.2", - "pkg:npm/eventemitter2@6.4.9", - "pkg:npm/executable@4.1.1", - "pkg:npm/extract-zip@1.7.0", - "pkg:npm/yauzl@2.10.0", - "pkg:npm/buffer-crc32@0.2.13", - "pkg:npm/fd-slicer@1.1.0", - "pkg:npm/pend@1.2.0", - "pkg:npm/universalify@2.0.0", - "pkg:npm/getos@3.2.1", - "pkg:npm/is-ci@2.0.0", - "pkg:npm/ci-info@2.0.0", - "pkg:npm/is-installed-globally@0.3.2", - "pkg:npm/global-dirs@2.1.0", - "pkg:npm/ini@1.3.7", - "pkg:npm/is-path-inside@3.0.3", - "pkg:npm/lazy-ass@1.6.0", - "pkg:npm/listr@0.14.3", - "pkg:npm/%40samverschueren/stream-to-observable@0.3.1", - "pkg:npm/any-observable@0.3.0", - "pkg:npm/is-observable@1.1.0", - "pkg:npm/symbol-observable@1.2.0", - "pkg:npm/is-stream@1.1.0", - "pkg:npm/listr-silent-renderer@1.1.1", - "pkg:npm/listr-update-renderer@0.5.0", - "pkg:npm/cli-truncate@0.2.1", - "pkg:npm/slice-ansi@0.0.4", - "pkg:npm/elegant-spinner@1.0.1", - "pkg:npm/indent-string@3.2.0", - "pkg:npm/log-symbols@1.0.2", - "pkg:npm/log-update@2.3.0", - "pkg:npm/ansi-escapes@3.2.0", - "pkg:npm/cli-cursor@2.1.0", - "pkg:npm/restore-cursor@2.0.0", - "pkg:npm/onetime@2.0.1", - "pkg:npm/mimic-fn@1.2.0", - "pkg:npm/wrap-ansi@3.0.1", - "pkg:npm/string-width@2.1.1", - "pkg:npm/is-fullwidth-code-point@2.0.0", - "pkg:npm/strip-ansi@4.0.0", - "pkg:npm/ansi-regex@3.0.1", - "pkg:npm/listr-verbose-renderer@0.5.0", - "pkg:npm/figures@2.0.0", - "pkg:npm/p-map@2.1.0", - "pkg:npm/ospath@1.2.2", - "pkg:npm/ramda@0.26.1", - "pkg:npm/request-progress@3.0.0", - "pkg:npm/throttleit@1.0.0", - "pkg:npm/tmp@0.2.3", - "pkg:npm/untildify@4.0.0", - "pkg:npm/cypress-failed-log@2.10.0", - "pkg:npm/logdown@3.3.1", - "pkg:npm/%40eslint/eslintrc@1.3.0", - "pkg:npm/espree@9.3.2", - "pkg:npm/acorn@8.7.1", - "pkg:npm/globals@13.16.0", - "pkg:npm/flatted@3.2.6", - "pkg:npm/decode-uri-component@0.2.0", - "pkg:npm/async-each@1.0.3", - "pkg:npm/spdx-license-ids@3.0.11", - "pkg:npm/gulp-babel@8.0.0", - "pkg:npm/minimist@0.2.1", - "pkg:npm/domutils@1.5.1", - "pkg:npm/dom-serializer@0.1.1", - "pkg:npm/gulp-systemjs-builder@0.15.0", - "pkg:npm/systemjs-builder@0.15.36", - "pkg:npm/babel-core@6.26.3", - "pkg:npm/babel-code-frame@6.26.0", - "pkg:npm/js-tokens@3.0.2", - "pkg:npm/babel-generator@6.26.1", - "pkg:npm/babel-messages@6.23.0", - "pkg:npm/babel-types@6.26.0", - "pkg:npm/to-fast-properties@1.0.3", - "pkg:npm/detect-indent@4.0.0", - "pkg:npm/jsesc@1.3.0", - "pkg:npm/trim-right@1.0.1", - "pkg:npm/babel-helpers@6.24.1", - "pkg:npm/babel-template@6.26.0", - "pkg:npm/babel-traverse@6.26.0", - "pkg:npm/babylon@6.18.0", - "pkg:npm/globals@9.18.0", - "pkg:npm/invariant@2.2.4", - "pkg:npm/loose-envify@1.4.0", - "pkg:npm/babel-register@6.26.0", - "pkg:npm/home-or-tmp@2.0.0", - "pkg:npm/os-homedir@1.0.2", - "pkg:npm/json5@0.5.1", - "pkg:npm/private@0.1.8", - "pkg:npm/slash@1.0.0", - "pkg:npm/babel-plugin-transform-cjs-system-wrapper@0.3.0", - "pkg:npm/babel-plugin-transform-es2015-modules-systemjs@6.24.1", - "pkg:npm/babel-helper-hoist-variables@6.24.1", - "pkg:npm/babel-plugin-transform-global-system-wrapper@0.0.1", - "pkg:npm/babel-plugin-transform-system-register@0.0.1", - "pkg:npm/data-uri-to-buffer@0.0.4", - "pkg:npm/es6-template-strings@2.0.1", - "pkg:npm/esniff@1.1.0", - "pkg:npm/rollup@0.36.4", - "pkg:npm/systemjs@0.19.47", - "pkg:npm/when@3.7.8", - "pkg:npm/traceur@0.0.105", - "pkg:npm/commander@2.9.0", - "pkg:npm/graceful-readlink@1.0.1", - "pkg:npm/rsvp@3.6.2", - "pkg:npm/semver@4.3.6", - "pkg:npm/source-map-support@0.2.10", - "pkg:npm/source-map@0.1.32", - "pkg:npm/uglify-js@2.7.5", - "pkg:npm/through-gulp@0.5.0", - "pkg:npm/uglify-js@3.15.4", - "pkg:npm/mochawesome@5.0.0", - "pkg:npm/chalk@3.0.0", - "pkg:npm/lodash.isempty@4.4.0", - "pkg:npm/lodash.isfunction@3.0.9", - "pkg:npm/lodash.isobject@3.0.2", - "pkg:npm/lodash.isstring@4.0.1", - "pkg:npm/mochawesome-report-generator@4.1.0", - "pkg:npm/dateformat@3.0.3", - "pkg:npm/fs-extra@7.0.1", - "pkg:npm/jsonfile@4.0.0", - "pkg:npm/fsu@1.1.1", - "pkg:npm/opener@1.5.2", - "pkg:npm/prop-types@15.8.1", - "pkg:npm/react-is@16.13.1", - "pkg:npm/react@16.14.0", - "pkg:npm/react-dom@16.14.0", - "pkg:npm/scheduler@0.19.1", - "pkg:npm/tcomb@3.2.29", - "pkg:npm/tcomb-validation@3.4.1", - "pkg:npm/validator@10.11.0", - "pkg:npm/yargs@13.3.2", - "pkg:npm/cliui@5.0.0", - "pkg:npm/string-width@3.1.0", - "pkg:npm/emoji-regex@7.0.3", - "pkg:npm/strip-ansi@5.2.0", - "pkg:npm/ansi-regex@4.1.1", - "pkg:npm/wrap-ansi@5.1.0", - "pkg:npm/find-up@3.0.0", - "pkg:npm/locate-path@3.0.0", - "pkg:npm/p-locate@3.0.0", - "pkg:npm/path-exists@3.0.0", - "pkg:npm/which-module@2.0.0", - "pkg:npm/yargs-parser@13.1.2", - "pkg:npm/uuid@7.0.3", - "pkg:npm/mock-local-storage@1.1.21", - "pkg:npm/core-js@3.21.1", - "pkg:npm/global@4.4.0", - "pkg:npm/min-document@2.19.0", - "pkg:npm/dom-walk@0.1.2", - "pkg:npm/istanbul-lib-coverage@3.2.0", - "pkg:npm/default-require-extensions@3.0.0", - "pkg:npm/istanbul-lib-processinfo@2.0.2", - "pkg:npm/istanbul-lib-report@3.0.0", - "pkg:npm/istanbul-reports@3.1.4", - "pkg:npm/typescript@3.9.9", - "pkg:npm/jsonify@0.0.0", - "pkg:npm/webpack@4.46.0", - "pkg:npm/%40webassemblyjs/ast@1.9.0", - "pkg:npm/%40webassemblyjs/helper-module-context@1.9.0", - "pkg:npm/%40webassemblyjs/helper-wasm-bytecode@1.9.0", - "pkg:npm/%40webassemblyjs/wast-parser@1.9.0", - "pkg:npm/%40webassemblyjs/floating-point-hex-parser@1.9.0", - "pkg:npm/%40webassemblyjs/helper-api-error@1.9.0", - "pkg:npm/%40webassemblyjs/helper-code-frame@1.9.0", - "pkg:npm/%40webassemblyjs/wast-printer@1.9.0", - "pkg:npm/%40webassemblyjs/helper-fsm@1.9.0", - "pkg:npm/%40webassemblyjs/wasm-edit@1.9.0", - "pkg:npm/%40webassemblyjs/helper-buffer@1.9.0", - "pkg:npm/%40webassemblyjs/helper-wasm-section@1.9.0", - "pkg:npm/%40webassemblyjs/wasm-gen@1.9.0", - "pkg:npm/%40webassemblyjs/ieee754@1.9.0", - "pkg:npm/%40webassemblyjs/leb128@1.9.0", - "pkg:npm/%40webassemblyjs/utf8@1.9.0", - "pkg:npm/%40webassemblyjs/wasm-opt@1.9.0", - "pkg:npm/%40webassemblyjs/wasm-parser@1.9.0", - "pkg:npm/acorn@6.4.2", - "pkg:npm/enhanced-resolve@4.5.0", - "pkg:npm/memory-fs@0.5.0", - "pkg:npm/tapable@1.1.3", - "pkg:npm/eslint-scope@4.0.3", - "pkg:npm/json-parse-better-errors@1.0.2", - "pkg:npm/loader-runner@2.4.0", - "pkg:npm/memory-fs@0.4.1", - "pkg:npm/node-libs-browser@2.2.1", - "pkg:npm/stream-http@2.8.3", - "pkg:npm/to-arraybuffer@1.0.1", - "pkg:npm/timers-browserify@2.0.12", - "pkg:npm/setimmediate@1.0.5", - "pkg:npm/tty-browserify@0.0.0", - "pkg:npm/util@0.11.1", - "pkg:npm/schema-utils@1.0.0", - "pkg:npm/ajv-errors@1.0.1", - "pkg:npm/terser-webpack-plugin@1.4.5", - "pkg:npm/cacache@12.0.4", - "pkg:npm/chownr@1.1.4", - "pkg:npm/figgy-pudding@3.5.2", - "pkg:npm/mississippi@3.0.0", - "pkg:npm/from2@2.3.0", - "pkg:npm/parallel-transform@1.2.0", - "pkg:npm/cyclist@1.0.1", - "pkg:npm/stream-each@1.2.3", - "pkg:npm/move-concurrently@1.0.1", - "pkg:npm/copy-concurrently@1.0.5", - "pkg:npm/fs-write-stream-atomic@1.0.10", - "pkg:npm/iferr@0.1.5", - "pkg:npm/run-queue@1.0.3", - "pkg:npm/ssri@6.0.2", - "pkg:npm/find-cache-dir@2.1.0", - "pkg:npm/pkg-dir@3.0.0", - "pkg:npm/is-wsl@1.1.0", - "pkg:npm/serialize-javascript@4.0.0", - "pkg:npm/terser@4.8.0", - "pkg:npm/webpack-sources@1.4.3", - "pkg:npm/source-list-map@2.0.1", - "pkg:npm/worker-farm@1.7.0", - "pkg:npm/watchpack@1.7.5", - "pkg:npm/watchpack-chokidar2@2.0.1", - "pkg:npm/tslib@2.6.3", - "pkg:npm/typescript@4.2.3", - "pkg:npm/%40babel/core@7.25.2", - "pkg:npm/picocolors@1.0.1", - "pkg:npm/%40babel/helper-compilation-targets@7.25.2", - "pkg:npm/%40babel/compat-data@7.25.2", - "pkg:npm/%40babel/helper-validator-option@7.24.8", - "pkg:npm/browserslist@4.23.3", - "pkg:npm/caniuse-lite@1.0.30001651", - "pkg:npm/electron-to-chromium@1.5.8", - "pkg:npm/update-browserslist-db@1.1.0", - "pkg:npm/escalade@3.1.2", - "pkg:npm/%40babel/helper-module-transforms@7.25.2", - "pkg:npm/%40babel/helper-module-imports@7.24.7", - "pkg:npm/debug@4.3.6", - "pkg:npm/%40babel/helper-simple-access@7.24.7", - "pkg:npm/%40babel/helpers@7.25.0", - "pkg:npm/chokidar@3.6.0", - "pkg:npm/braces@3.0.3", - "pkg:npm/fill-range@7.1.1", - "pkg:npm/binary-extensions@2.3.0", - "pkg:npm/minimist@1.2.8", - "pkg:npm/reflect-metadata@0.1.14", - "pkg:npm/semver@7.6.3", - "pkg:npm/resolve@1.22.8", - "pkg:npm/is-core-module@2.15.0", - "pkg:npm/hasown@2.0.2", - "pkg:npm/function-bind@1.1.2", - "pkg:npm/supports-preserve-symlinks-flag@1.0.0", - "pkg:npm/%40babel/plugin-transform-modules-commonjs@7.24.8", - "pkg:npm/%40babel/helper-plugin-utils@7.24.8", - "pkg:npm/%40opentok/client@2.28.1", - "pkg:npm/%40sentry/browser@7.118.0", - "pkg:npm/%40sentry-internal/feedback@7.118.0", - "pkg:npm/%40sentry/core@7.118.0", - "pkg:npm/%40sentry/types@7.118.0", - "pkg:npm/%40sentry/utils@7.118.0", - "pkg:npm/%40sentry-internal/replay-canvas@7.118.0", - "pkg:npm/%40sentry/replay@7.118.0", - "pkg:npm/%40sentry-internal/tracing@7.118.0", - "pkg:npm/%40sentry/integrations@7.118.0", - "pkg:npm/%40sentry/tracing@7.118.0", - "pkg:npm/async@3.2.5", - "pkg:npm/assert@1.5.1", - "pkg:npm/object.assign@4.1.5", - "pkg:npm/call-bind@1.0.7", - "pkg:npm/es-define-property@1.0.0", - "pkg:npm/get-intrinsic@1.2.4", - "pkg:npm/es-errors@1.3.0", - "pkg:npm/has-proto@1.0.3", - "pkg:npm/set-function-length@1.2.2", - "pkg:npm/define-data-property@1.1.4", - "pkg:npm/has-property-descriptors@1.0.2", - "pkg:npm/define-properties@1.2.1", - "pkg:npm/inline-source-map@0.6.3", - "pkg:npm/readable-stream@3.6.2", - "pkg:npm/des.js@1.1.0", - "pkg:npm/browserify-sign@4.2.3", - "pkg:npm/hash-base@3.0.4", - "pkg:npm/parse-asn1@5.1.7", - "pkg:npm/asn1.js@4.10.1", - "pkg:npm/has@1.0.4", - "pkg:npm/shell-quote@1.8.1", - "pkg:npm/url@0.11.4", - "pkg:npm/qs@6.13.0", - "pkg:npm/side-channel@1.0.6", - "pkg:npm/object-inspect@1.13.2", - "pkg:npm/has-tostringtag@1.0.2", - "pkg:npm/is-typed-array@1.1.13", - "pkg:npm/which-typed-array@1.1.15", - "pkg:npm/available-typed-arrays@1.0.7", - "pkg:npm/possible-typed-array-names@1.0.0", - "pkg:npm/word-wrap@1.2.5", - "pkg:npm/acorn@8.12.1", - "pkg:npm/acorn-walk@8.3.3", - "pkg:npm/punycode@2.3.1", - "pkg:npm/form-data@4.0.0", - "pkg:npm/nwsapi@2.2.12", - "pkg:npm/parse5@7.1.2", - "pkg:npm/graceful-fs@4.2.11", - "pkg:npm/mock-socket@9.3.1", - "pkg:npm/moment-timezone@0.5.45", - "pkg:npm/loglevel@1.9.1", - "pkg:npm/sdp-transform@2.14.2", - "pkg:npm/component-emitter@1.3.1", - "pkg:npm/%40types/eslint-scope@3.7.7", - "pkg:npm/%40types/eslint@9.6.0", - "pkg:npm/%40types/estree@0.0.50", - "pkg:npm/%40types/json-schema@7.0.15", - "pkg:npm/acorn-import-assertions@1.9.0", - "pkg:npm/chrome-trace-event@1.0.4", - "pkg:npm/enhanced-resolve@5.17.1", - "pkg:npm/schema-utils@3.3.0", - "pkg:npm/terser-webpack-plugin@5.1.4", - "pkg:npm/p-limit@3.1.0", - "pkg:npm/yocto-queue@0.1.0", - "pkg:npm/serialize-javascript@6.0.2", - "pkg:npm/terser@5.7.1", - "pkg:npm/source-map@0.7.4", - "pkg:npm/watchpack@2.4.2", - "pkg:npm/socket.io-client@4.7.5", - "pkg:npm/engine.io-client@6.5.4", - "pkg:npm/xmlhttprequest-ssl@2.0.0", - "pkg:npm/%40angular-devkit/build-angular@12.2.2", - "pkg:npm/karma@5.2.3", - "pkg:npm/body-parser@1.20.2", - "pkg:npm/content-type@1.0.5", - "pkg:npm/destroy@1.2.0", - "pkg:npm/http-errors@2.0.0", - "pkg:npm/setprototypeof@1.2.0", - "pkg:npm/statuses@2.0.1", - "pkg:npm/toidentifier@1.0.1", - "pkg:npm/on-finished@2.4.1", - "pkg:npm/ee-first@1.1.1", - "pkg:npm/qs@6.11.0", - "pkg:npm/raw-body@2.5.2", - "pkg:npm/unpipe@1.0.0", - "pkg:npm/type-is@1.6.18", - "pkg:npm/media-typer@0.3.0", - "pkg:npm/connect@3.7.0", - "pkg:npm/finalhandler@1.1.2", - "pkg:npm/encodeurl@1.0.2", - "pkg:npm/escape-html@1.0.3", - "pkg:npm/on-finished@2.3.0", - "pkg:npm/statuses@1.5.0", - "pkg:npm/utils-merge@1.0.1", - "pkg:npm/di@0.0.1", - "pkg:npm/dom-serialize@2.2.1", - "pkg:npm/custom-event@1.0.1", - "pkg:npm/ent@2.2.1", - "pkg:npm/void-elements@2.0.1", - "pkg:npm/http-proxy@1.18.1", - "pkg:npm/follow-redirects@1.15.6", - "pkg:npm/isbinaryfile@4.0.10", - "pkg:npm/log4js@6.9.1", - "pkg:npm/date-format@4.0.14", - "pkg:npm/flatted@3.3.1", - "pkg:npm/rfdc@1.4.1", - "pkg:npm/streamroller@3.1.5", - "pkg:npm/fs-extra@8.1.0", - "pkg:npm/mime@2.6.0", - "pkg:npm/qjobs@1.2.0", - "pkg:npm/range-parser@1.2.1", - "pkg:npm/socket.io@2.5.1", - "pkg:npm/debug@4.1.1", - "pkg:npm/engine.io@3.6.2", - "pkg:npm/socket.io-adapter@1.1.2", - "pkg:npm/socket.io-parser@3.4.3", - "pkg:npm/component-emitter@1.2.1", - "pkg:npm/tmp@0.2.1", - "pkg:npm/ua-parser-js@0.7.22", - "pkg:npm/ng-packagr@11.2.4", - "pkg:npm/%40rollup/plugin-commonjs@17.1.0", - "pkg:npm/estree-walker@2.0.2", - "pkg:npm/is-reference@1.2.1", - "pkg:npm/%40rollup/plugin-json@4.1.0", - "pkg:npm/%40rollup/plugin-node-resolve@11.2.1", - "pkg:npm/%40types/resolve@1.17.1", - "pkg:npm/deepmerge@4.3.1", - "pkg:npm/ajv@7.2.4", - "pkg:npm/autoprefixer@10.4.20", - "pkg:npm/postcss@8.4.41", - "pkg:npm/nanoid@3.3.7", - "pkg:npm/source-map-js@1.2.0", - "pkg:npm/fraction.js@4.3.7", - "pkg:npm/normalize-range@0.1.2", - "pkg:npm/postcss-value-parser@4.2.0", - "pkg:npm/cssnano@4.1.11", - "pkg:npm/cosmiconfig@5.2.1", - "pkg:npm/import-fresh@2.0.0", - "pkg:npm/caller-path@2.0.0", - "pkg:npm/caller-callsite@2.0.0", - "pkg:npm/callsites@2.0.0", - "pkg:npm/resolve-from@3.0.0", - "pkg:npm/is-directory@0.3.1", - "pkg:npm/parse-json@4.0.0", - "pkg:npm/cssnano-preset-default@4.0.8", - "pkg:npm/css-declaration-sorter@4.0.1", - "pkg:npm/postcss@7.0.39", - "pkg:npm/picocolors@0.2.1", - "pkg:npm/timsort@0.3.0", - "pkg:npm/cssnano-util-raw-cache@4.0.1", - "pkg:npm/postcss-calc@7.0.5", - "pkg:npm/postcss-selector-parser@6.1.2", - "pkg:npm/cssesc@3.0.0", - "pkg:npm/postcss-colormin@4.0.3", - "pkg:npm/color@3.2.1", - "pkg:npm/color-string@1.9.1", - "pkg:npm/simple-swizzle@0.2.2", - "pkg:npm/is-arrayish@0.3.2", - "pkg:npm/postcss-value-parser@3.3.1", - "pkg:npm/postcss-convert-values@4.0.1", - "pkg:npm/postcss-discard-comments@4.0.2", - "pkg:npm/postcss-discard-duplicates@4.0.2", - "pkg:npm/postcss-discard-empty@4.0.1", - "pkg:npm/postcss-discard-overridden@4.0.1", - "pkg:npm/postcss-merge-longhand@4.0.11", - "pkg:npm/css-color-names@0.0.4", - "pkg:npm/stylehacks@4.0.3", - "pkg:npm/postcss-selector-parser@3.1.2", - "pkg:npm/dot-prop@5.3.0", - "pkg:npm/is-obj@2.0.0", - "pkg:npm/indexes-of@1.0.1", - "pkg:npm/uniq@1.0.1", - "pkg:npm/postcss-merge-rules@4.0.3", - "pkg:npm/caniuse-api@3.0.0", - "pkg:npm/lodash.memoize@4.1.2", - "pkg:npm/lodash.uniq@4.5.0", - "pkg:npm/cssnano-util-same-parent@4.0.1", - "pkg:npm/vendors@1.0.4", - "pkg:npm/postcss-minify-font-values@4.0.2", - "pkg:npm/postcss-minify-gradients@4.0.2", - "pkg:npm/cssnano-util-get-arguments@4.0.0", - "pkg:npm/is-color-stop@1.1.0", - "pkg:npm/hex-color-regex@1.1.0", - "pkg:npm/hsl-regex@1.0.0", - "pkg:npm/hsla-regex@1.0.0", - "pkg:npm/rgb-regex@1.0.1", - "pkg:npm/rgba-regex@1.0.0", - "pkg:npm/postcss-minify-params@4.0.2", - "pkg:npm/alphanum-sort@1.0.2", - "pkg:npm/uniqs@2.0.0", - "pkg:npm/postcss-minify-selectors@4.0.2", - "pkg:npm/postcss-normalize-charset@4.0.1", - "pkg:npm/postcss-normalize-display-values@4.0.2", - "pkg:npm/cssnano-util-get-match@4.0.0", - "pkg:npm/postcss-normalize-positions@4.0.2", - "pkg:npm/postcss-normalize-repeat-style@4.0.2", - "pkg:npm/postcss-normalize-string@4.0.2", - "pkg:npm/postcss-normalize-timing-functions@4.0.2", - "pkg:npm/postcss-normalize-unicode@4.0.1", - "pkg:npm/postcss-normalize-url@4.0.1", - "pkg:npm/is-absolute-url@2.1.0", - "pkg:npm/normalize-url@3.3.0", - "pkg:npm/postcss-normalize-whitespace@4.0.2", - "pkg:npm/postcss-ordered-values@4.1.2", - "pkg:npm/postcss-reduce-initial@4.0.3", - "pkg:npm/postcss-reduce-transforms@4.0.2", - "pkg:npm/postcss-svgo@4.0.3", - "pkg:npm/svgo@1.3.2", - "pkg:npm/coa@2.0.2", - "pkg:npm/%40types/q@1.5.8", - "pkg:npm/q@1.5.1", - "pkg:npm/css-select@2.1.0", - "pkg:npm/boolbase@1.0.0", - "pkg:npm/css-what@3.4.2", - "pkg:npm/nth-check@1.0.2", - "pkg:npm/css-select-base-adapter@0.1.1", - "pkg:npm/css-tree@1.0.0-alpha.37", - "pkg:npm/mdn-data@2.0.4", - "pkg:npm/csso@4.2.0", - "pkg:npm/css-tree@1.1.3", - "pkg:npm/mdn-data@2.0.14", - "pkg:npm/object.values@1.2.0", - "pkg:npm/es-object-atoms@1.0.0", - "pkg:npm/sax@1.2.4", - "pkg:npm/stable@0.1.8", - "pkg:npm/unquote@1.1.1", - "pkg:npm/util.promisify@1.0.1", - "pkg:npm/es-abstract@1.23.3", - "pkg:npm/array-buffer-byte-length@1.0.1", - "pkg:npm/is-array-buffer@3.0.4", - "pkg:npm/arraybuffer.prototype.slice@1.0.3", - "pkg:npm/is-shared-array-buffer@1.0.3", - "pkg:npm/data-view-buffer@1.0.1", - "pkg:npm/is-data-view@1.0.1", - "pkg:npm/data-view-byte-length@1.0.1", - "pkg:npm/data-view-byte-offset@1.0.0", - "pkg:npm/es-set-tostringtag@2.0.3", - "pkg:npm/function.prototype.name@1.1.6", - "pkg:npm/functions-have-names@1.2.3", - "pkg:npm/get-symbol-description@1.0.2", - "pkg:npm/globalthis@1.0.4", - "pkg:npm/internal-slot@1.0.7", - "pkg:npm/is-negative-zero@2.0.3", - "pkg:npm/regexp.prototype.flags@1.5.2", - "pkg:npm/set-function-name@2.0.2", - "pkg:npm/safe-array-concat@1.1.2", - "pkg:npm/safe-regex-test@1.0.3", - "pkg:npm/string.prototype.trim@1.2.9", - "pkg:npm/string.prototype.trimend@1.0.8", - "pkg:npm/string.prototype.trimstart@1.0.8", - "pkg:npm/typed-array-buffer@1.0.2", - "pkg:npm/typed-array-byte-length@1.0.1", - "pkg:npm/typed-array-byte-offset@1.0.2", - "pkg:npm/typed-array-length@1.0.6", - "pkg:npm/unbox-primitive@1.0.2", - "pkg:npm/has-bigints@1.0.2", - "pkg:npm/object.getownpropertydescriptors@2.1.8", - "pkg:npm/array.prototype.reduce@1.0.7", - "pkg:npm/es-array-method-boxes-properly@1.0.0", - "pkg:npm/postcss-unique-selectors@4.0.1", - "pkg:npm/is-resolvable@1.1.0", - "pkg:npm/injection-js@2.4.0", - "pkg:npm/less@4.2.0", - "pkg:npm/needle@3.3.1", - "pkg:npm/node-sass-tilde-importer@1.0.2", - "pkg:npm/cli-spinners@2.9.2", - "pkg:npm/postcss-url@10.1.3", - "pkg:npm/postcss@8.3.6", - "pkg:npm/colorette@1.4.0", - "pkg:npm/source-map-js@0.6.2", - "pkg:npm/mime@2.5.2", - "pkg:npm/minimatch@3.0.8", - "pkg:npm/xxhashjs@0.2.2", - "pkg:npm/cuint@0.2.2", - "pkg:npm/rollup@2.79.1", - "pkg:npm/rollup-plugin-sourcemaps@0.6.3", - "pkg:npm/source-map-resolve@0.6.0", - "pkg:npm/sass@1.36.0", - "pkg:npm/stylus@0.54.8", - "pkg:npm/css-parse@2.0.0", - "pkg:npm/sync-rpc@1.3.6", - "pkg:npm/get-port@3.2.0", - "pkg:npm/protractor@7.0.0", - "pkg:npm/%40types/selenium-webdriver@3.0.26", - "pkg:npm/blocking-proxy@1.0.1", - "pkg:npm/browserstack@1.6.1", - "pkg:npm/https-proxy-agent@2.2.4", - "pkg:npm/agent-base@4.3.0", - "pkg:npm/es6-promisify@5.0.0", - "pkg:npm/es6-promise@4.2.8", - "pkg:npm/jasmine@2.8.0", - "pkg:npm/jasmine-core@2.8.0", - "pkg:npm/jasminewd2@2.2.0", - "pkg:npm/saucelabs@1.5.0", - "pkg:npm/selenium-webdriver@3.6.0", - "pkg:npm/jszip@3.10.1", - "pkg:npm/lie@3.3.0", - "pkg:npm/tmp@0.0.30", - "pkg:npm/xml2js@0.4.23", - "pkg:npm/webdriver-js-extender@2.1.0", - "pkg:npm/webdriver-manager@12.1.9", - "pkg:npm/adm-zip@0.5.15", - "pkg:npm/aws4@1.13.1", - "pkg:npm/sshpk@1.18.0", - "pkg:npm/tslint@6.1.3", - "pkg:npm/%40ampproject/remapping@1.0.1", - "pkg:npm/%40jridgewell/resolve-uri@1.0.0", - "pkg:npm/ajv@8.17.1", - "pkg:npm/fast-uri@3.0.2", - "pkg:npm/%40angular-devkit/build-optimizer@0.1202.2", - "pkg:npm/tslib@2.3.0", - "pkg:npm/typescript@4.3.5", - "pkg:npm/%40angular-devkit/build-webpack@0.1202.2", - "pkg:npm/webpack-dev-server@3.11.2", - "pkg:npm/ansi-html@0.0.7", - "pkg:npm/bonjour@3.5.0", - "pkg:npm/array-flatten@2.1.2", - "pkg:npm/deep-equal@1.1.2", - "pkg:npm/object-is@1.1.6", - "pkg:npm/dns-equal@1.0.0", - "pkg:npm/dns-txt@2.0.2", - "pkg:npm/buffer-indexof@1.1.1", - "pkg:npm/multicast-dns@6.2.3", - "pkg:npm/dns-packet@1.3.4", - "pkg:npm/ip@1.1.9", - "pkg:npm/thunky@1.1.0", - "pkg:npm/multicast-dns-service-types@1.1.0", - "pkg:npm/is-descriptor@0.1.7", - "pkg:npm/is-accessor-descriptor@1.0.1", - "pkg:npm/is-data-descriptor@1.0.1", - "pkg:npm/is-descriptor@1.0.3", - "pkg:npm/async-each@1.0.6", - "pkg:npm/nan@2.20.0", - "pkg:npm/compression@1.7.4", - "pkg:npm/bytes@3.0.0", - "pkg:npm/compressible@2.0.18", - "pkg:npm/connect-history-api-fallback@1.6.0", - "pkg:npm/del@4.1.1", - "pkg:npm/%40types/glob@7.2.0", - "pkg:npm/%40types/minimatch@5.1.2", - "pkg:npm/is-path-cwd@2.2.0", - "pkg:npm/is-path-in-cwd@2.1.0", - "pkg:npm/is-path-inside@2.1.0", - "pkg:npm/express@4.21.1", - "pkg:npm/array-flatten@1.1.1", - "pkg:npm/body-parser@1.20.3", - "pkg:npm/content-disposition@0.5.4", - "pkg:npm/cookie@0.7.1", - "pkg:npm/encodeurl@2.0.0", - "pkg:npm/etag@1.8.1", - "pkg:npm/finalhandler@1.3.1", - "pkg:npm/fresh@0.5.2", - "pkg:npm/merge-descriptors@1.0.3", - "pkg:npm/methods@1.1.2", - "pkg:npm/path-to-regexp@0.1.10", - "pkg:npm/proxy-addr@2.0.7", - "pkg:npm/forwarded@0.2.0", - "pkg:npm/ipaddr.js@1.9.1", - "pkg:npm/send@0.19.0", - "pkg:npm/ms@2.1.3", - "pkg:npm/serve-static@1.16.2", - "pkg:npm/html-entities@1.4.0", - "pkg:npm/http-proxy-middleware@0.19.1", - "pkg:npm/import-local@2.0.0", - "pkg:npm/resolve-cwd@2.0.0", - "pkg:npm/internal-ip@4.3.0", - "pkg:npm/default-gateway@4.2.0", - "pkg:npm/execa@1.0.0", - "pkg:npm/cross-spawn@6.0.5", - "pkg:npm/nice-try@1.0.5", - "pkg:npm/path-key@2.0.1", - "pkg:npm/shebang-command@1.2.0", - "pkg:npm/shebang-regex@1.0.0", - "pkg:npm/npm-run-path@2.0.2", - "pkg:npm/strip-eof@1.0.0", - "pkg:npm/ip-regex@2.1.0", - "pkg:npm/is-absolute-url@3.0.3", - "pkg:npm/killable@1.0.1", - "pkg:npm/opn@5.5.0", - "pkg:npm/p-retry@3.0.1", - "pkg:npm/portfinder@1.0.32", - "pkg:npm/async@2.6.4", - "pkg:npm/selfsigned@1.10.14", - "pkg:npm/node-forge@0.10.0", - "pkg:npm/serve-index@1.9.1", - "pkg:npm/batch@0.6.1", - "pkg:npm/http-errors@1.6.3", - "pkg:npm/setprototypeof@1.1.0", - "pkg:npm/sockjs@0.3.24", - "pkg:npm/faye-websocket@0.11.4", - "pkg:npm/websocket-driver@0.7.4", - "pkg:npm/http-parser-js@0.5.8", - "pkg:npm/websocket-extensions@0.1.4", - "pkg:npm/sockjs-client@1.6.1", - "pkg:npm/eventsource@2.0.2", - "pkg:npm/spdy@4.0.2", - "pkg:npm/handle-thing@2.0.1", - "pkg:npm/http-deceiver@1.2.7", - "pkg:npm/select-hose@2.0.0", - "pkg:npm/spdy-transport@3.0.0", - "pkg:npm/detect-node@2.1.0", - "pkg:npm/hpack.js@2.1.6", - "pkg:npm/obuf@1.1.2", - "pkg:npm/wbuf@1.7.3", - "pkg:npm/supports-color@6.1.0", - "pkg:npm/webpack-dev-middleware@3.7.3", - "pkg:npm/webpack-log@2.0.0", - "pkg:npm/ansi-colors@3.2.4", - "pkg:npm/ws@6.2.3", - "pkg:npm/async-limiter@1.0.1", - "pkg:npm/%40babel/core@7.14.8", - "pkg:npm/%40babel/generator@7.14.8", - "pkg:npm/%40babel/template@7.14.5", - "pkg:npm/%40babel/helper-annotate-as-pure@7.14.5", - "pkg:npm/%40babel/plugin-proposal-async-generator-functions@7.14.7", - "pkg:npm/%40babel/helper-remap-async-to-generator@7.25.7", - "pkg:npm/%40babel/helper-annotate-as-pure@7.25.7", - "pkg:npm/%40babel/types@7.25.8", - "pkg:npm/%40babel/helper-string-parser@7.25.7", - "pkg:npm/%40babel/helper-validator-identifier@7.25.7", - "pkg:npm/%40babel/helper-wrap-function@7.25.7", - "pkg:npm/%40babel/template@7.25.7", - "pkg:npm/%40babel/code-frame@7.25.7", - "pkg:npm/%40babel/highlight@7.25.7", - "pkg:npm/%40babel/parser@7.25.8", - "pkg:npm/%40babel/traverse@7.25.7", - "pkg:npm/%40babel/generator@7.25.7", - "pkg:npm/%40babel/plugin-syntax-async-generators@7.8.4", - "pkg:npm/%40babel/plugin-transform-async-to-generator@7.14.5", - "pkg:npm/%40babel/plugin-transform-runtime@7.14.5", - "pkg:npm/babel-plugin-polyfill-corejs2@0.2.3", - "pkg:npm/%40babel/helper-define-polyfill-provider@0.2.4", - "pkg:npm/lodash.debounce@4.0.8", - "pkg:npm/babel-plugin-polyfill-corejs3@0.2.5", - "pkg:npm/core-js-compat@3.38.1", - "pkg:npm/babel-plugin-polyfill-regenerator@0.2.3", - "pkg:npm/%40babel/preset-env@7.14.8", - "pkg:npm/%40babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.25.7", - "pkg:npm/%40babel/helper-plugin-utils@7.25.7", - "pkg:npm/%40babel/helper-skip-transparent-expression-wrappers@7.25.7", - "pkg:npm/%40babel/plugin-transform-optional-chaining@7.25.8", - "pkg:npm/%40babel/plugin-proposal-class-properties@7.18.6", - "pkg:npm/%40babel/helper-create-class-features-plugin@7.25.7", - "pkg:npm/%40babel/helper-member-expression-to-functions@7.25.7", - "pkg:npm/%40babel/helper-optimise-call-expression@7.25.7", - "pkg:npm/%40babel/helper-replace-supers@7.25.7", - "pkg:npm/%40babel/plugin-proposal-class-static-block@7.21.0", - "pkg:npm/%40babel/plugin-syntax-class-static-block@7.14.5", - "pkg:npm/%40babel/plugin-proposal-dynamic-import@7.18.6", - "pkg:npm/%40babel/plugin-syntax-dynamic-import@7.8.3", - "pkg:npm/%40babel/plugin-proposal-export-namespace-from@7.18.9", - "pkg:npm/%40babel/plugin-syntax-export-namespace-from@7.8.3", - "pkg:npm/%40babel/plugin-proposal-json-strings@7.18.6", - "pkg:npm/%40babel/plugin-syntax-json-strings@7.8.3", - "pkg:npm/%40babel/plugin-proposal-logical-assignment-operators@7.20.7", - "pkg:npm/%40babel/plugin-syntax-logical-assignment-operators@7.10.4", - "pkg:npm/%40babel/plugin-proposal-nullish-coalescing-operator@7.18.6", - "pkg:npm/%40babel/plugin-syntax-nullish-coalescing-operator@7.8.3", - "pkg:npm/%40babel/plugin-proposal-numeric-separator@7.18.6", - "pkg:npm/%40babel/plugin-syntax-numeric-separator@7.10.4", - "pkg:npm/%40babel/plugin-proposal-object-rest-spread@7.20.7", - "pkg:npm/%40babel/plugin-transform-parameters@7.25.7", - "pkg:npm/%40babel/plugin-proposal-optional-catch-binding@7.18.6", - "pkg:npm/%40babel/plugin-syntax-optional-catch-binding@7.8.3", - "pkg:npm/%40babel/plugin-proposal-optional-chaining@7.21.0", - "pkg:npm/%40babel/plugin-syntax-optional-chaining@7.8.3", - "pkg:npm/%40babel/plugin-proposal-private-methods@7.18.6", - "pkg:npm/%40babel/plugin-proposal-private-property-in-object@7.21.11", - "pkg:npm/%40babel/plugin-syntax-private-property-in-object@7.14.5", - "pkg:npm/%40babel/plugin-proposal-unicode-property-regex@7.18.6", - "pkg:npm/%40babel/helper-create-regexp-features-plugin@7.25.7", - "pkg:npm/regexpu-core@6.1.1", - "pkg:npm/regenerate@1.4.2", - "pkg:npm/regenerate-unicode-properties@10.2.0", - "pkg:npm/regjsgen@0.8.0", - "pkg:npm/regjsparser@0.11.1", - "pkg:npm/unicode-match-property-ecmascript@2.0.0", - "pkg:npm/unicode-canonical-property-names-ecmascript@2.0.1", - "pkg:npm/unicode-property-aliases-ecmascript@2.1.0", - "pkg:npm/unicode-match-property-value-ecmascript@2.2.0", - "pkg:npm/%40babel/plugin-syntax-class-properties@7.12.13", - "pkg:npm/%40babel/plugin-syntax-top-level-await@7.14.5", - "pkg:npm/%40babel/plugin-transform-arrow-functions@7.25.7", - "pkg:npm/%40babel/plugin-transform-block-scoped-functions@7.25.7", - "pkg:npm/%40babel/plugin-transform-block-scoping@7.25.7", - "pkg:npm/%40babel/plugin-transform-classes@7.25.7", - "pkg:npm/%40babel/helper-compilation-targets@7.25.7", - "pkg:npm/%40babel/compat-data@7.25.8", - "pkg:npm/%40babel/helper-validator-option@7.25.7", - "pkg:npm/browserslist@4.24.0", - "pkg:npm/caniuse-lite@1.0.30001668", - "pkg:npm/electron-to-chromium@1.5.36", - "pkg:npm/%40babel/plugin-transform-computed-properties@7.25.7", - "pkg:npm/%40babel/plugin-transform-destructuring@7.25.7", - "pkg:npm/%40babel/plugin-transform-dotall-regex@7.25.7", - "pkg:npm/%40babel/plugin-transform-duplicate-keys@7.25.7", - "pkg:npm/%40babel/plugin-transform-exponentiation-operator@7.25.7", - "pkg:npm/%40babel/helper-builder-binary-assignment-operator-visitor@7.25.7", - "pkg:npm/%40babel/plugin-transform-for-of@7.25.7", - "pkg:npm/%40babel/plugin-transform-function-name@7.25.7", - "pkg:npm/%40babel/plugin-transform-literals@7.25.7", - "pkg:npm/%40babel/plugin-transform-member-expression-literals@7.25.7", - "pkg:npm/%40babel/plugin-transform-modules-amd@7.25.7", - "pkg:npm/%40babel/helper-module-transforms@7.25.7", - "pkg:npm/%40babel/helper-module-imports@7.25.7", - "pkg:npm/%40babel/helper-simple-access@7.25.7", - "pkg:npm/%40babel/plugin-transform-modules-systemjs@7.25.7", - "pkg:npm/%40babel/plugin-transform-modules-umd@7.25.7", - "pkg:npm/%40babel/plugin-transform-named-capturing-groups-regex@7.25.7", - "pkg:npm/%40babel/plugin-transform-new-target@7.25.7", - "pkg:npm/%40babel/plugin-transform-object-super@7.25.7", - "pkg:npm/%40babel/plugin-transform-property-literals@7.25.7", - "pkg:npm/%40babel/plugin-transform-regenerator@7.25.7", - "pkg:npm/regenerator-transform@0.15.2", - "pkg:npm/%40babel/runtime@7.14.8", - "pkg:npm/regenerator-runtime@0.13.11", - "pkg:npm/%40babel/plugin-transform-reserved-words@7.25.7", - "pkg:npm/%40babel/plugin-transform-shorthand-properties@7.25.7", - "pkg:npm/%40babel/plugin-transform-spread@7.25.7", - "pkg:npm/%40babel/plugin-transform-sticky-regex@7.25.7", - "pkg:npm/%40babel/plugin-transform-template-literals@7.25.7", - "pkg:npm/%40babel/plugin-transform-typeof-symbol@7.25.7", - "pkg:npm/%40babel/plugin-transform-unicode-escapes@7.25.7", - "pkg:npm/%40babel/plugin-transform-unicode-regex@7.25.7", - "pkg:npm/%40babel/preset-modules@0.1.6", - "pkg:npm/%40discoveryjs/json-ext@0.5.3", - "pkg:npm/%40jsdevtools/coverage-istanbul-loader@3.0.5", - "pkg:npm/loader-utils@2.0.4", - "pkg:npm/merge-source-map@1.1.0", - "pkg:npm/schema-utils@2.7.1", - "pkg:npm/%40ngtools/webpack@12.2.2", - "pkg:npm/babel-loader@8.2.2", - "pkg:npm/find-cache-dir@3.3.1", - "pkg:npm/cacache@15.2.0", - "pkg:npm/tar@6.2.1", - "pkg:npm/minipass@5.0.0", - "pkg:npm/circular-dependency-plugin@5.2.2", - "pkg:npm/copy-webpack-plugin@9.0.1", - "pkg:npm/fast-glob@3.3.2", - "pkg:npm/fastq@1.17.1", - "pkg:npm/micromatch@4.0.7", - "pkg:npm/ignore@5.3.2", - "pkg:npm/core-js@3.16.0", - "pkg:npm/critters@0.0.10", - "pkg:npm/css@3.0.0", - "pkg:npm/parse5-htmlparser2-tree-adapter@6.0.1", - "pkg:npm/css-loader@6.2.0", - "pkg:npm/icss-utils@5.1.0", - "pkg:npm/postcss-modules-extract-imports@3.1.0", - "pkg:npm/postcss-modules-local-by-default@4.0.5", - "pkg:npm/postcss-modules-scope@3.2.0", - "pkg:npm/postcss-modules-values@4.0.0", - "pkg:npm/css-minimizer-webpack-plugin@3.0.2", - "pkg:npm/cssnano@5.1.15", - "pkg:npm/cssnano-preset-default@5.2.14", - "pkg:npm/css-declaration-sorter@6.4.1", - "pkg:npm/cssnano-utils@3.1.0", - "pkg:npm/postcss-calc@8.2.4", - "pkg:npm/postcss-colormin@5.3.1", - "pkg:npm/colord@2.9.3", - "pkg:npm/postcss-convert-values@5.1.3", - "pkg:npm/postcss-discard-comments@5.1.2", - "pkg:npm/postcss-discard-duplicates@5.1.0", - "pkg:npm/postcss-discard-empty@5.1.1", - "pkg:npm/postcss-discard-overridden@5.1.0", - "pkg:npm/postcss-merge-longhand@5.1.7", - "pkg:npm/stylehacks@5.1.1", - "pkg:npm/postcss-merge-rules@5.1.4", - "pkg:npm/postcss-minify-font-values@5.1.0", - "pkg:npm/postcss-minify-gradients@5.1.1", - "pkg:npm/postcss-minify-params@5.1.4", - "pkg:npm/postcss-minify-selectors@5.2.1", - "pkg:npm/postcss-normalize-charset@5.1.0", - "pkg:npm/postcss-normalize-display-values@5.1.0", - "pkg:npm/postcss-normalize-positions@5.1.1", - "pkg:npm/postcss-normalize-repeat-style@5.1.1", - "pkg:npm/postcss-normalize-string@5.1.0", - "pkg:npm/postcss-normalize-timing-functions@5.1.0", - "pkg:npm/postcss-normalize-unicode@5.1.1", - "pkg:npm/postcss-normalize-url@5.1.0", - "pkg:npm/normalize-url@6.1.0", - "pkg:npm/postcss-normalize-whitespace@5.1.1", - "pkg:npm/postcss-ordered-values@5.1.3", - "pkg:npm/postcss-reduce-initial@5.1.2", - "pkg:npm/postcss-reduce-transforms@5.1.0", - "pkg:npm/postcss-svgo@5.1.0", - "pkg:npm/svgo@2.8.0", - "pkg:npm/%40trysound/sax@0.2.0", - "pkg:npm/css-select@4.3.0", - "pkg:npm/css-what@6.1.0", - "pkg:npm/domhandler@4.3.1", - "pkg:npm/domutils@2.8.0", - "pkg:npm/dom-serializer@1.4.1", - "pkg:npm/nth-check@2.1.1", - "pkg:npm/postcss-unique-selectors@5.1.1", - "pkg:npm/lilconfig@2.1.0", - "pkg:npm/yaml@1.10.2", - "pkg:npm/esbuild@0.12.17", - "pkg:npm/rxjs@7.8.1", - "pkg:npm/karma-source-map-support@1.4.0", - "pkg:npm/less@4.1.1", - "pkg:npm/needle@2.9.1", - "pkg:npm/sax@1.4.1", - "pkg:npm/less-loader@10.0.1", - "pkg:npm/klona@2.0.6", - "pkg:npm/license-webpack-plugin@2.3.20", - "pkg:npm/%40types/webpack-sources@0.1.12", - "pkg:npm/%40types/source-list-map@0.1.6", - "pkg:npm/loader-utils@2.0.0", - "pkg:npm/mini-css-extract-plugin@2.1.0", - "pkg:npm/minimatch@3.0.4", - "pkg:npm/parse5-html-rewriting-stream@6.0.1", - "pkg:npm/parse5-sax-parser@6.0.1", - "pkg:npm/piscina@3.1.0", - "pkg:npm/eventemitter-asyncresource@1.0.0", - "pkg:npm/hdr-histogram-js@2.0.3", - "pkg:npm/%40assemblyscript/loader@0.10.1", - "pkg:npm/hdr-histogram-percentiles-obj@3.0.0", - "pkg:npm/nice-napi@1.0.2", - "pkg:npm/node-addon-api@3.2.1", - "pkg:npm/node-gyp-build@4.8.2", - "pkg:npm/postcss-import@14.0.2", - "pkg:npm/read-cache@1.0.0", - "pkg:npm/postcss-loader@6.1.1", - "pkg:npm/cosmiconfig@7.1.0", - "pkg:npm/%40types/parse-json@4.0.2", - "pkg:npm/parse-json@5.2.0", - "pkg:npm/lines-and-columns@1.2.4", - "pkg:npm/postcss-preset-env@6.7.0", - "pkg:npm/autoprefixer@9.8.8", - "pkg:npm/css-blank-pseudo@0.1.4", - "pkg:npm/css-has-pseudo@0.10.0", - "pkg:npm/postcss-selector-parser@5.0.0", - "pkg:npm/cssesc@2.0.0", - "pkg:npm/css-prefers-color-scheme@3.1.1", - "pkg:npm/cssdb@4.4.0", - "pkg:npm/postcss-attribute-case-insensitive@4.0.2", - "pkg:npm/postcss-color-functional-notation@2.0.1", - "pkg:npm/postcss-values-parser@2.0.1", - "pkg:npm/flatten@1.0.3", - "pkg:npm/postcss-color-gray@5.0.0", - "pkg:npm/%40csstools/convert-colors@1.4.0", - "pkg:npm/postcss-color-hex-alpha@5.0.3", - "pkg:npm/postcss-color-mod-function@3.0.3", - "pkg:npm/postcss-color-rebeccapurple@4.0.1", - "pkg:npm/postcss-custom-media@7.0.8", - "pkg:npm/postcss-custom-properties@8.0.11", - "pkg:npm/postcss-custom-selectors@5.1.2", - "pkg:npm/postcss-dir-pseudo-class@5.0.0", - "pkg:npm/postcss-double-position-gradients@1.0.0", - "pkg:npm/postcss-env-function@2.0.2", - "pkg:npm/postcss-focus-visible@4.0.0", - "pkg:npm/postcss-focus-within@3.0.0", - "pkg:npm/postcss-font-variant@4.0.1", - "pkg:npm/postcss-gap-properties@2.0.0", - "pkg:npm/postcss-image-set-function@3.0.1", - "pkg:npm/postcss-initial@3.0.4", - "pkg:npm/postcss-lab-function@2.0.1", - "pkg:npm/postcss-logical@3.0.0", - "pkg:npm/postcss-media-minmax@4.0.0", - "pkg:npm/postcss-nesting@7.0.1", - "pkg:npm/postcss-overflow-shorthand@2.0.0", - "pkg:npm/postcss-page-break@2.0.0", - "pkg:npm/postcss-place@4.0.1", - "pkg:npm/postcss-pseudo-class-any-link@6.0.0", - "pkg:npm/postcss-replace-overflow-wrap@3.0.0", - "pkg:npm/postcss-selector-matches@4.0.0", - "pkg:npm/postcss-selector-not@4.0.1", - "pkg:npm/regenerator-runtime@0.13.9", - "pkg:npm/resolve-url-loader@4.0.0", - "pkg:npm/adjust-sourcemap-loader@4.0.0", - "pkg:npm/regex-parser@2.3.0", - "pkg:npm/sass-loader@12.1.0", - "pkg:npm/source-map-loader@3.0.0", - "pkg:npm/source-map-support@0.5.19", - "pkg:npm/style-loader@3.2.1", - "pkg:npm/stylus-loader@6.1.0", - "pkg:npm/webpack@5.50.0", - "pkg:npm/es-module-lexer@0.7.1", - "pkg:npm/webpack-dev-middleware@5.0.0", - "pkg:npm/mem@8.1.1", - "pkg:npm/map-age-cleaner@0.1.3", - "pkg:npm/p-defer@1.0.0", - "pkg:npm/mimic-fn@3.1.0", - "pkg:npm/memfs@3.5.3", - "pkg:npm/fs-monkey@1.0.6", - "pkg:npm/wildcard@2.0.1", - "pkg:npm/webpack-subresource-integrity@1.5.2", - "pkg:npm/%40angular-devkit/build-ng-packagr@0.1002.0", - "pkg:npm/tsickle@0.39.1", - "pkg:npm/%40angular-devkit/architect@0.1002.0", - "pkg:npm/%40angular-devkit/core@10.2.0", - "pkg:npm/ajv@6.12.4", - "pkg:npm/rxjs@6.6.2", - "pkg:npm/agentkeepalive@4.5.0", - "pkg:npm/socks@2.8.3", - "pkg:npm/ip-address@9.0.5", - "pkg:npm/jsbn@1.1.0", - "pkg:npm/sprintf-js@1.1.3", - "pkg:npm/minipass-json-stream@1.0.2", - "pkg:npm/%40types/angular@1.8.9", - "pkg:npm/%40types/angular-animate@1.5.14", - "pkg:npm/%40types/angular-cookies@1.8.4", - "pkg:npm/%40types/angular-mocks@1.5.11", - "pkg:npm/%40types/angular-resource@1.5.20", - "pkg:npm/%40types/angular-route@1.7.6", - "pkg:npm/%40types/angular-sanitize@1.8.4", - "pkg:npm/%40types/angular-translate@2.19.4", - "pkg:npm/%40types/jasmine@3.10.18", - "pkg:npm/%40types/jquery@3.5.30", - "pkg:npm/%40types/sizzle@2.3.8", - "pkg:npm/espree@9.6.1", - "pkg:npm/eslint-visitor-keys@3.4.3", - "pkg:npm/globals@13.24.0", - "pkg:npm/eslint-scope@7.2.2", - "pkg:npm/esquery@1.6.0", - "pkg:npm/flat-cache@3.2.0", - "pkg:npm/keyv@4.5.4", - "pkg:npm/json-buffer@3.0.1", - "pkg:npm/optionator@0.9.4", - "pkg:npm/v8-compile-cache@2.4.0", - "pkg:npm/aliasify@2.1.0", - "pkg:npm/browserify-transform-tools@1.7.0", - "pkg:npm/babel-plugin-add-module-exports@1.0.4", - "pkg:npm/babel-plugin-transform-function-bind@6.22.0", - "pkg:npm/babel-plugin-syntax-function-bind@6.13.0", - "pkg:npm/babel-plugin-transform-object-rest-spread@6.26.0", - "pkg:npm/babel-plugin-syntax-object-rest-spread@6.13.0", - "pkg:npm/babel-plugin-transform-runtime@6.23.0", - "pkg:npm/babelify@10.0.0", - "pkg:npm/brfs@2.0.2", - "pkg:npm/static-module@3.0.4", - "pkg:npm/magic-string@0.25.1", - "pkg:npm/scope-analyzer@2.1.2", - "pkg:npm/array-from@2.1.1", - "pkg:npm/dash-ast@2.0.1", - "pkg:npm/es6-map@0.1.5", - "pkg:npm/d@1.0.2", - "pkg:npm/es5-ext@0.10.64", - "pkg:npm/es6-symbol@3.1.4", - "pkg:npm/type@2.7.3", - "pkg:npm/esniff@2.0.1", - "pkg:npm/es6-set@0.1.6", - "pkg:npm/estree-is-function@1.0.0", - "pkg:npm/static-eval@2.1.1", - "pkg:npm/chai@4.5.0", - "pkg:npm/check-error@1.0.3", - "pkg:npm/get-func-name@2.0.2", - "pkg:npm/deep-eql@4.1.4", - "pkg:npm/type-detect@4.1.0", - "pkg:npm/loupe@2.3.7", - "pkg:npm/del@6.1.1", - "pkg:npm/esmify@2.1.1", - "pkg:npm/babel-plugin-import-to-require@1.0.0", - "pkg:npm/spdx-correct@3.2.0", - "pkg:npm/spdx-exceptions@2.5.0", - "pkg:npm/spdx-license-ids@3.0.18", - "pkg:npm/stream-shift@1.0.3", - "pkg:npm/gulp-angular-templatecache@3.0.1", - "pkg:npm/gulp-footer@2.1.0", - "pkg:npm/gulp-clean-css@4.3.0", - "pkg:npm/clean-css@4.2.3", - "pkg:npm/through2@3.0.1", - "pkg:npm/gulp-sourcemaps@3.0.0", - "pkg:npm/%40gulp-sourcemaps/identity-map@2.0.1", - "pkg:npm/memoizee@0.4.17", - "pkg:npm/timers-ext@0.1.8", - "pkg:npm/esniff@1.1.3", - "pkg:npm/underscore@1.13.7", - "pkg:npm/jasmine-core@3.99.1", - "pkg:npm/%40jscpd/core@3.5.10", - "pkg:npm/eventemitter3@5.0.1", - "pkg:npm/cli-table3@0.6.5", - "pkg:npm/pug@3.0.3", - "pkg:npm/pug-code-gen@3.0.3", - "pkg:npm/pug-error@2.1.0", - "pkg:npm/assert-never@1.3.0", - "pkg:npm/karma-chrome-launcher@3.2.0", - "pkg:npm/karma-jasmine@4.0.2", - "pkg:npm/karma-jasmine-html-reporter@1.7.0", - "pkg:npm/ts-mocha@10.0.0", - "pkg:npm/tsconfig-paths@3.15.0", - "pkg:npm/envinfo@7.13.0", - "pkg:npm/colorette@2.0.20", - "pkg:npm/import-local@3.2.0", - "pkg:npm/%40aws-sdk/client-chime-sdk-meetings@3.699.0", - "pkg:npm/%40aws-crypto/sha256-browser@5.2.0", - "pkg:npm/%40aws-crypto/sha256-js@5.2.0", - "pkg:npm/%40aws-crypto/util@5.2.0", - "pkg:npm/%40aws-sdk/types@3.696.0", - "pkg:npm/%40smithy/types@3.7.1", - "pkg:npm/tslib@2.8.1", - "pkg:npm/%40smithy/util-utf8@2.3.0", - "pkg:npm/%40smithy/util-buffer-from@2.2.0", - "pkg:npm/%40smithy/is-array-buffer@2.2.0", - "pkg:npm/%40aws-crypto/supports-web-crypto@5.2.0", - "pkg:npm/%40aws-sdk/util-locate-window@3.693.0", - "pkg:npm/%40aws-sdk/client-sso-oidc@3.699.0", - "pkg:npm/%40aws-sdk/core@3.696.0", - "pkg:npm/%40smithy/core@2.5.4", - "pkg:npm/%40smithy/middleware-serde@3.0.10", - "pkg:npm/%40smithy/protocol-http@4.1.7", - "pkg:npm/%40smithy/util-body-length-browser@3.0.0", - "pkg:npm/%40smithy/util-middleware@3.0.10", - "pkg:npm/%40smithy/util-stream@3.3.1", - "pkg:npm/%40smithy/fetch-http-handler@4.1.1", - "pkg:npm/%40smithy/querystring-builder@3.0.10", - "pkg:npm/%40smithy/util-uri-escape@3.0.0", - "pkg:npm/%40smithy/util-base64@3.0.0", - "pkg:npm/%40smithy/util-buffer-from@3.0.0", - "pkg:npm/%40smithy/is-array-buffer@3.0.0", - "pkg:npm/%40smithy/util-utf8@3.0.0", - "pkg:npm/%40smithy/node-http-handler@3.3.1", - "pkg:npm/%40smithy/abort-controller@3.1.8", - "pkg:npm/%40smithy/util-hex-encoding@3.0.0", - "pkg:npm/%40smithy/node-config-provider@3.1.11", - "pkg:npm/%40smithy/property-provider@3.1.10", - "pkg:npm/%40smithy/shared-ini-file-loader@3.1.11", - "pkg:npm/%40smithy/signature-v4@4.2.3", - "pkg:npm/%40smithy/smithy-client@3.4.5", - "pkg:npm/%40smithy/middleware-endpoint@3.2.4", - "pkg:npm/%40smithy/url-parser@3.0.10", - "pkg:npm/%40smithy/querystring-parser@3.0.10", - "pkg:npm/%40smithy/middleware-stack@3.0.10", - "pkg:npm/fast-xml-parser@4.4.1", - "pkg:npm/strnum@1.0.5", - "pkg:npm/%40aws-sdk/credential-provider-node@3.699.0", - "pkg:npm/%40aws-sdk/credential-provider-env@3.696.0", - "pkg:npm/%40aws-sdk/credential-provider-http@3.696.0", - "pkg:npm/%40aws-sdk/credential-provider-ini@3.699.0", - "pkg:npm/%40aws-sdk/credential-provider-process@3.696.0", - "pkg:npm/%40aws-sdk/credential-provider-sso@3.699.0", - "pkg:npm/%40aws-sdk/client-sso@3.696.0", - "pkg:npm/%40aws-sdk/middleware-host-header@3.696.0", - "pkg:npm/%40aws-sdk/middleware-logger@3.696.0", - "pkg:npm/%40aws-sdk/middleware-recursion-detection@3.696.0", - "pkg:npm/%40aws-sdk/middleware-user-agent@3.696.0", - "pkg:npm/%40aws-sdk/util-endpoints@3.696.0", - "pkg:npm/%40smithy/util-endpoints@2.1.6", - "pkg:npm/%40aws-sdk/region-config-resolver@3.696.0", - "pkg:npm/%40smithy/util-config-provider@3.0.0", - "pkg:npm/%40aws-sdk/util-user-agent-browser@3.696.0", - "pkg:npm/%40aws-sdk/util-user-agent-node@3.696.0", - "pkg:npm/%40smithy/config-resolver@3.0.12", - "pkg:npm/%40smithy/hash-node@3.0.10", - "pkg:npm/%40smithy/invalid-dependency@3.0.10", - "pkg:npm/%40smithy/middleware-content-length@3.0.12", - "pkg:npm/%40smithy/middleware-retry@3.0.28", - "pkg:npm/%40smithy/service-error-classification@3.0.10", - "pkg:npm/%40smithy/util-retry@3.0.10", - "pkg:npm/uuid@9.0.1", - "pkg:npm/%40smithy/util-body-length-node@3.0.0", - "pkg:npm/%40smithy/util-defaults-mode-browser@3.0.28", - "pkg:npm/%40smithy/util-defaults-mode-node@3.0.28", - "pkg:npm/%40smithy/credential-provider-imds@3.2.7", - "pkg:npm/%40aws-sdk/token-providers@3.699.0", - "pkg:npm/%40aws-sdk/credential-provider-web-identity@3.696.0", - "pkg:npm/%40aws-sdk/client-sts@3.699.0", - "pkg:npm/%40types/uuid@9.0.8", - "pkg:npm/%40azure-tools/azcopy-node@3.4.2", - "pkg:npm/json-stream@1.0.0", - "pkg:npm/jsonwebtoken@9.0.2", - "pkg:npm/lodash.includes@4.3.0", - "pkg:npm/lodash.isboolean@3.0.3", - "pkg:npm/lodash.isinteger@4.0.4", - "pkg:npm/lodash.isnumber@3.0.3", - "pkg:npm/lodash.isplainobject@4.0.6", - "pkg:npm/%40azure-tools/azcopy-darwin@10.26.0", - "pkg:npm/%40azure-tools/azcopy-darwin-arm64@10.26.0", - "pkg:npm/%40azure-tools/azcopy-linux@10.26.0", - "pkg:npm/%40azure-tools/azcopy-win32@10.26.0", - "pkg:npm/%40azure-tools/azcopy-win64@10.26.0", - "pkg:npm/%40azure/arm-compute@21.6.0", - "pkg:npm/%40azure/abort-controller@1.1.0", - "pkg:npm/%40azure/core-auth@1.9.0", - "pkg:npm/%40azure/abort-controller@2.1.2", - "pkg:npm/%40azure/core-util@1.11.0", - "pkg:npm/%40azure/core-client@1.9.2", - "pkg:npm/%40azure/core-rest-pipeline@1.18.1", - "pkg:npm/%40azure/core-tracing@1.2.0", - "pkg:npm/%40azure/logger@1.1.4", - "pkg:npm/http-proxy-agent@7.0.2", - "pkg:npm/agent-base@7.1.1", - "pkg:npm/debug@4.3.7", - "pkg:npm/https-proxy-agent@7.0.5", - "pkg:npm/%40azure/core-lro@2.7.2", - "pkg:npm/%40azure/core-paging@1.6.2", - "pkg:npm/%40azure/identity@4.5.0", - "pkg:npm/%40azure/msal-browser@3.27.0", - "pkg:npm/%40azure/msal-common@14.16.0", - "pkg:npm/%40azure/msal-node@2.16.2", - "pkg:npm/jws@4.0.0", - "pkg:npm/jwa@2.0.0", - "pkg:npm/open@8.4.2", - "pkg:npm/stoppable@1.1.0", - "pkg:npm/%40azure/storage-blob@12.26.0", - "pkg:npm/%40azure/core-http-compat@2.1.2", - "pkg:npm/%40azure/core-xml@1.4.4", - "pkg:npm/node-stringbuilder@2.2.7", - "pkg:npm/tosource@0.1.1", - "pkg:npm/migrate-mongo@8.1.4", - "pkg:npm/commander@6.2.1", - "pkg:npm/date-fns@2.30.0", - "pkg:npm/%40babel/runtime@7.26.0", - "pkg:npm/regenerator-runtime@0.14.1", - "pkg:npm/fn-args@5.0.0", - "pkg:npm/mongodb@3.7.4", - "pkg:npm/p-each-series@2.2.0", - "pkg:npm/which-typed-array@1.1.16", - "pkg:npm/%40types/node@22.10.1", - "pkg:npm/undici-types@6.20.0", - "pkg:npm/nan@2.22.0", - "pkg:npm/aws4@1.13.2", - "pkg:npm/psl@1.13.0", - "pkg:npm/ioredis@5.3.1", - "pkg:npm/%40ioredis/commands@1.2.0", - "pkg:npm/cluster-key-slot@1.1.2", - "pkg:npm/denque@2.1.0", - "pkg:npm/lodash.defaults@4.2.0", - "pkg:npm/redis-errors@1.2.0", - "pkg:npm/redis-parser@3.0.0", - "pkg:npm/standard-as-callback@2.1.0", - "pkg:npm/%40google-cloud/translate@6.3.1", - "pkg:npm/%40google-cloud/common@3.10.0", - "pkg:npm/%40google-cloud/projectify@2.1.1", - "pkg:npm/%40google-cloud/promisify@2.0.4", - "pkg:npm/arrify@2.0.1", - "pkg:npm/duplexify@4.1.3", - "pkg:npm/google-auth-library@7.14.1", - "pkg:npm/fast-text-encoding@1.0.6", - "pkg:npm/gaxios@4.3.3", - "pkg:npm/abort-controller@3.0.0", - "pkg:npm/event-target-shim@5.0.1", - "pkg:npm/node-fetch@2.7.0", - "pkg:npm/gcp-metadata@4.3.1", - "pkg:npm/json-bigint@1.0.0", - "pkg:npm/bignumber.js@9.1.2", - "pkg:npm/gtoken@5.3.2", - "pkg:npm/google-p12-pem@3.1.4", - "pkg:npm/retry-request@4.2.2", - "pkg:npm/teeny-request@7.2.0", - "pkg:npm/stream-events@1.0.5", - "pkg:npm/stubs@3.0.0", - "pkg:npm/google-gax@2.30.5", - "pkg:npm/%40grpc/grpc-js@1.6.12", - "pkg:npm/%40grpc/proto-loader@0.6.13", - "pkg:npm/protobufjs@6.11.3", - "pkg:npm/yargs@16.2.0", - "pkg:npm/cliui@7.0.4", - "pkg:npm/yargs-parser@20.2.9", - "pkg:npm/is-stream-ended@0.1.4", - "pkg:npm/object-hash@3.0.0", - "pkg:npm/proto3-json-serializer@0.1.9", - "pkg:npm/is-html@2.0.0", - "pkg:npm/html-tags@3.3.1", - "pkg:npm/mongodb-uri@0.9.7", - "pkg:npm/%40sentry/node@7.30.0", - "pkg:npm/lru_map@0.3.3", - "pkg:npm/winston@3.7.2", - "pkg:npm/%40dabh/diagnostics@2.0.3", - "pkg:npm/colorspace@1.1.4", - "pkg:npm/text-hex@1.0.0", - "pkg:npm/enabled@2.0.0", - "pkg:npm/kuler@2.0.0", - "pkg:npm/logform@2.7.0", - "pkg:npm/%40colors/colors@1.6.0", - "pkg:npm/%40types/triple-beam@1.3.5", - "pkg:npm/fecha@4.2.3", - "pkg:npm/safe-stable-stringify@2.5.0", - "pkg:npm/triple-beam@1.4.1", - "pkg:npm/one-time@1.0.0", - "pkg:npm/fn.name@1.1.0", - "pkg:npm/winston-transport@4.9.0", - "pkg:npm/object-inspect@1.13.3", - "pkg:npm/exponential-backoff@2.2.1", - "pkg:npm/joi@14.3.1", - "pkg:npm/hoek@6.1.3", - "pkg:npm/isemail@3.2.0", - "pkg:npm/topo@3.0.3", - "pkg:npm/mongoose@5.9.12", - "pkg:npm/kareem@2.3.1", - "pkg:npm/mongodb@3.5.7", - "pkg:npm/require_optional@1.0.1", - "pkg:npm/resolve-from@2.0.0", - "pkg:npm/mongoose-legacy-pluralize@1.0.2", - "pkg:npm/mpath@0.7.0", - "pkg:npm/mquery@3.2.2", - "pkg:npm/bluebird@3.5.1", - "pkg:npm/regexp-clone@1.0.0", - "pkg:npm/sliced@1.0.1", - "pkg:npm/sift@7.0.1", - "pkg:npm/async@3.2.0", - "pkg:npm/flat@5.0.2", - "pkg:npm/mongoose-hidden@1.9.1", - "pkg:npm/mpath@0.8.4", - "pkg:npm/mongoose-patcher@0.2.15", - "pkg:npm/json-patch-rules@0.1.0", - "pkg:npm/json-predicate@0.9.5", - "pkg:npm/valid-url@1.0.9", - "pkg:npm/slack-node@0.1.8", - "pkg:npm/requestretry@1.13.0", - "pkg:npm/node-schedule-tz@1.2.1-4", - "pkg:npm/cron-parser@2.18.0", - "pkg:npm/is-nan@1.3.2", - "pkg:npm/moment-timezone@0.5.37", - "pkg:npm/long-timeout@0.1.1", - "pkg:npm/sorted-array-functions@1.3.0", - "pkg:npm/winston@3.17.0", - "pkg:npm/morgan@1.10.0", - "pkg:npm/basic-auth@2.0.1", - "pkg:npm/await-lock@2.2.2", - "pkg:npm/crypto@1.0.1", - "pkg:npm/lokijs@1.5.12", - "pkg:npm/mocha@8.4.0", - "pkg:npm/%40ungap/promise-all-settled@1.1.2", - "pkg:npm/browser-stdout@1.3.1", - "pkg:npm/chokidar@3.5.1", - "pkg:npm/readdirp@3.5.0", - "pkg:npm/debug@4.3.1", - "pkg:npm/diff@5.0.0", - "pkg:npm/find-up@5.0.0", - "pkg:npm/locate-path@6.0.0", - "pkg:npm/p-locate@5.0.0", - "pkg:npm/glob@7.1.6", - "pkg:npm/growl@1.10.5", - "pkg:npm/he@1.2.0", - "pkg:npm/js-yaml@4.0.0", - "pkg:npm/log-symbols@4.0.0", - "pkg:npm/nanoid@3.1.20", - "pkg:npm/serialize-javascript@5.0.1", - "pkg:npm/wide-align@1.1.3", - "pkg:npm/workerpool@6.1.0", - "pkg:npm/yargs-parser@20.2.4", - "pkg:npm/yargs-unparser@2.0.0", - "pkg:npm/camelcase@6.3.0", - "pkg:npm/decamelize@4.0.0", - "pkg:npm/is-plain-obj@2.1.0", - "pkg:npm/ioredis@4.28.5", - "pkg:npm/lodash.flatten@4.4.0", - "pkg:npm/redis-commands@1.7.0", - "pkg:npm/uuid@9.0.0", - "pkg:npm/%40sentry/integrations@7.114.0", - "pkg:npm/%40sentry/core@7.114.0", - "pkg:npm/%40sentry/types@7.114.0", - "pkg:npm/%40sentry/utils@7.114.0", - "pkg:npm/fast-uri@3.0.3", - "pkg:npm/ajv-formats@2.1.1", - "pkg:npm/authy@1.4.0", - "pkg:npm/aws-sdk-mock@5.5.0", - "pkg:npm/sinon@11.1.2", - "pkg:npm/%40sinonjs/fake-timers@7.1.2", - "pkg:npm/%40sinonjs/samsam@6.1.3", - "pkg:npm/nise@5.1.9", - "pkg:npm/%40sinonjs/commons@3.0.1", - "pkg:npm/%40sinonjs/fake-timers@11.3.1", - "pkg:npm/%40sinonjs/text-encoding@0.7.3", - "pkg:npm/just-extend@6.2.0", - "pkg:npm/path-to-regexp@6.3.0", - "pkg:npm/traverse@0.6.10", - "pkg:npm/typedarray.prototype.slice@1.0.3", - "pkg:npm/es-abstract@1.23.5", - "pkg:npm/es-to-primitive@1.3.0", - "pkg:npm/regexp.prototype.flags@1.5.3", - "pkg:npm/typed-array-byte-offset@1.0.3", - "pkg:npm/reflect.getprototypeof@1.0.7", - "pkg:npm/which-builtin-type@1.2.0", - "pkg:npm/is-async-function@2.0.0", - "pkg:npm/is-finalizationregistry@1.1.0", - "pkg:npm/which-collection@1.0.2", - "pkg:npm/is-map@2.0.3", - "pkg:npm/is-set@2.0.3", - "pkg:npm/is-weakmap@2.0.2", - "pkg:npm/is-weakset@2.0.3", - "pkg:npm/typed-array-length@1.0.7", - "pkg:npm/axios@1.7.8", - "pkg:npm/follow-redirects@1.15.9", - "pkg:npm/proxy-from-env@1.1.0", - "pkg:npm/azure-arm-commerce@2.1.0", - "pkg:npm/ms-rest@2.5.6", - "pkg:npm/ajv@6.12.3", - "pkg:npm/tunnel@0.0.5", - "pkg:npm/ms-rest-azure@2.6.2", - "pkg:npm/adal-node@0.2.4", - "pkg:npm/%40xmldom/xmldom@0.8.10", - "pkg:npm/axios@0.21.4", - "pkg:npm/date-utils@1.2.21", - "pkg:npm/xpath.js@1.1.0", - "pkg:npm/azure-arm-network@12.1.0", - "pkg:npm/azure-arm-resource@7.4.0", - "pkg:npm/backoff@2.5.0", - "pkg:npm/precond@0.2.3", - "pkg:npm/bowser@1.9.4", - "pkg:npm/chardet@2.0.0", - "pkg:npm/cheerio@1.0.0-rc.3", - "pkg:npm/css-select@1.2.0", - "pkg:npm/css-what@2.1.3", - "pkg:npm/parse5@3.0.3", - "pkg:npm/connect-redis@7.1.1", - "pkg:npm/console-stamp@0.2.10", - "pkg:npm/dateformat@1.0.11", - "pkg:npm/get-stdin@9.0.0", - "pkg:npm/meow@13.2.0", - "pkg:npm/cookie-parser@1.4.7", - "pkg:npm/cookie@0.7.2", - "pkg:npm/core-js@3.39.0", - "pkg:npm/csclient@0.6.4", - "pkg:npm/csv-parse@5.6.0", - "pkg:npm/csv-stringify@6.5.2", - "pkg:npm/micromatch@4.0.8", - "pkg:npm/email-templates@2.7.1", - "pkg:npm/consolidate@0.14.5", - "pkg:npm/glob@6.0.4", - "pkg:npm/juice@4.3.2", - "pkg:npm/cheerio@0.22.0", - "pkg:npm/lodash.assignin@4.2.0", - "pkg:npm/lodash.bind@4.2.1", - "pkg:npm/lodash.filter@4.6.0", - "pkg:npm/lodash.foreach@4.5.0", - "pkg:npm/lodash.map@4.6.0", - "pkg:npm/lodash.pick@4.4.0", - "pkg:npm/lodash.reduce@4.6.0", - "pkg:npm/lodash.reject@4.6.0", - "pkg:npm/lodash.some@4.6.0", - "pkg:npm/cross-spawn@5.1.0", - "pkg:npm/deep-extend@0.5.1", - "pkg:npm/mensch@0.3.4", - "pkg:npm/slick@1.12.2", - "pkg:npm/web-resource-inliner@4.3.4", - "pkg:npm/datauri@2.0.0", - "pkg:npm/image-size@0.7.5", - "pkg:npm/mimer@1.1.1", - "pkg:npm/htmlparser2@4.1.0", - "pkg:npm/domhandler@3.3.0", - "pkg:npm/lodash.unescape@4.0.1", - "pkg:npm/valid-data-url@2.0.0", - "pkg:npm/exponential-backoff@3.1.0", - "pkg:npm/express-cache-controller@1.1.0", - "pkg:npm/express-session@1.17.3", - "pkg:npm/cookie@0.4.2", - "pkg:npm/file-type@16.5.4", - "pkg:npm/readable-web-to-node-stream@3.0.2", - "pkg:npm/strtok3@6.3.0", - "pkg:npm/%40tokenizer/token@0.3.0", - "pkg:npm/peek-readable@4.1.0", - "pkg:npm/token-types@4.2.1", - "pkg:npm/filestorage@1.7.1", - "pkg:npm/git-rev-sync@3.0.2", - "pkg:npm/graceful-fs@4.1.15", - "pkg:npm/shelljs@0.8.5", - "pkg:npm/is-core-module@2.15.1", - "pkg:npm/heapdump@0.3.15", - "pkg:npm/html-pdf@3.0.1", - "pkg:npm/phantomjs-prebuilt@2.1.16", - "pkg:npm/fs-extra@1.0.0", - "pkg:npm/jsonfile@2.4.0", - "pkg:npm/klaw@1.3.1", - "pkg:npm/hasha@2.2.0", - "pkg:npm/kew@0.7.0", - "pkg:npm/progress@1.1.8", - "pkg:npm/request-progress@2.0.1", - "pkg:npm/throttleit@1.0.1", - "pkg:npm/html-to-text@6.0.0", - "pkg:npm/ioredis@5.4.1", - "pkg:npm/jsforce@3.6.3", - "pkg:npm/%40babel/runtime-corejs3@7.26.0", - "pkg:npm/core-js-pure@3.39.0", - "pkg:npm/%40sindresorhus/is@4.6.0", - "pkg:npm/base64url@3.0.1", - "pkg:npm/commander@4.1.1", - "pkg:npm/faye@1.4.0", - "pkg:npm/csprng@0.1.2", - "pkg:npm/sequin@0.1.1", - "pkg:npm/inquirer@8.2.6", - "pkg:npm/multistream@3.1.0", - "pkg:npm/open@7.4.2", - "pkg:npm/ms-rest-azure@3.0.2", - "pkg:npm/multiparty@4.2.3", - "pkg:npm/http-errors@1.8.1", - "pkg:npm/newrelic@9.7.4", - "pkg:npm/%40newrelic/aws-sdk@5.0.5", - "pkg:npm/%40newrelic/koa@7.2.0", - "pkg:npm/%40newrelic/superagent@6.0.0", - "pkg:npm/%40contrast/fn-inspect@3.4.0", - "pkg:npm/node-gyp-build@4.8.4", - "pkg:npm/%40newrelic/native-metrics@9.0.1", - "pkg:npm/nexmo@2.7.0-beta-1", - "pkg:npm/jsonwebtoken@8.5.1", - "pkg:npm/nodemailer@6.9.16", - "pkg:npm/nodemailer-smtp-transport@2.7.4", - "pkg:npm/nodemailer-shared@1.1.0", - "pkg:npm/nodemailer-fetch@1.6.0", - "pkg:npm/nodemailer-wellknown@0.1.10", - "pkg:npm/smtp-connection@2.12.0", - "pkg:npm/httpntlm@1.6.1", - "pkg:npm/httpreq@1.1.1", - "pkg:npm/underscore@1.7.0", - "pkg:npm/passport@0.4.1", - "pkg:npm/passport-strategy@1.0.0", - "pkg:npm/pause@0.0.1", - "pkg:npm/passport-jwt@4.0.1", - "pkg:npm/passport-saml@3.2.4", - "pkg:npm/%40xmldom/xmldom@0.7.13", - "pkg:npm/xml-crypto@2.1.5", - "pkg:npm/xpath@0.0.32", - "pkg:npm/xml-encryption@2.0.0", - "pkg:npm/xmlbuilder@15.1.1", - "pkg:npm/pm2@4.5.1", - "pkg:npm/%40pm2/agent@1.0.8", - "pkg:npm/dayjs@1.8.36", - "pkg:npm/eventemitter2@5.0.1", - "pkg:npm/fclone@1.0.11", - "pkg:npm/nssocket@0.6.0", - "pkg:npm/eventemitter2@0.4.14", - "pkg:npm/lazy@1.0.11", - "pkg:npm/pm2-axon@4.0.1", - "pkg:npm/amp@0.3.1", - "pkg:npm/amp-message@0.1.2", - "pkg:npm/pm2-axon-rpc@0.7.1", - "pkg:npm/proxy-agent@4.0.1", - "pkg:npm/pac-proxy-agent@4.1.0", - "pkg:npm/get-uri@3.0.2", - "pkg:npm/data-uri-to-buffer@3.0.1", - "pkg:npm/file-uri-to-path@2.0.0", - "pkg:npm/ftp@0.3.10", - "pkg:npm/xregexp@2.0.0", - "pkg:npm/pac-resolver@4.2.0", - "pkg:npm/degenerator@2.2.0", - "pkg:npm/ast-types@0.13.4", - "pkg:npm/netmask@2.0.2", - "pkg:npm/socks-proxy-agent@5.0.1", - "pkg:npm/semver@7.2.3", - "pkg:npm/ws@7.2.5", - "pkg:npm/%40pm2/io@4.3.5", - "pkg:npm/%40opencensus/core@0.0.9", - "pkg:npm/continuation-local-storage@3.2.1", - "pkg:npm/async-listener@0.6.10", - "pkg:npm/shimmer@1.2.1", - "pkg:npm/emitter-listener@1.1.2", - "pkg:npm/log-driver@1.2.7", - "pkg:npm/%40opencensus/propagation-b3@0.0.8", - "pkg:npm/%40opencensus/core@0.0.8", - "pkg:npm/%40pm2/agent-node@1.1.10", - "pkg:npm/proxy-agent@3.1.1", - "pkg:npm/http-proxy-agent@2.1.0", - "pkg:npm/https-proxy-agent@3.0.1", - "pkg:npm/pac-proxy-agent@3.0.1", - "pkg:npm/get-uri@2.0.4", - "pkg:npm/data-uri-to-buffer@1.2.0", - "pkg:npm/pac-resolver@3.0.0", - "pkg:npm/co@4.6.0", - "pkg:npm/degenerator@1.0.4", - "pkg:npm/netmask@1.0.6", - "pkg:npm/thunkify@2.1.2", - "pkg:npm/socks-proxy-agent@4.0.2", - "pkg:npm/agent-base@4.2.1", - "pkg:npm/socks@2.3.3", - "pkg:npm/require-in-the-middle@5.2.0", - "pkg:npm/module-details-from-path@1.0.3", - "pkg:npm/tslib@1.9.3", - "pkg:npm/%40pm2/js-api@0.6.7", - "pkg:npm/%40pm2/pm2-version-check@1.0.4", - "pkg:npm/blessed@0.1.81", - "pkg:npm/cli-tableau@2.0.1", - "pkg:npm/commander@2.15.1", - "pkg:npm/cron@1.8.2", - "pkg:npm/enquirer@2.3.6", - "pkg:npm/needle@2.4.0", - "pkg:npm/pidusage@2.0.21", - "pkg:npm/pm2-axon@4.0.0", - "pkg:npm/pm2-axon-rpc@0.6.0", - "pkg:npm/pm2-deploy@1.0.2", - "pkg:npm/run-series@1.1.9", - "pkg:npm/tv4@1.3.0", - "pkg:npm/pm2-multimeter@0.1.2", - "pkg:npm/charm@0.1.2", - "pkg:npm/promptly@2.2.0", - "pkg:npm/read@1.0.7", - "pkg:npm/ps-list@6.3.0", - "pkg:npm/vizion@0.2.13", - "pkg:npm/async@1.5.2", - "pkg:npm/yamljs@0.3.0", - "pkg:npm/systeminformation@4.34.23", - "pkg:npm/querystring@0.2.1", - "pkg:npm/readmeio@6.2.1", - "pkg:npm/%40types/har-format@1.2.16", - "pkg:npm/%40types/node-fetch@2.6.12", - "pkg:npm/flatted@3.3.2", - "pkg:npm/ssri@10.0.6", - "pkg:npm/minipass@7.1.2", - "pkg:npm/timeout-signal@1.1.0", - "pkg:npm/request-promise@4.2.6", - "pkg:npm/request-promise-core@1.1.4", - "pkg:npm/stealthy-require@1.1.1", - "pkg:npm/semaphore@1.1.0", - "pkg:npm/%40types/component-emitter@1.2.14", - "pkg:npm/stream-to-array@2.3.0", - "pkg:npm/swagger-ui-express@4.6.3", - "pkg:npm/swagger-ui-dist@5.18.2", - "pkg:npm/%40scarf/scarf@1.4.0", - "pkg:npm/tsoa@5.1.1", - "pkg:npm/%40tsoa/cli@5.1.1", - "pkg:npm/%40tsoa/runtime@5.0.0", - "pkg:npm/%40types/multer@1.4.12", - "pkg:npm/%40types/express@5.0.0", - "pkg:npm/%40types/body-parser@1.19.5", - "pkg:npm/%40types/connect@3.4.38", - "pkg:npm/%40types/express-serve-static-core@5.0.2", - "pkg:npm/%40types/qs@6.9.17", - "pkg:npm/%40types/range-parser@1.2.7", - "pkg:npm/%40types/send@0.17.4", - "pkg:npm/%40types/mime@1.3.5", - "pkg:npm/%40types/serve-static@1.15.7", - "pkg:npm/%40types/http-errors@2.0.4", - "pkg:npm/promise.any@2.0.6", - "pkg:npm/array.prototype.map@1.0.7", - "pkg:npm/es-aggregate-error@1.0.13", - "pkg:npm/iterate-value@1.0.2", - "pkg:npm/es-get-iterator@1.1.3", - "pkg:npm/stop-iteration-iterator@1.0.0", - "pkg:npm/iterate-iterator@1.0.2", - "pkg:npm/validator@13.12.0", - "pkg:npm/fs-extra@10.1.0", - "pkg:npm/glob@8.1.0", - "pkg:npm/minimatch@5.1.6", - "pkg:npm/brace-expansion@2.0.1", - "pkg:npm/handlebars@4.7.8", - "pkg:npm/wordwrap@1.0.0", - "pkg:npm/uglify-js@3.19.3", - "pkg:npm/merge@2.1.1", - "pkg:npm/typescript@4.9.5", - "pkg:npm/tsyringe@4.8.0", - "pkg:npm/twilio@4.23.0", - "pkg:npm/dayjs@1.11.13", - "pkg:npm/qs@6.13.1", - "pkg:npm/scmp@2.1.0", - "pkg:npm/xmlbuilder@13.0.2", - "pkg:npm/websocket@1.0.35", - "pkg:npm/bufferutil@4.0.8", - "pkg:npm/utf-8-validate@5.0.10", - "pkg:npm/yaeti@0.0.6", - "pkg:npm/%40faker-js/faker@7.6.0", - "pkg:npm/%40types/express@4.17.21", - "pkg:npm/%40types/express-serve-static-core@4.19.6", - "pkg:npm/%40types/node@20.17.9", - "pkg:npm/undici-types@6.19.8", - "pkg:npm/%40types/jasmine@4.6.4", - "pkg:npm/%40types/jsonwebtoken@5.7.15-alpha", - "pkg:npm/%40types/multiparty@0.0.33", - "pkg:npm/%40types/opentok@2.14.4", - "pkg:npm/%40types/proxyquire@1.3.31", - "pkg:npm/%40types/twilio@3.19.3", - "pkg:npm/%40typescript-eslint/eslint-plugin@5.62.0", - "pkg:npm/%40eslint-community/regexpp@4.12.1", - "pkg:npm/%40typescript-eslint/scope-manager@5.62.0", - "pkg:npm/%40typescript-eslint/types@5.62.0", - "pkg:npm/%40typescript-eslint/visitor-keys@5.62.0", - "pkg:npm/%40typescript-eslint/type-utils@5.62.0", - "pkg:npm/%40typescript-eslint/typescript-estree@5.62.0", - "pkg:npm/%40typescript-eslint/utils@5.62.0", - "pkg:npm/%40eslint-community/eslint-utils@4.4.1", - "pkg:npm/%40types/semver@7.5.8", - "pkg:npm/graphemer@1.4.0", - "pkg:npm/natural-compare-lite@1.4.0", - "pkg:npm/%40typescript-eslint/parser@5.62.0", - "pkg:npm/cli-table@0.3.11", - "pkg:npm/colors@1.0.3", - "pkg:npm/csv@5.5.3", - "pkg:npm/csv-generate@3.4.3", - "pkg:npm/csv-parse@4.16.3", - "pkg:npm/csv-stringify@5.6.5", - "pkg:npm/stream-transform@2.1.3", - "pkg:npm/mixme@0.5.10", - "pkg:npm/cross-spawn@7.0.6", - "pkg:npm/spdx-license-ids@3.0.20", - "pkg:npm/jasmine-core@4.6.1", - "pkg:npm/nock@12.0.3", - "pkg:npm/propagate@2.0.1", - "pkg:npm/nodemon@2.0.22", - "pkg:npm/simple-update-notifier@1.1.0", - "pkg:npm/semver@7.0.0", - "pkg:npm/electron-to-chromium@1.5.66", - "pkg:npm/process-on-spawn@1.1.0", - "pkg:npm/opentok@2.19.0", - "pkg:npm/opentok-token@1.1.1", - "pkg:npm/nonce@1.0.4", - "pkg:npm/unix-timestamp@0.1.2", - "pkg:npm/proxyquire@2.1.3", - "pkg:npm/fill-keys@1.0.2", - "pkg:npm/is-object@1.0.2", - "pkg:npm/module-not-found-error@1.0.1", - "pkg:npm/rdme@8.6.6", - "pkg:npm/%40actions/core@1.11.1", - "pkg:npm/%40actions/exec@1.1.1", - "pkg:npm/%40actions/io@1.1.3", - "pkg:npm/%40actions/http-client@2.2.3", - "pkg:npm/tunnel@0.0.6", - "pkg:npm/undici@5.28.4", - "pkg:npm/%40fastify/busboy@2.1.1", - "pkg:npm/ci-info@3.9.0", - "pkg:npm/command-line-args@5.2.1", - "pkg:npm/array-back@3.1.0", - "pkg:npm/find-replace@3.0.0", - "pkg:npm/typical@4.0.0", - "pkg:npm/command-line-usage@7.0.3", - "pkg:npm/array-back@6.2.2", - "pkg:npm/chalk-template@0.4.0", - "pkg:npm/table-layout@4.1.1", - "pkg:npm/wordwrapjs@5.1.0", - "pkg:npm/typical@7.3.0", - "pkg:npm/config@3.3.12", - "pkg:npm/configstore@5.0.1", - "pkg:npm/unique-string@2.0.0", - "pkg:npm/crypto-random-string@2.0.0", - "pkg:npm/xdg-basedir@4.0.0", - "pkg:npm/editor@1.0.0", - "pkg:npm/gray-matter@4.0.3", - "pkg:npm/section-matter@1.0.0", - "pkg:npm/oas@20.10.3", - "pkg:npm/%40readme/json-schema-ref-parser@1.2.0", - "pkg:npm/%40jsdevtools/ono@7.1.3", - "pkg:npm/call-me-maybe@1.0.2", - "pkg:npm/json-schema-merge-allof@0.8.1", - "pkg:npm/compute-lcm@1.1.2", - "pkg:npm/compute-gcd@1.2.1", - "pkg:npm/validate.io-array@1.0.6", - "pkg:npm/validate.io-function@1.0.2", - "pkg:npm/validate.io-integer-array@1.0.0", - "pkg:npm/validate.io-integer@1.0.5", - "pkg:npm/validate.io-number@1.0.3", - "pkg:npm/json-schema-compare@0.2.2", - "pkg:npm/jsonpath-plus@7.2.0", - "pkg:npm/jsonpointer@5.0.1", - "pkg:npm/oas-normalize@8.4.1", - "pkg:npm/%40readme/openapi-parser@2.6.0", - "pkg:npm/%40apidevtools/swagger-methods@3.0.2", - "pkg:npm/%40readme/better-ajv-errors@1.6.0", - "pkg:npm/%40humanwhocodes/momoa@2.0.4", - "pkg:npm/json-to-ast@2.1.0", - "pkg:npm/code-error-fragment@0.0.230", - "pkg:npm/grapheme-splitter@1.0.4", - "pkg:npm/leven@3.1.0", - "pkg:npm/%40readme/openapi-schemas@3.1.0", - "pkg:npm/ajv-draft-04@1.0.0", - "pkg:npm/%40readme/postman-to-openapi@4.1.0", - "pkg:npm/%40readme/http-status-codes@7.2.0", - "pkg:npm/jsonc-parser@3.2.0", - "pkg:npm/marked@4.3.0", - "pkg:npm/mustache@4.2.0", - "pkg:npm/openapi-types@12.1.3", - "pkg:npm/swagger2openapi@7.0.8", - "pkg:npm/node-fetch-h2@2.3.0", - "pkg:npm/http2-client@1.3.5", - "pkg:npm/node-readfiles@0.2.0", - "pkg:npm/es6-promise@3.3.1", - "pkg:npm/oas-kit-common@1.0.8", - "pkg:npm/oas-resolver@2.5.6", - "pkg:npm/reftools@1.1.9", - "pkg:npm/oas-schema-walker@1.1.5", - "pkg:npm/oas-validator@5.0.8", - "pkg:npm/oas-linter@3.2.2", - "pkg:npm/%40exodus/schemasafe@1.3.0", - "pkg:npm/should@13.2.3", - "pkg:npm/should-equal@2.0.0", - "pkg:npm/should-type@1.4.0", - "pkg:npm/should-format@3.0.3", - "pkg:npm/should-type-adaptors@1.1.0", - "pkg:npm/should-util@1.0.1", - "pkg:npm/remove-undefined-objects@3.0.0", - "pkg:npm/parse-link-header@2.0.0", - "pkg:npm/pluralize@8.0.0", - "pkg:npm/prompts@2.4.2", - "pkg:npm/kleur@3.0.3", - "pkg:npm/sisteransi@1.0.5", - "pkg:npm/simple-git@3.27.0", - "pkg:npm/%40kwsites/file-exists@1.1.1", - "pkg:npm/%40kwsites/promise-deferred@1.1.1", - "pkg:npm/string-argv@0.3.2", - "pkg:npm/table@6.8.2", - "pkg:npm/lodash.truncate@4.4.2", - "pkg:npm/slice-ansi@4.0.0", - "pkg:npm/astral-regex@2.0.0", - "pkg:npm/tmp-promise@3.0.3", - "pkg:npm/update-notifier-cjs@5.1.6", - "pkg:npm/boxen@5.1.2", - "pkg:npm/ansi-align@3.0.1", - "pkg:npm/cli-boxes@2.2.1", - "pkg:npm/widest-line@3.1.0", - "pkg:npm/has-yarn@2.1.0", - "pkg:npm/import-lazy@2.1.0", - "pkg:npm/is-installed-globally@0.4.0", - "pkg:npm/global-dirs@3.0.1", - "pkg:npm/is-npm@5.0.0", - "pkg:npm/is-yarn-global@0.3.0", - "pkg:npm/pupa@2.1.1", - "pkg:npm/escape-goat@2.1.1", - "pkg:npm/registry-auth-token@5.0.3", - "pkg:npm/%40pnpm/npm-conf@2.3.1", - "pkg:npm/%40pnpm/config.env-replace@1.1.0", - "pkg:npm/%40pnpm/network.ca-file@1.0.2", - "pkg:npm/config-chain@1.1.13", - "pkg:npm/proto-list@1.2.4", - "pkg:npm/registry-url@5.1.0", - "pkg:npm/rc@1.2.8", - "pkg:npm/deep-extend@0.6.0", - "pkg:npm/semver-diff@3.1.1", - "pkg:npm/rimraf@4.4.1", - "pkg:npm/glob@9.3.5", - "pkg:npm/minimatch@8.0.4", - "pkg:npm/minipass@4.2.8", - "pkg:npm/path-scurry@1.11.1", - "pkg:npm/lru-cache@10.4.3", - "pkg:npm/sabik@0.2.4", - "pkg:npm/%40types/codemirror@0.0.109", - "pkg:npm/%40types/tern@0.23.9", - "pkg:npm/%40types/estree@1.0.6", - "pkg:npm/bulma@0.9.4", - "pkg:npm/codemirror@5.65.18", - "pkg:npm/esbuild@0.17.19", - "pkg:npm/%40esbuild/android-arm@0.17.19", - "pkg:npm/%40esbuild/android-arm64@0.17.19", - "pkg:npm/%40esbuild/android-x64@0.17.19", - "pkg:npm/%40esbuild/darwin-arm64@0.17.19", - "pkg:npm/%40esbuild/darwin-x64@0.17.19", - "pkg:npm/%40esbuild/freebsd-arm64@0.17.19", - "pkg:npm/%40esbuild/freebsd-x64@0.17.19", - "pkg:npm/%40esbuild/linux-arm@0.17.19", - "pkg:npm/%40esbuild/linux-arm64@0.17.19", - "pkg:npm/%40esbuild/linux-ia32@0.17.19", - "pkg:npm/%40esbuild/linux-loong64@0.17.19", - "pkg:npm/%40esbuild/linux-mips64el@0.17.19", - "pkg:npm/%40esbuild/linux-ppc64@0.17.19", - "pkg:npm/%40esbuild/linux-riscv64@0.17.19", - "pkg:npm/%40esbuild/linux-s390x@0.17.19", - "pkg:npm/%40esbuild/linux-x64@0.17.19", - "pkg:npm/%40esbuild/netbsd-x64@0.17.19", - "pkg:npm/%40esbuild/openbsd-x64@0.17.19", - "pkg:npm/%40esbuild/sunos-x64@0.17.19", - "pkg:npm/%40esbuild/win32-arm64@0.17.19", - "pkg:npm/%40esbuild/win32-ia32@0.17.19", - "pkg:npm/%40esbuild/win32-x64@0.17.19", - "pkg:npm/inversify@6.1.4", - "pkg:npm/%40inversifyjs/common@1.3.3", - "pkg:npm/%40inversifyjs/core@1.3.4", - "pkg:npm/%40inversifyjs/reflect-metadata-utils@0.2.3", - "pkg:npm/mithril@2.2.11", - "pkg:npm/php-parser@3.2.1", - "pkg:npm/postcss@8.4.49", - "pkg:npm/nanoid@3.3.8", - "pkg:npm/source-map-js@1.2.1", - "pkg:npm/postcss-import@14.1.0", - "pkg:npm/psl@1.14.0", - "pkg:npm/%40aws-sdk/client-secrets-manager@3.670.0", - "pkg:npm/%40aws-sdk/types@3.667.0", - "pkg:npm/%40smithy/types@3.5.0", - "pkg:npm/tslib@2.8.0", - "pkg:npm/%40aws-sdk/util-locate-window@3.568.0", - "pkg:npm/%40aws-sdk/client-sso-oidc@3.670.0", - "pkg:npm/%40aws-sdk/client-sts@3.670.0", - "pkg:npm/%40aws-sdk/core@3.667.0", - "pkg:npm/%40smithy/core@2.4.8", - "pkg:npm/%40smithy/middleware-endpoint@3.1.4", - "pkg:npm/%40smithy/middleware-serde@3.0.7", - "pkg:npm/%40smithy/node-config-provider@3.1.8", - "pkg:npm/%40smithy/property-provider@3.1.7", - "pkg:npm/%40smithy/shared-ini-file-loader@3.1.8", - "pkg:npm/%40smithy/url-parser@3.0.7", - "pkg:npm/%40smithy/querystring-parser@3.0.7", - "pkg:npm/%40smithy/util-middleware@3.0.7", - "pkg:npm/%40smithy/middleware-retry@3.0.23", - "pkg:npm/%40smithy/protocol-http@4.1.4", - "pkg:npm/%40smithy/service-error-classification@3.0.7", - "pkg:npm/%40smithy/smithy-client@3.4.0", - "pkg:npm/%40smithy/middleware-stack@3.0.7", - "pkg:npm/%40smithy/util-stream@3.1.9", - "pkg:npm/%40smithy/fetch-http-handler@3.2.9", - "pkg:npm/%40smithy/querystring-builder@3.0.7", - "pkg:npm/%40smithy/node-http-handler@3.2.4", - "pkg:npm/%40smithy/abort-controller@3.1.5", - "pkg:npm/%40smithy/util-retry@3.0.7", - "pkg:npm/%40smithy/signature-v4@4.2.0", - "pkg:npm/%40aws-sdk/credential-provider-node@3.670.0", - "pkg:npm/%40aws-sdk/credential-provider-env@3.667.0", - "pkg:npm/%40aws-sdk/credential-provider-http@3.667.0", - "pkg:npm/%40aws-sdk/credential-provider-ini@3.670.0", - "pkg:npm/%40aws-sdk/credential-provider-process@3.667.0", - "pkg:npm/%40aws-sdk/credential-provider-sso@3.670.0", - "pkg:npm/%40aws-sdk/client-sso@3.670.0", - "pkg:npm/%40aws-sdk/middleware-host-header@3.667.0", - "pkg:npm/%40aws-sdk/middleware-logger@3.667.0", - "pkg:npm/%40aws-sdk/middleware-recursion-detection@3.667.0", - "pkg:npm/%40aws-sdk/middleware-user-agent@3.669.0", - "pkg:npm/%40aws-sdk/util-endpoints@3.667.0", - "pkg:npm/%40smithy/util-endpoints@2.1.3", - "pkg:npm/%40aws-sdk/region-config-resolver@3.667.0", - "pkg:npm/%40aws-sdk/util-user-agent-browser@3.670.0", - "pkg:npm/%40aws-sdk/util-user-agent-node@3.669.0", - "pkg:npm/%40smithy/config-resolver@3.0.9", - "pkg:npm/%40smithy/hash-node@3.0.7", - "pkg:npm/%40smithy/invalid-dependency@3.0.7", - "pkg:npm/%40smithy/middleware-content-length@3.0.9", - "pkg:npm/%40smithy/util-defaults-mode-browser@3.0.23", - "pkg:npm/%40smithy/util-defaults-mode-node@3.0.23", - "pkg:npm/%40smithy/credential-provider-imds@3.2.4", - "pkg:npm/%40aws-sdk/token-providers@3.667.0", - "pkg:npm/%40aws-sdk/credential-provider-web-identity@3.667.0", - "pkg:npm/%40aws-sdk/client-ssm@3.670.0", - "pkg:npm/%40smithy/util-waiter@3.1.6", - "pkg:npm/dotenv@16.4.5", - "pkg:npm/%40types/node@20.16.11", - "pkg:npm/nodemon@3.1.7", - "pkg:npm/nyc@17.1.0", - "pkg:npm/foreground-child@3.3.0", - "pkg:npm/signal-exit@4.1.0", - "pkg:npm/istanbul-lib-instrument@6.0.3", - "pkg:npm/%40babel/core@7.25.8", - "pkg:npm/picocolors@1.1.0", - "pkg:npm/electron-to-chromium@1.5.38", - "pkg:npm/%40babel/helpers@7.25.7", - "pkg:npm/rimraf@6.0.1", - "pkg:npm/glob@11.0.0", - "pkg:npm/jackspeak@4.0.2", - "pkg:npm/%40isaacs/cliui@8.0.2", - "pkg:npm/string-width@5.1.2", - "pkg:npm/eastasianwidth@0.2.0", - "pkg:npm/emoji-regex@9.2.2", - "pkg:npm/strip-ansi@7.1.0", - "pkg:npm/ansi-regex@6.1.0", - "pkg:npm/wrap-ansi@8.1.0", - "pkg:npm/ansi-styles@6.2.1", - "pkg:npm/minimatch@10.0.1", - "pkg:npm/package-json-from-dist@1.0.1", - "pkg:npm/path-scurry@2.0.0", - "pkg:npm/lru-cache@11.0.1", - "pkg:npm/%40azure/functions@4.5.1", - "pkg:npm/cookie@0.6.0", - "pkg:pypi/azure-identity@1.21.0", - "pkg:pypi/azure-core@1.32.0", - "pkg:pypi/requests@2.32.3", - "pkg:pypi/charset-normalizer@3.4.1", - "pkg:pypi/idna@3.10", - "pkg:pypi/urllib3@2.3.0", - "pkg:pypi/certifi@2025.1.31", - "pkg:pypi/six@1.17.0", - "pkg:pypi/typing-extensions@4.13.0", - "pkg:pypi/cryptography@44.0.2", - "pkg:pypi/cffi@1.17.1", - "pkg:pypi/pycparser@2.22", - "pkg:pypi/msal@1.32.0", - "pkg:pypi/pyjwt@2.10.1", - "pkg:pypi/msal-extensions@1.3.1", - "pkg:pypi/azure-mgmt-compute@34.1.0", - "pkg:pypi/isodate@0.7.2", - "pkg:pypi/azure-common@1.1.28", - "pkg:pypi/azure-mgmt-core@1.5.0", - "pkg:pypi/azure-mgmt-resource@23.3.0", - "pkg:pypi/pytz@2025.2", - "pkg:pypi/boto3@1.37.22", - "pkg:pypi/botocore@1.37.22", - "pkg:pypi/jmespath@1.0.1", - "pkg:pypi/python-dateutil@2.9.0.post0", - "pkg:pypi/s3transfer@0.11.4", - "pkg:pypi/cloudflare@4.1.0", - "pkg:pypi/anyio@4.9.0", - "pkg:pypi/sniffio@1.3.1", - "pkg:pypi/distro@1.9.0", - "pkg:pypi/httpx@0.28.1", - "pkg:pypi/httpcore@1.0.7", - "pkg:pypi/h11@0.14.0", - "pkg:pypi/pydantic@2.11.0", - "pkg:pypi/annotated-types@0.7.0", - "pkg:pypi/pydantic-core@2.33.0", - "pkg:pypi/typing-inspection@0.4.0", - "pkg:pypi/tldextract@5.1.3", - "pkg:pypi/requests-file@2.1.0", - "pkg:pypi/filelock@3.18.0", - "pkg:pypi/pendulum@3.0.0", - "pkg:pypi/tzdata@2025.2", - "pkg:pypi/time-machine@2.16.0", - "pkg:pypi/argparse@1.4.0", - "pkg:pypi/awscli@1.38.22", - "pkg:pypi/docutils@0.16", - "pkg:pypi/pyyaml@6.0.2", - "pkg:pypi/colorama@0.4.6", - "pkg:pypi/rsa@4.7.2", - "pkg:pypi/pyasn1@0.6.1", - "pkg:pypi/ansible-lint@25.1.3", - "pkg:pypi/ansible-core@2.18.4", - "pkg:pypi/jinja2@3.1.6", - "pkg:pypi/markupsafe@3.0.2", - "pkg:pypi/packaging@24.2", - "pkg:pypi/resolvelib@1.0.1", - "pkg:pypi/ansible-compat@25.1.4", - "pkg:pypi/subprocess-tee@0.4.2", - "pkg:pypi/jsonschema@4.23.0", - "pkg:pypi/attrs@25.3.0", - "pkg:pypi/jsonschema-specifications@2024.10.1", - "pkg:pypi/referencing@0.36.2", - "pkg:pypi/rpds-py@0.24.0", - "pkg:pypi/black@25.1.0", - "pkg:pypi/click@8.1.8", - "pkg:pypi/mypy-extensions@1.0.0", - "pkg:pypi/pathspec@0.12.1", - "pkg:pypi/platformdirs@4.3.7", - "pkg:pypi/importlib-metadata@8.6.1", - "pkg:pypi/zipp@3.21.0", - "pkg:pypi/ruamel.yaml@0.18.10", - "pkg:pypi/ruamel.yaml.clib@0.2.12", - "pkg:pypi/yamllint@1.37.0", - "pkg:pypi/wcmatch@10.0", - "pkg:pypi/bracex@2.5.post1", - "pkg:pypi/molecule-docker@2.1.0", - "pkg:pypi/molecule@25.3.1", - "pkg:pypi/click-help-colors@0.9.4", - "pkg:pypi/enrich@1.2.7", - "pkg:pypi/rich@13.9.4", - "pkg:pypi/markdown-it-py@3.0.0", - "pkg:pypi/mdurl@0.1.2", - "pkg:pypi/pygments@2.19.1", - "pkg:pypi/pluggy@1.5.0", - "pkg:pypi/docker@7.1.0", - "pkg:pypi/passlib@1.7.4", - "pkg:pypi/selinux@0.3.0", - "pkg:pypi/testinfra@6.0.0", - "pkg:pypi/pytest-testinfra@10.1.1", - "pkg:pypi/pytest@8.3.5", - "pkg:pypi/iniconfig@2.1.0", - "pkg:maven/io.aexp.nodes.graphql/nodes@0.5.0?type=jar", - "pkg:maven/software.amazon.ion/ion-java@1.0.1?type=jar" - ] -} diff --git a/e2e/fixtures/npm/no-components.sbom.json b/e2e/fixtures/npm/no-components.sbom.json new file mode 100644 index 00000000..f3fb56c5 --- /dev/null +++ b/e2e/fixtures/npm/no-components.sbom.json @@ -0,0 +1,6 @@ +{ + "bomFormat": "CycloneDX", + "specVersion": "1.6", + "version": 1, + "components": [] +} diff --git a/e2e/fixtures/npm/simple.purls.json b/e2e/fixtures/npm/simple.purls.json deleted file mode 100644 index 3af162d0..00000000 --- a/e2e/fixtures/npm/simple.purls.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "purls": [ - "pkg:npm/bootstrap@3.1.1", - "pkg:npm/vue@3.5.13" - ] -} diff --git a/e2e/fixtures/npm/simple.sbom.json b/e2e/fixtures/npm/simple.sbom.json new file mode 100644 index 00000000..0f71b73c --- /dev/null +++ b/e2e/fixtures/npm/simple.sbom.json @@ -0,0 +1,13 @@ +{ + "bomFormat": "CycloneDX", + "specVersion": "1.6", + "version": 1, + "components": [ + { + "purl": "pkg:npm/bootstrap@3.1.1" + }, + { + "purl": "pkg:npm/vue@3.5.13" + } + ] +} diff --git a/e2e/fixtures/npm/up-to-date.purls.json b/e2e/fixtures/npm/up-to-date.purls.json deleted file mode 100644 index 76ede72f..00000000 --- a/e2e/fixtures/npm/up-to-date.purls.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "purls": [ - "pkg:npm/bootstrap@5.3.5", - "pkg:npm/vue@3.5.13" - ] -} diff --git a/e2e/fixtures/npm/up-to-date.sbom.json b/e2e/fixtures/npm/up-to-date.sbom.json new file mode 100644 index 00000000..042458e6 --- /dev/null +++ b/e2e/fixtures/npm/up-to-date.sbom.json @@ -0,0 +1,13 @@ +{ + "bomFormat": "CycloneDX", + "specVersion": "1.6", + "version": 1, + "components": [ + { + "purl": "pkg:npm/bootstrap@5.3.5" + }, + { + "purl": "pkg:npm/vue@3.5.13" + } + ] +} diff --git a/e2e/scan/eol.test.ts b/e2e/scan/eol.test.ts index 19329d7b..67468f14 100644 --- a/e2e/scan/eol.test.ts +++ b/e2e/scan/eol.test.ts @@ -1,289 +1,369 @@ -import { doesNotThrow, notStrictEqual } from 'node:assert'; -import { doesNotMatch, match, strictEqual } from 'node:assert/strict'; +import { doesNotThrow } from 'node:assert'; +import { doesNotMatch, match, notStrictEqual, strictEqual } from 'node:assert/strict'; import { exec } from 'node:child_process'; -import { existsSync, readFileSync, unlinkSync } from 'node:fs'; +import { existsSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs'; import { mkdir } from 'node:fs/promises'; import path from 'node:path'; -import { describe, it } from 'node:test'; +import { afterEach, beforeEach, describe, it } from 'node:test'; import { promisify } from 'node:util'; +import type { DeepPartial } from '@apollo/client/utilities'; +import type { EolScanComponent } from '@herodevs/eol-shared'; import { runCommand } from '@oclif/test'; -import { config, filenamePrefix } from '../../src/config/constants'; +import { config, filenamePrefix } from '../../src/config/constants.ts'; +import { FetchMock } from '../../test/utils/mocks/fetch.mock.ts'; const execAsync = promisify(exec); const fixturesDir = path.resolve(import.meta.dirname, '../fixtures'); +const simpleDir = path.resolve(fixturesDir, 'npm/simple'); +const simpleSbom = path.join(simpleDir, 'sbom.json'); +const upToDateDir = path.resolve(fixturesDir, 'npm/up-to-date'); +const upToDateSbom = path.join(fixturesDir, 'npm/up-to-date.sbom.json'); +const noComponentsSbom = path.join(fixturesDir, 'npm/no-components.sbom.json'); + +function mockReport(components: DeepPartial[] = []) { + return { + eol: { + createReport: { + success: true, + report: { + id: 'test-123', + createdOn: new Date().toISOString(), + metadata: {}, + components, + }, + }, + }, + }; +} describe('environment', () => { it('should not be configured to run against the production environment', () => { - notStrictEqual(process.env.GRAPHQL_HOST, 'https://api.nes.herodevs.com'); - notStrictEqual(process.env.EOL_REPORT_URL, 'https://eol-report-card.apps.herodevs.com/reports'); - notStrictEqual(config.graphqlHost, 'https://api.nes.herodevs.com'); - notStrictEqual(config.eolReportUrl, 'https://eol-report-card.apps.herodevs.com/reports'); + notStrictEqual(process.env.GRAPHQL_HOST, 'https://api.nes.herodevs.com', 'GRAPHQL_HOST should not be production'); + notStrictEqual( + process.env.EOL_REPORT_URL, + 'https://eol-report-card.apps.herodevs.com/reports', + 'EOL_REPORT_URL should not be production', + ); + notStrictEqual(config.graphqlHost, 'https://api.nes.herodevs.com', 'config.graphqlHost should not be production'); + notStrictEqual( + config.eolReportUrl, + 'https://eol-report-card.apps.herodevs.com/reports', + 'config.eolReportUrl should not be production', + ); }); }); -describe('default arguments', () => { - it('defaults to scan:eol when no arguments are provided', async () => { - // Run the CLI directly with no arguments - const { stdout } = await execAsync('node bin/run.js'); +describe('scan:eol e2e', () => { + let fetchMock: FetchMock; - match(stdout, /[1-9]\d*( .*)End-of-Life \(EOL\)/, 'Should show non-zero EOL count'); + beforeEach(async () => { + await mkdir(fixturesDir, { recursive: true }); + fetchMock = new FetchMock().addGraphQL( + mockReport([ + { purl: 'pkg:npm/bootstrap@3.1.1', metadata: { isEol: true } }, + { + purl: 'pkg:npm/is-core-module@2.11.0', + metadata: {}, + nesRemediation: { remediations: [{ urls: { main: 'https://example.com' } }] }, + }, + ]), + ); }); - it('runs scan:eol --json when --json is passed in', async () => { - // Run the CLI with --json flag - const { stdout } = await execAsync('node bin/run.js --json'); - - // Verify JSON output - doesNotMatch(stdout, /Scan results:/, 'Should not show results header'); - doesNotThrow(() => JSON.parse(stdout), 'Output should be valid JSON'); + afterEach(() => { + fetchMock.restore(); }); - it('shows help for scan:eol when --help is passed in', async () => { - const { stdout } = await execAsync('node bin/run.js --help'); + describe('default arguments', () => { + it('runs scan:eol with file flag and shows results', async () => { + const { stdout } = await execAsync(`node bin/run.js scan:eol --file ${simpleSbom}`); + match(stdout, /Scan results:/, 'Should show results header'); + match(stdout, /1( .*)End-of-Life \(EOL\)/, 'Should show EOL count of 1'); + match(stdout, /total packages scanned/i, 'Should show total packages scanned'); + }); - // Verify help output - match(stdout, /USAGE/, 'Should show usage section'); - match(stdout, /FLAGS/, 'Should show flags section'); - match(stdout, /EXAMPLES/, 'Should show examples section'); - }); + it('runs scan:eol with --json and produces valid JSON', async () => { + const { stdout } = await execAsync(`node bin/run.js scan:eol --file ${simpleSbom} --json`); + doesNotMatch(stdout, /Scan results:/, 'Should not show results header'); + doesNotThrow(() => JSON.parse(stdout), 'Output should be valid JSON'); + }); - it('shows global help when help is passed in', async () => { - const { stdout } = await execAsync('node bin/run.js help'); + it('shows help for scan:eol when --help is passed in', async () => { + const { stdout } = await execAsync('node bin/run.js --help'); + match(stdout, /USAGE/, 'Should show usage section'); + match(stdout, /FLAGS/, 'Should show flags section'); + match(stdout, /EXAMPLES/, 'Should show examples section'); + }); - // Verify help output - match(stdout, /USAGE/, 'Should show usage section'); - match(stdout, /TOPICS/, 'Should show topics section'); - match(stdout, /COMMANDS/, 'Should show commands section'); + it('shows global help when help is passed in', async () => { + const { stdout } = await execAsync('node bin/run.js help'); + match(stdout, /USAGE/, 'Should show usage section'); + match(stdout, /TOPICS/, 'Should show topics section'); + match(stdout, /COMMANDS/, 'Should show commands section'); + }); }); -}); - -describe('scan:eol e2e', () => { - const simplePurls = path.resolve(fixturesDir, 'npm/simple.purls.json'); - const simpleSbom = path.join(fixturesDir, `npm/${filenamePrefix}.sbom.json`); - const reportPath = path.resolve(fixturesDir, `${filenamePrefix}.report.json`); - const emptyPurlsPath = path.resolve(fixturesDir, 'npm/empty.purls.json'); async function run(cmd: string) { - // Ensure fixtures directory exists and is clean - await mkdir(fixturesDir, { recursive: true }); - const output = await runCommand(cmd); - - // Log any errors for debugging if (output.error) { console.error('Command failed with error:', output.error); console.error('Error details:', output.stderr); } - - // Verify command executed successfully strictEqual(output.error, undefined, 'Command should execute without errors'); - return output; } it('scans existing SBOM for EOL components', async () => { const cmd = `scan:eol --file ${simpleSbom}`; const { stdout } = await run(cmd); - - // Match EOL count + match(stdout, /Scan results:/, 'Should show results header'); match(stdout, /1( .*)End-of-Life \(EOL\)/, 'Should show EOL count'); + match(stdout, /2 total packages scanned/, 'Should show total packages scanned'); }); - it('generates purls from SBOM for direct and transitive dependencies', async () => { - const transitiveDependenciesSbom = path.join(fixturesDir, 'npm/transitive-dependencies.sbom.json'); - const cmd = `report:purls --file ${transitiveDependenciesSbom} --json`; + it('shows warning and does not generate report when no components are found in scan', async () => { + const cmd = `scan:eol --file ${noComponentsSbom}`; const { stdout } = await run(cmd); - - const { purls } = JSON.parse(stdout); - - // Direct dependency - strictEqual(purls.includes('pkg:npm/is-core-module@2.11.0'), true); - - // Transitive dependency - strictEqual(purls.includes('pkg:npm/@babel/helper-validator-identifier@7.19.1'), true); + match( + stdout, + /No components found in scan. Report not generated./, + 'Should show warning, no results header or package totals', + ); }); - it('saves report when --save flag is used', async () => { - const cmd = `scan:eol --purls=${simplePurls} --dir=${fixturesDir} --save`; + it('saves report when --save flag is used (directory scan)', async () => { + const reportPath = path.join(simpleDir, `${filenamePrefix}.report.json`); + const cmd = `scan:eol --dir ${simpleDir} --save`; await run(cmd); - // Verify report was saved const reportExists = existsSync(reportPath); - strictEqual(reportExists, true, 'Report file should be created'); - // Verify report content - const report = readFileSync(reportPath, 'utf-8'); - - // Verify report structure using match - match(report, /"components":\s*\[/, 'Report should contain components array'); - match(report, /"purl":\s*"pkg:npm\/bootstrap@3\.1\.1"/, 'Report should contain bootstrap package'); - match(report, /"isEol":\s*true/, 'Bootstrap should be marked as EOL'); + const reportText = readFileSync(reportPath, 'utf-8'); + const reportJson = JSON.parse(reportText); + strictEqual(Array.isArray(reportJson.components), true, 'components should be an array'); + strictEqual(reportJson.components.length, 2, 'should have 2 components'); + const bootstrap = reportJson.components.find((c: { purl?: string }) => c.purl === 'pkg:npm/bootstrap@3.1.1'); + strictEqual(!!bootstrap, true, 'should include bootstrap 3.1.1'); + strictEqual(bootstrap.metadata?.isEol, true, 'bootstrap should be EOL'); + const hasNes = reportJson.components.some( + (c: { nesRemediation?: { remediations?: unknown[] } }) => !!c.nesRemediation?.remediations?.length, + ); + strictEqual(hasNes, true, 'should include at least one NES remediation'); match( - report, + reportText, /"createdOn": "\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z)"/, 'Report should contain a date created on property in ISO format', ); - unlinkSync(reportPath); }); - it.skip('scans extra-large.purls.json for EOL components', async () => { - const extraLargePurlsPath = path.resolve(fixturesDir, 'npm/extra-large.purls.json'); - const cmd = `scan:eol --purls ${extraLargePurlsPath}`; + it('outputs JSON only when using the --json flag', async () => { + const cmd = `scan:eol --file ${simpleSbom} --json`; const { stdout } = await run(cmd); + doesNotMatch(stdout, /Scan results:/, 'Should not show results header'); + const json = JSON.parse(stdout); + // Accept either SBOM or report JSON depending on CLI output behavior + if (json.bomFormat) { + strictEqual(json.bomFormat, 'CycloneDX', 'Should be CycloneDX format'); + } else { + strictEqual(Array.isArray(json.components), true, 'Should have components array'); + const bootstrap = json.components.find( + (c: { purl?: string }) => typeof c.purl === 'string' && c.purl.includes('pkg:npm/bootstrap@'), + ); + strictEqual(!!bootstrap, true, 'Should include bootstrap'); + } + }); - // Match command output patterns + it('shows zero EOL components when scanning up-to-date packages', async () => { + fetchMock.restore(); + fetchMock = new FetchMock().addGraphQL( + mockReport([ + { purl: 'pkg:npm/bootstrap@5.3.5', metadata: {} }, + { purl: 'pkg:npm/vue@3.5.13', metadata: {} }, + ]), + ); + const cmd = `scan:eol --file ${upToDateSbom}`; + const { stdout } = await run(cmd); match(stdout, /Scan results:/, 'Should show results header'); - - // Match specific EOL packages - match(stdout, /pkg:npm\/%40angular\/core@12\.2\.2/, 'Should detect Angular core package'); - match(stdout, /EOL Date: 2022-11-12/, 'Should show correct EOL date for Angular core'); - match(stdout, /pkg:npm\/bootstrap@3\.4\.1/, 'Should detect Bootstrap 3.4.1'); - match(stdout, /EOL Date: 2019-07-24/, 'Should show correct EOL date for Bootstrap 3.4.1'); - match(stdout, /pkg:npm\/bootstrap@4\.6\.2/, 'Should detect Bootstrap 4.6.2'); - // TODO(ED): Debug why data is missing for this package - // match(stdout, /EOL Date: 2023-01-01/, 'Should show correct EOL date for Bootstrap 4.6.2'); - - // Match legend - match(stdout, /• = No Known Issues/, 'Should show legend for No Known Issues'); - match(stdout, /✔ = OK/, 'Should show legend for OK status'); - match(stdout, /⚡= Supported: End-of-Life \(EOL\) is scheduled/, 'Should show legend for SUPPORTED status'); - match(stdout, /✗ = End of Life \(EOL\)/, 'Should show legend for EOL status'); + match(stdout, /0( .*)End-of-Life \(EOL\)/, 'Should show EOL count of 0'); + match(stdout, /2 total packages scanned/, 'Should show total packages scanned'); }); - it('outputs JSON when using the --json flag', async () => { - const cmd = `scan:eol --purls=${simplePurls} --json`; + it('handles empty components array without errors', async () => { + fetchMock.restore(); + fetchMock = new FetchMock().addGraphQL(mockReport([])); + const cmd = `scan:eol --file ${noComponentsSbom}`; const { stdout } = await run(cmd); + match(stdout, /No components found in scan/, 'Should show no packages found in scan'); + }); - // Match command output patterns - doesNotMatch(stdout, /Scan results:/, 'Should not show results header'); - doesNotThrow(() => JSON.parse(stdout)); + it('saves SBOM when --saveSbom flag is used (directory scan)', async () => { + const sbomPath = path.join(simpleDir, `${filenamePrefix}.sbom.json`); + const cmd = `scan:eol --dir ${simpleDir} --saveSbom`; + await run(cmd); + + const sbomExists = existsSync(sbomPath); + strictEqual(sbomExists, true, 'SBOM file should be created'); + + const sbomText = readFileSync(sbomPath, 'utf-8'); + const sbomJson = JSON.parse(sbomText); + strictEqual(sbomJson.bomFormat, 'CycloneDX', 'Should be CycloneDX format'); + strictEqual(Array.isArray(sbomJson.components), true, 'Should have components array'); + match(sbomText, /"name": "@herodevs\/cli"/, 'Should include HeroDevs CLI attribution'); + unlinkSync(sbomPath); }); - it('correctly identifies Bootstrap as having EOL status and remediation available when using the --json flag', async () => { - const cmd = `scan:eol --purls=${simplePurls} --json`; - const { stdout } = await run(cmd); + it('saves both report and SBOM when both --save and --saveSbom flags are used', async () => { + const reportPath = path.join(simpleDir, `${filenamePrefix}.report.json`); + const sbomPath = path.join(simpleDir, `${filenamePrefix}.sbom.json`); + const cmd = `scan:eol --dir ${simpleDir} --save --saveSbom`; + await run(cmd); - const json = JSON.parse(stdout); - const bootstrap = json.components.find((component) => component.purl.startsWith('pkg:npm/bootstrap@')); - strictEqual(bootstrap?.metadata.isEol, true, 'Bootstrap should be marked as EOL'); - strictEqual( - !!bootstrap?.nesRemediation?.remediations?.length, - true, - 'Bootstrap should have NES remediation available', - ); + const reportExists = existsSync(reportPath); + const sbomExists = existsSync(sbomPath); + strictEqual(reportExists, true, 'Report file should be created'); + strictEqual(sbomExists, true, 'SBOM file should be created'); + + const reportText = readFileSync(reportPath, 'utf-8'); + const sbomText = readFileSync(sbomPath, 'utf-8'); + const reportJson = JSON.parse(reportText); + const sbomJson = JSON.parse(sbomText); + + strictEqual(Array.isArray(reportJson.components), true, 'Report should have components array'); + strictEqual(sbomJson.bomFormat, 'CycloneDX', 'SBOM should be CycloneDX format'); + + unlinkSync(reportPath); + unlinkSync(sbomPath); }); - it('correctly identifies Angular 17 as having a EOL date when using --json flag', async () => { - const angular17Purls = path.resolve(fixturesDir, 'npm/angular-17.purls.json'); - const cmd = `scan:eol --purls=${angular17Purls} --json`; + it('scans up-to-date directory and shows modern packages', async () => { + fetchMock.restore(); + fetchMock = new FetchMock().addGraphQL( + mockReport([ + { purl: 'pkg:npm/bootstrap@5.3.5', metadata: {} }, + { purl: 'pkg:npm/vue@3.5.13', metadata: {} }, + ]), + ); + const cmd = `scan:eol --dir ${upToDateDir}`; const { stdout } = await run(cmd); - - const json = JSON.parse(stdout); - const angular17 = json.components.find((component) => component.purl.startsWith('pkg:npm/%40angular/core@')); - strictEqual(angular17?.metadata.isEol, true, 'Angular 17 should be marked as EOL'); + match(stdout, /Scan results:/, 'Should show results header'); + match(stdout, /0( .*)End-of-Life \(EOL\)/, 'Should show EOL count of 0'); + match(stdout, /total packages scanned/, 'Should show total packages scanned'); }); describe('web report URL', () => { it('displays web report URL with scan ID when scan is successful', async () => { - const cmd = `scan:eol --purls=${simplePurls}`; + const cmd = `scan:eol --file ${simpleSbom}`; const { stdout } = await run(cmd); - - // Match the key text and scan ID pattern - match(stdout, /View your full EOL report at.*[a-zA-Z0-9-]+/, 'Should show web report text and scan ID'); + match(stdout, /View your full EOL report at.*test-123/, 'Should show web report text and scan ID'); }); it('does not display web report URL when using --json flag', async () => { - const cmd = `scan:eol --purls=${simplePurls} --json`; + const cmd = `scan:eol --file ${simpleSbom} --json`; const { stdout } = await run(cmd); - - // Verify URL text is not in output - doesNotMatch(stdout, /View your free EOL report/, 'Should not show web report text in JSON output'); + doesNotMatch(stdout, /View your full EOL report/, 'Should not show web report text in JSON output'); }); }); -}); - -/** - * Directory scan tests - * Please see CONTRIBUTING.md before adding new tests to this section. - */ -describe('with directory flag', () => { - const simpleDir = path.resolve(fixturesDir, 'npm/simple'); - const reportPath = path.join(simpleDir, `${filenamePrefix}.report.json`); - - async function run(cmd: string) { - // Ensure test directory exists and is clean - await mkdir(simpleDir, { recursive: true }); - - const output = await runCommand(cmd); - - // Log any errors for debugging - if (output.error) { - console.error('Command failed with error:', output.error); - console.error('Error details:', output.stderr); - } - // Verify command executed successfully - strictEqual(output.error, undefined, 'Command should execute without errors'); - - return output; - } - - it('scans a directory or sbom for EOL components', async () => { - const cmd = `scan:eol --dir ${simpleDir}`; - const { stdout } = await run(cmd); - - // Match EOL count - match(stdout, /1( .*)End-of-Life \(EOL\)/, 'Should show EOL count'); - }); + describe('with directory flag', () => { + it('scans a directory for EOL components', async () => { + const cmd = `scan:eol --dir ${simpleDir}`; + const { stdout } = await run(cmd); + match(stdout, /Scan results:/, 'Should show results header'); + match(stdout, /total packages scanned/, 'Should show total packages scanned'); + }); - it('displays web report URL when scanning directory', async () => { - const cmd = `scan:eol --dir ${simpleDir}`; - const { stdout } = await run(cmd); + it('displays web report URL when scanning directory', async () => { + const cmd = `scan:eol --dir ${simpleDir}`; + const { stdout } = await run(cmd); + match(stdout, /View your full EOL report at.*test-123/, 'Should show web report with scan ID'); + }); - // Match the key text and scan ID pattern - match(stdout, /View your full EOL report at.*[a-zA-Z0-9-]+/, 'Should show web report text and scan ID'); + it('outputs JSON when using the --json flag', async () => { + const cmd = `scan:eol --dir ${simpleDir} --json`; + const { stdout } = await run(cmd); + doesNotMatch(stdout, /Generating SBOM/, 'Should not show progress messages'); + doesNotMatch(stdout, /Scan results:/, 'Should not show results header'); + const json = JSON.parse(stdout); + if (json.bomFormat) { + strictEqual(json.bomFormat, 'CycloneDX', 'Should be CycloneDX format'); + strictEqual(Array.isArray(json.components), true, 'Should have components array'); + } else { + strictEqual(Array.isArray(json.components), true, 'Should have components array'); + } + const hasBootstrap = json.components.some( + (c: { purl?: string }) => typeof c.purl === 'string' && c.purl.includes('pkg:npm/bootstrap@'), + ); + strictEqual(hasBootstrap, true, 'Should detect bootstrap package from package.json'); + }); }); - it('saves report when --save flag is used', async () => { - const cmd = `scan:eol --dir ${simpleDir} --save`; - await run(cmd); - - // Verify report was saved - const reportExists = existsSync(reportPath); - - strictEqual(reportExists, true, 'Report file should be created'); - - // Verify report content - const report = readFileSync(reportPath, 'utf-8'); + describe('negative paths', () => { + async function runExpectFail(cmd: string) { + const output = await runCommand(cmd); + notStrictEqual(output.error, undefined, 'Command should fail'); + // Some runners set process.exitCode on failures; ensure it doesn't leak and trip the file-level status + process.exitCode = 0; + return output; + } - // Verify report structure using match - match(report, /"components":\s*\[/, 'Report should contain components array'); - match(report, /"purl":\s*"pkg:npm\/bootstrap@3\.1\.1"/, 'Report should contain bootstrap package'); - match(report, /"isEol":\s*true/, 'Bootstrap should be marked as EOL'); - match( - report, - /"createdOn": "\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z)"/, - 'Report should contain a date created on property in ISO format', - ); + function expectAny(output: { stdout: string; stderr: string }, patterns: RegExp[], message: string) { + const text = `${output.stderr}\n${output.stdout}`; + const matched = patterns.some((re) => re.test(text)); + strictEqual(matched, true, message); + } - unlinkSync(reportPath); - }); + it('fails when SBOM file does not exist', async () => { + const missing = path.join(fixturesDir, 'npm', 'does-not-exist.json'); + const out = await runExpectFail(`scan:eol --file ${missing}`); + expectAny( + out, + [/SBOM file not found:/i, /Failed to read SBOM file/i, /Failed to load SBOM file/i, /Loading SBOM file/i], + 'Should indicate missing SBOM file', + ); + }); - it('scans existing SBOM for EOL components', async () => { - const cmd = `scan:eol --file ${simpleDir}/sbom.json`; - const { stdout } = await run(cmd); + it('fails when SBOM file is invalid JSON', async () => { + const badFile = path.join(fixturesDir, 'npm', 'invalid.json'); + writeFileSync(badFile, '{not-json'); + try { + const out = await runExpectFail(`scan:eol --file ${badFile}`); + expectAny( + out, + [/Failed to read SBOM file/i, /Failed to load SBOM file/i, /Loading SBOM file/i], + 'Should indicate invalid SBOM', + ); + } finally { + unlinkSync(badFile); + } + }); - match(stdout, /1( .*)End-of-Life \(EOL\)/, 'Should show EOL count'); - }); + it('fails when directory does not exist', async () => { + const missingDir = path.join(fixturesDir, 'npm', 'no-such-dir'); + const out = await runExpectFail(`scan:eol --dir ${missingDir}`); + expectAny( + out, + [/Directory not found:/i, /Failed to scan directory/i, /Generating SBOM/i], + 'Should indicate missing directory', + ); + }); - it('outputs JSON when using the --json flag', async () => { - const cmd = `scan:eol --dir ${simpleDir} --json`; - const { stdout } = await run(cmd); + it('fails when provided path is not a directory', async () => { + const out = await runExpectFail(`scan:eol --dir ${simpleSbom}`); + expectAny( + out, + [/Path is not a directory:/i, /Failed to scan directory/i, /Generating SBOM/i], + 'Should indicate non-directory path', + ); + }); - // Match command output patterns - doesNotMatch(stdout, /Scan results:/, 'Should not show results header'); - doesNotThrow(() => JSON.parse(stdout)); + it('fails when NES returns unsuccessful result', async () => { + // Override fetch mock to return unsuccessful mutation for this test + fetchMock.restore(); + fetchMock = new FetchMock().addGraphQL({ eol: { createReport: { success: false, report: null } } }); + const out = await runExpectFail(`scan:eol --file ${simpleSbom}`); + expectAny(out, [/Failed to submit scan to NES/i, /Scanning failed/i], 'Should indicate NES submission failure'); + }); }); }); diff --git a/e2e/scan/sbom.test.ts b/e2e/scan/sbom.test.ts deleted file mode 100644 index 25a21ab6..00000000 --- a/e2e/scan/sbom.test.ts +++ /dev/null @@ -1,113 +0,0 @@ -import { doesNotThrow } from 'node:assert'; -import { doesNotMatch, strictEqual } from 'node:assert/strict'; -import { mkdir } from 'node:fs/promises'; -import path from 'node:path'; -import { describe, it } from 'node:test'; -import { runCommand } from '@oclif/test'; - -describe('scan:sbom e2e', () => { - const fixturesDir = path.resolve(import.meta.dirname, '../fixtures'); - const simpleDir = path.resolve(fixturesDir, 'npm/simple'); - - async function run(cmd: string) { - // Ensure fixtures directory exists and is clean - await mkdir(fixturesDir, { recursive: true }); - - const output = await runCommand(cmd); - - // Log any errors for debugging - if (output.error) { - console.error('Command failed with error:', output.error); - console.error('Error details:', output.stderr); - } - - // Verify command executed successfully - strictEqual(output.error, undefined, 'Command should execute without errors'); - - return output; - } - - describe('SBOM generation and attribution', () => { - it('generates SBOM with correct HeroDevs attribution', async () => { - const cmd = `scan:sbom --dir ${simpleDir} --json`; - const { stdout } = await run(cmd); - - // Verify JSON output is valid - doesNotThrow(() => JSON.parse(stdout), 'Output should be valid JSON'); - - const sbom = JSON.parse(stdout); - - // Verify SBOM structure - strictEqual(sbom.bomFormat, 'CycloneDX', 'Should be CycloneDX format'); - strictEqual(Array.isArray(sbom.components), true, 'Should have components array'); - - // Verify author attribution - strictEqual(Array.isArray(sbom.metadata?.authors), true, 'Should have authors array'); - strictEqual(sbom.metadata.authors.length, 1, 'Should have exactly one author'); - strictEqual(sbom.metadata.authors[0].name, 'HeroDevs, Inc.', 'Should have correct author name'); - - // Verify tools attribution (in CycloneDX, tools is an object with components array) - strictEqual(typeof sbom.metadata?.tools, 'object', 'Should have tools object'); - strictEqual(Array.isArray(sbom.metadata.tools?.components), true, 'Should have tools components array'); - strictEqual(sbom.metadata.tools.components.length > 0, true, 'Should have at least one tool'); - - // Find our CLI tool in the tools components - const cliTool = sbom.metadata.tools.components.find((tool: { name?: string }) => tool.name === '@herodevs/cli'); - - strictEqual(cliTool !== undefined, true, 'Should find @herodevs/cli tool'); - strictEqual(cliTool.publisher, 'HeroDevs, Inc.', 'Should have correct tool publisher'); - - // Verify version is present (don't check exact value as it may vary) - strictEqual(typeof cliTool.version, 'string', 'Should have tool version as string'); - strictEqual(cliTool.version.length > 0, true, 'Should have non-empty tool version'); - }); - - it('outputs valid CycloneDX SBOM format', async () => { - const cmd = `scan:sbom --dir ${simpleDir} --json`; - const { stdout } = await run(cmd); - - const sbom = JSON.parse(stdout); - - // Verify SBOM format and spec version - strictEqual(sbom.bomFormat, 'CycloneDX', 'Should be CycloneDX format'); - strictEqual(sbom.specVersion, '1.6', 'Should use CycloneDX spec version 1.6'); - - // Verify metadata structure - strictEqual(typeof sbom.metadata, 'object', 'Should have metadata object'); - strictEqual(typeof sbom.serialNumber, 'string', 'Should have serial number'); - - // Verify components are detected - strictEqual(Array.isArray(sbom.components), true, 'Should have components array'); - strictEqual(sbom.components.length > 0, true, 'Should detect at least one component'); - }); - - it('does not show progress output when using --json flag', async () => { - const cmd = `scan:sbom --dir ${simpleDir} --json`; - const { stdout } = await run(cmd); - - // Should not contain progress indicators or non-JSON output - doesNotMatch(stdout, /Generating SBOM/, 'Should not show progress messages'); - doesNotMatch(stdout, /Scan results:/, 'Should not show results header'); - - // Verify output is pure JSON - doesNotThrow(() => JSON.parse(stdout), 'Output should be valid JSON'); - }); - - it('detects npm packages in simple fixture', async () => { - const cmd = `scan:sbom --dir ${simpleDir} --json`; - const { stdout } = await run(cmd); - - const sbom = JSON.parse(stdout); - - // Verify components are detected - strictEqual(Array.isArray(sbom.components), true, 'Should have components array'); - strictEqual(sbom.components.length > 0, true, 'Should detect components'); - - // Look for bootstrap package that should be in the simple fixture - const hasBootstrap = sbom.components.some((component: { purl?: string }) => - component.purl?.includes('pkg:npm/bootstrap@'), - ); - strictEqual(hasBootstrap, true, 'Should detect bootstrap package from package.json'); - }); - }); -}); diff --git a/src/api/client.ts b/src/api/client.ts deleted file mode 100644 index 73aca2dc..00000000 --- a/src/api/client.ts +++ /dev/null @@ -1,58 +0,0 @@ -import * as apollo from '@apollo/client/core/index.js'; -import { ApolloError } from '../service/error.svc.ts'; - -export interface ApolloHelper { - mutate( - mutation: apollo.DocumentNode, - variables?: V, - ): Promise>; - query( - query: apollo.DocumentNode, - variables?: V, - ): Promise>; -} - -export const createApollo = (url: string) => - new apollo.ApolloClient({ - cache: new apollo.InMemoryCache({ - addTypename: false, - }), - headers: { - 'User-Agent': `hdcli/${process.env.npm_package_version ?? 'unknown'}`, - }, - link: apollo.ApolloLink.from([ - new apollo.HttpLink({ - uri: url, - }), - ]), - }); - -export class ApolloClient implements ApolloHelper { - #apollo: apollo.ApolloClient; - - constructor(url: string) { - this.#apollo = createApollo(url); - } - - async mutate(mutation: apollo.DocumentNode, variables?: V) { - try { - return await this.#apollo.mutate({ - mutation, - variables, - }); - } catch (error: unknown) { - throw new ApolloError('GraphQL mutation failed', error); - } - } - - async query(query: apollo.DocumentNode, variables?: V) { - try { - return await this.#apollo.query({ - query, - variables, - }); - } catch (error) { - throw new ApolloError('GraphQL query failed', error); - } - } -} diff --git a/src/api/gql-operations.ts b/src/api/gql-operations.ts new file mode 100644 index 00000000..58faf7a0 --- /dev/null +++ b/src/api/gql-operations.ts @@ -0,0 +1,27 @@ +import { gql } from '@apollo/client/core/core.cjs'; + +export const createReportMutation = gql` +mutation createReport($input: CreateEolReportInput) { + eol { + createReport(input: $input) { + success + report { + createdOn + id + metadata + components { + purl + metadata + nesRemediation { + remediations { + urls { + main + } + } + } + } + } + } + } +} +`; diff --git a/src/api/nes.client.ts b/src/api/nes.client.ts new file mode 100644 index 00000000..b609913c --- /dev/null +++ b/src/api/nes.client.ts @@ -0,0 +1,49 @@ +import { ApolloClient, ApolloLink, HttpLink, InMemoryCache } from '@apollo/client/core/index.js'; +import type { CreateEolReportInput, EolReport, EolReportMutationResponse } from '@herodevs/eol-shared'; +import { config } from '../config/constants.ts'; +import { debugLogger } from '../service/log.svc.ts'; +import { createReportMutation } from './gql-operations.ts'; + +export const createApollo = (uri: string) => + new ApolloClient({ + cache: new InMemoryCache({ addTypename: false }), + headers: { + 'User-Agent': `hdcli/${process.env.npm_package_version ?? 'unknown'}`, + }, + link: ApolloLink.from([new HttpLink({ uri })]), + }); + +export const SbomScanner = (client: ReturnType) => { + return async (input: CreateEolReportInput): Promise => { + const res = await client.mutate({ + mutation: createReportMutation, + variables: { input }, + }); + + const result = res.data?.eol?.createReport; + if (!result?.success || !result.report) { + debugLogger('failed scan %o', result || {}); + throw new Error('Failed to create EOL report'); + } + + return result.report; + }; +}; + +export class NesClient { + startScan: ReturnType; + + constructor(url: string) { + this.startScan = SbomScanner(createApollo(url)); + } +} + +/** + * Submit a scan for a list of purls + */ +export function submitScan(input: CreateEolReportInput): Promise { + const url = config.graphqlHost + config.graphqlPath; + debugLogger('Submitting scan to %s', url); + const client = new NesClient(url); + return client.startScan(input); +} diff --git a/src/api/nes/nes.client.ts b/src/api/nes/nes.client.ts deleted file mode 100644 index 50c222d9..00000000 --- a/src/api/nes/nes.client.ts +++ /dev/null @@ -1,87 +0,0 @@ -import type * as apollo from '@apollo/client/core/index.js'; -import { PackageURL } from 'packageurl-js'; - -import type { CreateEolReportInput, EolReport } from '@herodevs/eol-shared'; -import { ApolloClient } from '../../api/client.ts'; -import { config } from '../../config/constants.ts'; -import { debugLogger } from '../../service/log.svc.ts'; -import { SbomScanner } from '../../service/nes/nes.svc.ts'; - -export interface NesClient { - scan: { - purls: (input: CreateEolReportInput) => Promise; - }; -} - -export class NesApolloClient implements NesClient { - scan = { - purls: SbomScanner(this), - }; - #apollo: ApolloClient; - - constructor(url: string) { - this.#apollo = new ApolloClient(url); - } - - mutate>(mutation: apollo.DocumentNode, variables?: V) { - return this.#apollo.mutate(mutation, variables); - } - - query | undefined>(query: apollo.DocumentNode, variables?: V) { - return this.#apollo.query(query, variables); - } -} - -/** - * Submit a scan for a list of purls - */ -export function submitScan(input: CreateEolReportInput): Promise { - const host = config.graphqlHost; - const path = config.graphqlPath; - const url = host + path; - debugLogger('Submitting scan to %s', url); - const client = new NesApolloClient(url); - return client.scan.purls(input); -} - -export const submitPurls = async (purls: string[]): Promise => { - try { - const dedupedAndEncodedPurls = dedupeAndEncodePurls(purls); - debugLogger('Submitting %d purls', dedupedAndEncodedPurls.length); - - if (dedupedAndEncodedPurls.length === 0) { - return { - components: [], - createdOn: new Date().toISOString(), - id: '', - metadata: { - unknownComponentsCount: 0, - totalComponentsCount: 0, - }, - } satisfies EolReport; - } - - return submitScan({ components: dedupedAndEncodedPurls }); - } catch (error) { - debugLogger('Fatal error in submitPurls: %s', error); - throw new Error(`Failed to process purls: ${error instanceof Error ? error.message : String(error)}`); - } -}; - -export const dedupeAndEncodePurls = (purls: string[]): string[] => { - const dedupedAndEncodedPurls = new Set(); - - for (const purl of purls) { - try { - // The PackageURL.fromString method encodes each part of the purl - const encodedPurl = PackageURL.fromString(purl).toString(); - if (!dedupedAndEncodedPurls.has(encodedPurl)) { - dedupedAndEncodedPurls.add(encodedPurl); - } - } catch (error) { - debugLogger('Error encoding purl: %s', error); - } - } - - return Array.from(dedupedAndEncodedPurls); -}; diff --git a/src/api/queries/nes/sbom.ts b/src/api/queries/nes/sbom.ts deleted file mode 100644 index 58b56119..00000000 --- a/src/api/queries/nes/sbom.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { gql } from '@apollo/client/core/core.cjs'; - -export const M_SCAN = { - gql: gql` - mutation createReport($input: CreateEolReportInput) { - eol { - createReport(input: $input) { - success - report { - createdOn - id - metadata - components { - purl - metadata - nesRemediation { - remediations { - urls { - main - } - } - } - } - } - } - } - } - `, -}; diff --git a/src/commands/report/committers.ts b/src/commands/report/committers.ts deleted file mode 100644 index b442ea68..00000000 --- a/src/commands/report/committers.ts +++ /dev/null @@ -1,173 +0,0 @@ -import { spawnSync } from 'node:child_process'; -import { Command, Flags } from '@oclif/core'; - -import fs from 'node:fs'; -import path from 'node:path'; -import { filenamePrefix } from '../../config/constants.ts'; -import { - type CommitEntry, - type ReportData, - calculateOverallStats, - formatAsCsv, - formatAsText, - groupCommitsByMonth, - parseGitLogOutput, -} from '../../service/committers.svc.ts'; -import { getErrorMessage, isErrnoException } from '../../service/error.svc.ts'; - -export default class Committers extends Command { - static override description = 'Generate report of committers to a git repository'; - static enableJsonFlag = true; - static override examples = [ - '<%= config.bin %> <%= command.id %>', - '<%= config.bin %> <%= command.id %> --csv -s', - '<%= config.bin %> <%= command.id %> --json', - '<%= config.bin %> <%= command.id %> --csv', - ]; - - static override flags = { - months: Flags.integer({ - char: 'm', - description: 'The number of months of git history to review', - default: 12, - }), - csv: Flags.boolean({ - char: 'c', - description: 'Output in CSV format', - default: false, - }), - save: Flags.boolean({ - char: 's', - description: `Save the committers report as ${filenamePrefix}.committers.`, - default: false, - }), - }; - - public async run(): Promise { - const { flags } = await this.parse(Committers); - const { months, csv, save } = flags; - const isJson = this.jsonEnabled(); - - const sinceDate = `${months} months ago`; - this.log('Starting committers report with flags: %O', flags); - - try { - // Generate structured report data - const entries = this.fetchGitCommitData(sinceDate); - this.log('Fetched %d commit entries', entries.length); - const reportData = this.generateReportData(entries); - - // Handle different output scenarios - if (isJson) { - // JSON mode - if (save) { - try { - fs.writeFileSync(path.resolve(`${filenamePrefix}.committers.json`), JSON.stringify(reportData, null, 2)); - this.log('Report written to json'); - } catch (error) { - this.error(`Failed to save JSON report: ${getErrorMessage(error)}`); - } - } - return reportData; - } - - const textOutput = formatAsText(reportData); - - if (csv) { - // CSV mode - const csvOutput = formatAsCsv(reportData); - if (save) { - try { - fs.writeFileSync(path.resolve(`${filenamePrefix}.committers.csv`), csvOutput); - this.log('Report written to csv'); - } catch (error) { - this.error(`Failed to save CSV report: ${getErrorMessage(error)}`); - } - } else { - this.log(textOutput); - } - return csvOutput; - } - - if (save) { - try { - fs.writeFileSync(path.resolve(`${filenamePrefix}.committers.txt`), textOutput); - this.log('Report written to txt'); - } catch (error) { - this.error(`Failed to save txt report: ${getErrorMessage(error)}`); - } - } else { - this.log(textOutput); - } - return textOutput; - } catch (error) { - this.error(`Failed to generate report: ${getErrorMessage(error)}`); - } - } - - /** - * Generates structured report data - * @param entries - parsed git log output for commits - */ - private generateReportData(entries: CommitEntry[]): ReportData { - if (entries.length === 0) { - return { monthly: {}, overall: { total: 0 } }; - } - - const monthlyData = groupCommitsByMonth(entries); - const overallStats = calculateOverallStats(entries); - const grandTotal = entries.length; - - // Format into a structured report data object - const report: ReportData = { - monthly: {}, - overall: { ...overallStats, total: grandTotal }, - }; - - // Add monthly totals - for (const [month, authors] of Object.entries(monthlyData)) { - const monthTotal = Object.values(authors).reduce((sum, count) => sum + count, 0); - report.monthly[month] = { ...authors, total: monthTotal }; - } - - return report; - } - - /** - * Fetches git commit data with month and author information - * @param sinceDate - Date range for git log - */ - private fetchGitCommitData(sinceDate: string): CommitEntry[] { - const logProcess = spawnSync( - 'git', - [ - 'log', - '--all', // Include committers on all branches in the repo - '--format="%ad|%an"', // Format: date|author - '--date=format:%Y-%m', // Format date as YYYY-MM - `--since="${sinceDate}"`, - ], - { encoding: 'utf-8' }, - ); - - if (logProcess.error) { - if (isErrnoException(logProcess.error)) { - if (logProcess.error.code === 'ENOENT') { - this.error('Git command not found. Please ensure git is installed and available in your PATH.'); - } - this.error(`Git command failed: ${getErrorMessage(logProcess.error)}`); - } - this.error(`Git command failed: ${getErrorMessage(logProcess.error)}`); - } - - if (logProcess.status !== 0) { - this.error(`Git command failed with status ${logProcess.status}: ${logProcess.stderr}`); - } - - if (!logProcess.stdout) { - return []; - } - - return parseGitLogOutput(logProcess.stdout); - } -} diff --git a/src/commands/report/purls.ts b/src/commands/report/purls.ts deleted file mode 100644 index cebe7e99..00000000 --- a/src/commands/report/purls.ts +++ /dev/null @@ -1,92 +0,0 @@ -import fs from 'node:fs'; -import path from 'node:path'; -import { Command, Flags, ux } from '@oclif/core'; - -import { filenamePrefix } from '../../config/constants.ts'; -import { getErrorMessage, isErrnoException } from '../../service/error.svc.ts'; -import { extractPurls, getPurlOutput } from '../../service/purls.svc.ts'; -import ScanSbom from '../scan/sbom.ts'; - -export default class ReportPurls extends Command { - static override description = 'Generate a list of purls from a sbom'; - static enableJsonFlag = true; - static override examples = [ - '<%= config.bin %> <%= command.id %> --json -s', - '<%= config.bin %> <%= command.id %> --dir=./my-project', - '<%= config.bin %> <%= command.id %> --file=path/to/sbom.json', - '<%= config.bin %> <%= command.id %> --dir=./my-project --save', - '<%= config.bin %> <%= command.id %> --save --csv', - ]; - static override flags = { - file: Flags.string({ - char: 'f', - description: 'The file path of an existing cyclonedx sbom to scan for EOL', - }), - dir: Flags.string({ - char: 'd', - description: 'The directory to scan in order to create a cyclonedx sbom', - }), - save: Flags.boolean({ - char: 's', - default: false, - description: `Save the list of purls as ${filenamePrefix}.purls.`, - }), - csv: Flags.boolean({ - char: 'c', - default: false, - description: 'Save output in CSV format (only applies when using --save)', - }), - }; - - public async run(): Promise<{ purls: string[] }> { - const { flags } = await this.parse(ReportPurls); - const { dir: _dirFlag, file: _fileFlag, save, csv } = flags; - - try { - const sbom = await ScanSbom.loadSbom(flags, this.config); - const purls = await extractPurls(sbom); - this.log('Extracted %d purls from SBOM', purls.length); - - ux.action.stop('Scan completed'); - - // Print the purls - for (const purl of purls) { - this.log(purl); - } - - // Save if requested - if (save) { - try { - const outputFile = csv && !this.jsonEnabled() ? 'csv' : 'json'; - const outputPath = path.join(_dirFlag || process.cwd(), `${filenamePrefix}.purls.${outputFile}`); - const purlOutput = getPurlOutput(purls, outputFile); - fs.writeFileSync(outputPath, purlOutput); - - this.log('Purls saved to %s', outputPath); - } catch (error: unknown) { - if (isErrnoException(error)) { - switch (error.code) { - case 'EACCES': - this.error('Permission denied: Cannot write to output file'); - break; - case 'ENOSPC': - this.error('No space left on device'); - break; - case 'EISDIR': - this.error('Cannot write to output file: Is a directory'); - break; - default: - this.error(`Failed to save purls: ${getErrorMessage(error)}`); - } - } - this.error(`Failed to save purls: ${getErrorMessage(error)}`); - } - } - - // Return wrapped object with metadata - return { purls }; - } catch (error) { - this.error(`Failed to generate PURLs: ${getErrorMessage(error)}`); - } - } -} diff --git a/src/commands/scan/eol.ts b/src/commands/scan/eol.ts index e628e04c..3b7d5be8 100644 --- a/src/commands/scan/eol.ts +++ b/src/commands/scan/eol.ts @@ -1,110 +1,108 @@ -import fs from 'node:fs'; -import path from 'node:path'; -import { deriveComponentStatus, trimCdxBom } from '@herodevs/eol-shared'; -import type { CdxBom, ComponentStatus, EolReport } from '@herodevs/eol-shared'; -import { Command, Flags, ux } from '@oclif/core'; +import { trimCdxBom } from '@herodevs/eol-shared'; +import type { CdxBom, EolReport } from '@herodevs/eol-shared'; +import { Command, Flags } from '@oclif/core'; import ora from 'ora'; -import terminalLink from 'terminal-link'; -import { submitPurls, submitScan } from '../../api/nes/nes.client.ts'; +import { submitScan } from '../../api/nes.client.ts'; import { config, filenamePrefix } from '../../config/constants.ts'; -import type { Sbom } from '../../service/eol/cdx.svc.ts'; -import { getErrorMessage, isErrnoException } from '../../service/error.svc.ts'; -import { parsePurlsFile } from '../../service/purls.svc.ts'; -import { INDICATORS, STATUS_COLORS } from '../../ui/shared.ui.ts'; -import ScanSbom from './sbom.ts'; +import { createSbom } from '../../service/cdx.svc.ts'; +import { formatScanResults, formatWebReportUrl } from '../../service/display.svc.ts'; +import { readSbomFromFile, saveReportToFile, saveSbomToFile, validateDirectory } from '../../service/file.svc.ts'; +import { getErrorMessage } from '../../service/log.svc.ts'; export default class ScanEol extends Command { - static override description = 'Scan a given sbom for EOL data'; + static override description = 'Scan a given SBOM for EOL data'; static enableJsonFlag = true; static override examples = [ - '<%= config.bin %> <%= command.id %> --dir=./my-project', - '<%= config.bin %> <%= command.id %> --file=path/to/sbom.json', - '<%= config.bin %> <%= command.id %> --purls=path/to/purls.json', - '<%= config.bin %> <%= command.id %> -a --dir=./my-project', + { description: 'Default behavior (no command or flags specified)', command: '<%= config.bin %>' }, + { description: 'Equivalent to', command: '<%= config.bin %> <%= command.id %> --dir .' }, + { + description: 'Skip SBOM generation and specify an existing file', + command: '<%= config.bin %> <%= command.id %> --file /path/to/sbom.json', + }, + { + description: 'Save the report or SBOM to a file', + command: '<%= config.bin %> <%= command.id %> --save --saveSbom', + }, + { + description: 'Output the report in JSON format (for APIs, CI, etc.)', + command: '<%= config.bin %> <%= command.id %> --json', + }, ]; static override flags = { file: Flags.string({ char: 'f', - description: 'The file path of an existing cyclonedx sbom to scan for EOL', - }), - purls: Flags.string({ - char: 'p', - description: 'The file path of a list of purls to scan for EOL', + description: 'The file path of an existing cyclonedx SBOM to scan for EOL', + exclusive: ['dir'], }), dir: Flags.string({ char: 'd', - description: 'The directory to scan in order to create a cyclonedx sbom', + default: process.cwd(), + defaultHelp: async () => '', + description: 'The directory to scan in order to create a cyclonedx SBOM', + exclusive: ['file'], }), save: Flags.boolean({ char: 's', default: false, description: `Save the generated report as ${filenamePrefix}.report.json in the scanned directory`, }), + saveSbom: Flags.boolean({ + aliases: ['save-sbom'], + default: false, + description: `Save the generated SBOM as ${filenamePrefix}.sbom.json in the scanned directory`, + }), }; - public async run(): Promise { + public async run(): Promise { const { flags } = await this.parse(ScanEol); - const scan = await this.getScan(flags, this.config); + const sbom = await this.loadSbom(); + + if (!sbom.components?.length) { + this.log('No components found in scan. Report not generated.'); + return; + } - ux.action.stop(); + const scan = await this.scanSbom(sbom); if (flags.save) { - await this.saveReport(scan); + const reportPath = this.saveReport(scan, flags.dir); + this.log(`Report saved to ${reportPath}`); + } + + if (flags.saveSbom && !flags.file) { + const sbomPath = this.saveSbom(flags.dir, sbom); + this.log(`SBOM saved to ${sbomPath}`); } if (!this.jsonEnabled()) { this.displayResults(scan); - - if (scan.id) { - this.printWebReportUrl(scan.id); - } - - this.log('* Use --json to output the report payload'); - this.log(`* Use --save to save the report to ${filenamePrefix}.report.json`); - this.log('* Use --help for more commands or options'); } return scan; } - private async getScan(flags: Record, config: Command['config']): Promise { - if (flags.purls) { - const purls = this.getPurlsFromFile(flags.purls); - return this.scanPurls(purls); - } + private async loadSbom(): Promise { + const { flags } = await this.parse(ScanEol); - const sbom = await ScanSbom.loadSbom(flags, config); - return this.scanSbom(sbom); - } + const spinner = ora(); + spinner.start(flags.file ? 'Loading SBOM file' : 'Generating SBOM'); - private getPurlsFromFile(filePath: string): string[] { - const spinner = ora().start(`Loading purls from \`${filePath}\``); - try { - const purlsFileString = fs.readFileSync(filePath, 'utf8'); - const purls = parsePurlsFile(purlsFileString); - spinner.succeed(`Loaded purls from \`${filePath}\``); - return purls; - } catch (error) { - spinner.fail(`Failed to read purls from \`${filePath}\``); - this.error(`Failed to read purls file. ${getErrorMessage(error)}`); + const sbom = flags.file ? this.getSbomFromFile(flags.file) : await this.getSbomFromScan(flags.dir); + if (!sbom) { + spinner.fail(flags.file ? 'Failed to load SBOM file' : 'Failed to generate SBOM'); + throw new Error('SBOM not generated'); } - } - private printWebReportUrl(id: string): void { - this.log(ux.colorize('bold', '-'.repeat(40))); - const reportCardUrl = config.eolReportUrl; - const url = ux.colorize( - 'blue', - terminalLink(new URL(reportCardUrl).hostname, `${reportCardUrl}/${id}`, { fallback: (_, url) => url }), - ); - this.log(`🌐 View your full EOL report at: ${url}\n`); + spinner.succeed(flags.file ? 'Loaded SBOM file' : 'Generated SBOM'); + + return sbom; } - private async scanSbom(sbom: Sbom): Promise { + private async scanSbom(sbom: CdxBom): Promise { const spinner = ora().start('Scanning for EOL packages'); try { - const scan = await submitScan({ sbom: trimCdxBom(sbom as CdxBom) }); + const scan = await submitScan({ sbom: trimCdxBom(sbom) }); spinner.succeed('Scan completed'); return scan; } catch (error) { @@ -113,87 +111,58 @@ export default class ScanEol extends Command { } } - private async scanPurls(purls: string[]): Promise { - const spinner = ora().start('Scanning for EOL packages'); + private saveReport(report: EolReport, dir: string): string { try { - const scan = await submitPurls(purls); - spinner.succeed('Scan completed'); - return scan; + return saveReportToFile(dir, report); } catch (error) { - spinner.fail('Scanning failed'); - this.error(`Failed to submit scan to NES. ${getErrorMessage(error)}`); + this.error(getErrorMessage(error)); } } - private async saveReport(report: EolReport): Promise { - const { flags } = await this.parse(ScanEol); - const reportPath = path.join(flags.dir || process.cwd(), `${filenamePrefix}.report.json`); - + private saveSbom(dir: string, sbom: CdxBom): string { try { - fs.writeFileSync(reportPath, JSON.stringify(report, null, 2)); - this.log(`Report saved to ${filenamePrefix}.report.json`); + return saveSbomToFile(dir, sbom); } catch (error) { - if (!isErrnoException(error)) { - this.error(`Failed to save report: ${getErrorMessage(error)}`); - } - if (error.code === 'EACCES') { - this.error(`Permission denied. Unable to save report to ${filenamePrefix}.report.json`); - } else if (error.code === 'ENOSPC') { - this.error(`No space left on device. Unable to save report to ${filenamePrefix}.report.json`); - } else { - this.error(`Failed to save report: ${getErrorMessage(error)}`); - } + this.error(getErrorMessage(error)); } } - private displayResults(report: EolReport) { - const { UNKNOWN, OK, EOL_UPCOMING, EOL, NES_AVAILABLE } = countComponentsByStatus(report); + private displayResults(report: EolReport): void { + const lines = formatScanResults(report); + for (const line of lines) { + this.log(line); + } - if (!UNKNOWN && !OK && !EOL_UPCOMING && !EOL) { - this.log(ux.colorize('yellow', 'No components found in scan.')); - return; + if (report.id) { + const lines = formatWebReportUrl(report.id, config.eolReportUrl); + for (const line of lines) { + this.log(line); + } } - this.log(ux.colorize('bold', 'Scan results:')); - this.log(ux.colorize('bold', '-'.repeat(40))); - this.log(ux.colorize('bold', `${report.components.length.toLocaleString()} total packages scanned`)); - this.log(ux.colorize(STATUS_COLORS.EOL, `${INDICATORS.EOL} ${EOL.toLocaleString().padEnd(5)} End-of-Life (EOL)`)); - this.log( - ux.colorize( - STATUS_COLORS.EOL_UPCOMING, - `${INDICATORS.EOL_UPCOMING}${EOL_UPCOMING.toLocaleString().padEnd(5)} EOL Upcoming`, - ), - ); - this.log(ux.colorize(STATUS_COLORS.OK, `${INDICATORS.OK} ${OK.toLocaleString().padEnd(5)} OK`)); - this.log( - ux.colorize(STATUS_COLORS.UNKNOWN, `${INDICATORS.UNKNOWN} ${UNKNOWN.toLocaleString().padEnd(5)} Unknown Status`), - ); - this.log( - ux.colorize( - STATUS_COLORS.UNKNOWN, - `${INDICATORS.UNKNOWN} ${NES_AVAILABLE.toLocaleString().padEnd(5)} HeroDevs NES Remediation${NES_AVAILABLE !== 1 ? 's' : ''} Available`, - ), - ); + this.log('* Use --json to output the report payload'); + this.log(`* Use --save to save the report to ${filenamePrefix}.report.json`); + this.log('* Use --help for more commands or options'); } -} -export function countComponentsByStatus(report: EolReport): Record { - const grouped: Record = { - UNKNOWN: 0, - OK: 0, - EOL_UPCOMING: 0, - EOL: 0, - NES_AVAILABLE: 0, - }; - - for (const component of report.components) { - const status = deriveComponentStatus(component.metadata); - grouped[status]++; - - if (component.nesRemediation?.remediations?.length) { - grouped.NES_AVAILABLE++; + private async getSbomFromScan(dirPath: string): Promise { + try { + validateDirectory(dirPath); + const sbom = await createSbom(dirPath); + if (!sbom) { + this.error(`SBOM failed to generate for dir: ${dirPath}`); + } + return sbom; + } catch (error) { + this.error(`Failed to scan directory: ${getErrorMessage(error)}`); } } - return grouped; + private getSbomFromFile(filePath: string): CdxBom { + try { + return readSbomFromFile(filePath); + } catch (error) { + this.error(getErrorMessage(error)); + } + } } diff --git a/src/commands/scan/sbom.ts b/src/commands/scan/sbom.ts deleted file mode 100644 index 893a5d8f..00000000 --- a/src/commands/scan/sbom.ts +++ /dev/null @@ -1,182 +0,0 @@ -import { spawn } from 'node:child_process'; -import fs from 'node:fs'; -import { join, resolve } from 'node:path'; -import { Command, Flags } from '@oclif/core'; -import ora from 'ora'; -import { filenamePrefix } from '../../config/constants.ts'; -import type { Sbom } from '../../service/eol/cdx.svc.ts'; -import { createSbom, validateIsCycloneDxSbom } from '../../service/eol/eol.svc.ts'; -import { getErrorMessage } from '../../service/error.svc.ts'; - -export default class ScanSbom extends Command { - static override description = 'Scan a SBOM for purls'; - static enableJsonFlag = true; - static override examples = [ - '<%= config.bin %> <%= command.id %> --dir=./my-project', - '<%= config.bin %> <%= command.id %> --file=path/to/sbom.json', - ]; - static override flags = { - file: Flags.string({ - char: 'f', - description: 'The file path of an existing cyclonedx sbom to scan for EOL', - }), - dir: Flags.string({ - char: 'd', - description: 'The directory to scan in order to create a cyclonedx sbom', - }), - save: Flags.boolean({ - char: 's', - default: false, - description: `Save the generated SBOM as ${filenamePrefix}.sbom.json in the scanned directory`, - }), - background: Flags.boolean({ - char: 'b', - default: false, - description: 'Run the scan in the background', - }), - }; - - static async loadSbom(flags: Record, config: Command['config']) { - const sbomArgs = ScanSbom.getSbomArgs(flags); - const sbomCommand = new ScanSbom(sbomArgs, config); - const sbom = await sbomCommand.run(); - if (!sbom) { - throw new Error('SBOM not generated'); - } - return sbom; - } - - static getSbomArgs(flags: Record): string[] { - const { dir, file, background } = flags ?? {}; - - const sbomArgs = ['--json']; - - if (file) sbomArgs.push('--file', file); - if (dir) sbomArgs.push('--dir', dir); - // if (save) sbomArgs.push('--save'); // only save if sbom command is used directly with -s flag - if (background) sbomArgs.push('--background'); - - return sbomArgs; - } - - getScanOptions() { - // intentionally provided for mocking - return {}; - } - - public async run(): Promise { - const { flags } = await this.parse(ScanSbom); - const { dir, save, file, background } = flags; - - // Validate that exactly one of --file or --dir is provided - if (file && dir) { - this.error('Cannot specify both --file and --dir flags. Please use one or the other.'); - } - let sbom: Sbom; - const path = dir || process.cwd(); - - const spinner = ora(); - if (!background) { - spinner.start(flags.file ? 'Loading SBOM file' : 'Generating SBOM'); - } - - if (file) { - sbom = this._getSbomFromFile(file); - } else if (background) { - this._getSbomInBackground(path); - this.log(`The scan is running in the background. The file will be saved at ${path}/${filenamePrefix}.sbom.json`); - return; - } else { - sbom = await this._getSbomFromScan(path); - if (save) { - this._saveSbom(path, sbom); - } - } - - if (sbom) { - spinner.succeed(flags.file ? 'Loaded SBOM file' : 'Generated SBOM'); - } else { - spinner.fail(flags.file ? 'Failed to load SBOM file' : 'Failed to generate SBOM'); - } - - if (!save) { - this.log(JSON.stringify(sbom, null, 2)); - } else if (sbom && !this.jsonEnabled()) { - this.log(`SBOM saved to ${path}/${filenamePrefix}.sbom.json`); - } - - return sbom; - } - - private async _getSbomFromScan(_dirFlag: string): Promise { - const dir = resolve(_dirFlag); - try { - if (!fs.existsSync(dir)) { - this.error(`Directory not found: ${dir}`); - } - const stats = fs.statSync(dir); - if (!stats.isDirectory()) { - this.error(`Path is not a directory: ${dir}`); - } - - const options = this.getScanOptions(); - const sbom = await createSbom(dir, options); - if (!sbom) { - this.error(`SBOM failed to generate for dir: ${dir}`); - } - return sbom; - } catch (error) { - this.error(`Failed to scan directory: ${getErrorMessage(error)}`); - } - } - - private _getSbomInBackground(path: string): void { - try { - const opts = this.getScanOptions(); - const args = [ - JSON.stringify({ - opts, - path, - }), - ]; - - const workerProcess = spawn('node', [join(import.meta.url, '../../service/eol/sbom.worker.js'), ...args], { - stdio: 'ignore', - detached: true, - env: { ...process.env }, - }); - - workerProcess.unref(); - } catch (error) { - this.error(`Failed to start background scan: ${getErrorMessage(error)}`); - } - } - - private _getSbomFromFile(_fileFlag: string): Sbom { - const file = resolve(_fileFlag); - try { - if (!fs.existsSync(file)) { - this.error(`SBOM file not found: ${file}`); - } - - const fileContent = fs.readFileSync(file, { - encoding: 'utf8', - flag: 'r', - }); - const sbom = JSON.parse(fileContent) as Sbom; - validateIsCycloneDxSbom(sbom); - return sbom; - } catch (error) { - this.error(`Failed to read SBOM file: ${getErrorMessage(error)}`); - } - } - - private _saveSbom(dir: string, sbom: Sbom) { - try { - const outputPath = join(dir, `${filenamePrefix}.sbom.json`); - fs.writeFileSync(outputPath, JSON.stringify(sbom, null, 2)); - } catch (error) { - this.error(`Failed to save SBOM: ${getErrorMessage(error)}`); - } - } -} diff --git a/src/service/eol/cdx.svc.ts b/src/service/cdx.svc.ts similarity index 77% rename from src/service/eol/cdx.svc.ts rename to src/service/cdx.svc.ts index 8ec9e5dc..7fe43ad0 100644 --- a/src/service/eol/cdx.svc.ts +++ b/src/service/cdx.svc.ts @@ -1,23 +1,6 @@ import { createBom } from '@cyclonedx/cdxgen'; -import { debugLogger } from '../../service/log.svc.ts'; -import type { CdxGenOptions } from './eol.svc.ts'; - -export interface SbomDependency { - ref: string; - dependsOn: string[]; -} - -export interface SbomEntry { - group: string; - name: string; - purl: string; - version: string; -} - -export interface Sbom { - components: SbomEntry[]; - dependencies: SbomDependency[]; -} +import type { CdxBom } from '@herodevs/eol-shared'; +import { debugLogger } from './log.svc.ts'; const author = process.env.npm_package_author ?? 'HeroDevs, Inc.'; @@ -87,8 +70,9 @@ export const SBOM_DEFAULT__OPTIONS = { * Lazy loads cdxgen (for ESM purposes), scans * `directory`, and returns the `bomJson` property. */ -export async function createBomFromDir(directory: string, opts: CdxGenOptions = {}) { - const sbom = await createBom(directory, { ...SBOM_DEFAULT__OPTIONS, ...opts }); +export async function createSbom(directory: string): Promise { + const sbom = await createBom(directory, SBOM_DEFAULT__OPTIONS); + if (!sbom) throw new Error('SBOM not generated'); debugLogger('Successfully generated SBOM'); - return sbom?.bomJson; + return sbom.bomJson; } diff --git a/src/service/committers.svc.ts b/src/service/committers.svc.ts deleted file mode 100644 index 180b6f4c..00000000 --- a/src/service/committers.svc.ts +++ /dev/null @@ -1,272 +0,0 @@ -export interface CommitEntry { - month: string; - author: string; -} - -export interface AuthorCommitCounts { - [author: string]: number; -} - -export interface MonthlyData { - [month: string]: AuthorCommitCounts; -} - -export interface ReportData { - monthly: { - [month: string]: { - [author: string]: number; - total: number; - }; - }; - overall: { - [author: string]: number; - total: number; - }; -} - -/** - * Parses git log output into structured data - * @param output - Git log command output - * @returns Parsed commit entries - */ -export function parseGitLogOutput(output: string): CommitEntry[] { - return output - .split('\n') - .filter(Boolean) - .map((line) => { - // Remove surrounding double quotes if present (e.g. "March|John Doe" → March|John Doe) - const [month, author] = line.replace(/^"(.*)"$/, '$1').split('|'); - return { month, author }; - }); -} - -/** - * Groups commit data by month - * @param entries - Commit entries - * @returns Object with months as keys and author commit counts as values - */ -export function groupCommitsByMonth(entries: CommitEntry[]): MonthlyData { - const result: MonthlyData = {}; - - // Group commits by month - const commitsByMonth = entries.reduce>((acc, entry) => { - const monthKey = entry.month; - - if (!acc[monthKey]) { - acc[monthKey] = []; - } - - acc[monthKey].push(entry); - - return acc; - }, {}); - - // Process each month - for (const [month, commits] of Object.entries(commitsByMonth)) { - if (!commits) { - result[month] = {}; - continue; - } - - // Count commits per author for this month - const commitsByAuthor = commits.reduce>((acc, entry) => { - const authorKey = entry.author; - - if (!acc[authorKey]) { - acc[authorKey] = []; - } - - acc[authorKey].push(entry); - - return acc; - }, {}); - - const authorCounts: AuthorCommitCounts = {}; - - for (const [author, authorCommits] of Object.entries(commitsByAuthor)) { - authorCounts[author] = authorCommits?.length ?? 0; - } - - result[month] = authorCounts; - } - - return result; -} - -/** - * Calculates overall commit statistics by author - * @param entries - Commit entries - * @returns Object with authors as keys and total commit counts as values - */ -export function calculateOverallStats(entries: CommitEntry[]): AuthorCommitCounts { - const commitsByAuthor = entries.reduce>((acc, entry) => { - const authorKey = entry.author; - - if (!acc[authorKey]) { - acc[authorKey] = []; - } - - acc[authorKey].push(entry); - - return acc; - }, {}); - const result: AuthorCommitCounts = {}; - - // Count commits for each author - for (const author in commitsByAuthor) { - result[author] = commitsByAuthor[author]?.length ?? 0; - } - - return result; -} - -/** - * Formats monthly report sections - * @param monthlyData - Grouped commit data by month - * @returns Formatted monthly report sections - */ -export function formatMonthlyReport(monthlyData: MonthlyData): string { - const sortedMonths = Object.keys(monthlyData).sort(); - let report = ''; - - for (const month of sortedMonths) { - report += `\n## ${month}\n`; - - const authors = Object.entries(monthlyData[month]).sort((a, b) => b[1] - a[1]); - - for (const [author, count] of authors) { - report += `${count.toString().padStart(6)} ${author}\n`; - } - - const monthTotal = authors.reduce((sum, [_, count]) => sum + count, 0); - report += `${monthTotal.toString().padStart(6)} TOTAL\n`; - } - - return report; -} - -/** - * Formats overall statistics section - * @param overallStats - Overall commit counts by author - * @param grandTotal - Total number of commits - * @returns Formatted overall statistics section - */ -export function formatOverallStats(overallStats: AuthorCommitCounts, grandTotal: number): string { - let report = '\n## Overall Statistics\n'; - - const sortedStats = Object.entries(overallStats).sort((a, b) => b[1] - a[1]); - - for (const [author, count] of sortedStats) { - report += `${count.toString().padStart(6)} ${author}\n`; - } - - report += `${grandTotal.toString().padStart(6)} GRAND TOTAL\n`; - - return report; -} - -/** - * Formats the report data as CSV - * @param data - The structured report data - */ -export function formatAsCsv(data: ReportData): string { - // First prepare all author names (for columns) - const allAuthors = new Set(); - - // Collect all unique author names - for (const monthData of Object.values(data.monthly)) { - for (const author of Object.keys(monthData)) { - if (author !== 'total') allAuthors.add(author); - } - } - - const authors = Array.from(allAuthors).sort(); - - // Create CSV header - let csv = `Month,${authors.join(',')},Total\n`; - - // Add monthly data rows - const sortedMonths = Object.keys(data.monthly).sort(); - for (const month of sortedMonths) { - csv += month; - - // Add data for each author - for (const author of authors) { - const count = data.monthly[month][author] || 0; - csv += `,${count}`; - } - - // Add monthly total - csv += `,${`${data.monthly[month].total}\n`}`; - } - - // Add overall totals row - csv += 'Overall'; - for (const author of authors) { - const count = data.overall[author] || 0; - csv += `,${count}`; - } - - csv += `,${data.overall.total}\n`; - - return csv; -} - -/** - * Formats the report data as text - * @param data - The structured report data - */ -export function formatAsText(data: ReportData): string { - let report = 'Monthly Commit Report\n'; - - // Monthly sections - const sortedMonths = Object.keys(data.monthly).sort(); - for (const month of sortedMonths) { - report += `\n## ${month}\n`; - - const authors = Object.entries(data.monthly[month]) - .filter(([author]) => author !== 'total') - .sort((a, b) => b[1] - a[1]); - - for (const [author, count] of authors) { - report += `${count.toString().padStart(6)} ${author}\n`; - } - - report += `${data.monthly[month].total.toString().padStart(6)} TOTAL\n`; - } - - // Overall statistics - report += '\n## Overall Statistics\n'; - const sortedEntries = Object.entries(data.overall) - .filter(([author]) => author !== 'total') - .sort((a, b) => b[1] - a[1]); - - for (const [author, count] of sortedEntries) { - report += `${count.toString().padStart(6)} ${author}\n`; - } - - report += `${data.overall.total.toString().padStart(6)} GRAND TOTAL\n`; - - return report; -} - -/** - * Format output based on user preference - * @param output - * @param reportData - * @returns - */ -export function formatOutputBasedOnFlag(output: string, reportData: ReportData) { - let formattedOutput: string; - switch (output) { - case 'json': - formattedOutput = JSON.stringify(reportData, null, 2); - break; - case 'csv': - formattedOutput = formatAsCsv(reportData); - break; - default: - formattedOutput = formatAsText(reportData); - } - return formattedOutput; -} diff --git a/src/service/display.svc.ts b/src/service/display.svc.ts new file mode 100644 index 00000000..05a88546 --- /dev/null +++ b/src/service/display.svc.ts @@ -0,0 +1,81 @@ +import { deriveComponentStatus } from '@herodevs/eol-shared'; +import type { ComponentStatus, EolReport } from '@herodevs/eol-shared'; +import { ux } from '@oclif/core'; +import terminalLink from 'terminal-link'; + +const STATUS_COLORS: Record = { + EOL: 'red', + UNKNOWN: 'default', + OK: 'green', + EOL_UPCOMING: 'yellow', +}; + +/** + * Formats status row text with appropriate color and icon + */ +export const getStatusRowText: Record string> = { + EOL: (text: string) => ux.colorize(STATUS_COLORS.EOL, `✗ ${text}`), + UNKNOWN: (text: string) => ux.colorize(STATUS_COLORS.UNKNOWN, `• ${text}`), + OK: (text: string) => ux.colorize(STATUS_COLORS.OK, `✔ ${text}`), + EOL_UPCOMING: (text: string) => ux.colorize(STATUS_COLORS.EOL_UPCOMING, `! ${text}`), +}; + +/** + * Counts components by their status, including NES remediation availability + */ +export function countComponentsByStatus(report: EolReport): Record { + const grouped: Record = { + UNKNOWN: 0, + OK: 0, + EOL_UPCOMING: 0, + EOL: 0, + NES_AVAILABLE: 0, + }; + + for (const component of report.components) { + const status = deriveComponentStatus(component.metadata); + grouped[status]++; + + if (component.nesRemediation?.remediations?.length) { + grouped.NES_AVAILABLE++; + } + } + + return grouped; +} + +/** + * Formats scan results for console display + */ +export function formatScanResults(report: EolReport): string[] { + const { UNKNOWN, OK, EOL_UPCOMING, EOL, NES_AVAILABLE } = countComponentsByStatus(report); + + if (!UNKNOWN && !OK && !EOL_UPCOMING && !EOL) { + return [ux.colorize('yellow', 'No components found in scan.')]; + } + + return [ + ux.colorize('bold', 'Scan results:'), + ux.colorize('bold', '-'.repeat(40)), + ux.colorize('bold', `${report.components.length.toLocaleString()} total packages scanned`), + getStatusRowText.EOL(`${EOL.toLocaleString().padEnd(5)} End-of-Life (EOL)`), + getStatusRowText.EOL_UPCOMING(`${EOL_UPCOMING.toLocaleString().padEnd(5)} EOL Upcoming`), + getStatusRowText.OK(`${OK.toLocaleString().padEnd(5)} OK`), + getStatusRowText.UNKNOWN(`${UNKNOWN.toLocaleString().padEnd(5)} Unknown Status`), + getStatusRowText.UNKNOWN( + `${NES_AVAILABLE.toLocaleString().padEnd(5)} HeroDevs NES Remediation${NES_AVAILABLE !== 1 ? 's' : ''} Available`, + ), + ]; +} + +/** + * Formats web report URL for console display + */ +export function formatWebReportUrl(id: string, reportCardUrl: string): string[] { + const url = ux.colorize( + 'blue', + terminalLink(new URL(reportCardUrl).hostname, `${reportCardUrl}/${id}`, { fallback: (_, url) => url }), + ); + + return [ux.colorize('bold', '-'.repeat(40)), `🌐 View your full EOL report at: ${url}\n`]; +} diff --git a/src/service/eol/eol.svc.ts b/src/service/eol/eol.svc.ts deleted file mode 100644 index 65398af6..00000000 --- a/src/service/eol/eol.svc.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { debugLogger } from '../../service/log.svc.ts'; -import { type Sbom, createBomFromDir } from './cdx.svc.ts'; - -export interface CdxGenOptions { - projectType?: string[]; -} - -export interface ScanOptions { - cdxgen?: CdxGenOptions; -} - -export type CdxCreator = (dir: string, opts: CdxGenOptions) => Promise<{ bomJson: Sbom }>; -export async function createSbom(directory: string, opts: ScanOptions = {}) { - const sbom = await createBomFromDir(directory, opts.cdxgen || {}); - if (!sbom) throw new Error('SBOM not generated'); - debugLogger('SBOM generated'); - return sbom; -} - -export function validateIsCycloneDxSbom(sbom: unknown): asserts sbom is Sbom { - if (!sbom || typeof sbom !== 'object') { - throw new Error('SBOM must be an object'); - } - - const s = sbom as Record; - - // Basic CycloneDX validation - if (!('bomFormat' in s) || s.bomFormat !== 'CycloneDX') { - throw new Error('Invalid SBOM format: must be CycloneDX'); - } - - if (!('specVersion' in s) || typeof s.specVersion !== 'string') { - throw new Error('Invalid SBOM: missing specVersion'); - } - - if (!('components' in s) || !Array.isArray(s.components)) { - throw new Error('Invalid SBOM: missing or invalid components array'); - } -} diff --git a/src/service/error.svc.ts b/src/service/error.svc.ts deleted file mode 100644 index 9a0df49e..00000000 --- a/src/service/error.svc.ts +++ /dev/null @@ -1,32 +0,0 @@ -export const isError = (error: unknown): error is Error => { - return error instanceof Error; -}; - -export const isErrnoException = (error: unknown): error is NodeJS.ErrnoException => { - return isError(error) && 'code' in error; -}; - -export const isApolloError = (error: unknown): error is ApolloError => { - return error instanceof ApolloError; -}; - -export const getErrorMessage = (error: unknown): string => { - if (isError(error)) { - return error.message; - } - return 'Unknown error'; -}; - -export class ApolloError extends Error { - public readonly originalError?: unknown; - - constructor(message: string, original?: unknown) { - if (isError(original)) { - super(`${message}: ${original.message}`); - } else { - super(`${message}: ${String(original)}`); - } - this.name = 'ApolloError'; - this.originalError = original; - } -} diff --git a/src/service/file.svc.ts b/src/service/file.svc.ts new file mode 100644 index 00000000..2725243e --- /dev/null +++ b/src/service/file.svc.ts @@ -0,0 +1,84 @@ +import fs from 'node:fs'; +import path, { join, resolve } from 'node:path'; +import { isCdxBom } from '@herodevs/eol-shared'; +import type { CdxBom, EolReport } from '@herodevs/eol-shared'; +import { filenamePrefix } from '../config/constants.ts'; +import { getErrorMessage } from './log.svc.ts'; + +export interface FileError extends Error { + code?: string; +} + +/** + * Reads an SBOM from a file path + */ +export function readSbomFromFile(filePath: string): CdxBom { + const file = resolve(filePath); + + if (!fs.existsSync(file)) { + throw new Error(`SBOM file not found: ${file}`); + } + + try { + const fileContent = fs.readFileSync(file, 'utf8'); + const sbom = JSON.parse(fileContent) as CdxBom; + if (!isCdxBom(sbom)) { + throw new Error(`Invalid SBOM file: ${file}`); + } + return sbom; + } catch (error) { + throw new Error(`Failed to read SBOM file: ${getErrorMessage(error)}`); + } +} + +/** + * Validates that a directory path exists and is actually a directory + */ +export function validateDirectory(dirPath: string): void { + const dir = resolve(dirPath); + + if (!fs.existsSync(dir)) { + throw new Error(`Directory not found: ${dir}`); + } + + const stats = fs.statSync(dir); + if (!stats.isDirectory()) { + throw new Error(`Path is not a directory: ${dir}`); + } +} + +/** + * Saves an SBOM to a file in the specified directory + */ +export function saveSbomToFile(dir: string, sbom: CdxBom): string { + const outputPath = join(dir, `${filenamePrefix}.sbom.json`); + + try { + fs.writeFileSync(outputPath, JSON.stringify(sbom, null, 2)); + return outputPath; + } catch (error) { + throw new Error(`Failed to save SBOM: ${getErrorMessage(error)}`); + } +} + +/** + * Saves an EOL report to a file in the specified directory + */ +export function saveReportToFile(dir: string, report: EolReport): string { + const reportPath = path.join(dir, `${filenamePrefix}.report.json`); + + try { + fs.writeFileSync(reportPath, JSON.stringify(report, null, 2)); + return reportPath; + } catch (error) { + const fileError = error as FileError; + + if (fileError.code === 'EACCES') { + throw new Error(`Permission denied. Unable to save report to ${filenamePrefix}.report.json`); + } + if (fileError.code === 'ENOSPC') { + throw new Error(`No space left on device. Unable to save report to ${filenamePrefix}.report.json`); + } + throw new Error(`Failed to save report: ${getErrorMessage(error)}`); + } +} diff --git a/src/service/log.svc.ts b/src/service/log.svc.ts index c5e3f0a5..d1cafa30 100644 --- a/src/service/log.svc.ts +++ b/src/service/log.svc.ts @@ -6,3 +6,13 @@ import debug from 'debug'; * All user-facing output should be handled by commands. */ export const debugLogger = debug('oclif:herodevs-debug'); + +export function getErrorMessage(error: unknown): string { + if (error instanceof Error) { + return error.message; + } + if (error && typeof error === 'object') { + return JSON.stringify(error); + } + return String(error) || 'Unknown error'; +} diff --git a/src/service/nes/nes.svc.ts b/src/service/nes/nes.svc.ts deleted file mode 100644 index 42dba399..00000000 --- a/src/service/nes/nes.svc.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { CdxBom, CreateEolReportInput, EolReport, EolReportMutationResponse } from '@herodevs/eol-shared'; -import type { NesApolloClient } from '../../api/nes/nes.client.ts'; -import { M_SCAN } from '../../api/queries/nes/sbom.ts'; -import { debugLogger } from '../log.svc.ts'; - -export const SbomScanner = - (client: NesApolloClient) => - async (input: CreateEolReportInput): Promise => { - const res = await client.mutate(M_SCAN.gql, { input }); - - const result = res.data?.eol?.createReport; - if (!result?.success || !result.report) { - debugLogger('failed scan %o', result || {}); - debugLogger('scan failed'); - - throw new Error('Failed to create EOL report'); - } - - return result.report; - }; diff --git a/src/service/purls.svc.ts b/src/service/purls.svc.ts deleted file mode 100644 index 644437df..00000000 --- a/src/service/purls.svc.ts +++ /dev/null @@ -1,115 +0,0 @@ -import type { Sbom, SbomDependency, SbomEntry } from './eol/cdx.svc.ts'; - -/** - * Formats a value for CSV output by wrapping it in quotes if it contains commas. - * This ensures that values containing commas aren't split into multiple columns - * when the CSV is opened in a spreadsheet application. - */ -export function formatCsvValue(value: string): string { - // If the value contains a comma, wrap it in quotes to preserve it as a single cell - return value.includes(',') ? `"${value}"` : value; -} - -/** - * Converts an array of PURLs into either CSV or JSON format. - * For CSV output, adds a header row with "purl" and formats values to preserve commas. - * For JSON output, returns a properly indented JSON string. - */ -export function getPurlOutput(purls: string[], output: string): string { - switch (output) { - case 'csv': - return ['purl', ...purls].map(formatCsvValue).join('\n'); - default: - return JSON.stringify({ purls }, null, 2); - } -} - -/** - * Extract PURLs from components recursively - */ -function extractPurlsFromComponents(components: SbomEntry[], purlSet: Set): void { - for (const component of components) { - if (component.purl) { - purlSet.add(component.purl); - } - } -} - -/** - * Extract PURLs from dependencies - */ -function extractPurlsFromDependencies(dependencies: SbomDependency[], purlSet: Set): void { - for (const dependency of dependencies) { - if (dependency.ref) { - purlSet.add(dependency.ref); - } - - if (dependency.dependsOn) { - for (const dep of dependency.dependsOn) { - purlSet.add(dep); - } - } - } -} - -/** - * Extract all PURLs from a CycloneDX SBOM, including components and dependencies - */ -export function extractPurls(sbom: Sbom): string[] { - const purlSet = new Set(); - - // Extract from direct components - if (sbom.components) { - extractPurlsFromComponents(sbom.components, purlSet); - } - - // Extract from dependencies - if (sbom.dependencies) { - extractPurlsFromDependencies(sbom.dependencies, purlSet); - } - - return Array.from(purlSet); -} - -/** - * Parse a purls file in either JSON or text format, including the format of - * herodevs.purls.json - { purls: [ 'pkg:npm/express@4.18.2', 'pkg:npm/react@18.3.1' ] } - * or a text file with one purl per line. - */ -export function parsePurlsFile(purlsFileString: string): string[] { - // Handle empty string - if (!purlsFileString.trim()) { - return []; - } - - try { - // Try parsing as JSON first - const parsed = JSON.parse(purlsFileString); - - if (parsed && Array.isArray(parsed.purls)) { - return parsed.purls; - } - - if (Array.isArray(parsed)) { - return parsed; - } - } catch { - // If not JSON, try parsing as text file - const lines = purlsFileString - .split('\n') - .map((line) => line.trim()) - .filter((line) => line.length > 0 && line.startsWith('pkg:')); - - // Handle single purl case (no newlines) - if (lines.length === 0 && purlsFileString.trim().startsWith('pkg:')) { - return [purlsFileString.trim()]; - } - - // Return any valid purls found - if (lines.length > 0) { - return lines; - } - } - - throw new Error('Invalid purls file: must be either JSON with purls array or text file with one purl per line'); -} diff --git a/src/service/eol/sbom.worker.ts b/src/service/sbom.worker.ts similarity index 93% rename from src/service/eol/sbom.worker.ts rename to src/service/sbom.worker.ts index 358d8473..188784e5 100644 --- a/src/service/eol/sbom.worker.ts +++ b/src/service/sbom.worker.ts @@ -1,7 +1,7 @@ import { writeFileSync } from 'node:fs'; import { join } from 'node:path'; import { createBom } from '@cyclonedx/cdxgen'; -import { filenamePrefix } from '../../config/constants.ts'; +import { filenamePrefix } from '../config/constants.ts'; import { SBOM_DEFAULT__OPTIONS } from './cdx.svc.ts'; process.on('uncaughtException', (err) => { diff --git a/src/ui/shared.ui.ts b/src/ui/shared.ui.ts deleted file mode 100644 index 79d5ef72..00000000 --- a/src/ui/shared.ui.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type { ComponentStatus } from '@herodevs/eol-shared'; -import { ux } from '@oclif/core'; - -export const STATUS_COLORS: Record = { - EOL: 'red', - UNKNOWN: 'default', - OK: 'green', - EOL_UPCOMING: 'yellow', -}; - -export const INDICATORS: Record = { - EOL: ux.colorize(STATUS_COLORS.EOL, '✗'), - UNKNOWN: ux.colorize(STATUS_COLORS.UNKNOWN, '•'), - OK: ux.colorize(STATUS_COLORS.OK, '✔'), - EOL_UPCOMING: ux.colorize(STATUS_COLORS.EOL_UPCOMING, '⚡'), -}; diff --git a/test/api/nes.client.test.ts b/test/api/nes.client.test.ts index bd1e8619..7262b5a8 100644 --- a/test/api/nes.client.test.ts +++ b/test/api/nes.client.test.ts @@ -1,35 +1,57 @@ import assert from 'node:assert'; -import { describe, it } from 'node:test'; -import { dedupeAndEncodePurls } from '../../src/api/nes/nes.client.ts'; +import { afterEach, beforeEach, describe, it } from 'node:test'; +import type { CreateEolReportInput } from '@herodevs/eol-shared'; +import { submitScan } from '../../src/api/nes.client.ts'; +import { FetchMock } from '../utils/mocks/fetch.mock.ts'; describe('nes.client', () => { - describe('dedupeAndEncodePurls', () => { - const inputs = [ - { - purls: ['pkg:npm/@angular/core@14.3.0', 'pkg:npm/npm-bundled@2.0.1', 'pkg:npm/%40angular/core@14.3.0'], - expected: ['pkg:npm/%40angular/core@14.3.0', 'pkg:npm/npm-bundled@2.0.1'], - description: 'should dedupe angular core purls', - }, - { - purls: ['pkg:npm/@angular/core@14.3.0', 'pkg:npm/npm-bundled@2.0.1', 'pkg:npm/rxjs@6.6.7'], - expected: ['pkg:npm/%40angular/core@14.3.0', 'pkg:npm/npm-bundled@2.0.1', 'pkg:npm/rxjs@6.6.7'], - description: 'should not dedupe unique purls', - }, - { - purls: [ - 'pkg:maven/org.apache.commons/commons-lang3@3.12.0', - 'pkg:maven/org.apache.commons/commons-lang3@3.12.0', - 'pkg:maven/org.apache.commons/commons-lang3@3.12.0', - ], - expected: ['pkg:maven/org.apache.commons/commons-lang3@3.12.0'], - description: 'should dedupe maven purls', - }, - ]; - for (const input of inputs) { - it(`should dedupe and encode purls: ${input.description}`, () => { - const result = dedupeAndEncodePurls(input.purls); - assert.deepStrictEqual(result, input.expected); - }); - } + let fetchMock: FetchMock; + + beforeEach(() => { + fetchMock = new FetchMock(); + }); + + afterEach(() => { + fetchMock.restore(); + }); + + it('returns report on successful createReport mutation', async () => { + const report = { + id: 'test-123', + createdOn: new Date().toISOString(), + metadata: {}, + components: [ + { purl: 'pkg:npm/bootstrap@3.1.1', metadata: { isEol: true } }, + { + purl: 'pkg:npm/is-core-module@2.11.0', + metadata: {}, + nesRemediation: { remediations: [{ urls: { main: 'https://example.com' } }] }, + }, + ], + }; + + fetchMock.addGraphQL({ + eol: { createReport: { success: true, report } }, + }); + + const input: CreateEolReportInput = { + sbom: { bomFormat: 'CycloneDX', components: [], specVersion: '1.4', version: 1 }, + }; + const res = await submitScan(input); + + assert.strictEqual(res.id, report.id); + assert.strictEqual(Array.isArray(res.components), true); + assert.strictEqual(res.components.length, 2); + }); + + it('throws when mutation returns unsuccessful response or no report', async () => { + fetchMock.addGraphQL({ + eol: { createReport: { success: false, report: null } }, + }); + + const input: CreateEolReportInput = { + sbom: { bomFormat: 'CycloneDX', components: [], specVersion: '1.4', version: 1 }, + }; + await assert.rejects(() => submitScan(input), /Failed to create EOL report/); }); }); diff --git a/test/commands/scan/eol.count.test.ts b/test/commands/scan/eol.count.test.ts new file mode 100644 index 00000000..70ee530e --- /dev/null +++ b/test/commands/scan/eol.count.test.ts @@ -0,0 +1,28 @@ +import assert from 'node:assert'; +import { describe, it } from 'node:test'; +import type { EolReport } from '@herodevs/eol-shared'; +import { countComponentsByStatus } from '../../../src/service/display.svc.ts'; + +describe('countComponentsByStatus', () => { + it('sums to total components and counts NES availability', () => { + const report: EolReport = { + id: 'r1', + createdOn: new Date().toISOString(), + metadata: {} as unknown as Record, + components: [ + { purl: 'pkg:npm/a@1.0.0', metadata: {} as unknown as Record }, + { + purl: 'pkg:npm/b@1.0.0', + metadata: {} as unknown as Record, + nesRemediation: { remediations: [{ urls: { main: 'https://example.com' } }] }, + }, + ], + } as unknown as EolReport; + + const counts = countComponentsByStatus(report); + const statusSum = counts.EOL + counts.EOL_UPCOMING + counts.OK + counts.UNKNOWN; + + assert.strictEqual(statusSum, report.components.length); + assert.strictEqual(counts.NES_AVAILABLE, 1); + }); +}); diff --git a/test/service/cdx.svc.test.ts b/test/service/cdx.svc.test.ts new file mode 100644 index 00000000..29dd178e --- /dev/null +++ b/test/service/cdx.svc.test.ts @@ -0,0 +1,33 @@ +import assert from 'node:assert'; +import { describe, it, mock } from 'node:test'; +// Node <22 may not support mock.module; skip tests if unavailable +const hasMockModule = typeof (mock as unknown as { module?: unknown }).module === 'function'; + +describe('cdx.svc createSbom', () => { + it('returns bomJson when cdxgen returns an object', { skip: !hasMockModule }, async () => { + const bomJson = { bomFormat: 'CycloneDX', specVersion: '1.6', components: [] }; + await mock.module('@cyclonedx/cdxgen', { + namedExports: { + // biome-ignore lint/suspicious/noExplicitAny: test-time ESM mock + createBom: async () => ({ bomJson }) as any, + }, + }); + + const mod = await import('../../src/service/cdx.svc.ts'); + const res = await mod.createSbom('/tmp/project'); + assert.deepStrictEqual(res, bomJson); + mock.restoreAll(); + }); + + it('throws when cdxgen returns a falsy value', { skip: !hasMockModule }, async () => { + await mock.module('@cyclonedx/cdxgen', { + namedExports: { + createBom: async () => null, + }, + }); + + const mod = await import('../../src/service/cdx.svc.ts'); + await assert.rejects(() => mod.createSbom('/tmp/project'), /SBOM not generated/); + mock.restoreAll(); + }); +}); diff --git a/test/service/committers.svc.test.ts b/test/service/committers.svc.test.ts deleted file mode 100644 index 77a09e65..00000000 --- a/test/service/committers.svc.test.ts +++ /dev/null @@ -1,352 +0,0 @@ -import assert from 'node:assert'; -import { describe, it } from 'node:test'; -import { - calculateOverallStats, - formatAsCsv, - formatAsText, - formatMonthlyReport, - formatOutputBasedOnFlag, - formatOverallStats, - groupCommitsByMonth, - parseGitLogOutput, -} from '../../src/service/committers.svc.ts'; - -describe('committers', () => { - // Sample test data to be reused across tests - const sampleGitLog = `January|John Doe -February|Jane Smith -January|John Doe -February|Bob Johnson -March|Jane Smith -January|Alice Brown`; - - const sampleEntries = [ - { month: 'January', author: 'John Doe' }, - { month: 'February', author: 'Jane Smith' }, - { month: 'January', author: 'John Doe' }, - { month: 'February', author: 'Bob Johnson' }, - { month: 'March', author: 'Jane Smith' }, - { month: 'January', author: 'Alice Brown' }, - ]; - - const sampleMonthlyData = { - January: { - 'John Doe': 2, - 'Alice Brown': 1, - }, - February: { - 'Jane Smith': 1, - 'Bob Johnson': 1, - }, - March: { - 'Jane Smith': 1, - }, - }; - - const sampleOverallStats = { - 'John Doe': 2, - 'Jane Smith': 2, - 'Bob Johnson': 1, - 'Alice Brown': 1, - }; - - const sampleReportData = { - monthly: { - January: { - 'John Doe': 2, - 'Alice Brown': 1, - total: 3, - }, - February: { - 'Jane Smith': 1, - 'Bob Johnson': 1, - total: 2, - }, - March: { - 'Jane Smith': 1, - total: 1, - }, - }, - overall: { - 'John Doe': 2, - 'Jane Smith': 2, - 'Bob Johnson': 1, - 'Alice Brown': 1, - total: 6, - }, - }; - - describe('parseGitLogOutput', () => { - it('should parse git log output into commit entries', () => { - const result = parseGitLogOutput(sampleGitLog); - - assert.deepStrictEqual(result, sampleEntries); - }); - - it('should handle empty input', () => { - const result = parseGitLogOutput(''); - - assert.deepStrictEqual(result, []); - }); - - it('should handle quoted input', () => { - const quotedLog = `"January|John Doe" -"February|Jane Smith"`; - - const expected = [ - { month: 'January', author: 'John Doe' }, - { month: 'February', author: 'Jane Smith' }, - ]; - - const result = parseGitLogOutput(quotedLog); - - assert.deepStrictEqual(result, expected); - }); - }); - - describe('groupCommitsByMonth', () => { - it('should group commit entries by month', () => { - const result = groupCommitsByMonth(sampleEntries); - - assert.deepStrictEqual(result, sampleMonthlyData); - }); - - it('should handle empty array', () => { - const result = groupCommitsByMonth([]); - - assert.deepStrictEqual(result, {}); - }); - }); - - describe('calculateOverallStats', () => { - it('should calculate overall commit statistics by author', () => { - const result = calculateOverallStats(sampleEntries); - - assert.deepStrictEqual(result, sampleOverallStats); - }); - - it('should handle empty array', () => { - const result = calculateOverallStats([]); - - assert.deepStrictEqual(result, {}); - }); - }); - - describe('formatMonthlyReport', () => { - it('should format monthly report sections correctly', () => { - const result = formatMonthlyReport(sampleMonthlyData); - - // Check for expected sections and content patterns - assert.ok(result.includes('## January')); - assert.ok(result.includes('## February')); - assert.ok(result.includes('## March')); - assert.ok(result.includes(' 2 John Doe')); - assert.ok(result.includes(' 1 Alice Brown')); - assert.ok(result.includes(' 3 TOTAL')); - }); - - it('should sort months alphabetically', () => { - // Create sample with unordered months - const unorderedMonths = { - March: { 'Jane Smith': 1 }, - January: { 'John Doe': 2 }, - February: { 'Bob Johnson': 1 }, - }; - - const result = formatMonthlyReport(unorderedMonths); - const lines = result.split('\n'); - - // Find the actual order of month headings in the output - const monthLines = lines.filter((line) => line.startsWith('## ')); - const months = monthLines.map((line) => line.substring(3).trim()); - - // Check correct order - assert.strictEqual(months[0], 'February'); - assert.strictEqual(months[1], 'January'); - assert.strictEqual(months[2], 'March'); - }); - - it('should sort authors by commit count', () => { - const result = formatMonthlyReport({ - January: { - 'John Doe': 2, - 'Alice Brown': 1, - }, - }); - - const lines = result.split('\n'); - - // Find the lines containing the authors within the January section - const johnDoeLine = lines.find((line) => line.includes('John Doe')); - const aliceBrownLine = lines.find((line) => line.includes('Alice Brown')); - - if (!johnDoeLine) { - assert.fail('Could not find expected lines in output: John Doe missing'); - } else if (!aliceBrownLine) { - assert.fail('Could not find expected lines in output:Alice Brown missing'); - } else { - // Find their positions in the array - const johnDoeIndex = lines.indexOf(johnDoeLine); - const aliceBrownIndex = lines.indexOf(aliceBrownLine); - - // John Doe should come before Alice Brown due to higher commit count - assert.ok(johnDoeIndex >= 0, 'John Doe line not found'); - assert.ok(aliceBrownIndex >= 0, 'Alice Brown line not found'); - assert.ok(johnDoeIndex < aliceBrownIndex, 'John Doe line should appear before Alice Brown line'); - } - }); - }); - - describe('formatOverallStats', () => { - it('should format overall statistics section correctly', () => { - const grandTotal = 6; // Sum of all commits - const result = formatOverallStats(sampleOverallStats, grandTotal); - - assert.ok(result.includes('## Overall Statistics')); - assert.ok(result.includes(' 2 John Doe')); - assert.ok(result.includes(' 2 Jane Smith')); - assert.ok(result.includes(' 1 Bob Johnson')); - assert.ok(result.includes(' 1 Alice Brown')); - assert.ok(result.includes(' 6 GRAND TOTAL')); - }); - - it('should sort authors by commit count', () => { - const grandTotal = 6; - const result = formatOverallStats(sampleOverallStats, grandTotal); - - const lines = result.split('\n'); - - // Find lines with John Doe and Bob Johnson - const johnDoeLine = lines.find((line) => line.includes('John Doe')); - const bobJohnsonLine = lines.find((line) => line.includes('Bob Johnson')); - - // Find their positions in the array - if (!johnDoeLine) { - assert.fail('Could not find expected lines in output: John Doe missing'); - } else if (!bobJohnsonLine) { - assert.fail('Could not find expected lines in output:Bob Johnson missing'); - } else { - const johnDoeIndex = lines.indexOf(johnDoeLine); - const bobJohnsonIndex = lines.indexOf(bobJohnsonLine); - - assert.ok(johnDoeIndex >= 0, 'John Doe line not found'); - assert.ok(bobJohnsonIndex >= 0, 'Bob Johnson line not found'); - assert.ok(johnDoeIndex < bobJohnsonIndex, 'John Doe line should appear before Bob Johnson line'); - } - }); - }); - - describe('formatAsCsv', () => { - it('should format report data as CSV', () => { - const result = formatAsCsv(sampleReportData); - - // Check that output is a string and contains expected header - assert.strictEqual(typeof result, 'string'); - assert.ok(result.startsWith('Month,')); - - // Check that all months are included - assert.ok(result.includes('\nJanuary,')); - assert.ok(result.includes('\nFebruary,')); - assert.ok(result.includes('\nMarch,')); - assert.ok(result.includes('\nOverall,')); - - // Check that columns are correctly ordered and total is included - const lines = result.split('\n'); - assert.ok(lines[0].endsWith(',Total')); - - // Check that values are included properly - const januaryLine = lines.find((line) => line.startsWith('January')); - if (januaryLine) { - assert.ok(januaryLine.includes(',2,') && januaryLine.includes(',3')); - } else { - assert.fail('Cannot find January line'); - } - }); - - it('should handle empty data', () => { - const emptyData = { - monthly: {}, - overall: { total: 0 }, - }; - - const result = formatAsCsv(emptyData); - - assert.strictEqual(typeof result, 'string'); - assert.ok(result.startsWith('Month,')); - assert.ok(result.includes('Overall')); - }); - }); - - describe('formatAsText', () => { - it('should format report data as text', () => { - const result = formatAsText(sampleReportData); - - // Check for expected sections and formatting - assert.ok(result.includes('Monthly Commit Report')); - assert.ok(result.includes('## January')); - assert.ok(result.includes('## February')); - assert.ok(result.includes('## March')); - assert.ok(result.includes('## Overall Statistics')); - - // Check for correct counts and totals - assert.ok(result.includes(' 2 John Doe')); - assert.ok(result.includes(' 3 TOTAL')); // January total - assert.ok(result.includes(' 6 GRAND TOTAL')); - }); - - it('should sort authors by commit count within each section', () => { - const result = formatAsText(sampleReportData); - - const lines = result.split('\n'); - - // Find the January section boundaries - const januarySectionStart = lines.findIndex((line) => line === '## January'); - const nextSectionStart = lines.findIndex((line, index) => index > januarySectionStart && line.startsWith('## ')); - - // Extract just the January section lines - const januarySectionLines = lines.slice(januarySectionStart, nextSectionStart); - - // Find the lines for John Doe and Alice Brown within this section - const johnDoeLine = januarySectionLines.find((line) => line.includes('John Doe')); - const aliceBrownLine = januarySectionLines.find((line) => line.includes('Alice Brown')); - - if (!johnDoeLine) { - assert.fail('Could not find expected lines in output: John Doe missing'); - } else if (!aliceBrownLine) { - assert.fail('Could not find expected lines in output:Alice Brown missing'); - } else { - // Get their positions within the January section array - const johnDoeIndex = januarySectionLines.indexOf(johnDoeLine); - const aliceBrownIndex = januarySectionLines.indexOf(aliceBrownLine); - - assert.ok(johnDoeIndex >= 0, 'John Doe line not found in January section'); - assert.ok(aliceBrownIndex >= 0, 'Alice Brown line not found in January section'); - assert.ok(johnDoeIndex < aliceBrownIndex, 'John Doe line should appear before Alice Brown line'); - } - }); - }); - - describe('formatOutputBasedOnFlag', () => { - it('should format as JSON when json flag is provided', () => { - const result = formatOutputBasedOnFlag('json', sampleReportData); - - // Verify result is valid JSON - const parsedResult = JSON.parse(result); - assert.deepStrictEqual(parsedResult, sampleReportData); - }); - - it('should format as CSV when csv flag is provided', () => { - const result = formatOutputBasedOnFlag('csv', sampleReportData); - - assert.strictEqual(typeof result, 'string'); - assert.ok(result.startsWith('Month,')); - }); - - it('should format as text when any other flag is provided', () => { - const result = formatOutputBasedOnFlag('text', sampleReportData); - - assert.ok(result.includes('Monthly Commit Report')); - assert.ok(result.includes('## January')); - }); - }); -}); diff --git a/test/service/display.svc.test.ts b/test/service/display.svc.test.ts new file mode 100644 index 00000000..522f7d3a --- /dev/null +++ b/test/service/display.svc.test.ts @@ -0,0 +1,124 @@ +import assert from 'node:assert'; +import { describe, it } from 'node:test'; +import type { EolReport } from '@herodevs/eol-shared'; +import { countComponentsByStatus, formatScanResults, formatWebReportUrl } from '../../src/service/display.svc.ts'; + +describe('display.svc', () => { + const mockReport: EolReport = { + id: 'test-id', + components: [ + { + purl: 'pkg:npm/test@1.0.0', + metadata: { + isEol: true, + eolAt: '2023-01-01T00:00:00.000Z', + eolReasons: ['End of life'], + cve: [], + }, + nesRemediation: { + remediations: [ + { + purls: { nes: 'pkg:npm/test-nes@1.0.0', oss: 'pkg:npm/test@1.0.0' }, + urls: { main: 'https://herodevs.com' }, + }, + ], + }, + }, + { + purl: 'pkg:npm/test2@2.0.0', + metadata: { + isEol: false, + eolAt: null, + eolReasons: [], + cve: [], + }, + }, + { + purl: 'pkg:npm/test3@3.0.0', + metadata: null, + }, + ], + createdOn: new Date().toISOString(), + metadata: { + totalComponentsCount: 3, + unknownComponentsCount: 1, + }, + }; + + describe('countComponentsByStatus', () => { + it('should count components by status correctly', () => { + const counts = countComponentsByStatus(mockReport); + + assert.strictEqual(counts.EOL, 1); + assert.strictEqual(counts.OK, 1); + assert.strictEqual(counts.UNKNOWN, 1); + assert.strictEqual(counts.EOL_UPCOMING, 0); + assert.strictEqual(counts.NES_AVAILABLE, 1); + }); + + it('should handle empty report', () => { + const emptyReport: EolReport = { + id: 'empty', + createdOn: new Date().toISOString(), + components: [], + metadata: { + totalComponentsCount: 0, + unknownComponentsCount: 0, + }, + }; + + const counts = countComponentsByStatus(emptyReport); + + assert.strictEqual(counts.EOL, 0); + assert.strictEqual(counts.OK, 0); + assert.strictEqual(counts.UNKNOWN, 0); + assert.strictEqual(counts.EOL_UPCOMING, 0); + assert.strictEqual(counts.NES_AVAILABLE, 0); + }); + }); + + describe('formatScanResults', () => { + it('should format scan results with components', () => { + const lines = formatScanResults(mockReport); + + assert.ok(lines.length > 0); + assert.ok(lines.some((line) => line.includes('Scan results:'))); + assert.ok(lines.some((line) => line.includes('3 total packages scanned'))); + }); + + it('should handle empty scan results', () => { + const emptyReport: EolReport = { + id: 'empty', + createdOn: new Date().toISOString(), + components: [], + metadata: { + totalComponentsCount: 0, + unknownComponentsCount: 0, + }, + }; + + const lines = formatScanResults(emptyReport); + + assert.strictEqual(lines.length, 1); + assert.ok(lines[0].includes('No components found')); + }); + }); + + describe('formatWebReportUrl', () => { + it('should format web report URL correctly', () => { + const lines = formatWebReportUrl('test-id', 'https://example.com'); + + assert.strictEqual(lines.length, 2); + assert.ok(lines[0].includes('-'.repeat(40))); + assert.ok(lines[1].includes('View your full EOL report')); + assert.ok(lines[1].includes('test-id')); + }); + + it('should handle different URLs', () => { + const lines = formatWebReportUrl('another-id', 'https://reports.herodevs.com'); + + assert.ok(lines[1].includes('reports.herodevs.com')); + assert.ok(lines[1].includes('another-id')); + }); + }); +}); diff --git a/test/service/eol.svc.test.ts b/test/service/eol.svc.test.ts deleted file mode 100644 index 0caccfb9..00000000 --- a/test/service/eol.svc.test.ts +++ /dev/null @@ -1,46 +0,0 @@ -import assert from 'node:assert'; -import { describe, it } from 'node:test'; -import { validateIsCycloneDxSbom } from '../../src/service/eol/eol.svc.ts'; - -describe('eol.svc', () => { - describe('validateIsCycloneDxSbom', () => { - it('should throw an error if the SBOM is not an object', () => { - assert.throws(() => validateIsCycloneDxSbom('hello')); - }); - - it('should throw an error if the SBOM is not in CycloneDX format', () => { - assert.throws(() => - validateIsCycloneDxSbom({ - bomFormat: 'SPDX', - }), - ); - }); - - it('should throw an error if the SBOM is missing a specVersion', () => { - assert.throws(() => - validateIsCycloneDxSbom({ - bomFormat: 'CycloneDX', - }), - ); - }); - - it('should throw an error if the SBOM has no component array', () => { - assert.throws(() => - validateIsCycloneDxSbom({ - bomFormat: 'CycloneDX', - specVersion: 1, - }), - ); - }); - - it('should not throw an error if all criteria pass', () => { - assert.throws(() => - validateIsCycloneDxSbom({ - bomFormat: 'CycloneDX', - components: [], - specVersion: 1, - }), - ); - }); - }); -}); diff --git a/test/service/file.svc.test.ts b/test/service/file.svc.test.ts new file mode 100644 index 00000000..fef46c74 --- /dev/null +++ b/test/service/file.svc.test.ts @@ -0,0 +1,121 @@ +import assert from 'node:assert'; +import fs from 'node:fs'; +import { mkdtemp, writeFile } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { after, describe, it } from 'node:test'; +import type { CdxBom, EolReport } from '@herodevs/eol-shared'; +import { readSbomFromFile, saveReportToFile, saveSbomToFile, validateDirectory } from '../../src/service/file.svc.ts'; + +describe('file.svc', () => { + let tempDir: string; + + const mockSbom: CdxBom = { + bomFormat: 'CycloneDX', + specVersion: '1.6', + version: 1, + components: [], + } as unknown as CdxBom; + + const mockReport: EolReport = { + id: 'test-id', + createdOn: new Date().toISOString(), + components: [], + metadata: { + totalComponentsCount: 0, + unknownComponentsCount: 0, + }, + }; + + after(() => { + if (tempDir && fs.existsSync(tempDir)) { + fs.rmSync(tempDir, { recursive: true, force: true }); + } + }); + + describe('readSbomFromFile', () => { + it('should read and parse a valid SBOM file', async () => { + tempDir = await mkdtemp(join(tmpdir(), 'file-svc-test-')); + const filePath = join(tempDir, 'test.json'); + await writeFile(filePath, JSON.stringify(mockSbom)); + + const result = readSbomFromFile(filePath); + assert.deepStrictEqual(result, mockSbom); + }); + + it('should throw error for non-existent file', () => { + assert.throws(() => readSbomFromFile('/non/existent/path'), /SBOM file not found/); + }); + + it('should throw error for invalid JSON', async () => { + tempDir = await mkdtemp(join(tmpdir(), 'file-svc-test-')); + const filePath = join(tempDir, 'invalid.json'); + await writeFile(filePath, 'invalid json'); + + assert.throws(() => readSbomFromFile(filePath), /Failed to read SBOM file/); + }); + }); + + describe('validateDirectory', () => { + it('should not throw for valid directory', async () => { + tempDir = await mkdtemp(join(tmpdir(), 'file-svc-test-')); + assert.doesNotThrow(() => validateDirectory(tempDir)); + }); + + it('should throw error for non-existent directory', () => { + assert.throws(() => validateDirectory('/non/existent/directory'), /Directory not found/); + }); + + it('should throw error for file instead of directory', async () => { + tempDir = await mkdtemp(join(tmpdir(), 'file-svc-test-')); + const filePath = join(tempDir, 'file.txt'); + await writeFile(filePath, 'content'); + + assert.throws(() => validateDirectory(filePath), /Path is not a directory/); + }); + }); + + describe('saveSbomToFile', () => { + it('should save SBOM to file successfully', async () => { + tempDir = await mkdtemp(join(tmpdir(), 'file-svc-test-')); + + const outputPath = saveSbomToFile(tempDir, mockSbom); + + assert.ok(fs.existsSync(outputPath)); + const content = fs.readFileSync(outputPath, 'utf8'); + const parsed = JSON.parse(content); + assert.deepStrictEqual(parsed, mockSbom); + }); + + it('should return the correct output path', async () => { + tempDir = await mkdtemp(join(tmpdir(), 'file-svc-test-')); + + const outputPath = saveSbomToFile(tempDir, mockSbom); + + assert.ok(outputPath.endsWith('herodevs.sbom.json')); + assert.ok(outputPath.includes(tempDir)); + }); + }); + + describe('saveReportToFile', () => { + it('should save report to file successfully', async () => { + tempDir = await mkdtemp(join(tmpdir(), 'file-svc-test-')); + + const outputPath = saveReportToFile(tempDir, mockReport); + + assert.ok(fs.existsSync(outputPath)); + const content = fs.readFileSync(outputPath, 'utf8'); + const parsed = JSON.parse(content); + assert.deepStrictEqual(parsed, mockReport); + }); + + it('should return the correct output path', async () => { + tempDir = await mkdtemp(join(tmpdir(), 'file-svc-test-')); + + const outputPath = saveReportToFile(tempDir, mockReport); + + assert.ok(outputPath.endsWith('herodevs.report.json')); + assert.ok(outputPath.includes(tempDir)); + }); + }); +}); diff --git a/test/service/log.svc.test.ts b/test/service/log.svc.test.ts new file mode 100644 index 00000000..b76a57e0 --- /dev/null +++ b/test/service/log.svc.test.ts @@ -0,0 +1,22 @@ +import assert from 'node:assert'; +import { describe, it } from 'node:test'; +import { debugLogger, getErrorMessage } from '../../src/service/log.svc.ts'; + +describe('log.svc', () => { + it('getErrorMessage returns error.message for Error', () => { + const msg = getErrorMessage(new Error('boom')); + assert.strictEqual(msg, 'boom'); + }); + + it('getErrorMessage stringifies non-Error objects', () => { + assert.strictEqual(getErrorMessage({ bad: 'x' }), '{"bad":"x"}'); + }); + + it('getErrorMessage stringifies non-Error', () => { + assert.strictEqual(getErrorMessage('x'), 'x'); + }); + + it('debugLogger is a function', () => { + assert.strictEqual(typeof debugLogger, 'function'); + }); +}); diff --git a/test/service/purls.svc.test.ts b/test/service/purls.svc.test.ts deleted file mode 100644 index 35accce6..00000000 --- a/test/service/purls.svc.test.ts +++ /dev/null @@ -1,233 +0,0 @@ -import assert from 'node:assert'; -import { describe, it } from 'node:test'; -import type { Sbom } from '../../src/service/eol/cdx.svc.ts'; -import { extractPurls, formatCsvValue, getPurlOutput, parsePurlsFile } from '../../src/service/purls.svc.ts'; - -describe('getPurlOutput', () => { - describe('json output', () => { - it('should format purls as JSON with proper indentation', () => { - const purls = ['pkg:npm/react@18.2.0', 'pkg:npm/typescript@5.0.0']; - const result = getPurlOutput(purls, 'json'); - const expected = JSON.stringify({ purls }, null, 2); - assert.strictEqual(result, expected); - }); - - it('should handle empty array', () => { - const purls: string[] = []; - const result = getPurlOutput(purls, 'json'); - assert.strictEqual(result, JSON.stringify({ purls }, null, 2)); - }); - }); - - describe('csv output', () => { - it('should format purls with header', () => { - const purls = ['pkg:npm/react@18.2.0', 'pkg:npm/typescript@5.0.0']; - const result = getPurlOutput(purls, 'csv'); - const expected = 'purl\npkg:npm/react@18.2.0\npkg:npm/typescript@5.0.0'; - assert.strictEqual(result, expected); - }); - - it('should handle empty array', () => { - const purls: string[] = []; - const result = getPurlOutput(purls, 'csv'); - assert.strictEqual(result, 'purl'); - }); - }); -}); - -describe('formatCsvValue', () => { - it('should return value unchanged when no commas present', () => { - const value = 'pkg:npm/react@18.2.0'; - assert.strictEqual(formatCsvValue(value), value); - }); - - it('should wrap value in quotes when comma present', () => { - const value = 'pkg:npm/bar@1.0.0,beta'; - assert.strictEqual(formatCsvValue(value), '"pkg:npm/bar@1.0.0,beta"'); - }); - - it('should handle empty string', () => { - assert.strictEqual(formatCsvValue(''), ''); - }); -}); - -describe('parsePurlsFile', () => { - describe('JSON format', () => { - it('should parse herodevs.purls.json format', () => { - const input = JSON.stringify({ - purls: ['pkg:npm/@apollo/client@3.13.5', 'pkg:npm/react@18.2.0'], - }); - const result = parsePurlsFile(input); - assert.deepStrictEqual(result, ['pkg:npm/@apollo/client@3.13.5', 'pkg:npm/react@18.2.0']); - }); - - it('should parse direct JSON array', () => { - const input = JSON.stringify(['pkg:npm/express@4.18.2', 'pkg:npm/typescript@5.0.0']); - const result = parsePurlsFile(input); - assert.deepStrictEqual(result, ['pkg:npm/express@4.18.2', 'pkg:npm/typescript@5.0.0']); - }); - }); - - describe('text format', () => { - it('should parse text file with one purl per line', () => { - const input = `pkg:npm/react@18.2.0 -pkg:npm/typescript@5.0.0`; - const result = parsePurlsFile(input); - assert.deepStrictEqual(result, ['pkg:npm/react@18.2.0', 'pkg:npm/typescript@5.0.0']); - }); - - it('should handle empty lines and whitespace', () => { - const input = ` - pkg:npm/react@18.2.0 - - pkg:npm/typescript@5.0.0 - `; - const result = parsePurlsFile(input); - assert.deepStrictEqual(result, ['pkg:npm/react@18.2.0', 'pkg:npm/typescript@5.0.0']); - }); - - it('should filter out invalid lines', () => { - const input = ` - not-a-purl - pkg:npm/react@18.2.0 - also-not-a-purl - pkg:npm/typescript@5.0.0 - `; - const result = parsePurlsFile(input); - assert.deepStrictEqual(result, ['pkg:npm/react@18.2.0', 'pkg:npm/typescript@5.0.0']); - }); - }); - - describe('error handling', () => { - it('should throw error for invalid JSON', () => { - const input = '{ invalid json }'; - assert.throws(() => parsePurlsFile(input), { - message: 'Invalid purls file: must be either JSON with purls array or text file with one purl per line', - }); - }); - - it('should return empty array for empty file', () => { - const result = parsePurlsFile(''); - assert.deepStrictEqual(result, []); - }); - - it('should return empty array for whitespace-only file', () => { - const result = parsePurlsFile(' \n \t '); - assert.deepStrictEqual(result, []); - }); - - it('should throw error for file with no valid purls', () => { - const input = 'not-a-purl\nstill-not-a-purl'; - assert.throws(() => parsePurlsFile(input), { - message: 'Invalid purls file: must be either JSON with purls array or text file with one purl per line', - }); - }); - }); -}); - -describe('extractPurls', () => { - it('should extract purls from components', () => { - const sbom: Sbom = { - components: [ - { - group: '', - name: 'react', - version: '18.2.0', - purl: 'pkg:npm/react@18.2.0', - }, - { - group: '', - name: 'typescript', - version: '5.0.0', - purl: 'pkg:npm/typescript@5.0.0', - }, - ], - dependencies: [], - }; - const result = extractPurls(sbom); - assert.deepStrictEqual(result, ['pkg:npm/react@18.2.0', 'pkg:npm/typescript@5.0.0']); - }); - - it('should extract purls from direct dependencies', () => { - const sbom: Sbom = { - components: [], - dependencies: [ - { ref: 'pkg:npm/express@4.18.2', dependsOn: [] }, - { ref: 'pkg:npm/lodash@4.17.21', dependsOn: [] }, - ], - }; - const result = extractPurls(sbom); - assert.deepStrictEqual(result, ['pkg:npm/express@4.18.2', 'pkg:npm/lodash@4.17.21']); - }); - - it('should extract purls from transitive dependencies', () => { - const sbom: Sbom = { - components: [], - dependencies: [ - { - ref: 'pkg:npm/express@4.18.2', - dependsOn: ['pkg:npm/body-parser@1.20.2', 'pkg:npm/cookie-parser@1.4.6'], - }, - { - ref: 'pkg:npm/react@18.2.0', - dependsOn: ['pkg:npm/scheduler@0.23.0'], - }, - ], - }; - const result = extractPurls(sbom); - assert.deepStrictEqual( - result.sort(), - [ - 'pkg:npm/express@4.18.2', - 'pkg:npm/body-parser@1.20.2', - 'pkg:npm/cookie-parser@1.4.6', - 'pkg:npm/react@18.2.0', - 'pkg:npm/scheduler@0.23.0', - ].sort(), - ); - }); - - it('should handle empty components and dependencies', () => { - const sbom: Sbom = { - components: [], - dependencies: [], - }; - const result = extractPurls(sbom); - assert.deepStrictEqual(result, []); - }); - - it('should handle mixed components and dependencies', () => { - const sbom: Sbom = { - components: [ - { - group: '', - name: 'react', - version: '18.2.0', - purl: 'pkg:npm/react@18.2.0', - }, - { - group: '', - name: 'typescript', - version: '5.0.0', - purl: 'pkg:npm/typescript@5.0.0', - }, - ], - dependencies: [ - { - ref: 'pkg:npm/express@4.18.2', - dependsOn: ['pkg:npm/body-parser@1.20.2'], - }, - ], - }; - const result = extractPurls(sbom); - assert.deepStrictEqual( - result.sort(), - [ - 'pkg:npm/react@18.2.0', - 'pkg:npm/typescript@5.0.0', - 'pkg:npm/express@4.18.2', - 'pkg:npm/body-parser@1.20.2', - ].sort(), - ); - }); -}); diff --git a/test/service/sbom.background.test.ts b/test/service/sbom.background.test.ts deleted file mode 100644 index 52403713..00000000 --- a/test/service/sbom.background.test.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { ok } from 'node:assert'; -import child_process from 'node:child_process'; -import { test } from 'node:test'; -import type { Config } from '@oclif/core'; -import sinon from 'sinon'; -import ScanSbom from '../../src/commands/scan/sbom.ts'; - -test('ScanSbom - Run scan in the background', async () => { - const cmd = new ScanSbom([], {} as Config); - const flags = { - background: true, - dir: './some-dir', - }; - - // @ts-ignore - const parseStub = sinon.stub(cmd, 'parse' as keyof ScanSbom).returns({ flags }); - - sinon.stub(child_process, 'spawn').returns({ - unref: sinon.stub(), - } as unknown as child_process.ChildProcess); - - const logSpy = sinon.spy(cmd, 'log'); - - await cmd.run(); - - ok(parseStub.calledOnce); - - ok( - logSpy.calledWith('The scan is running in the background. The file will be saved at ./some-dir/herodevs.sbom.json'), - ); -});