diff --git a/pkg/fanal/analyzer/language/nodejs/yarn/testdata/monorepo/c/package.json b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/monorepo/c/package.json new file mode 100644 index 000000000000..7fc842765891 --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/monorepo/c/package.json @@ -0,0 +1,7 @@ +{ + "name": "c", + "version": "0.0.0", + "dependencies": { + "is-number": "^7.0.0" + } +} diff --git a/pkg/fanal/analyzer/language/nodejs/yarn/testdata/monorepo/package.json b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/monorepo/package.json new file mode 100644 index 000000000000..ce55f5435751 --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/monorepo/package.json @@ -0,0 +1,13 @@ +{ + "name": "yarn-workspace-test", + "version": "1.0.0", + "packageManager": "yarn@3.4.1", + "private": true, + "workspaces": [ + "packages/**", + "c" + ], + "devDependencies": { + "prettier": "^2.8.8" + } +} diff --git a/pkg/fanal/analyzer/language/nodejs/yarn/testdata/monorepo/packages/package1/package.json b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/monorepo/packages/package1/package.json new file mode 100644 index 000000000000..0983a7733942 --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/monorepo/packages/package1/package.json @@ -0,0 +1,8 @@ +{ + "name": "package1", + "version": "0.0.0", + "private": true, + "dependencies": { + "scheduler": "^0.23.0" + } +} diff --git a/pkg/fanal/analyzer/language/nodejs/yarn/testdata/monorepo/packages/package2/package.json b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/monorepo/packages/package2/package.json new file mode 100644 index 000000000000..29b60ba87602 --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/monorepo/packages/package2/package.json @@ -0,0 +1,9 @@ +{ + "name": "package2", + "private": true, + "version": "0.0.0", + "type": "module", + "dependencies": { + "is-odd": "^3.0.1" + } +} diff --git a/pkg/fanal/analyzer/language/nodejs/yarn/testdata/monorepo/packages/utils/util1/package.json b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/monorepo/packages/utils/util1/package.json new file mode 100644 index 000000000000..7bcf16729401 --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/monorepo/packages/utils/util1/package.json @@ -0,0 +1,10 @@ +{ + "name": "util1", + "version": "0.0.0", + "dependencies": { + "js-tokens": "^8.0.1" + }, + "devDependencies": { + "prop-types": "^15.8.1" + } +} diff --git a/pkg/fanal/analyzer/language/nodejs/yarn/testdata/monorepo/yarn.lock b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/monorepo/yarn.lock new file mode 100644 index 000000000000..ff1ff18ef57c --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/monorepo/yarn.lock @@ -0,0 +1,138 @@ +# This file is generated by running "yarn install" inside your project. +# Manual changes might be lost - proceed with caution! + +__metadata: + version: 6 + cacheKey: 8 + +"c@workspace:c": + version: 0.0.0-use.local + resolution: "c@workspace:c" + dependencies: + is-number: ^7.0.0 + languageName: unknown + linkType: soft + +"is-number@npm:^6.0.0": + version: 6.0.0 + resolution: "is-number@npm:6.0.0" + checksum: f73bfced022128b5684bf77e0266a74e5222522bbc40f81cc1e949170c774a3c14b59a208be025d2d97a9c6b79c7c45fe351ab1c2c780872464fdedde0ae067a + languageName: node + linkType: hard + +"is-number@npm:^7.0.0": + version: 7.0.0 + resolution: "is-number@npm:7.0.0" + checksum: 456ac6f8e0f3111ed34668a624e45315201dff921e5ac181f8ec24923b99e9f32ca1a194912dc79d539c97d33dba17dc635202ff0b2cf98326f608323276d27a + languageName: node + linkType: hard + +"is-odd@npm:^3.0.1": + version: 3.0.1 + resolution: "is-odd@npm:3.0.1" + dependencies: + is-number: ^6.0.0 + checksum: 4e2b20764dd2296bafe44823d127f281c7039b37d2feaf5caffc1bf162502ef2920bcd4ad171490f371d3f15f52232c763a8ffc0b3633d4c83385fe20f3493af + languageName: node + linkType: hard + +"js-tokens@npm:^3.0.0 || ^4.0.0": + version: 4.0.0 + resolution: "js-tokens@npm:4.0.0" + checksum: 8a95213a5a77deb6cbe94d86340e8d9ace2b93bc367790b260101d2f36a2eaf4e4e22d9fa9cf459b38af3a32fb4190e638024cf82ec95ef708680e405ea7cc78 + languageName: node + linkType: hard + +"js-tokens@npm:^8.0.1": + version: 8.0.1 + resolution: "js-tokens@npm:8.0.1" + checksum: fb7bcd476c5b902ffb766382ca85aecb86ec66a607e419377026293b5877774e465f6cbe4229c8d85db3776ccc91c3aee518a0e04a005e260e57353f6f9278a8 + languageName: node + linkType: hard + +"loose-envify@npm:^1.1.0, loose-envify@npm:^1.4.0": + version: 1.4.0 + resolution: "loose-envify@npm:1.4.0" + dependencies: + js-tokens: ^3.0.0 || ^4.0.0 + bin: + loose-envify: cli.js + checksum: 6517e24e0cad87ec9888f500c5b5947032cdfe6ef65e1c1936a0c48a524b81e65542c9c3edc91c97d5bddc806ee2a985dbc79be89215d613b1de5db6d1cfe6f4 + languageName: node + linkType: hard + +"object-assign@npm:^4.1.1": + version: 4.1.1 + resolution: "object-assign@npm:4.1.1" + checksum: fcc6e4ea8c7fe48abfbb552578b1c53e0d194086e2e6bbbf59e0a536381a292f39943c6e9628af05b5528aa5e3318bb30d6b2e53cadaf5b8fe9e12c4b69af23f + languageName: node + linkType: hard + +"package1@workspace:packages/package1": + version: 0.0.0-use.local + resolution: "package1@workspace:packages/package1" + dependencies: + scheduler: ^0.23.0 + languageName: unknown + linkType: soft + +"package2@workspace:packages/package2": + version: 0.0.0-use.local + resolution: "package2@workspace:packages/package2" + dependencies: + is-odd: ^3.0.1 + languageName: unknown + linkType: soft + +"prettier@npm:^2.8.8": + version: 2.8.8 + resolution: "prettier@npm:2.8.8" + bin: + prettier: bin-prettier.js + checksum: b49e409431bf129dd89238d64299ba80717b57ff5a6d1c1a8b1a28b590d998a34e083fa13573bc732bb8d2305becb4c9a4407f8486c81fa7d55100eb08263cf8 + languageName: node + linkType: hard + +"prop-types@npm:^15.8.1": + version: 15.8.1 + resolution: "prop-types@npm:15.8.1" + dependencies: + loose-envify: ^1.4.0 + object-assign: ^4.1.1 + react-is: ^16.13.1 + checksum: c056d3f1c057cb7ff8344c645450e14f088a915d078dcda795041765047fa080d38e5d626560ccaac94a4e16e3aa15f3557c1a9a8d1174530955e992c675e459 + languageName: node + linkType: hard + +"react-is@npm:^16.13.1": + version: 16.13.1 + resolution: "react-is@npm:16.13.1" + checksum: f7a19ac3496de32ca9ae12aa030f00f14a3d45374f1ceca0af707c831b2a6098ef0d6bdae51bd437b0a306d7f01d4677fcc8de7c0d331eb47ad0f46130e53c5f + languageName: node + linkType: hard + +"scheduler@npm:^0.23.0": + version: 0.23.0 + resolution: "scheduler@npm:0.23.0" + dependencies: + loose-envify: ^1.1.0 + checksum: d79192eeaa12abef860c195ea45d37cbf2bbf5f66e3c4dcd16f54a7da53b17788a70d109ee3d3dde1a0fd50e6a8fc171f4300356c5aee4fc0171de526bf35f8a + languageName: node + linkType: hard + +"util1@workspace:packages/utils/util1": + version: 0.0.0-use.local + resolution: "util1@workspace:packages/utils/util1" + dependencies: + js-tokens: ^8.0.1 + prop-types: ^15.8.1 + languageName: unknown + linkType: soft + +"yarn-workspace-test@workspace:.": + version: 0.0.0-use.local + resolution: "yarn-workspace-test@workspace:." + dependencies: + prettier: ^2.8.8 + languageName: unknown + linkType: soft diff --git a/pkg/fanal/analyzer/language/nodejs/yarn/yarn.go b/pkg/fanal/analyzer/language/nodejs/yarn/yarn.go index ce06dae3fe3a..aafb69c01834 100644 --- a/pkg/fanal/analyzer/language/nodejs/yarn/yarn.go +++ b/pkg/fanal/analyzer/language/nodejs/yarn/yarn.go @@ -169,11 +169,54 @@ func (a yarnAnalyzer) parsePackageJsonDependencies(fsys fs.FS, path string) (map } defer func() { _ = f.Close() }() - pkg, err := a.packageJsonParser.Parse(f) + rootPkg, err := a.packageJsonParser.Parse(f) if err != nil { return nil, xerrors.Errorf("parse error: %w", err) } // Merge dependencies and optionalDependencies - return lo.Assign(pkg.Dependencies, pkg.OptionalDependencies), nil + dependencies := lo.Assign(rootPkg.Dependencies, rootPkg.OptionalDependencies) + + if len(rootPkg.Workspaces) > 0 { + pkgs, err := a.traverseWorkspaces(fsys, rootPkg.Workspaces) + if err != nil { + return nil, xerrors.Errorf("traverse workspaces error: %w", err) + } + for _, pkg := range pkgs { + dependencies = lo.Assign(dependencies, pkg.Dependencies, pkg.OptionalDependencies) + } + } + + return dependencies, nil +} + +func (a yarnAnalyzer) traverseWorkspaces(fsys fs.FS, workspaces []string) ([]packagejson.Package, error) { + var pkgs []packagejson.Package + + required := func(path string, _ fs.DirEntry) bool { + return filepath.Base(path) == types.NpmPkg + } + + walkDirFunc := func(path string, d fs.DirEntry, r dio.ReadSeekerAt) error { + pkg, err := a.packageJsonParser.Parse(r) + if err != nil { + return xerrors.Errorf("unable to parse %q: %w", path, err) + } + pkgs = append(pkgs, pkg) + return nil + } + + for _, workspace := range workspaces { + matches, err := fs.Glob(fsys, workspace) + if err != nil { + return nil, err + } + for _, match := range matches { + if err := fsutils.WalkDir(fsys, match, required, walkDirFunc); err != nil { + return nil, xerrors.Errorf("walk error: %w", err) + } + } + + } + return pkgs, nil } diff --git a/pkg/fanal/analyzer/language/nodejs/yarn/yarn_test.go b/pkg/fanal/analyzer/language/nodejs/yarn/yarn_test.go index d108b3feb30b..f7f7c65ffb0b 100644 --- a/pkg/fanal/analyzer/language/nodejs/yarn/yarn_test.go +++ b/pkg/fanal/analyzer/language/nodejs/yarn/yarn_test.go @@ -230,6 +230,103 @@ func Test_yarnLibraryAnalyzer_Analyze(t *testing.T) { dir: "testdata/unsupported_protocol", want: &analyzer.AnalysisResult{}, }, + { + name: "monorepo", + dir: "testdata/monorepo", + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.Yarn, + FilePath: "yarn.lock", + Libraries: []types.Package{ + { + ID: "is-number@6.0.0", + Name: "is-number", + Version: "6.0.0", + Indirect: true, + Locations: []types.Location{ + { + StartLine: 16, + EndLine: 21, + }, + }, + }, + { + ID: "is-number@7.0.0", + Name: "is-number", + Version: "7.0.0", + Locations: []types.Location{ + { + StartLine: 23, + EndLine: 28, + }, + }, + }, + { + ID: "is-odd@3.0.1", + Name: "is-odd", + Version: "3.0.1", + DependsOn: []string{"is-number@6.0.0"}, + Locations: []types.Location{ + { + StartLine: 30, + EndLine: 37, + }, + }, + }, + { + ID: "js-tokens@4.0.0", + Name: "js-tokens", + Version: "4.0.0", + Indirect: true, + Locations: []types.Location{ + { + StartLine: 39, + EndLine: 44, + }, + }, + }, + { + ID: "js-tokens@8.0.1", + Name: "js-tokens", + Version: "8.0.1", + Locations: []types.Location{ + { + StartLine: 46, + EndLine: 51, + }, + }, + }, + { + ID: "loose-envify@1.4.0", + Name: "loose-envify", + Version: "1.4.0", + Indirect: true, + DependsOn: []string{"js-tokens@4.0.0"}, + Locations: []types.Location{ + { + StartLine: 53, + EndLine: 62, + }, + }, + }, + { + ID: "scheduler@0.23.0", + Name: "scheduler", + Version: "0.23.0", + DependsOn: []string{"loose-envify@1.4.0"}, + Locations: []types.Location{ + { + StartLine: 114, + EndLine: 121, + }, + }, + }, + }, + }, + }, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) {