diff --git a/CHANGES.md b/CHANGES.md index 938e3c385c..50b69be234 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -14,6 +14,12 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format ( * Support for `idea` ([#2020](https://github.com/diffplug/spotless/pull/2020), [#2535](https://github.com/diffplug/spotless/pull/2535)) * Add support for removing wildcard imports via `removeWildcardImports` step. ([#2517](https://github.com/diffplug/spotless/pull/2517)) +### Fixed +* Make sure npm-based formatters use the correct `node_modules` directory when running in parallel. ([#2542](https://github.com/diffplug/spotless/pull/2542)) + +### Changed +* Bump internal dependencies for npm-based formatters ([#2542](https://github.com/diffplug/spotless/pull/2542)) + ## [3.1.2] - 2025-05-27 ### Fixed * Fix `UnsupportedOperationException` in the Gradle plugin when using `targetExcludeContent[Pattern]` ([#2487](https://github.com/diffplug/spotless/pull/2487)) diff --git a/lib/build.gradle b/lib/build.gradle index fcf59cd414..4f4addc2d5 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -76,6 +76,7 @@ dependencies { testCommonImplementation "org.junit.jupiter:junit-jupiter:$VER_JUNIT" testCommonImplementation "org.assertj:assertj-core:$VER_ASSERTJ" testCommonImplementation "com.diffplug.durian:durian-testlib:$VER_DURIAN" + testCommonImplementation projects.testlib testCommonRuntimeOnly "org.junit.platform:junit-platform-launcher" // GLUE CODE (alphabetic order please) diff --git a/lib/src/main/java/com/diffplug/spotless/npm/NodeServerLayout.java b/lib/src/main/java/com/diffplug/spotless/npm/NodeServerLayout.java index 850ea4eb6b..a6089c3972 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/NodeServerLayout.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/NodeServerLayout.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2023 DiffPlug + * Copyright 2020-2025 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -37,16 +37,16 @@ class NodeServerLayout { private final File serveJsFile; private final File npmrcFile; - NodeServerLayout(File buildDir, String packageJsonContent) { - this.nodeModulesDir = new File(buildDir, nodeModulesDirName(packageJsonContent)); + NodeServerLayout(File buildDir, String packageJsonContent, String serveJsContent) { + this.nodeModulesDir = new File(buildDir, nodeModulesDirName(packageJsonContent, serveJsContent)); this.packageJsonFile = new File(nodeModulesDir, "package.json"); this.packageLockJsonFile = new File(nodeModulesDir, "package-lock.json"); this.serveJsFile = new File(nodeModulesDir, "serve.js"); this.npmrcFile = new File(nodeModulesDir, ".npmrc"); } - private static String nodeModulesDirName(String packageJsonContent) { - String md5Hash = NpmResourceHelper.md5(packageJsonContent); + private static String nodeModulesDirName(String packageJsonContent, String serveJsContent) { + String md5Hash = NpmResourceHelper.md5(packageJsonContent, serveJsContent); Matcher matcher = PACKAGE_JSON_NAME_PATTERN.matcher(packageJsonContent); if (!matcher.find()) { throw new IllegalArgumentException("package.json must contain a name property"); diff --git a/lib/src/main/java/com/diffplug/spotless/npm/NpmFormatterStepStateBase.java b/lib/src/main/java/com/diffplug/spotless/npm/NpmFormatterStepStateBase.java index 9b37533880..9f27942abc 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/NpmFormatterStepStateBase.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/NpmFormatterStepStateBase.java @@ -71,7 +71,7 @@ public static class Runtime { Runtime(NpmFormatterStepStateBase parent) { this.parent = parent; - this.nodeServerLayout = new NodeServerLayout(parent.locations.buildDir(), parent.npmConfig.getPackageJsonContent()); + this.nodeServerLayout = new NodeServerLayout(parent.locations.buildDir(), parent.npmConfig.getPackageJsonContent(), parent.npmConfig.getServeScriptContent()); this.nodeServeApp = new NodeServeApp(nodeServerLayout, parent.npmConfig, parent.locations); } diff --git a/lib/src/main/java/com/diffplug/spotless/npm/NpmResourceHelper.java b/lib/src/main/java/com/diffplug/spotless/npm/NpmResourceHelper.java index 7a28685de0..3c146cd315 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/NpmResourceHelper.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/NpmResourceHelper.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2023 DiffPlug + * Copyright 2016-2025 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,10 +27,14 @@ import java.util.Objects; import java.util.concurrent.TimeoutException; import java.util.stream.Collectors; +import java.util.stream.Stream; import com.diffplug.spotless.ThrowingEx; final class NpmResourceHelper { + + public static final String MD5_STRING_DELIMITER = "@@@"; + private NpmResourceHelper() { // no instance required } @@ -140,9 +144,15 @@ static String md5(File file) { return md5(readUtf8StringFromFile(file)); } - static String md5(String fileContent) { + static String md5(String fileContent, String... additionalFileContents) { + Objects.requireNonNull(fileContent, "fileContent must not be null"); + Stream additionalFileContentStream = Stream.concat( + Stream.of(fileContent), + Stream.of(additionalFileContents)); MessageDigest md = ThrowingEx.get(() -> MessageDigest.getInstance("MD5")); - md.update(fileContent.getBytes(StandardCharsets.UTF_8)); + String stringToHash = additionalFileContentStream.collect(Collectors.joining(MD5_STRING_DELIMITER)); + md.update(stringToHash.getBytes(StandardCharsets.UTF_8)); + byte[] digest = md.digest(); // convert byte array digest to hex string StringBuilder sb = new StringBuilder(); diff --git a/lib/src/main/resources/com/diffplug/spotless/npm/common-serve.js b/lib/src/main/resources/com/diffplug/spotless/npm/common-serve.js index 5bfcb35612..edce61465a 100644 --- a/lib/src/main/resources/com/diffplug/spotless/npm/common-serve.js +++ b/lib/src/main/resources/com/diffplug/spotless/npm/common-serve.js @@ -1,6 +1,6 @@ // this file will be glued to the top of the specific xy-serve.js file const debug_serve = false; // set to true for debug log output in node process -const GracefulShutdownManager = require("@moebius/http-graceful-shutdown").GracefulShutdownManager; +const shutdownServer = require("http-graceful-shutdown"); const express = require("express"); const app = express(); @@ -48,12 +48,18 @@ var listener = app.listen(0, "127.0.0.1", () => { } }); }); -const shutdownManager = new GracefulShutdownManager(listener); +const shutdown = shutdownServer(listener, { + forceExit: false, // let the event loop clear + finally: () => debugLog("graceful shutdown finished."), +}); app.post("/shutdown", (req, res) => { res.status(200).send("Shutting down"); - setTimeout(function () { - shutdownManager.terminate(() => debugLog("graceful shutdown finished.")); + setTimeout(async () => { + try { + await shutdown(); + } catch (err) { + console.error("Error during shutdown:", err); + } }, 200); }); - diff --git a/lib/src/main/resources/com/diffplug/spotless/npm/eslint-package.json b/lib/src/main/resources/com/diffplug/spotless/npm/eslint-package.json index 0d7ce930f7..c4bdba5209 100644 --- a/lib/src/main/resources/com/diffplug/spotless/npm/eslint-package.json +++ b/lib/src/main/resources/com/diffplug/spotless/npm/eslint-package.json @@ -1,6 +1,6 @@ { "name": "spotless-eslint", - "version": "2.0.0", + "version": "4.0.0", "description": "Spotless formatter step for running eslint as a rest service.", "repository": "https://github.com/diffplug/spotless", "license": "Apache-2.0", @@ -9,11 +9,11 @@ }, "devDependencies": { ${devDependencies}, - "express": "4.18.2", - "@moebius/http-graceful-shutdown": "1.1.0" + "express": "5.1.0", + "http-graceful-shutdown": "3.1.14" }, "dependencies": {}, "engines": { - "node": ">=6" + "node": ">= 18" } } diff --git a/lib/src/main/resources/com/diffplug/spotless/npm/prettier-package.json b/lib/src/main/resources/com/diffplug/spotless/npm/prettier-package.json index 395a05da67..a7c219625c 100644 --- a/lib/src/main/resources/com/diffplug/spotless/npm/prettier-package.json +++ b/lib/src/main/resources/com/diffplug/spotless/npm/prettier-package.json @@ -1,6 +1,6 @@ { "name": "spotless-prettier", - "version": "2.0.0", + "version": "4.0.0", "description": "Spotless formatter step for running prettier as a rest service.", "repository": "https://github.com/diffplug/spotless", "license": "Apache-2.0", @@ -9,11 +9,11 @@ }, "devDependencies": { ${devDependencies}, - "express": "4.18.2", - "@moebius/http-graceful-shutdown": "1.1.0" + "express": "5.1.0", + "http-graceful-shutdown": "3.1.14" }, "dependencies": {}, "engines": { - "node": ">=6" + "node": ">= 18" } } diff --git a/lib/src/main/resources/com/diffplug/spotless/npm/tsfmt-package.json b/lib/src/main/resources/com/diffplug/spotless/npm/tsfmt-package.json index 483dd0753d..d6c9a8a656 100644 --- a/lib/src/main/resources/com/diffplug/spotless/npm/tsfmt-package.json +++ b/lib/src/main/resources/com/diffplug/spotless/npm/tsfmt-package.json @@ -1,6 +1,6 @@ { "name": "spotless-tsfmt", - "version": "2.0.0", + "version": "4.0.0", "description": "Spotless formatter step for running tsfmt as a rest service.", "repository": "https://github.com/diffplug/spotless", "license": "Apache-2.0", @@ -9,11 +9,11 @@ }, "devDependencies": { ${devDependencies}, - "express": "4.18.2", - "@moebius/http-graceful-shutdown": "1.1.0" + "express": "5.1.0", + "http-graceful-shutdown": "3.1.14" }, "dependencies": {}, "engines": { - "node": ">= 4.2.0" + "node": ">= 18" } } diff --git a/lib/src/main/resources/com/diffplug/spotless/npm/tsfmt-serve.js b/lib/src/main/resources/com/diffplug/spotless/npm/tsfmt-serve.js index b9f20a1472..ffbb36c8a4 100644 --- a/lib/src/main/resources/com/diffplug/spotless/npm/tsfmt-serve.js +++ b/lib/src/main/resources/com/diffplug/spotless/npm/tsfmt-serve.js @@ -19,7 +19,7 @@ app.post("/tsfmt/format", (req, res) => { */ // result contains 'message' (String), 'error' (boolean), 'dest' (String) => formatted if (resultMap.error !== undefined && resultMap.error) { - res.status(400).send(resultmap.message); + res.status(400).send(resultMap.message); return; } res.set("Content-Type", "text/plain"); diff --git a/lib/src/test/java/com/diffplug/spotless/npm/NodeServerLayoutTest.java b/lib/src/test/java/com/diffplug/spotless/npm/NodeServerLayoutTest.java new file mode 100644 index 0000000000..f2cb55943d --- /dev/null +++ b/lib/src/test/java/com/diffplug/spotless/npm/NodeServerLayoutTest.java @@ -0,0 +1,77 @@ +/* + * Copyright 2025 DiffPlug + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.diffplug.spotless.npm; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.File; +import java.io.IOException; +import java.util.Collections; +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import com.diffplug.spotless.ResourceHarness; + +class NodeServerLayoutTest extends ResourceHarness { + + @Test + void itCalculatesSameNodeModulesDirForSameContent() throws IOException { + File testDir = newFolder("build"); + String packageJsonContent = prettierPackageJson(Collections.emptyMap()); + String serveJsContent = "fun main() { console.log('Hello, world!'); }"; + NodeServerLayout layout1 = new NodeServerLayout(testDir, packageJsonContent, serveJsContent); + NodeServerLayout layout2 = new NodeServerLayout(testDir, packageJsonContent, serveJsContent); + + assertThat(layout1.nodeModulesDir()).isEqualTo(layout2.nodeModulesDir()); + } + + @Test + void itCalculatesDifferentNodeModulesDirForDifferentPackageJson() throws IOException { + File testDir = newFolder("build"); + String packageJsonContent1 = prettierPackageJson(Collections.singletonMap("prettier-plugin-xy", "^2.0.0")); + String packageJsonContent2 = prettierPackageJson(Collections.singletonMap("prettier-plugin-xy", "^2.1.0")); + String serveJsContent = "fun main() { console.log('Hello, world!'); }"; + + NodeServerLayout layout1 = new NodeServerLayout(testDir, packageJsonContent1, serveJsContent); + NodeServerLayout layout2 = new NodeServerLayout(testDir, packageJsonContent2, serveJsContent); + + assertThat(layout1.nodeModulesDir()).isNotEqualTo(layout2.nodeModulesDir()); + } + + @Test + void itCalculatesDifferentNodeModulesDirForDifferentServeJs() throws IOException { + File testDir = newFolder("build"); + String packageJsonContent = prettierPackageJson(Collections.emptyMap()); + String serveJsContent1 = "fun main() { console.log('Hello, world!'); }"; + String serveJsContent2 = "fun main() { console.log('Goodbye, world!'); }"; + + NodeServerLayout layout1 = new NodeServerLayout(testDir, packageJsonContent, serveJsContent1); + NodeServerLayout layout2 = new NodeServerLayout(testDir, packageJsonContent, serveJsContent2); + + assertThat(layout1.nodeModulesDir()).isNotEqualTo(layout2.nodeModulesDir()); + } + + static String prettierPackageJson(Map dependencies) { + String templateContent = NpmResourceHelper.readUtf8StringFromClasspath(NodeServerLayoutTest.class, "/com/diffplug/spotless/npm/prettier-package.json"); + String dependenciesList = dependencies.entrySet().stream() + .map(entry -> String.format("\"%s\": \"%s\"", entry.getKey(), entry.getValue())) + .reduce((a, b) -> a + ",\n " + b) + .orElse(""); + + return templateContent.replace("${devDependencies}", dependenciesList); + } +} diff --git a/lib/src/test/java/com/diffplug/spotless/npm/NpmResourceHelperTest.java b/lib/src/test/java/com/diffplug/spotless/npm/NpmResourceHelperTest.java new file mode 100644 index 0000000000..2fd969f864 --- /dev/null +++ b/lib/src/test/java/com/diffplug/spotless/npm/NpmResourceHelperTest.java @@ -0,0 +1,38 @@ +/* + * Copyright 2025 DiffPlug + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.diffplug.spotless.npm; + +import static com.diffplug.selfie.Selfie.expectSelfie; + +import org.junit.jupiter.api.Test; + +class NpmResourceHelperTest { + + @Test + void itCalculatesMd5ForSingleString() { + String input = "Hello, World!"; + + expectSelfie(NpmResourceHelper.md5(input)).toBe("65a8e27d8879283831b664bd8b7f0ad4"); + } + + @Test + void itCalculatesMd5ForMultipleStrings() { + String input1 = "Hello, World!"; + String input2 = "Hello, Spencer!"; + + expectSelfie(NpmResourceHelper.md5(input1, input2)).toBe("371ba0fbf3d73b33e71b4af8dc6afe00"); + } +} diff --git a/plugin-gradle/CHANGES.md b/plugin-gradle/CHANGES.md index 121d9d922c..f0e50b198b 100644 --- a/plugin-gradle/CHANGES.md +++ b/plugin-gradle/CHANGES.md @@ -7,6 +7,12 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format ( * Support for `idea` ([#2020](https://github.com/diffplug/spotless/pull/2020), [#2535](https://github.com/diffplug/spotless/pull/2535)) * Add support for removing wildcard imports via `removeWildcardImports` step. ([#2517](https://github.com/diffplug/spotless/pull/2517)) +### Fixed +* Make sure npm-based formatters use the correct `node_modules` directory when running in parallel. ([#2542](https://github.com/diffplug/spotless/pull/2542)) + +### Changed +* Bump internal dependencies for npm-based formatters ([#2542](https://github.com/diffplug/spotless/pull/2542))\ + ## [7.0.4] - 2025-05-27 ### Fixed * Fix `UnsupportedOperationException` in the Gradle plugin when using `targetExcludeContent[Pattern]` ([#2487](https://github.com/diffplug/spotless/pull/2487)) diff --git a/plugin-maven/CHANGES.md b/plugin-maven/CHANGES.md index cab00b07af..ea9a32b918 100644 --- a/plugin-maven/CHANGES.md +++ b/plugin-maven/CHANGES.md @@ -7,6 +7,12 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format ( * Support for `idea` ([#2020](https://github.com/diffplug/spotless/pull/2020), [#2535](https://github.com/diffplug/spotless/pull/2535)) * Add support for removing wildcard imports via `removeWildcardImports` step. ([#2517](https://github.com/diffplug/spotless/pull/2517)) +### Fixed +* Make sure npm-based formatters use the correct `node_modules` directory when running in parallel. ([#2542](https://github.com/diffplug/spotless/pull/2542)) + +### Changed +* Bump internal dependencies for npm-based formatters ([#2542](https://github.com/diffplug/spotless/pull/2542)) + ## [2.44.5] - 2025-05-27 ### Changed * Bump default `eclipse` version to latest `4.34` -> `4.35`. ([#2458](https://github.com/diffplug/spotless/pull/2458))