diff --git a/src/org/zowe/pipelines/generic/GenericPipeline.groovy b/src/org/zowe/pipelines/generic/GenericPipeline.groovy index 58f3f40a..3d605b85 100644 --- a/src/org/zowe/pipelines/generic/GenericPipeline.groovy +++ b/src/org/zowe/pipelines/generic/GenericPipeline.groovy @@ -546,15 +546,18 @@ class GenericPipeline extends Pipeline { } catch (err) { steps.error "${err.message}" } - String target = steps.CHANGE_TARGET - String changedFiles = steps.sh(returnStdout: true, script: "git --no-pager diff origin/${target} --name-only").trim() + String labels = getLabels() if (labels == null) { steps.echo "Unable to read labels for this Pull Request. Forcing changelog check." } + + String target = steps.CHANGE_TARGET + String changedFiles = steps.sh(returnStdout: true, script: "git --no-pager diff origin/${target} --name-only").trim() + String[] projectDirs = getProjectDirs() if (labels != null && labels.contains("no-changelog")) { steps.echo "no-changelog label found on Pull Request. Skipping changelog check." - } else if (args._dirs == null) { + } else if (projectDirs.length == 0) { if (changedFiles.contains(args.file)) { def contents = steps.sh(returnStdout: true, script: "cat ${args.file}").trim() if (contents.contains(args.header)) { @@ -566,7 +569,7 @@ class GenericPipeline extends Pipeline { steps.error "Changelog has not been modified from origin/master. Please see CONTRIBUTING.md for changelog format." } } else { - for (dirname in args._dirs) { + for (dirname in projectDirs) { steps.dir(dirname) { if (changedFiles.contains(args.file)) { def contents = steps.sh(returnStdout: true, script: "cat ${args.file}").trim() @@ -885,4 +888,13 @@ class GenericPipeline extends Pipeline { throw new TestStageException("${reportName} is missing property `name`", stageName) } } + + /** + * Returns list of package directories to check for changelog files in. + * If the list is empty, only the root directory is checked. + * For a monorepo project, override this method to return a non-empty list. + */ + String[] getProjectDirs() { + return [] + } } diff --git a/src/org/zowe/pipelines/generic/arguments/ChangelogStageArguments.groovy b/src/org/zowe/pipelines/generic/arguments/ChangelogStageArguments.groovy index ec37cb9b..f0cf187a 100644 --- a/src/org/zowe/pipelines/generic/arguments/ChangelogStageArguments.groovy +++ b/src/org/zowe/pipelines/generic/arguments/ChangelogStageArguments.groovy @@ -35,10 +35,4 @@ class ChangelogStageArguments extends GenericStageArguments { * @default {@code "## Recent Changes"} */ String header = "## Recent Changes" - - /** - * List of directories to check for changelog files in. - * If not specified, only the root directory is checked. - */ - protected String[] _dirs } diff --git a/src/org/zowe/pipelines/nodejs/NodeJSPipeline.groovy b/src/org/zowe/pipelines/nodejs/NodeJSPipeline.groovy index 7f1c91b1..5047bda8 100644 --- a/src/org/zowe/pipelines/nodejs/NodeJSPipeline.groovy +++ b/src/org/zowe/pipelines/nodejs/NodeJSPipeline.groovy @@ -564,8 +564,21 @@ class NodeJSPipeline extends GenericPipeline { throw preSetupException } - runForEachMonorepoPackage(false) { - steps.sh "npm audit ${arguments.dev ? "" : "--production"} --audit-level=${arguments.auditLevel} ${arguments.registry != "" ? "--registry ${arguments.registry}" : ""}" + steps.sh "npm audit ${arguments.dev ? "" : "--production"} --audit-level=${arguments.auditLevel} ${arguments.registry != "" ? "--registry ${arguments.registry}" : ""}" + + if (isLernaMonorepo) { + // Bootstrap again to unhoist any dependencies missing from package-lock files + steps.sh "npx lerna bootstrap --no-ci" + + // Remove dependencies from package.json files that would cause ELOCKVERIFY error + prunePackageJsonsBeforeAudit() + + runForEachMonorepoPackage(false) { + steps.sh "npm audit ${arguments.dev ? "" : "--production"} --audit-level=${arguments.auditLevel} ${arguments.registry != "" ? "--registry ${arguments.registry}" : ""}" + } + + // Revert package.json files to their old contents + steps.sh "git checkout **/package.json" } } @@ -983,22 +996,6 @@ class NodeJSPipeline extends GenericPipeline { super.testGeneric(arguments) } - /** - * Verify that the changelog has been modified. - * - * @param file Indicates the file to be checked - * @param lines Indicates the number of lines to check for the header - * @param header Indicates the header that should exist in the changelog - * @return void - */ - void checkChangelog(Map arguments = [:]) { - if (isLernaMonorepo && arguments._dirs == null) { - arguments._dirs = _getLernaPkgInfo(true).collect { it.location } as String[] - } - - super.checkChangelog(arguments) - } - /** * Update the header in the changelog * @@ -1223,4 +1220,56 @@ expect { } } } + + /** + * Remove dependencies from package.json files that point to other packages + * in the same monorepo. This prevents ELOCKVERIFY errors when audit is run. + * See https://github.com/lerna/lerna/issues/1663#issuecomment-559010254 + */ + protected void prunePackageJsonsBeforeAudit() { + def lernaPkgInfo = _getLernaPkgInfo(false) + def lernaPkgNames = lernaPkgInfo.collect { it.name } as String[] + + for (pkgInfo in lernaPkgInfo) { + steps.dir(pkgInfo.location) { + def packageJSON = steps.readJSON file: "package.json" + def numPruned = 0 + + if (packageJSON.dependencies != null) { + for (def pkgName in packageJSON.dependencies.keySet()) { + if (lernaPkgNames.contains(pkgName)) { + packageJSON.dependencies.remove(pkgName) + numPruned++ + } + } + } + + if (packageJSON.devDependencies != null) { + for (def pkgName in packageJSON.devDependencies.keySet()) { + if (lernaPkgNames.contains(pkgName)) { + packageJSON.devDependencies.remove(pkgName) + numPruned++ + } + } + } + + if (numPruned > 0) { + steps.writeJSON file: "package.json", json: packageJSON, pretty: 2 + } + } + } + } + + /** + * Returns list of package directories to check for changelog files in. + * If the list is empty, only the root directory is checked. + * For a monorepo project, override this method to return a non-empty list. + */ + String[] getProjectDirs() { + if (isLernaMonorepo) { + return _getLernaPkgInfo(true).collect { it.location } as String[] + } + + return super.getProjectDirs() + } }