mirror of
https://github.com/aquasecurity/trivy.git
synced 2025-12-12 07:40:48 -08:00
feat(nodejs): add root and workspace for yarn packages (#8535)
Co-authored-by: knqyf263 <knqyf263@gmail.com>
This commit is contained in:
@@ -43,14 +43,26 @@ Trivy analyzes `node_modules` for licenses.
|
||||
By default, Trivy doesn't report development dependencies. Use the `--include-dev-deps` flag to include them.
|
||||
|
||||
### Yarn
|
||||
Trivy parses `yarn.lock`, which doesn't contain information about development dependencies.
|
||||
Trivy also uses `package.json` file to handle [aliases](https://classic.yarnpkg.com/lang/en/docs/cli/add/#toc-yarn-add-alias).
|
||||
Trivy parses `yarn.lock`.
|
||||
|
||||
To exclude devDependencies and allow aliases, `package.json` also needs to be present next to `yarn.lock`.
|
||||
Trivy also analyzes additional files to gather more information about the detected dependencies.
|
||||
|
||||
Trivy analyzes `.yarn` (Yarn 2+) or `node_modules` (Yarn Classic) folder next to the yarn.lock file to detect licenses.
|
||||
- package.json
|
||||
- node_modules/**
|
||||
|
||||
By default, Trivy doesn't report development dependencies. Use the `--include-dev-deps` flag to include them.
|
||||
#### Package relationships
|
||||
`yarn.lock` files don't contain information about package relationships, such as direct or indirect dependencies.
|
||||
To enrich this information, Trivy parses the `package.json` file located next to the `yarn.lock` file as well as workspace `package.json` files.
|
||||
|
||||
By default, Trivy doesn't report development dependencies.
|
||||
Use the `--include-dev-deps` flag to include them in the results.
|
||||
|
||||
#### Development dependencies
|
||||
`yarn.lock` files don't contain information about package groups, such as production and development dependencies.
|
||||
To identify dev dependencies and support [aliases][yarn-aliases], Trivy parses the `package.json` file located next to the `yarn.lock` file as well as workspace `package.json` files.
|
||||
|
||||
#### Licenses
|
||||
Trivy analyzes the `.yarn` directory (for Yarn 2+) or the `node_modules` directory (for Yarn Classic) located next to the `yarn.lock` file to detect licenses.
|
||||
|
||||
### pnpm
|
||||
Trivy parses `pnpm-lock.yaml`, then finds production dependencies and builds a [tree][dependency-graph] of dependencies with vulnerabilities.
|
||||
@@ -74,5 +86,6 @@ It only extracts package names, versions and licenses for those packages.
|
||||
|
||||
[dependency-graph]: ../../configuration/reporting.md#show-origins-of-vulnerable-dependencies
|
||||
[pnpm-lockfile-v6]: https://github.com/pnpm/spec/blob/fd3238639af86c09b7032cc942bab3438b497036/lockfile/6.0.md
|
||||
[yarn-aliases]: https://classic.yarnpkg.com/lang/en/docs/cli/add/#toc-yarn-add-alias
|
||||
|
||||
[^1]: [yarn.lock](#bun) must be generated
|
||||
|
||||
@@ -294,7 +294,7 @@ This feature allows you to focus on vulnerabilities in specific types of depende
|
||||
In Trivy, there are four types of package relationships:
|
||||
|
||||
1. `root`: The root package being scanned
|
||||
2. `workspace`: Workspaces of the root package (Currently only `pom.xml` and `cargo.lock` files are supported)
|
||||
2. `workspace`: Workspaces of the root package (Currently only `pom.xml`, `yarn.lock` and `cargo.lock` files are supported)
|
||||
3. `direct`: Direct dependencies of the root/workspace package
|
||||
4. `indirect`: Transitive dependencies
|
||||
5. `unknown`: Packages whose relationship cannot be determined
|
||||
|
||||
17
integration/testdata/yarn.json.golden
vendored
17
integration/testdata/yarn.json.golden
vendored
@@ -21,6 +21,23 @@
|
||||
"Class": "lang-pkgs",
|
||||
"Type": "yarn",
|
||||
"Packages": [
|
||||
{
|
||||
"ID": "integration@1.0.0",
|
||||
"Name": "integration",
|
||||
"Identifier": {
|
||||
"PURL": "pkg:npm/integration@1.0.0",
|
||||
"UID": "830dfbb17accac93"
|
||||
},
|
||||
"Version": "1.0.0",
|
||||
"Licenses": [
|
||||
"MIT"
|
||||
],
|
||||
"Relationship": "root",
|
||||
"DependsOn": [
|
||||
"jquery@3.2.1"
|
||||
],
|
||||
"Layer": {}
|
||||
},
|
||||
{
|
||||
"ID": "jquery@3.2.1",
|
||||
"Name": "jquery",
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
{
|
||||
"name": "package2",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"is-odd": "^3.0.1"
|
||||
|
||||
@@ -2,6 +2,7 @@ package yarn
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"cmp"
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
@@ -27,6 +28,7 @@ import (
|
||||
"github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/nodejs/license"
|
||||
"github.com/aquasecurity/trivy/pkg/fanal/types"
|
||||
"github.com/aquasecurity/trivy/pkg/log"
|
||||
"github.com/aquasecurity/trivy/pkg/set"
|
||||
"github.com/aquasecurity/trivy/pkg/utils/fsutils"
|
||||
xio "github.com/aquasecurity/trivy/pkg/x/io"
|
||||
)
|
||||
@@ -162,36 +164,28 @@ func (a yarnAnalyzer) Version() int {
|
||||
// distinguishing between direct and transitive dependencies as well as production and development dependencies.
|
||||
func (a yarnAnalyzer) analyzeDependencies(fsys fs.FS, dir string, app *types.Application, patterns map[string][]string) error {
|
||||
packageJsonPath := path.Join(dir, types.NpmPkg)
|
||||
directDeps, directDevDeps, err := a.parsePackageJsonDependencies(fsys, packageJsonPath)
|
||||
root, workspaces, err := a.parsePackageJSON(fsys, packageJsonPath)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
a.logger.Debug("package.json not found", log.FilePath(packageJsonPath))
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return xerrors.Errorf("unable to parse %s: %w", dir, err)
|
||||
return xerrors.Errorf("unable to parse root package.json: %w", err)
|
||||
}
|
||||
|
||||
// yarn.lock file can contain same packages with different versions
|
||||
// save versions separately for version comparison by comparator
|
||||
pkgIDs := lo.SliceToMap(app.Packages, func(pkg types.Package) (string, types.Package) {
|
||||
// Since yarn.lock file can contain same packages with different versions
|
||||
// we need to save versions separately for version comparison.
|
||||
pkgs := lo.SliceToMap(app.Packages, func(pkg types.Package) (string, types.Package) {
|
||||
return pkg.ID, pkg
|
||||
})
|
||||
|
||||
// Walk prod dependencies
|
||||
pkgs, err := a.walkDependencies(app.Packages, pkgIDs, directDeps, patterns, false)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("unable to walk dependencies: %w", err)
|
||||
if err := a.resolveRootDependencies(&root, pkgs, patterns); err != nil {
|
||||
return xerrors.Errorf("unable to resolve root dependencies: %w", err)
|
||||
}
|
||||
|
||||
// Walk dev dependencies
|
||||
devPkgs, err := a.walkDependencies(app.Packages, pkgIDs, directDevDeps, patterns, true)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("unable to walk dependencies: %w", err)
|
||||
if err := a.resolveWorkspaceDependencies(workspaces, pkgs, patterns); err != nil {
|
||||
return xerrors.Errorf("unable to resolve workspace dependencies: %w", err)
|
||||
}
|
||||
|
||||
// Merge prod and dev dependencies.
|
||||
// If the same package is found in both prod and dev dependencies, use the one in prod.
|
||||
pkgs = lo.Assign(devPkgs, pkgs)
|
||||
|
||||
pkgSlice := lo.Values(pkgs)
|
||||
sort.Sort(types.Packages(pkgSlice))
|
||||
|
||||
@@ -200,11 +194,94 @@ func (a yarnAnalyzer) analyzeDependencies(fsys fs.FS, dir string, app *types.App
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a yarnAnalyzer) walkDependencies(pkgs []types.Package, pkgIDs map[string]types.Package,
|
||||
directDeps map[string]string, patterns map[string][]string, dev bool) (map[string]types.Package, error) {
|
||||
func (a yarnAnalyzer) parsePackageJSON(fsys fs.FS, filePath string) (packagejson.Package, []packagejson.Package, error) {
|
||||
// Parse package.json
|
||||
f, err := fsys.Open(filePath)
|
||||
if err != nil {
|
||||
return packagejson.Package{}, nil, xerrors.Errorf("file open error: %w", err)
|
||||
}
|
||||
defer func() { _ = f.Close() }()
|
||||
|
||||
root, err := a.packageJsonParser.Parse(f)
|
||||
if err != nil {
|
||||
return packagejson.Package{}, nil, xerrors.Errorf("parse error: %w", err)
|
||||
}
|
||||
|
||||
root.Package.ID = cmp.Or(root.Package.ID, filePath) // In case the package.json doesn't have a name or version
|
||||
root.Package.Relationship = types.RelationshipRoot
|
||||
|
||||
workspaces, err := a.traverseWorkspaces(fsys, path.Dir(filePath), root.Workspaces)
|
||||
if err != nil {
|
||||
return packagejson.Package{}, nil, xerrors.Errorf("traverse workspaces error: %w", err)
|
||||
}
|
||||
for i := range workspaces {
|
||||
workspaces[i].Package.Relationship = types.RelationshipWorkspace
|
||||
|
||||
// Add workspace as a child of root
|
||||
root.DependsOn = append(root.DependsOn, workspaces[i].ID)
|
||||
}
|
||||
|
||||
return root, workspaces, nil
|
||||
}
|
||||
|
||||
func (a yarnAnalyzer) resolveRootDependencies(root *packagejson.Package, pkgs map[string]types.Package,
|
||||
patterns map[string][]string) error {
|
||||
if err := a.resolveDependencies(root, pkgs, patterns); err != nil {
|
||||
return xerrors.Errorf("unable to resolve dependencies: %w", err)
|
||||
}
|
||||
|
||||
// Add root package to the package map
|
||||
slices.Sort(root.Package.DependsOn)
|
||||
pkgs[root.Package.ID] = root.Package
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a yarnAnalyzer) resolveWorkspaceDependencies(workspaces []packagejson.Package, pkgs map[string]types.Package,
|
||||
patterns map[string][]string) error {
|
||||
if len(workspaces) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, workspace := range workspaces {
|
||||
if err := a.resolveDependencies(&workspace, pkgs, patterns); err != nil {
|
||||
return xerrors.Errorf("unable to resolve dependencies: %w", err)
|
||||
}
|
||||
|
||||
// Add workspace to the package map
|
||||
slices.Sort(workspace.Package.DependsOn)
|
||||
pkgs[workspace.ID] = workspace.Package
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// resolveDependencies resolves production and development dependencies from direct dependencies and patterns.
|
||||
// It also flags dependencies as direct or indirect and updates the dependencies of the parent package.
|
||||
func (a yarnAnalyzer) resolveDependencies(pkg *packagejson.Package, pkgs map[string]types.Package, patterns map[string][]string) error {
|
||||
// Recursively walk dependencies and flags development dependencies.
|
||||
// Walk development dependencies first to avoid overwriting production dependencies.
|
||||
directDevDeps := pkg.DevDependencies
|
||||
if err := a.walkDependencies(&pkg.Package, pkgs, directDevDeps, patterns, true); err != nil {
|
||||
return xerrors.Errorf("unable to walk dependencies: %w", err)
|
||||
}
|
||||
|
||||
// Recursively walk dependencies and flags production dependencies.
|
||||
directProdDeps := lo.Assign(pkg.Dependencies, pkg.OptionalDependencies)
|
||||
if err := a.walkDependencies(&pkg.Package, pkgs, directProdDeps, patterns, false); err != nil {
|
||||
return xerrors.Errorf("unable to walk dependencies: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// walkDependencies recursively walk dependencies and flags them as direct or indirect.
|
||||
// Note that it overwrites the existing package map.
|
||||
func (a yarnAnalyzer) walkDependencies(parent *types.Package, pkgs map[string]types.Package, directDeps map[string]string,
|
||||
patterns map[string][]string, dev bool) error {
|
||||
|
||||
// Identify direct dependencies
|
||||
directPkgs := make(map[string]types.Package)
|
||||
seenIDs := set.New[string]()
|
||||
for _, pkg := range pkgs {
|
||||
constraint, ok := directDeps[pkg.Name]
|
||||
if !ok {
|
||||
@@ -224,78 +301,61 @@ func (a yarnAnalyzer) walkDependencies(pkgs []types.Package, pkgIDs map[string]t
|
||||
if pkgPatterns, found := patterns[pkg.ID]; !found || !slices.Contains(pkgPatterns, dependency.ID(types.Yarn, pkg.Name, constraint)) {
|
||||
// npm has own comparer to compare versions
|
||||
if match, err := a.comparer.MatchVersion(pkg.Version, constraint); err != nil {
|
||||
return nil, xerrors.Errorf("unable to match version for %s", pkg.Name)
|
||||
return xerrors.Errorf("unable to match version for %s", pkg.Name)
|
||||
} else if !match {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// If the package is already marked as a production dependency, skip overwriting it.
|
||||
// Since the dev field is boolean, it cannot determine if the package is already processed,
|
||||
// so we need to check the relationship field.
|
||||
if pkg.Relationship == types.RelationshipUnknown || pkg.Dev {
|
||||
pkg.Dev = dev
|
||||
}
|
||||
|
||||
// Mark as a direct dependency
|
||||
pkg.Indirect = false
|
||||
pkg.Relationship = types.RelationshipDirect
|
||||
pkg.Dev = dev
|
||||
directPkgs[pkg.ID] = pkg
|
||||
|
||||
pkgs[pkg.ID] = pkg
|
||||
seenIDs.Append(pkg.ID)
|
||||
|
||||
// Add a direct dependency to the parent package
|
||||
parent.DependsOn = append(parent.DependsOn, pkg.ID)
|
||||
|
||||
// Walk indirect dependencies
|
||||
a.walkIndirectDependencies(pkg, pkgs, seenIDs)
|
||||
}
|
||||
|
||||
// Walk indirect dependencies
|
||||
for _, pkg := range directPkgs {
|
||||
a.walkIndirectDependencies(pkg, pkgIDs, directPkgs)
|
||||
}
|
||||
|
||||
return directPkgs, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a yarnAnalyzer) walkIndirectDependencies(pkg types.Package, pkgIDs, deps map[string]types.Package) {
|
||||
func (a yarnAnalyzer) walkIndirectDependencies(pkg types.Package, pkgs map[string]types.Package, seenIDs set.Set[string]) {
|
||||
for _, pkgID := range pkg.DependsOn {
|
||||
if _, ok := deps[pkgID]; ok {
|
||||
continue
|
||||
if seenIDs.Contains(pkgID) {
|
||||
continue // Skip if we've already seen this package
|
||||
}
|
||||
|
||||
dep, ok := pkgIDs[pkgID]
|
||||
dep, ok := pkgs[pkgID]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if dep.Relationship == types.RelationshipUnknown || dep.Dev {
|
||||
dep.Dev = pkg.Dev
|
||||
}
|
||||
dep.Indirect = true
|
||||
dep.Relationship = types.RelationshipIndirect
|
||||
dep.Dev = pkg.Dev
|
||||
deps[dep.ID] = dep
|
||||
a.walkIndirectDependencies(dep, pkgIDs, deps)
|
||||
pkgs[dep.ID] = dep
|
||||
|
||||
seenIDs.Append(dep.ID)
|
||||
|
||||
// Recursively walk dependencies
|
||||
a.walkIndirectDependencies(dep, pkgs, seenIDs)
|
||||
}
|
||||
}
|
||||
|
||||
func (a yarnAnalyzer) parsePackageJsonDependencies(fsys fs.FS, filePath string) (map[string]string, map[string]string, error) {
|
||||
// Parse package.json
|
||||
f, err := fsys.Open(filePath)
|
||||
if err != nil {
|
||||
return nil, nil, xerrors.Errorf("file open error: %w", err)
|
||||
}
|
||||
defer func() { _ = f.Close() }()
|
||||
|
||||
rootPkg, err := a.packageJsonParser.Parse(f)
|
||||
if err != nil {
|
||||
return nil, nil, xerrors.Errorf("parse error: %w", err)
|
||||
}
|
||||
|
||||
// Merge dependencies and optionalDependencies
|
||||
dependencies := lo.Assign(rootPkg.Dependencies, rootPkg.OptionalDependencies)
|
||||
devDependencies := rootPkg.DevDependencies
|
||||
|
||||
if len(rootPkg.Workspaces) > 0 {
|
||||
pkgs, err := a.traverseWorkspaces(fsys, path.Dir(filePath), rootPkg.Workspaces)
|
||||
if err != nil {
|
||||
return nil, nil, xerrors.Errorf("traverse workspaces error: %w", err)
|
||||
}
|
||||
for _, pkg := range pkgs {
|
||||
dependencies = lo.Assign(dependencies, pkg.Dependencies, pkg.OptionalDependencies)
|
||||
devDependencies = lo.Assign(devDependencies, pkg.DevDependencies)
|
||||
}
|
||||
}
|
||||
|
||||
return dependencies, devDependencies, nil
|
||||
}
|
||||
|
||||
func (a yarnAnalyzer) traverseWorkspaces(fsys fs.FS, dir string, workspaces []string) ([]packagejson.Package, error) {
|
||||
var pkgs []packagejson.Package
|
||||
|
||||
@@ -308,6 +368,7 @@ func (a yarnAnalyzer) traverseWorkspaces(fsys fs.FS, dir string, workspaces []st
|
||||
if err != nil {
|
||||
return xerrors.Errorf("unable to parse %q: %w", path, err)
|
||||
}
|
||||
pkg.Package.ID = cmp.Or(pkg.Package.ID, path) // In case the package.json doesn't have a name or version
|
||||
pkgs = append(pkgs, pkg)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -26,6 +26,20 @@ func Test_yarnLibraryAnalyzer_Analyze(t *testing.T) {
|
||||
Type: types.Yarn,
|
||||
FilePath: "yarn.lock",
|
||||
Packages: types.Packages{
|
||||
{
|
||||
ID: "90@1.0.0",
|
||||
Name: "90",
|
||||
Version: "1.0.0",
|
||||
Relationship: types.RelationshipRoot,
|
||||
Licenses: []string{
|
||||
"MIT",
|
||||
},
|
||||
DependsOn: []string{
|
||||
"js-tokens@2.0.0",
|
||||
"prop-types@15.7.2",
|
||||
"scheduler@0.13.6",
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: "js-tokens@2.0.0",
|
||||
Name: "js-tokens",
|
||||
@@ -134,7 +148,7 @@ func Test_yarnLibraryAnalyzer_Analyze(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Project with workspace placed in sub dir",
|
||||
name: "project with workspace placed in sub dir",
|
||||
dir: "testdata/project-with-workspace-in-subdir",
|
||||
want: &analyzer.AnalysisResult{
|
||||
Applications: []types.Application{
|
||||
@@ -142,6 +156,27 @@ func Test_yarnLibraryAnalyzer_Analyze(t *testing.T) {
|
||||
Type: types.Yarn,
|
||||
FilePath: "foo/yarn.lock",
|
||||
Packages: types.Packages{
|
||||
{
|
||||
ID: "@test/foo@1.0.0",
|
||||
Name: "@test/foo",
|
||||
Version: "1.0.0",
|
||||
Relationship: types.RelationshipRoot,
|
||||
Licenses: []string{
|
||||
"MIT",
|
||||
},
|
||||
DependsOn: []string{
|
||||
"@test/bar-generators@0.0.1",
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: "@test/bar-generators@0.0.1",
|
||||
Name: "@test/bar-generators",
|
||||
Version: "0.0.1",
|
||||
Relationship: types.RelationshipWorkspace,
|
||||
DependsOn: []string{
|
||||
"hoek@6.1.3",
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: "hoek@6.1.3",
|
||||
Name: "hoek",
|
||||
@@ -307,6 +342,16 @@ func Test_yarnLibraryAnalyzer_Analyze(t *testing.T) {
|
||||
Type: types.Yarn,
|
||||
FilePath: "yarn.lock",
|
||||
Packages: []types.Package{
|
||||
{
|
||||
ID: "yarn-3-licenses@1.0.0",
|
||||
Name: "yarn-3-licenses",
|
||||
Version: "1.0.0",
|
||||
Relationship: types.RelationshipRoot,
|
||||
DependsOn: []string{
|
||||
"is-callable@1.2.7",
|
||||
"is-odd@3.0.1",
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: "is-callable@1.2.7",
|
||||
Name: "is-callable",
|
||||
@@ -362,6 +407,14 @@ func Test_yarnLibraryAnalyzer_Analyze(t *testing.T) {
|
||||
Type: types.Yarn,
|
||||
FilePath: "yarn.lock",
|
||||
Packages: types.Packages{
|
||||
{
|
||||
ID: "package.json",
|
||||
Relationship: types.RelationshipRoot,
|
||||
DependsOn: []string{
|
||||
"debug@4.3.5",
|
||||
"js-tokens@9.0.0",
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: "debug@4.3.5",
|
||||
Name: "debug",
|
||||
@@ -417,6 +470,21 @@ func Test_yarnLibraryAnalyzer_Analyze(t *testing.T) {
|
||||
Type: types.Yarn,
|
||||
FilePath: "yarn.lock",
|
||||
Packages: types.Packages{
|
||||
{
|
||||
ID: "test@1.0.0",
|
||||
Name: "test",
|
||||
Version: "1.0.0",
|
||||
Relationship: types.RelationshipRoot,
|
||||
Licenses: []string{
|
||||
"MIT",
|
||||
},
|
||||
DependsOn: []string{
|
||||
"foo-debug@4.3.4",
|
||||
"foo-json@0.8.33",
|
||||
"foo-ms@2.1.3",
|
||||
"foo-uuid@9.0.7",
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: "foo-json@0.8.33",
|
||||
Name: "@types/jsonstream",
|
||||
@@ -535,6 +603,54 @@ func Test_yarnLibraryAnalyzer_Analyze(t *testing.T) {
|
||||
Type: types.Yarn,
|
||||
FilePath: "yarn.lock",
|
||||
Packages: types.Packages{
|
||||
{
|
||||
ID: "yarn-workspace-test@1.0.0",
|
||||
Name: "yarn-workspace-test",
|
||||
Version: "1.0.0",
|
||||
Relationship: types.RelationshipRoot,
|
||||
DependsOn: []string{
|
||||
"c@0.0.0",
|
||||
"package1@0.0.0",
|
||||
"packages/package2/package.json",
|
||||
"prettier@2.8.8",
|
||||
"util1@0.0.0",
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: "packages/package2/package.json",
|
||||
Relationship: types.RelationshipWorkspace,
|
||||
DependsOn: []string{
|
||||
"is-odd@3.0.1",
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: "c@0.0.0",
|
||||
Name: "c",
|
||||
Version: "0.0.0",
|
||||
Relationship: types.RelationshipWorkspace,
|
||||
DependsOn: []string{
|
||||
"is-number@7.0.0",
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: "package1@0.0.0",
|
||||
Name: "package1",
|
||||
Version: "0.0.0",
|
||||
Relationship: types.RelationshipWorkspace,
|
||||
DependsOn: []string{
|
||||
"scheduler@0.23.0",
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: "util1@0.0.0",
|
||||
Name: "util1",
|
||||
Version: "0.0.0",
|
||||
Relationship: types.RelationshipWorkspace,
|
||||
DependsOn: []string{
|
||||
"js-tokens@8.0.1",
|
||||
"prop-types@15.8.1",
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: "is-number@7.0.0",
|
||||
Name: "is-number",
|
||||
@@ -702,6 +818,13 @@ func Test_yarnLibraryAnalyzer_Analyze(t *testing.T) {
|
||||
Type: types.Yarn,
|
||||
FilePath: "yarn.lock",
|
||||
Packages: []types.Package{
|
||||
{
|
||||
ID: "package.json",
|
||||
Relationship: types.RelationshipRoot,
|
||||
DependsOn: []string{
|
||||
"@vue/compiler-sfc@2.7.14",
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: "@vue/compiler-sfc@2.7.14",
|
||||
Name: "@vue/compiler-sfc",
|
||||
|
||||
@@ -267,6 +267,13 @@ func ApplyLayers(layers []ftypes.BlobInfo) ftypes.ArtifactDetail {
|
||||
}
|
||||
|
||||
func newPURL(pkgType ftypes.TargetType, metadata types.Metadata, pkg ftypes.Package) *packageurl.PackageURL {
|
||||
// Possible cases when package doesn't have name/version (e.g. local package.json).
|
||||
// For these cases we don't need to create PURL, because this PURL will be incorrect.
|
||||
// TODO Dmitriy - move to `purl` package
|
||||
if pkg.Name == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
p, err := purl.New(pkgType, metadata, pkg)
|
||||
if err != nil {
|
||||
log.Error("Failed to create PackageURL", log.Err(err))
|
||||
|
||||
@@ -487,13 +487,27 @@ func excludeDevDeps(apps []ftypes.Application, include bool) {
|
||||
onceInfo := sync.OnceFunc(func() {
|
||||
log.Info("Suppressing dependencies for development and testing. To display them, try the '--include-dev-deps' flag.")
|
||||
})
|
||||
|
||||
for i := range apps {
|
||||
apps[i].Packages = lo.Filter(apps[i].Packages, func(lib ftypes.Package, _ int) bool {
|
||||
if lib.Dev {
|
||||
devDeps := set.New[string]()
|
||||
apps[i].Packages = lo.Filter(apps[i].Packages, func(pkg ftypes.Package, _ int) bool {
|
||||
if pkg.Dev {
|
||||
onceInfo()
|
||||
devDeps.Append(pkg.ID)
|
||||
}
|
||||
return !lib.Dev
|
||||
return !pkg.Dev
|
||||
})
|
||||
|
||||
// Remove development dependencies from dependencies of root and workspace packages
|
||||
for j, pkg := range apps[i].Packages {
|
||||
if pkg.Relationship != ftypes.RelationshipRoot && pkg.Relationship != ftypes.RelationshipWorkspace {
|
||||
continue
|
||||
}
|
||||
apps[i].Packages[j].DependsOn = lo.Filter(apps[i].Packages[j].DependsOn, func(dep string, _ int) bool {
|
||||
return !devDeps.Contains(dep)
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user