Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
13 changes: 10 additions & 3 deletions lib/src/main/java/com/diffplug/spotless/npm/NpmResourceHelper.java
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -27,6 +27,7 @@
import java.util.Objects;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import com.diffplug.spotless.ThrowingEx;

Expand Down Expand Up @@ -140,9 +141,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<String> 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("@@@"));
Comment thread
simschla marked this conversation as resolved.
Outdated
md.update(stringToHash.getBytes(StandardCharsets.UTF_8));

byte[] digest = md.digest();
// convert byte array digest to hex string
StringBuilder sb = new StringBuilder();
Expand Down
12 changes: 7 additions & 5 deletions lib/src/main/resources/com/diffplug/spotless/npm/common-serve.js
Original file line number Diff line number Diff line change
@@ -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 gracefulShutdown = require("http-graceful-shutdown");
const express = require("express");
const app = express();

Expand Down Expand Up @@ -48,12 +48,14 @@ var listener = app.listen(0, "127.0.0.1", () => {
}
});
});
const shutdownManager = new GracefulShutdownManager(listener);
const shutdown = gracefulShutdown(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 () => {
await shutdown()
Comment thread
simschla marked this conversation as resolved.
Outdated
}, 200);
});

Original file line number Diff line number Diff line change
@@ -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",
Expand All @@ -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"
}
}
Original file line number Diff line number Diff line change
@@ -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",
Expand All @@ -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"
}
}
Original file line number Diff line number Diff line change
@@ -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",
Expand All @@ -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"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
Original file line number Diff line number Diff line change
@@ -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<String, String> 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);
}
}
Original file line number Diff line number Diff line change
@@ -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");
}
}
Loading