-
Notifications
You must be signed in to change notification settings - Fork 38
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
129 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -94,11 +94,17 @@ func GetYarnExecutable() (string, error) { | |
return yarnExecPath, nil | ||
} | ||
|
||
// GetYarnDependencies returns a map of the dependencies of a Yarn project and the root package of the project. | ||
// The keys are the packages' values (Yarn's full identifiers of the packages), for example: '@scope/[email protected]' | ||
// (for yarn v < 2.0.0) or @scope/package-name@npm:1.0.0 (for yarn v >= 2.0.0). | ||
// Pay attention that a package's value won't necessarily contain its version. Use the version in package's details instead. | ||
func GetYarnDependencies(executablePath, srcPath string, packageInfo *PackageInfo, log utils.Log) (dependenciesMap map[string]*YarnDependency, root *YarnDependency, err error) { | ||
// Returns a map of the dependencies of a Yarn project along with the root package of the project. | ||
// The map's keys are the full identifiers of the packages as used by Yarn; for example: | ||
// '@scope/[email protected]' (for Yarn versions < 2.0.0) or '@scope/package-name@npm:1.0.0' (for Yarn versions >= 2.0.0). | ||
// Note that a package's value may not necessarily contain its version; instead, use the version in the package's details. | ||
// Arguments: | ||
// executablePath - The path to the Yarn executable. | ||
// srcPath - The path to the project's source that we wish to work with. | ||
// packageInfo - The project's package information. | ||
// log - The logger. | ||
// allowPartialResults - If true, the function will allow some errors to occur without failing the flow and will generate partial results. | ||
func GetYarnDependencies(executablePath, srcPath string, packageInfo *PackageInfo, log utils.Log, allowPartialResults bool) (dependenciesMap map[string]*YarnDependency, root *YarnDependency, err error) { | ||
executableVersionStr, err := GetVersion(executablePath, srcPath) | ||
if err != nil { | ||
return | ||
|
@@ -124,7 +130,7 @@ func GetYarnDependencies(executablePath, srcPath string, packageInfo *PackageInf | |
if isV2AndAbove { | ||
dependenciesMap, root, err = buildYarnV2DependencyMap(packageInfo, responseStr) | ||
} else { | ||
dependenciesMap, root, err = buildYarnV1DependencyMap(packageInfo, responseStr) | ||
dependenciesMap, root, err = buildYarnV1DependencyMap(packageInfo, responseStr, allowPartialResults, log) | ||
} | ||
return | ||
} | ||
|
@@ -144,10 +150,15 @@ func GetVersion(executablePath, srcPath string) (string, error) { | |
return strings.TrimSpace(outBuffer.String()), err | ||
} | ||
|
||
// buildYarnV1DependencyMap builds a map of dependencies for Yarn versions < 2.0.0 | ||
// Pay attention that in Yarn < 2.0.0 the project itself with its direct dependencies is not presented when running the | ||
// command 'yarn list' therefore the root is built manually. | ||
func buildYarnV1DependencyMap(packageInfo *PackageInfo, responseStr string) (dependenciesMap map[string]*YarnDependency, root *YarnDependency, err error) { | ||
// Builds a map of dependencies for Yarn versions < 2.0.0. | ||
// Note that in Yarn < 2.0.0, the project itself, along with its direct dependencies, is not present when running the | ||
// command 'yarn list'; therefore, the root is built manually. | ||
// Arguments: | ||
// packageInfo - The project's package information. | ||
// responseStr - The response string from the 'yarn list' command. | ||
// allowPartialResults - If true, the function will allow some errors to occur without failing the flow and will generate partial results. | ||
// log - The logger. | ||
func buildYarnV1DependencyMap(packageInfo *PackageInfo, responseStr string, allowPartialResults bool, log utils.Log) (dependenciesMap map[string]*YarnDependency, root *YarnDependency, err error) { | ||
dependenciesMap = make(map[string]*YarnDependency) | ||
var depTree Yarn1Data | ||
err = json.Unmarshal([]byte(responseStr), &depTree) | ||
|
@@ -164,23 +175,26 @@ func buildYarnV1DependencyMap(packageInfo *PackageInfo, responseStr string) (dep | |
packNameToFullName := make(map[string]string) | ||
|
||
// Initializing dependencies map without child dependencies for each dependency + creating a map that maps: package-name -> package-name@version | ||
// The two phases mapping is performed since the responseStr from 'yarn list' contains the resolved versions of the dependencies at the map's first level, but may contain the caret version range (^) at the children level. | ||
// Therefore, a manual matching must be made in order to output a map with parent-child relation containing only resolved versions. | ||
for _, curDependency := range depTree.Data.DepTree { | ||
var packageCleanName, packageVersion string | ||
packageCleanName, packageVersion, err = splitNameAndVersion(curDependency.Name) | ||
if err != nil { | ||
return | ||
} | ||
|
||
// We insert to dependenciesMap dependencies with the resolved versions only. All dependencies at the responseStr first level contain resolved versions only (their children may contain caret version ranges). | ||
dependenciesMap[curDependency.Name] = &YarnDependency{ | ||
Value: curDependency.Name, | ||
Details: YarnDepDetails{Version: packageVersion}, | ||
} | ||
packNameToFullName[packageCleanName] = curDependency.Name | ||
} | ||
log.Debug(fmt.Sprintf("'yarn list' output string: %s\n\nPackage name to full name map content: %v", responseStr, packNameToFullName)) | ||
|
||
// Adding child dependencies for each dependency | ||
for _, curDependency := range depTree.Data.DepTree { | ||
dependency := dependenciesMap[curDependency.Name] | ||
dependencyToUpdateInMap := dependenciesMap[curDependency.Name] | ||
|
||
for _, subDep := range curDependency.Dependencies { | ||
var subDepName string | ||
|
@@ -191,12 +205,17 @@ func buildYarnV1DependencyMap(packageInfo *PackageInfo, responseStr string) (dep | |
|
||
packageWithResolvedVersion := packNameToFullName[subDepName] | ||
if packageWithResolvedVersion == "" { | ||
if allowPartialResults { | ||
log.Warn(fmt.Sprintf("error occurred during Yarn dependencies map calculation: couldn't find resolved version for '%s' in 'yarn list' output\nFinal rasults may be partial", subDep.DependencyName)) | ||
continue | ||
} | ||
|
||
err = fmt.Errorf("couldn't find resolved version for '%s' in 'yarn list' output", subDep.DependencyName) | ||
return | ||
} | ||
dependency.Details.Dependencies = append(dependency.Details.Dependencies, YarnDependencyPointer{subDep.DependencyName, packageWithResolvedVersion}) | ||
dependencyToUpdateInMap.Details.Dependencies = append(dependencyToUpdateInMap.Details.Dependencies, YarnDependencyPointer{subDep.DependencyName, packageWithResolvedVersion}) | ||
} | ||
dependenciesMap[curDependency.Name] = dependency | ||
dependenciesMap[curDependency.Name] = dependencyToUpdateInMap | ||
} | ||
|
||
rootDependency := buildYarn1Root(packageInfo, packNameToFullName) | ||
|
@@ -205,7 +224,7 @@ func buildYarnV1DependencyMap(packageInfo *PackageInfo, responseStr string) (dep | |
return | ||
} | ||
|
||
// buildYarnV2DependencyMap builds a map of dependencies for Yarn version >= 2.0.0 | ||
// Builds a map of dependencies for Yarn version >= 2.0.0 | ||
// Note that in some versions of Yarn, the version of the root package is '0.0.0-use.local', instead of the version in the package.json file. | ||
func buildYarnV2DependencyMap(packageInfo *PackageInfo, responseStr string) (dependenciesMap map[string]*YarnDependency, root *YarnDependency, err error) { | ||
dependenciesMap = make(map[string]*YarnDependency) | ||
|
@@ -233,7 +252,7 @@ func buildYarnV2DependencyMap(packageInfo *PackageInfo, responseStr string) (dep | |
return | ||
} | ||
|
||
// runYarnInfoOrList depends on the yarn version currently operating on the project, runs the command that gets the dependencies of the project | ||
// Depending on the Yarn version currently in use for the project, this function runs the command that retrieves the project's dependencies | ||
func runYarnInfoOrList(executablePath string, srcPath string, v2AndAbove bool) (outResult, errResult string, err error) { | ||
var command *exec.Cmd | ||
if v2AndAbove { | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,6 +4,7 @@ import ( | |
"github.com/jfrog/build-info-go/utils" | ||
"github.com/stretchr/testify/assert" | ||
"path/filepath" | ||
"sort" | ||
"strings" | ||
"testing" | ||
) | ||
|
@@ -53,7 +54,7 @@ func checkGetYarnDependencies(t *testing.T, versionDir string, expectedLocators | |
Dependencies: map[string]string{"react": "18.2.0", "xml": "1.0.1"}, | ||
DevDependencies: map[string]string{"json": "9.0.6"}, | ||
} | ||
dependenciesMap, root, err := GetYarnDependencies(executablePath, projectSrcPath, &pacInfo, &utils.NullLog{}) | ||
dependenciesMap, root, err := GetYarnDependencies(executablePath, projectSrcPath, &pacInfo, &utils.NullLog{}, false) | ||
assert.NoError(t, err) | ||
assert.NotNil(t, root) | ||
|
||
|
@@ -104,6 +105,97 @@ func checkGetYarnDependencies(t *testing.T, versionDir string, expectedLocators | |
} | ||
} | ||
|
||
// This test checks the error handling of buildYarnV1DependencyMap with a response string that is missing a dependency, when allow-partial-results is set to true. | ||
// responseStr, which is an output of 'yarn list' should contain every dependency (direct and indirect) of the project at the first level, with the direct children of each dependency. | ||
// Sometimes the first level is lacking a dependency that appears as a child, or the child dependency is not found at the first level of the map, hence an error should be thrown. | ||
// When apply-partial-results is set to true we expect to provide a partial map instead of dropping the entire flow and return an error in such a case. | ||
func TestBuildYarnV1DependencyMapWithLackingDependencyInResponseString(t *testing.T) { | ||
packageInfo := &PackageInfo{ | ||
Name: "test-project", | ||
Version: "1.0.0", | ||
Dependencies: map[string]string{"minimist": "1.2.5", "yarn-inner": "file:./yarn-inner"}, | ||
} | ||
|
||
// This responseStr simulates should trigger an error since it is missing 'tough-cookie' at the "trees" first level, but this dependency appears as a child for another dependency (and hence should have been in the "trees" level as well) | ||
responseStr := "{\"type\":\"tree\",\"data\":{\"type\":\"list\",\"trees\":[{\"name\":\"[email protected]\",\"children\":[],\"hint\":null,\"color\":\"bold\",\"depth\":0},{\"name\":\"[email protected]\",\"children\":[{\"name\":\"[email protected]\",\"color\":\"dim\",\"shadow\":true}],\"hint\":null,\"color\":\"bold\",\"depth\":0}]}}" | ||
|
||
expectedRoot := YarnDependency{ | ||
Value: "test-project", | ||
Details: YarnDepDetails{ | ||
Version: "1.0.0", | ||
Dependencies: []YarnDependencyPointer{ | ||
{ | ||
Descriptor: "", | ||
Locator: "[email protected]", | ||
}, | ||
{ | ||
Descriptor: "", | ||
Locator: "[email protected]", | ||
}, | ||
}, | ||
}, | ||
} | ||
|
||
expectedDependenciesMap := map[string]*YarnDependency{ | ||
"[email protected]": { | ||
Value: "[email protected]", | ||
Details: YarnDepDetails{ | ||
Version: "1.2.5", | ||
Dependencies: nil, | ||
}, | ||
}, | ||
"[email protected]": { | ||
Value: "[email protected]", | ||
Details: YarnDepDetails{ | ||
Version: "1.0.0", | ||
Dependencies: nil, | ||
}, | ||
}, | ||
"test-project": { | ||
Value: "test-project", | ||
Details: YarnDepDetails{ | ||
Version: "1.0.0", | ||
Dependencies: []YarnDependencyPointer{ | ||
{ | ||
Descriptor: "", | ||
Locator: "[email protected]", | ||
}, | ||
{ | ||
Descriptor: "", | ||
Locator: "[email protected]", | ||
}, | ||
}, | ||
}, | ||
}, | ||
} | ||
|
||
dependenciesMap, root, err := buildYarnV1DependencyMap(packageInfo, responseStr, true, &utils.NullLog{}) | ||
assert.NoError(t, err) | ||
// Verifying root | ||
assert.NotNil(t, root) | ||
assert.Equal(t, expectedRoot.Value, root.Value) | ||
assert.Len(t, root.Details.Dependencies, len(expectedRoot.Details.Dependencies)) | ||
sort.Slice(root.Details.Dependencies, func(i, j int) bool { | ||
return root.Details.Dependencies[i].Locator < root.Details.Dependencies[j].Locator | ||
}) | ||
assert.EqualValues(t, expectedRoot.Details.Dependencies, root.Details.Dependencies) | ||
|
||
// Verifying dependencies map | ||
assert.Equal(t, len(expectedDependenciesMap), len(dependenciesMap)) | ||
for expectedKey, expectedValue := range expectedDependenciesMap { | ||
value := dependenciesMap[expectedKey] | ||
assert.NotNil(t, value) | ||
assert.EqualValues(t, expectedValue.Value, value.Value) | ||
assert.EqualValues(t, expectedValue.Details.Version, value.Details.Version) | ||
if expectedValue.Details.Dependencies != nil { | ||
sort.Slice(value.Details.Dependencies, func(i, j int) bool { | ||
return value.Details.Dependencies[i].Locator < value.Details.Dependencies[j].Locator | ||
}) | ||
assert.EqualValues(t, expectedValue.Details.Dependencies, value.Details.Dependencies) | ||
} | ||
} | ||
} | ||
|
||
func TestYarnDependency_Name(t *testing.T) { | ||
testCases := []struct { | ||
packageFullName string | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters