Skip to content

Commit 63dd687

Browse files
authored
Merge branch 'main' into ProgressWithJackson_YamlAndJson
2 parents 00e6282 + a21049a commit 63dd687

27 files changed

+620
-67
lines changed

CHANGES.md

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (
1212
## [Unreleased]
1313
### Added
1414
* `ProcessRunner` has added some convenience methods so it can be used for maven testing. ([#1496](https://github.com/diffplug/spotless/pull/1496))
15+
* Allow to specify node executable for node-based formatters using `nodeExecutable` parameter ([#1500](https://github.com/diffplug/spotless/pull/1500))
1516
### Fixed
1617
* The default list of type annotations used by `formatAnnotations` has had 8 more annotations from the Checker Framework added [#1494](https://github.com/diffplug/spotless/pull/1494)
1718
### Changes

lib/src/main/java/com/diffplug/spotless/npm/EslintFormatterStep.java

+6-4
Original file line numberDiff line numberDiff line change
@@ -94,9 +94,11 @@ private static class State extends NpmFormatterStepStateBase implements Serializ
9494
"/com/diffplug/spotless/npm/common-serve.js",
9595
"/com/diffplug/spotless/npm/eslint-serve.js"),
9696
npmPathResolver.resolveNpmrcContent()),
97-
projectDir,
98-
buildDir,
99-
npmPathResolver.resolveNpmExecutable());
97+
new NpmFormatterStepLocations(
98+
projectDir,
99+
buildDir,
100+
npmPathResolver.resolveNpmExecutable(),
101+
npmPathResolver.resolveNodeExecutable()));
100102
this.eslintConfig = localCopyFiles(requireNonNull(eslintConfig));
101103
}
102104

@@ -119,7 +121,7 @@ public FormatterFunc createFormatterFunc() {
119121
FormattedPrinter.SYSOUT.print("creating formatter function (starting server)");
120122
ServerProcessInfo eslintRestServer = npmRunServer();
121123
EslintRestService restService = new EslintRestService(eslintRestServer.getBaseUrl());
122-
return Closeable.ofDangerous(() -> endServer(restService, eslintRestServer), new EslintFilePathPassingFormatterFunc(projectDir, nodeModulesDir, eslintConfig, restService));
124+
return Closeable.ofDangerous(() -> endServer(restService, eslintRestServer), new EslintFilePathPassingFormatterFunc(locations.projectDir(), nodeModulesDir, eslintConfig, restService));
123125
} catch (IOException e) {
124126
throw ThrowingEx.asRuntime(e);
125127
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Copyright 2023 DiffPlug
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.diffplug.spotless.npm;
17+
18+
import java.io.File;
19+
import java.util.Optional;
20+
21+
class NodeExecutableResolver {
22+
23+
private NodeExecutableResolver() {
24+
// no instance
25+
}
26+
27+
static String nodeExecutableName() {
28+
String nodeName = "node";
29+
if (PlatformInfo.normalizedOS() == PlatformInfo.OS.WINDOWS) {
30+
nodeName += ".exe";
31+
}
32+
return nodeName;
33+
}
34+
35+
static Optional<File> tryFindNextTo(File npmExecutable) {
36+
if (npmExecutable == null) {
37+
return Optional.empty();
38+
}
39+
File nodeExecutable = new File(npmExecutable.getParentFile(), nodeExecutableName());
40+
if (nodeExecutable.exists() && nodeExecutable.isFile() && nodeExecutable.canExecute()) {
41+
return Optional.of(nodeExecutable);
42+
}
43+
return Optional.empty();
44+
}
45+
46+
public static String explainMessage() {
47+
return "Spotless was unable to find a node executable.\n" +
48+
"Either specify the node executable explicitly or make sure it can be found next to the npm executable.";
49+
}
50+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright 2023 DiffPlug
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.diffplug.spotless.npm;
17+
18+
import static java.util.Objects.requireNonNull;
19+
20+
import java.io.File;
21+
import java.io.Serializable;
22+
23+
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
24+
25+
class NpmFormatterStepLocations implements Serializable {
26+
27+
private static final long serialVersionUID = -1055408537924029969L;
28+
@SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED")
29+
private final transient File projectDir;
30+
31+
@SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED")
32+
private final transient File buildDir;
33+
34+
@SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED")
35+
private final transient File npmExecutable;
36+
37+
@SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED")
38+
private final transient File nodeExecutable;
39+
40+
public NpmFormatterStepLocations(File projectDir, File buildDir, File npmExecutable, File nodeExecutable) {
41+
this.projectDir = requireNonNull(projectDir);
42+
this.buildDir = requireNonNull(buildDir);
43+
this.npmExecutable = requireNonNull(npmExecutable);
44+
this.nodeExecutable = requireNonNull(nodeExecutable);
45+
}
46+
47+
public File projectDir() {
48+
return projectDir;
49+
}
50+
51+
public File buildDir() {
52+
return buildDir;
53+
}
54+
55+
public File npmExecutable() {
56+
return npmExecutable;
57+
}
58+
59+
public File nodeExecutable() {
60+
return nodeExecutable;
61+
}
62+
}

lib/src/main/java/com/diffplug/spotless/npm/NpmFormatterStepStateBase.java

+6-12
Original file line numberDiff line numberDiff line change
@@ -48,23 +48,17 @@ abstract class NpmFormatterStepStateBase implements Serializable {
4848
@SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED")
4949
public final transient File nodeModulesDir;
5050

51-
@SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED")
52-
private final transient File npmExecutable;
53-
54-
@SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED")
55-
public final transient File projectDir;
51+
public final NpmFormatterStepLocations locations;
5652

5753
private final NpmConfig npmConfig;
5854

5955
private final String stepName;
6056

61-
protected NpmFormatterStepStateBase(String stepName, NpmConfig npmConfig, File projectDir, File buildDir, File npm) throws IOException {
57+
protected NpmFormatterStepStateBase(String stepName, NpmConfig npmConfig, NpmFormatterStepLocations locations) throws IOException {
6258
this.stepName = requireNonNull(stepName);
6359
this.npmConfig = requireNonNull(npmConfig);
64-
this.projectDir = requireNonNull(projectDir);
65-
this.npmExecutable = npm;
66-
67-
NodeServerLayout layout = prepareNodeServer(buildDir);
60+
this.locations = locations;
61+
NodeServerLayout layout = prepareNodeServer(locations.buildDir());
6862
this.nodeModulesDir = layout.nodeModulesDir();
6963
this.packageJsonSignature = FileSignature.signAsList(layout.packageJsonFile());
7064
}
@@ -88,7 +82,7 @@ private NodeServerLayout prepareNodeServer(File buildDir) throws IOException {
8882
}
8983

9084
private void runNpmInstall(File npmProjectDir) throws IOException {
91-
new NpmProcess(npmProjectDir, this.npmExecutable).install();
85+
new NpmProcess(npmProjectDir, this.locations.npmExecutable(), this.locations.nodeExecutable()).install();
9286
}
9387

9488
protected ServerProcessInfo npmRunServer() throws ServerStartException, IOException {
@@ -102,7 +96,7 @@ protected ServerProcessInfo npmRunServer() throws ServerStartException, IOExcept
10296
final File serverPortFile = new File(this.nodeModulesDir, "server.port");
10397
NpmResourceHelper.deleteFileIfExists(serverPortFile);
10498
// start the http server in node
105-
Process server = new NpmProcess(this.nodeModulesDir, this.npmExecutable).start();
99+
Process server = new NpmProcess(this.nodeModulesDir, this.locations.npmExecutable(), this.locations.nodeExecutable()).start();
106100

107101
// await the readiness of the http server - wait for at most 60 seconds
108102
try {

lib/src/main/java/com/diffplug/spotless/npm/NpmPathResolver.java

+46-7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2020 DiffPlug
2+
* Copyright 2020-2023 DiffPlug
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,28 +16,67 @@
1616
package com.diffplug.spotless.npm;
1717

1818
import java.io.File;
19-
import java.util.Arrays;
19+
import java.util.ArrayList;
20+
import java.util.Collections;
2021
import java.util.List;
2122
import java.util.Optional;
2223

2324
public class NpmPathResolver {
2425

2526
private final File explicitNpmExecutable;
2627

28+
private final File explicitNodeExecutable;
29+
2730
private final File explicitNpmrcFile;
2831

2932
private final List<File> additionalNpmrcLocations;
3033

31-
public NpmPathResolver(File explicitNpmExecutable, File explicitNpmrcFile, File... additionalNpmrcLocations) {
34+
public NpmPathResolver(File explicitNpmExecutable, File explicitNodeExecutable, File explicitNpmrcFile, List<File> additionalNpmrcLocations) {
3235
this.explicitNpmExecutable = explicitNpmExecutable;
36+
this.explicitNodeExecutable = explicitNodeExecutable;
3337
this.explicitNpmrcFile = explicitNpmrcFile;
34-
this.additionalNpmrcLocations = Arrays.asList(additionalNpmrcLocations);
38+
this.additionalNpmrcLocations = Collections.unmodifiableList(new ArrayList<>(additionalNpmrcLocations));
3539
}
3640

41+
/**
42+
* Finds the npm executable to use.
43+
* <br>
44+
* Either the explicit npm executable is returned, or - if an explicit node executable is configured - tries to find
45+
* the npm executable relative to the node executable.
46+
* Falls back to looking for npm on the user's system using {@link NpmExecutableResolver}
47+
*
48+
* @return the npm executable to use
49+
* @throws IllegalStateException if no npm executable could be found
50+
*/
3751
public File resolveNpmExecutable() {
38-
return Optional.ofNullable(this.explicitNpmExecutable)
39-
.orElseGet(() -> NpmExecutableResolver.tryFind()
40-
.orElseThrow(() -> new IllegalStateException("Can't automatically determine npm executable and none was specifically supplied!\n\n" + NpmExecutableResolver.explainMessage())));
52+
if (this.explicitNpmExecutable != null) {
53+
return this.explicitNpmExecutable;
54+
}
55+
if (this.explicitNodeExecutable != null) {
56+
File nodeExecutableCandidate = new File(this.explicitNodeExecutable.getParentFile(), NpmExecutableResolver.npmExecutableName());
57+
if (nodeExecutableCandidate.canExecute()) {
58+
return nodeExecutableCandidate;
59+
}
60+
}
61+
return NpmExecutableResolver.tryFind()
62+
.orElseThrow(() -> new IllegalStateException("Can't automatically determine npm executable and none was specifically supplied!\n\n" + NpmExecutableResolver.explainMessage()));
63+
}
64+
65+
/**
66+
* Finds the node executable to use.
67+
* <br>
68+
* Either the explicit node executable is returned, or tries to find the node executable relative to the npm executable
69+
* found by {@link #resolveNpmExecutable()}.
70+
* @return the node executable to use
71+
* @throws IllegalStateException if no node executable could be found
72+
*/
73+
public File resolveNodeExecutable() {
74+
if (this.explicitNodeExecutable != null) {
75+
return this.explicitNodeExecutable;
76+
}
77+
File npmExecutable = resolveNpmExecutable();
78+
return NodeExecutableResolver.tryFindNextTo(npmExecutable)
79+
.orElseThrow(() -> new IllegalStateException("Can't automatically determine node executable and none was specifically supplied!\n\n" + NodeExecutableResolver.explainMessage()));
4180
}
4281

4382
public String resolveNpmrcContent() {

lib/src/main/java/com/diffplug/spotless/npm/NpmProcess.java

+12-4
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,12 @@ class NpmProcess {
2828

2929
private final File npmExecutable;
3030

31-
NpmProcess(File workingDir, File npmExecutable) {
31+
private final File nodeExecutable;
32+
33+
NpmProcess(File workingDir, File npmExecutable, File nodeExecutable) {
3234
this.workingDir = workingDir;
3335
this.npmExecutable = npmExecutable;
36+
this.nodeExecutable = nodeExecutable;
3437
}
3538

3639
void install() {
@@ -61,11 +64,12 @@ private void npmAwait(String... args) {
6164
private Process npm(String... args) {
6265
List<String> processCommand = processCommand(args);
6366
try {
64-
return new ProcessBuilder()
67+
ProcessBuilder processBuilder = new ProcessBuilder()
6568
.inheritIO()
6669
.directory(this.workingDir)
67-
.command(processCommand)
68-
.start();
70+
.command(processCommand);
71+
addEnvironmentVariables(processBuilder);
72+
return processBuilder.start();
6973
} catch (IOException e) {
7074
throw new NpmProcessException("Failed to launch npm command '" + commandLine(args) + "'.", e);
7175
}
@@ -78,6 +82,10 @@ private List<String> processCommand(String... args) {
7882
return command;
7983
}
8084

85+
private void addEnvironmentVariables(ProcessBuilder processBuilder) {
86+
processBuilder.environment().put("PATH", this.nodeExecutable.getParentFile().getAbsolutePath() + File.pathSeparator + System.getenv("PATH"));
87+
}
88+
8189
private String commandLine(String... args) {
8290
return "npm " + Arrays.stream(args).collect(Collectors.joining(" "));
8391
}

lib/src/main/java/com/diffplug/spotless/npm/PrettierFormatterStep.java

+5-3
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,11 @@ private static class State extends NpmFormatterStepStateBase implements Serializ
7474
"/com/diffplug/spotless/npm/common-serve.js",
7575
"/com/diffplug/spotless/npm/prettier-serve.js"),
7676
npmPathResolver.resolveNpmrcContent()),
77-
projectDir,
78-
buildDir,
79-
npmPathResolver.resolveNpmExecutable());
77+
new NpmFormatterStepLocations(
78+
projectDir,
79+
buildDir,
80+
npmPathResolver.resolveNpmExecutable(),
81+
npmPathResolver.resolveNodeExecutable()));
8082
this.prettierConfig = requireNonNull(prettierConfig);
8183
}
8284

lib/src/main/java/com/diffplug/spotless/npm/TsFmtFormatterStep.java

+5-3
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,11 @@ public State(String stepName, Map<String, String> versions, File projectDir, Fil
8080
"/com/diffplug/spotless/npm/common-serve.js",
8181
"/com/diffplug/spotless/npm/tsfmt-serve.js"),
8282
npmPathResolver.resolveNpmrcContent()),
83-
projectDir,
84-
buildDir,
85-
npmPathResolver.resolveNpmExecutable());
83+
new NpmFormatterStepLocations(
84+
projectDir,
85+
buildDir,
86+
npmPathResolver.resolveNpmExecutable(),
87+
npmPathResolver.resolveNodeExecutable()));
8688
this.buildDir = requireNonNull(buildDir);
8789
this.configFile = configFile;
8890
this.inlineTsFmtSettings = inlineTsFmtSettings == null ? new TreeMap<>() : new TreeMap<>(inlineTsFmtSettings);

plugin-gradle/CHANGES.md

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (
66
### Added
77
* Support `jackson()` for YAML files ([#1492](https://github.com/diffplug/spotless/pull/1492))
88
* Support `jackson()` for JSON files ([#1492](https://github.com/diffplug/spotless/pull/1492))
9+
* Allow to specify node executable for node-based formatters using `nodeExecutable` parameter ([#1500](https://github.com/diffplug/spotless/pull/1500))
910
### Fixed
1011
* The default list of type annotations used by `formatAnnotations` has had 8 more annotations from the Checker Framework added [#1494](https://github.com/diffplug/spotless/pull/1494)
1112
### Changes

plugin-gradle/README.md

+8-3
Original file line numberDiff line numberDiff line change
@@ -895,18 +895,23 @@ spotless {
895895
### npm detection
896896
897897
Prettier is based on NodeJS, so a working NodeJS installation (especially npm) is required on the host running spotless.
898-
Spotless will try to auto-discover an npm installation. If that is not working for you, it is possible to directly configure the npm binary to use.
898+
Spotless will try to auto-discover an npm installation. If that is not working for you, it is possible to directly configure the npm
899+
and/or node binary to use.
899900
900901
```gradle
901902
spotless {
902903
format 'javascript', {
903-
prettier().npmExecutable('/usr/bin/npm').config(...)
904+
prettier().npmExecutable('/usr/bin/npm').nodeExecutable('/usr/bin/node').config(...)
904905
```
905906
907+
If you provide both `npmExecutable` and `nodeExecutable`, spotless will use these paths. If you specify only one of the
908+
two, spotless will assume the other one is in the same directory.
909+
906910
### `.npmrc` detection
907911
908912
Spotless picks up npm configuration stored in a `.npmrc` file either in the project directory or in your user home.
909-
Alternatively you can supply spotless with a location of the `.npmrc` file to use. (This can be combined with `npmExecutable`, of course.)
913+
Alternatively you can supply spotless with a location of the `.npmrc` file to use. (This can be combined with
914+
`npmExecutable` and `nodeExecutable`, of course.)
910915
911916
```gradle
912917
spotless {

0 commit comments

Comments
 (0)