Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Control the depth of the pnpm dependency tree #202

Merged
merged 12 commits into from
Dec 15, 2024
44 changes: 1 addition & 43 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,49 +54,7 @@ jobs:
matrix:
os: [ ubuntu, windows, macos ]
steps:
# Install dependencies
- name: Install Go
uses: actions/setup-go@v3
with:
go-version: 1.22.x
- name: Install npm
uses: actions/setup-node@v3
with:
node-version: "16"
- name: Setup Pnpm
uses: pnpm/action-setup@v3
with:
version: 8
- name: Install Java
uses: actions/setup-java@v3
with:
java-version: "11"
distribution: "adopt"
- name: Install NuGet
uses: nuget/setup-nuget@v2
with:
nuget-version: 6.x
- name: Install dotnet
uses: actions/setup-dotnet@v3
with:
dotnet-version: '6.x'
attiasas marked this conversation as resolved.
Show resolved Hide resolved
- name: Setup Python3
uses: actions/setup-python@v4
with:
python-version: "3.x"
- name: Setup Pipenv
run: python -m pip install pipenv
- name: Setup Poetry
run: python -m pip install poetry
- name: Setup Conan
run: |
python -m pip install conan
conan profile detect
- name: Setup Gradle
uses: gradle/gradle-build-action@v2
with:
gradle-version: 7.6
# Checkout code
# Prepare the environment
- name: Checkout code
uses: actions/checkout@v4
with:
Expand Down
25 changes: 9 additions & 16 deletions commands/audit/sca/pnpm/pnpm.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,6 @@ import (
"github.com/jfrog/gofrog/datastructures"
"github.com/jfrog/gofrog/io"
"github.com/jfrog/jfrog-cli-core/v2/utils/coreutils"
"golang.org/x/exp/maps"
"golang.org/x/exp/slices"

"github.com/jfrog/jfrog-cli-security/commands/audit/sca"
"github.com/jfrog/jfrog-cli-security/commands/audit/sca/npm"
"github.com/jfrog/jfrog-cli-security/utils"
Expand All @@ -21,6 +18,7 @@ import (
"github.com/jfrog/jfrog-client-go/utils/errorutils"
"github.com/jfrog/jfrog-client-go/utils/io/fileutils"
"github.com/jfrog/jfrog-client-go/utils/log"
"golang.org/x/exp/maps"

biutils "github.com/jfrog/build-info-go/utils"
xrayUtils "github.com/jfrog/jfrog-client-go/xray/services/utils"
Expand Down Expand Up @@ -133,6 +131,7 @@ func installProjectIfNeeded(pnpmExecPath, workingDir string) (dirForDependencies
// Run 'pnpm ls ...' command (project must be installed) and parse the returned result to create a dependencies trees for the projects.
func calculateDependencies(executablePath, workingDir string, params utils.AuditParams) (dependencyTrees []*xrayUtils.GraphNode, uniqueDeps []string, err error) {
attiasas marked this conversation as resolved.
Show resolved Hide resolved
lsArgs := append([]string{"--depth", params.MaxTreeDepth(), "--json", "--long"}, params.Args()...)
log.Debug("Running Pnpm ls command with args:", lsArgs)
npmLsCmdContent, err := getPnpmCmd(executablePath, workingDir, "ls", lsArgs...).RunWithOutput()
if err != nil {
return
Expand Down Expand Up @@ -166,13 +165,13 @@ func createProjectDependenciesTree(project pnpmLsProject) map[string]xray.DepTre
for depName, dependency := range project.Dependencies {
directDependency := getDependencyId(depName, dependency.Version)
directDependencies = append(directDependencies, directDependency)
appendTransitiveDependencies(directDependency, dependency.Dependencies, treeMap)
appendTransitiveDependencies(directDependency, dependency.Dependencies, &treeMap)
}
// Handle dev-dependencies
for depName, dependency := range project.DevDependencies {
directDependency := getDependencyId(depName, dependency.Version)
directDependencies = append(directDependencies, directDependency)
appendTransitiveDependencies(directDependency, dependency.Dependencies, treeMap)
appendTransitiveDependencies(directDependency, dependency.Dependencies, &treeMap)
}
if len(directDependencies) > 0 {
treeMap[getDependencyId(project.Name, project.Version)] = xray.DepTreeNode{Children: directDependencies}
Expand All @@ -185,21 +184,15 @@ func getDependencyId(depName, version string) string {
return techutils.Npm.GetPackageTypeId() + depName + ":" + version
}

func appendTransitiveDependencies(parent string, dependencies map[string]pnpmLsDependency, result map[string]xray.DepTreeNode) {
func appendTransitiveDependencies(parent string, dependencies map[string]pnpmLsDependency, result *map[string]xray.DepTreeNode) {
for depName, dependency := range dependencies {
dependencyId := getDependencyId(depName, dependency.Version)
if node, ok := result[parent]; ok {
node.Children = appendUniqueChild(node.Children, dependencyId)
if node, ok := (*result)[parent]; ok {
node.Children = append(node.Children, dependencyId)
(*result)[parent] = node
} else {
result[parent] = xray.DepTreeNode{Children: []string{dependencyId}}
(*result)[parent] = xray.DepTreeNode{Children: []string{dependencyId}}
}
appendTransitiveDependencies(dependencyId, dependency.Dependencies, result)
}
}

func appendUniqueChild(children []string, candidateDependency string) []string {
if slices.Contains(children, candidateDependency) {
return children
}
return append(children, candidateDependency)
}
66 changes: 64 additions & 2 deletions commands/audit/sca/pnpm/pnpm_test.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package pnpm

import (
"github.com/jfrog/jfrog-cli-core/v2/utils/coreutils"
"github.com/jfrog/jfrog-client-go/utils/io/fileutils"
"path/filepath"
"testing"

"github.com/jfrog/jfrog-cli-core/v2/utils/coreutils"
"github.com/jfrog/jfrog-client-go/utils/io/fileutils"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

Expand All @@ -16,6 +17,67 @@ import (
"github.com/jfrog/jfrog-cli-security/utils"
)

func TestBuildDependencyTreeLimitedDepth(t *testing.T) {
// Create and change directory to test workspace
_, cleanUp := sca.CreateTestWorkspace(t, filepath.Join("projects", "package-managers", "npm", "npm-big-tree"))
defer cleanUp()
testCases := []struct {
name string
treeDepth string
expectedUniqueDeps []string
expectedTree *xrayUtils.GraphNode
}{
// {
attiasas marked this conversation as resolved.
Show resolved Hide resolved
// name: "Only direct dependencies",
// treeDepth: "0",
// expectedUniqueDeps: []string{
// "npm://zen-website:1.0.0",
// "npm://balaganjs:1.0.0",
// },
// expectedTree: &xrayUtils.GraphNode{
// Id: "npm://zen-website:1.0.0",
// Nodes: []*xrayUtils.GraphNode{{Id: "npm://balaganjs:1.0.0"}},
// },
// },
{
name: "With transitive dependencies",
treeDepth: "1",
expectedUniqueDeps: []string{
"npm://zen-website:1.0.0",
"npm://balaganjs:1.0.0",
"npm://axios:1.7.8",
"npm://yargs:13.3.0",
},
expectedTree: &xrayUtils.GraphNode{
Id: "npm://zen-website:1.0.0",
Nodes: []*xrayUtils.GraphNode{
{
Id: "npm://balaganjs:1.0.0",
Nodes: []*xrayUtils.GraphNode{{Id: "npm://axios:1.7.8"}, {Id: "npm://yargs:13.3.0"}},
},
},
},
},
}

for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
// Build dependency tree
params := &utils.AuditBasicParams{}
rootNode, uniqueDeps, err := BuildDependencyTree(params.SetMaxTreeDepth(testCase.treeDepth))
require.NoError(t, err)
// Validations
assert.ElementsMatch(t, uniqueDeps, testCase.expectedUniqueDeps, "First is actual, Second is Expected")
if assert.Len(t, rootNode, 1) {
assert.Equal(t, rootNode[0].Id, testCase.expectedTree.Id)
if !tests.CompareTree(testCase.expectedTree, rootNode[0]) {
t.Error("expected:", testCase.expectedTree.Nodes, "got:", rootNode[0].Nodes)
}
}
})
}
}

func TestBuildDependencyTree(t *testing.T) {
// Create and change directory to test workspace
_, cleanUp := sca.CreateTestWorkspace(t, filepath.Join("projects", "package-managers", "npm", "npm-no-lock"))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"name": "zen-website",
"version": "1.0.0",
"description": "",
"main": "index.js",
"publishConfig": {
"registry": "http://artifactory-unified.soleng-us.jfrog.team/artifactory/api/npm/npm/"
},
"scripts": {
"dev": "nodemon ./index.js",
"ui": "browser-sync start --config bs-config.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"balaganjs": "1.0.0"
}
}

Loading