diff --git a/build.gradle b/build.gradle index 9e6ab83748..d184553297 100644 --- a/build.gradle +++ b/build.gradle @@ -21,7 +21,7 @@ buildscript { group = 'com.blackduck.integration' -version = '10.6.0-SIGQA7-SNAPSHOT' +version = '10.6.0-SIGQA8-andrians.yarn-optimizations-SNAPSHOT' apply plugin: 'com.blackduck.integration.solution' apply plugin: 'org.springframework.boot' diff --git a/detectable/src/main/java/com/blackduck/integration/detectable/detectables/yarn/packagejson/PackageJsonFiles.java b/detectable/src/main/java/com/blackduck/integration/detectable/detectables/yarn/packagejson/PackageJsonFiles.java index ccb6e653e8..3e5fabf347 100644 --- a/detectable/src/main/java/com/blackduck/integration/detectable/detectables/yarn/packagejson/PackageJsonFiles.java +++ b/detectable/src/main/java/com/blackduck/integration/detectable/detectables/yarn/packagejson/PackageJsonFiles.java @@ -38,42 +38,61 @@ public NullSafePackageJson read(File packageJsonFile) throws IOException { @NotNull public Collection readWorkspacePackageJsonFiles(File workspaceDir) throws IOException { - String forwardSlashedWorkspaceDirPath = deriveForwardSlashedPath(workspaceDir); File packageJsonFile = new File(workspaceDir, YarnLockDetectable.YARN_PACKAGE_JSON); List workspaceDirPatterns = extractWorkspaceDirPatterns(packageJsonFile); + if (workspaceDirPatterns.isEmpty()) { + logger.debug("No workspace patterns found in {}", packageJsonFile.getAbsolutePath()); + return new LinkedList<>(); + } + + List matchers = convertWorkspaceDirPatternsToPathMatchers(workspaceDirPatterns, workspaceDir); + Collection workspaces = new LinkedList<>(); - for (String workspaceSubdirPattern : workspaceDirPatterns) { - logger.trace("workspaceSubdirPattern: {}", workspaceSubdirPattern); - String globString = String.format("glob:%s/%s/package.json", forwardSlashedWorkspaceDirPath, workspaceSubdirPattern); - logger.trace("workspace subdir globString: {}", globString); - PathMatcher matcher = FileSystems.getDefault().getPathMatcher(globString); - Files.walkFileTree(workspaceDir.toPath(), new SimpleFileVisitor() { - @Override - public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { - if (matcher.matches(file)) { - logger.trace("\tFound a match: {}", file); - NullSafePackageJson packageJson = read(file.toFile()); - Path rel = workspaceDir.toPath().relativize(file.getParent()); - WorkspacePackageJson workspacePackageJson = new WorkspacePackageJson(file.toFile(), packageJson, rel.toString()); - YarnWorkspace workspace = new YarnWorkspace(workspacePackageJson); - workspaces.add(workspace); + + Files.walkFileTree(workspaceDir.toPath(), new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + if (file.getFileName().toString().equals(YarnLockDetectable.YARN_PACKAGE_JSON)) { // no need to try matching if not package.json + for (PathMatcher matcher : matchers) { + if (matcher.matches(file)) { + logger.trace("\tFound a match: {}", file); + NullSafePackageJson packageJson = read(file.toFile()); + Path rel = workspaceDir.toPath().relativize(file.getParent()); + WorkspacePackageJson workspacePackageJson = new WorkspacePackageJson(file.toFile(), packageJson, rel.toString()); + YarnWorkspace workspace = new YarnWorkspace(workspacePackageJson); + workspaces.add(workspace); + break; // no need to match the same file multiple times + } } - return FileVisitResult.CONTINUE; } + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFileFailed(Path file, IOException exc) { + return FileVisitResult.CONTINUE; + } + }); - @Override - public FileVisitResult visitFileFailed(Path file, IOException exc) { - return FileVisitResult.CONTINUE; - } - }); - } if (!workspaceDirPatterns.isEmpty()) { logger.debug("Found {} matching workspace package.json files for workspaces listed in {}", workspaces.size(), packageJsonFile.getAbsolutePath()); } return workspaces; } + private List convertWorkspaceDirPatternsToPathMatchers(List workspaceDirPatterns, File workspaceDir) { + List matchers = new LinkedList<>(); + String forwardSlashedWorkspaceDirPath = deriveForwardSlashedPath(workspaceDir); + for (String workspaceDirPattern : workspaceDirPatterns) { + String globString = String.format("glob:%s/%s/package.json", forwardSlashedWorkspaceDirPath, workspaceDirPattern); + logger.trace("workspace subdir globString: {}", globString); + PathMatcher matcher = FileSystems.getDefault().getPathMatcher(globString); + matchers.add(matcher); + } + return matchers; + } + @NotNull private String deriveForwardSlashedPath(File file) { String forwardSlashWorkspaceDirPath; diff --git a/detectable/src/test/java/com/blackduck/integration/detectable/detectables/yarn/packagejson/PackageJsonFilesTest.java b/detectable/src/test/java/com/blackduck/integration/detectable/detectables/yarn/packagejson/PackageJsonFilesTest.java new file mode 100644 index 0000000000..1dc0ad8205 --- /dev/null +++ b/detectable/src/test/java/com/blackduck/integration/detectable/detectables/yarn/packagejson/PackageJsonFilesTest.java @@ -0,0 +1,101 @@ +package com.blackduck.integration.detectable.detectables.yarn.packagejson; + +import com.blackduck.integration.detectable.detectables.yarn.workspace.YarnWorkspace; +import com.google.gson.Gson; +import org.apache.commons.io.FileUtils; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.Collection; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class PackageJsonFilesTest { + + private PackageJsonReader packageJsonReader; + private PackageJsonFiles packageJsonFiles; + private File tempDir; + + @BeforeEach + void setUp() throws IOException { + packageJsonReader = new PackageJsonReader(new Gson()); + packageJsonFiles = new PackageJsonFiles(packageJsonReader); + tempDir = Files.createTempDirectory("yarnws").toFile(); + } + + @AfterEach + void tearDown() throws IOException { + FileUtils.deleteDirectory(tempDir); + } + + @Test + void returnsEmptyWhenNoWorkspacePatterns() throws IOException { + // Setup a root package.json with no workspaces + File rootPackageJson = new File(tempDir, "package.json"); + Files.write(rootPackageJson.toPath(), "{ \"workspaces\": [] }".getBytes(StandardCharsets.UTF_8)); + + Collection result = packageJsonFiles.readWorkspacePackageJsonFiles(tempDir); + assertTrue(result.isEmpty()); + } + + @Test + void findsWorkspacePackageJsonFiles() throws IOException { + // Setup a root package.json with a workspace pattern + File rootPackageJson = new File(tempDir, "package.json"); + Files.write(rootPackageJson.toPath(), "{ \"workspaces\": [\"packages/*\"] }".getBytes(StandardCharsets.UTF_8)); + + // Create a workspace directory and its package.json + File wsDir = new File(tempDir, "packages/a"); + wsDir.mkdirs(); + File wsPackageJson = new File(wsDir, "package.json"); + Files.write(wsPackageJson.toPath(), "{ \"name\": \"a\" }".getBytes(StandardCharsets.UTF_8)); + + Collection result = packageJsonFiles.readWorkspacePackageJsonFiles(tempDir); + assertEquals(1, result.size()); + + YarnWorkspace foundWorkspace = result.iterator().next(); + assertEquals("a", foundWorkspace.getName().orElse(null)); + assertEquals(wsPackageJson.getAbsolutePath(), foundWorkspace.getWorkspacePackageJson().getFile().getAbsolutePath()); + } + + @Test + void findsMultipleWorkspacesWithDoubleWildcard() throws IOException { + // Setup a root package.json with a double wildcard workspace pattern + File rootPackageJson = new File(tempDir, "package.json"); + Files.write(rootPackageJson.toPath(), "{ \"workspaces\": [\"packages/**\"] }".getBytes(StandardCharsets.UTF_8)); + + // Create two workspace directories and their package.json files + File wsDir1 = new File(tempDir, "packages/a"); + wsDir1.mkdirs(); + File wsPackageJson1 = new File(wsDir1, "package.json"); + Files.write(wsPackageJson1.toPath(), "{ \"name\": \"a\" }".getBytes(StandardCharsets.UTF_8)); + + File wsDir2 = new File(tempDir, "packages/b/subdir"); + wsDir2.mkdirs(); + File wsPackageJson2 = new File(wsDir2, "package.json"); + Files.write(wsPackageJson2.toPath(), "{ \"name\": \"b\" }".getBytes(StandardCharsets.UTF_8)); + + Collection result = packageJsonFiles.readWorkspacePackageJsonFiles(tempDir); + assertEquals(2, result.size()); + + // Verify the workspaces + YarnWorkspace workspaceA = result.stream() + .filter(ws -> "a".equals(ws.getName().orElse(null))) + .findFirst() + .orElseThrow(() -> new AssertionError("Workspace 'a' not found")); + assertEquals(wsPackageJson1.getAbsolutePath(), workspaceA.getWorkspacePackageJson().getFile().getAbsolutePath()); + + YarnWorkspace workspaceB = result.stream() + .filter(ws -> "b".equals(ws.getName().orElse(null))) + .findFirst() + .orElseThrow(() -> new AssertionError("Workspace 'b' not found")); + assertEquals(wsPackageJson2.getAbsolutePath(), workspaceB.getWorkspacePackageJson().getFile().getAbsolutePath()); + } +} diff --git a/shared-version.properties b/shared-version.properties index 1666f23893..2c39ca70b9 100644 --- a/shared-version.properties +++ b/shared-version.properties @@ -1,2 +1,2 @@ -gradle.ext.blackDuckCommonVersion='67.0.11' +gradle.ext.blackDuckCommonVersion='67.0.12' gradle.ext.springBootVersion='2.7.12'