From 4b3cb2421996895eef8f871ac47a3089867780ae Mon Sep 17 00:00:00 2001 From: Simon Gamma Date: Mon, 16 Jan 2023 20:45:45 +0100 Subject: [PATCH 1/8] allow supplying node-binary path or look for it as a sibling to npm binary. the path of the npm binary is then prepended to the system PATH before launching the npm process. --- .../spotless/npm/EslintFormatterStep.java | 10 +-- .../spotless/npm/NodeExecutableResolver.java | 45 ++++++++++++++ .../npm/NpmFormatterStepLocations.java | 62 +++++++++++++++++++ .../npm/NpmFormatterStepStateBase.java | 18 ++---- .../spotless/npm/NpmPathResolver.java | 18 ++++-- .../com/diffplug/spotless/npm/NpmProcess.java | 16 +++-- .../spotless/npm/PrettierFormatterStep.java | 8 ++- .../spotless/npm/TsFmtFormatterStep.java | 8 ++- .../gradle/spotless/FormatExtension.java | 17 ++++- .../gradle/spotless/JavascriptExtension.java | 3 +- .../gradle/spotless/TypescriptExtension.java | 5 +- .../NpmTestsWithoutNpmInstallationTest.java | 59 ++++++++++++++++++ .../npm/AbstractNpmFormatterStepFactory.java | 11 +++- .../npm/NpmFormatterStepCommonTests.java | 9 ++- 14 files changed, 252 insertions(+), 37 deletions(-) create mode 100644 lib/src/main/java/com/diffplug/spotless/npm/NodeExecutableResolver.java create mode 100644 lib/src/main/java/com/diffplug/spotless/npm/NpmFormatterStepLocations.java create mode 100644 plugin-gradle/src/test/java/com/diffplug/gradle/spotless/NpmTestsWithoutNpmInstallationTest.java diff --git a/lib/src/main/java/com/diffplug/spotless/npm/EslintFormatterStep.java b/lib/src/main/java/com/diffplug/spotless/npm/EslintFormatterStep.java index 81c5b6ce78..74979d90aa 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/EslintFormatterStep.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/EslintFormatterStep.java @@ -94,9 +94,11 @@ private static class State extends NpmFormatterStepStateBase implements Serializ "/com/diffplug/spotless/npm/common-serve.js", "/com/diffplug/spotless/npm/eslint-serve.js"), npmPathResolver.resolveNpmrcContent()), - projectDir, - buildDir, - npmPathResolver.resolveNpmExecutable()); + new NpmFormatterStepLocations( + projectDir, + buildDir, + npmPathResolver.resolveNpmExecutable(), + npmPathResolver.resolveNodeExecutable())); this.eslintConfig = localCopyFiles(requireNonNull(eslintConfig)); } @@ -119,7 +121,7 @@ public FormatterFunc createFormatterFunc() { FormattedPrinter.SYSOUT.print("creating formatter function (starting server)"); ServerProcessInfo eslintRestServer = npmRunServer(); EslintRestService restService = new EslintRestService(eslintRestServer.getBaseUrl()); - return Closeable.ofDangerous(() -> endServer(restService, eslintRestServer), new EslintFilePathPassingFormatterFunc(projectDir, nodeModulesDir, eslintConfig, restService)); + return Closeable.ofDangerous(() -> endServer(restService, eslintRestServer), new EslintFilePathPassingFormatterFunc(locations.projectDir(), nodeModulesDir, eslintConfig, restService)); } catch (IOException e) { throw ThrowingEx.asRuntime(e); } diff --git a/lib/src/main/java/com/diffplug/spotless/npm/NodeExecutableResolver.java b/lib/src/main/java/com/diffplug/spotless/npm/NodeExecutableResolver.java new file mode 100644 index 0000000000..c9e04da620 --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/npm/NodeExecutableResolver.java @@ -0,0 +1,45 @@ +/* + * Copyright 2023 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 java.io.File; +import java.util.Optional; + +class NodeExecutableResolver { + + private NodeExecutableResolver() { + // no instance + } + + static String nodeExecutableName() { + String nodeName = "node"; + if (PlatformInfo.normalizedOS() == PlatformInfo.OS.WINDOWS) { + nodeName += ".exe"; + } + return nodeName; + } + + static Optional tryFindNextTo(File npmExecutable) { + if (npmExecutable == null) { + return Optional.empty(); + } + File nodeExecutable = new File(npmExecutable.getParentFile(), nodeExecutableName()); + if (nodeExecutable.exists() && nodeExecutable.isFile() && nodeExecutable.canExecute()) { + return Optional.of(nodeExecutable); + } + return Optional.empty(); + } +} diff --git a/lib/src/main/java/com/diffplug/spotless/npm/NpmFormatterStepLocations.java b/lib/src/main/java/com/diffplug/spotless/npm/NpmFormatterStepLocations.java new file mode 100644 index 0000000000..0c417733e8 --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/npm/NpmFormatterStepLocations.java @@ -0,0 +1,62 @@ +/* + * Copyright 2023 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 java.util.Objects.requireNonNull; + +import java.io.File; +import java.io.Serializable; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +class NpmFormatterStepLocations implements Serializable { + + private static final long serialVersionUID = -1055408537924029969L; + @SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED") + private final transient File projectDir; + + @SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED") + private final transient File buildDir; + + @SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED") + private final transient File npmExecutable; + + @SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED") + private final transient File nodeExecutable; + + public NpmFormatterStepLocations(File projectDir, File buildDir, File npmExecutable, File nodeExecutable) { + this.projectDir = requireNonNull(projectDir); + this.buildDir = requireNonNull(buildDir); + this.npmExecutable = requireNonNull(npmExecutable); + this.nodeExecutable = requireNonNull(nodeExecutable); + } + + public File projectDir() { + return projectDir; + } + + public File buildDir() { + return buildDir; + } + + public File npmExecutable() { + return npmExecutable; + } + + public File nodeExecutable() { + return nodeExecutable; + } +} 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 49a166e45c..4f555a177e 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/NpmFormatterStepStateBase.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/NpmFormatterStepStateBase.java @@ -48,23 +48,17 @@ abstract class NpmFormatterStepStateBase implements Serializable { @SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED") public final transient File nodeModulesDir; - @SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED") - private final transient File npmExecutable; - - @SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED") - public final transient File projectDir; + public final NpmFormatterStepLocations locations; private final NpmConfig npmConfig; private final String stepName; - protected NpmFormatterStepStateBase(String stepName, NpmConfig npmConfig, File projectDir, File buildDir, File npm) throws IOException { + protected NpmFormatterStepStateBase(String stepName, NpmConfig npmConfig, NpmFormatterStepLocations locations) throws IOException { this.stepName = requireNonNull(stepName); this.npmConfig = requireNonNull(npmConfig); - this.projectDir = requireNonNull(projectDir); - this.npmExecutable = npm; - - NodeServerLayout layout = prepareNodeServer(buildDir); + this.locations = locations; + NodeServerLayout layout = prepareNodeServer(locations.buildDir()); this.nodeModulesDir = layout.nodeModulesDir(); this.packageJsonSignature = FileSignature.signAsList(layout.packageJsonFile()); } @@ -88,7 +82,7 @@ private NodeServerLayout prepareNodeServer(File buildDir) throws IOException { } private void runNpmInstall(File npmProjectDir) throws IOException { - new NpmProcess(npmProjectDir, this.npmExecutable).install(); + new NpmProcess(npmProjectDir, this.locations.npmExecutable(), this.locations.nodeExecutable()).install(); } protected ServerProcessInfo npmRunServer() throws ServerStartException, IOException { @@ -102,7 +96,7 @@ protected ServerProcessInfo npmRunServer() throws ServerStartException, IOExcept final File serverPortFile = new File(this.nodeModulesDir, "server.port"); NpmResourceHelper.deleteFileIfExists(serverPortFile); // start the http server in node - Process server = new NpmProcess(this.nodeModulesDir, this.npmExecutable).start(); + Process server = new NpmProcess(this.nodeModulesDir, this.locations.npmExecutable(), this.locations.nodeExecutable()).start(); // await the readiness of the http server - wait for at most 60 seconds try { diff --git a/lib/src/main/java/com/diffplug/spotless/npm/NpmPathResolver.java b/lib/src/main/java/com/diffplug/spotless/npm/NpmPathResolver.java index a9a15bd49d..d18146188d 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/NpmPathResolver.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/NpmPathResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 DiffPlug + * Copyright 2020-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,8 @@ package com.diffplug.spotless.npm; import java.io.File; -import java.util.Arrays; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Optional; @@ -24,14 +25,17 @@ public class NpmPathResolver { private final File explicitNpmExecutable; + private final File explicitNodeExecutable; + private final File explicitNpmrcFile; private final List additionalNpmrcLocations; - public NpmPathResolver(File explicitNpmExecutable, File explicitNpmrcFile, File... additionalNpmrcLocations) { + public NpmPathResolver(File explicitNpmExecutable, File explicitNodeExecutable, File explicitNpmrcFile, List additionalNpmrcLocations) { this.explicitNpmExecutable = explicitNpmExecutable; + this.explicitNodeExecutable = explicitNodeExecutable; this.explicitNpmrcFile = explicitNpmrcFile; - this.additionalNpmrcLocations = Arrays.asList(additionalNpmrcLocations); + this.additionalNpmrcLocations = Collections.unmodifiableList(new ArrayList<>(additionalNpmrcLocations)); } public File resolveNpmExecutable() { @@ -40,6 +44,12 @@ public File resolveNpmExecutable() { .orElseThrow(() -> new IllegalStateException("Can't automatically determine npm executable and none was specifically supplied!\n\n" + NpmExecutableResolver.explainMessage()))); } + public File resolveNodeExecutable() { + return Optional.ofNullable(this.explicitNodeExecutable) + .orElseGet(() -> NodeExecutableResolver.tryFindNextTo(resolveNpmExecutable()) + .orElseThrow(() -> new IllegalStateException("Can't automatically determine node executable and none was specifically supplied!\n\n" + NpmExecutableResolver.explainMessage()))); + } + public String resolveNpmrcContent() { File npmrcFile = Optional.ofNullable(this.explicitNpmrcFile) .orElseGet(() -> new NpmrcResolver(additionalNpmrcLocations).tryFind() diff --git a/lib/src/main/java/com/diffplug/spotless/npm/NpmProcess.java b/lib/src/main/java/com/diffplug/spotless/npm/NpmProcess.java index 8be94a9ea3..1675a4aa4e 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/NpmProcess.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/NpmProcess.java @@ -28,9 +28,12 @@ class NpmProcess { private final File npmExecutable; - NpmProcess(File workingDir, File npmExecutable) { + private final File nodeExecutable; + + NpmProcess(File workingDir, File npmExecutable, File nodeExecutable) { this.workingDir = workingDir; this.npmExecutable = npmExecutable; + this.nodeExecutable = nodeExecutable; } void install() { @@ -61,11 +64,12 @@ private void npmAwait(String... args) { private Process npm(String... args) { List processCommand = processCommand(args); try { - return new ProcessBuilder() + ProcessBuilder processBuilder = new ProcessBuilder() .inheritIO() .directory(this.workingDir) - .command(processCommand) - .start(); + .command(processCommand); + addEnvironmentVariables(processBuilder); + return processBuilder.start(); } catch (IOException e) { throw new NpmProcessException("Failed to launch npm command '" + commandLine(args) + "'.", e); } @@ -78,6 +82,10 @@ private List processCommand(String... args) { return command; } + private void addEnvironmentVariables(ProcessBuilder processBuilder) { + processBuilder.environment().put("PATH", this.nodeExecutable.getParentFile().getAbsolutePath() + File.pathSeparator + System.getenv("PATH")); + } + private String commandLine(String... args) { return "npm " + Arrays.stream(args).collect(Collectors.joining(" ")); } diff --git a/lib/src/main/java/com/diffplug/spotless/npm/PrettierFormatterStep.java b/lib/src/main/java/com/diffplug/spotless/npm/PrettierFormatterStep.java index 22a162eb0b..20ff71d08e 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/PrettierFormatterStep.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/PrettierFormatterStep.java @@ -74,9 +74,11 @@ private static class State extends NpmFormatterStepStateBase implements Serializ "/com/diffplug/spotless/npm/common-serve.js", "/com/diffplug/spotless/npm/prettier-serve.js"), npmPathResolver.resolveNpmrcContent()), - projectDir, - buildDir, - npmPathResolver.resolveNpmExecutable()); + new NpmFormatterStepLocations( + projectDir, + buildDir, + npmPathResolver.resolveNpmExecutable(), + npmPathResolver.resolveNodeExecutable())); this.prettierConfig = requireNonNull(prettierConfig); } diff --git a/lib/src/main/java/com/diffplug/spotless/npm/TsFmtFormatterStep.java b/lib/src/main/java/com/diffplug/spotless/npm/TsFmtFormatterStep.java index 98d694ec33..14d6b1bbd0 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/TsFmtFormatterStep.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/TsFmtFormatterStep.java @@ -80,9 +80,11 @@ public State(String stepName, Map versions, File projectDir, Fil "/com/diffplug/spotless/npm/common-serve.js", "/com/diffplug/spotless/npm/tsfmt-serve.js"), npmPathResolver.resolveNpmrcContent()), - projectDir, - buildDir, - npmPathResolver.resolveNpmExecutable()); + new NpmFormatterStepLocations( + projectDir, + buildDir, + npmPathResolver.resolveNpmExecutable(), + npmPathResolver.resolveNodeExecutable())); this.buildDir = requireNonNull(buildDir); this.configFile = configFile; this.inlineTsFmtSettings = inlineTsFmtSettings == null ? new TreeMap<>() : new TreeMap<>(inlineTsFmtSettings); diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/FormatExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/FormatExtension.java index 4e33c45ed0..8012102200 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/FormatExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/FormatExtension.java @@ -23,6 +23,7 @@ import java.nio.charset.Charset; import java.nio.file.Files; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Map; @@ -524,6 +525,9 @@ public abstract static class NpmStepConfig> { @Nullable protected Object npmFile; + @Nullable + protected Object nodeFile; + @Nullable protected Object npmrcFile; @@ -543,6 +547,13 @@ public T npmExecutable(final Object npmFile) { return (T) this; } + @SuppressWarnings("unchecked") + public T nodeExecutable(final Object nodeFile) { + this.nodeFile = nodeFile; + replaceStep(); + return (T) this; + } + public T npmrc(final Object npmrcFile) { this.npmrcFile = npmrcFile; replaceStep(); @@ -553,6 +564,10 @@ File npmFileOrNull() { return fileOrNull(npmFile); } + File nodeFileOrNull() { + return fileOrNull(nodeFile); + } + File npmrcFileOrNull() { return fileOrNull(npmrcFile); } @@ -604,7 +619,7 @@ protected FormatterStep createStep() { provisioner(), project.getProjectDir(), project.getBuildDir(), - new NpmPathResolver(npmFileOrNull(), npmrcFileOrNull(), project.getProjectDir(), project.getRootDir()), + new NpmPathResolver(npmFileOrNull(), nodeFileOrNull(), npmrcFileOrNull(), Arrays.asList(project.getProjectDir(), project.getRootDir())), new com.diffplug.spotless.npm.PrettierConfig( this.prettierConfigFile != null ? project.file(this.prettierConfigFile) : null, this.prettierConfig)); diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JavascriptExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JavascriptExtension.java index dd476b6a69..e829c2b53a 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JavascriptExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JavascriptExtension.java @@ -17,6 +17,7 @@ import static java.util.Objects.requireNonNull; +import java.util.Arrays; import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; @@ -107,7 +108,7 @@ public FormatterStep createStep() { provisioner(), project.getProjectDir(), project.getBuildDir(), - new NpmPathResolver(npmFileOrNull(), npmrcFileOrNull(), project.getProjectDir(), project.getRootDir()), + new NpmPathResolver(npmFileOrNull(), nodeFileOrNull(), npmrcFileOrNull(), Arrays.asList(project.getProjectDir(), project.getRootDir())), eslintConfig()); } diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/TypescriptExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/TypescriptExtension.java index 0824caa769..2283afccff 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/TypescriptExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/TypescriptExtension.java @@ -17,6 +17,7 @@ import static java.util.Objects.requireNonNull; +import java.util.Arrays; import java.util.Collections; import java.util.Map; import java.util.Objects; @@ -116,7 +117,7 @@ public FormatterStep createStep() { provisioner(), project.getProjectDir(), project.getBuildDir(), - new NpmPathResolver(npmFileOrNull(), npmrcFileOrNull(), project.getProjectDir(), project.getRootDir()), + new NpmPathResolver(npmFileOrNull(), nodeFileOrNull(), npmrcFileOrNull(), Arrays.asList(project.getProjectDir(), project.getRootDir())), typedConfigFile(), config); } @@ -212,7 +213,7 @@ public FormatterStep createStep() { provisioner(), project.getProjectDir(), project.getBuildDir(), - new NpmPathResolver(npmFileOrNull(), npmrcFileOrNull(), project.getProjectDir(), project.getRootDir()), + new NpmPathResolver(npmFileOrNull(), nodeFileOrNull(), npmrcFileOrNull(), Arrays.asList(project.getProjectDir(), project.getRootDir())), eslintConfig()); } diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/NpmTestsWithoutNpmInstallationTest.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/NpmTestsWithoutNpmInstallationTest.java new file mode 100644 index 0000000000..93bc4b699b --- /dev/null +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/NpmTestsWithoutNpmInstallationTest.java @@ -0,0 +1,59 @@ +/* + * Copyright 2016-2023 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.gradle.spotless; + +import org.assertj.core.api.Assertions; +import org.gradle.testkit.runner.BuildResult; +import org.junit.jupiter.api.Test; + +class NpmTestsWithoutNpmInstallationTest extends GradleIntegrationHarness { + + @Test + void useNodeFromNodeGradlePlugin() throws Exception { + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + " id 'com.github.node-gradle.node' version '3.5.1'", + "}", + "repositories { mavenCentral() }", + "node {", + " download = true", + " version = '18.13.0'", + " npmVersion = '8.19.2'", + " workDir = file(\"${buildDir}/nodejs\")", + " npmWorkDir = file(\"${buildDir}/npm\")", + "}", + "def prettierConfig = [:]", + "prettierConfig['printWidth'] = 50", + "prettierConfig['parser'] = 'typescript'", + "spotless {", + " format 'mytypescript', {", + " target 'test.ts'", + " prettier()", + " .npmExecutable(\"${tasks.named('npmSetup').get().npmDir.get()}/bin/npm\")", + " .nodeExecutable(\"${tasks.named('nodeSetup').get().nodeDir.get()}/bin/node\")", + " .config(prettierConfig)", + " }", + "}"); + setFile("test.ts").toResource("npm/prettier/config/typescript.dirty"); + // make sure node binary is there + gradleRunner().withArguments("nodeSetup", "npmSetup").build(); + // then run spotless using that node installation + final BuildResult spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").build(); + Assertions.assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL"); + assertFile("test.ts").sameAsResource("npm/prettier/config/typescript.configfile.clean"); + } +} diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/npm/AbstractNpmFormatterStepFactory.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/npm/AbstractNpmFormatterStepFactory.java index 5467f4c3bc..e4a052106a 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/npm/AbstractNpmFormatterStepFactory.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/npm/AbstractNpmFormatterStepFactory.java @@ -18,6 +18,7 @@ import java.io.File; import java.util.AbstractMap; import java.util.Arrays; +import java.util.Collections; import java.util.Map; import java.util.Objects; import java.util.Properties; @@ -34,6 +35,9 @@ public abstract class AbstractNpmFormatterStepFactory implements FormatterStepFa @Parameter private String npmExecutable; + @Parameter + private String nodeExecutable; + @Parameter private String npmrc; @@ -42,6 +46,11 @@ protected File npm(FormatterStepConfig stepConfig) { return npm; } + protected File node(FormatterStepConfig stepConfig) { + File node = nodeExecutable != null ? stepConfig.getFileLocator().locateFile(nodeExecutable) : null; + return node; + } + protected File npmrc(FormatterStepConfig stepConfig) { File npmrc = this.npmrc != null ? stepConfig.getFileLocator().locateFile(this.npmrc) : null; return npmrc; @@ -56,7 +65,7 @@ protected File baseDir(FormatterStepConfig stepConfig) { } protected NpmPathResolver npmPathResolver(FormatterStepConfig stepConfig) { - return new NpmPathResolver(npm(stepConfig), npmrc(stepConfig), baseDir(stepConfig)); + return new NpmPathResolver(npm(stepConfig), node(stepConfig), npmrc(stepConfig), Collections.singletonList(baseDir(stepConfig))); } protected boolean moreThanOneNonNull(Object... objects) { diff --git a/testlib/src/test/java/com/diffplug/spotless/npm/NpmFormatterStepCommonTests.java b/testlib/src/test/java/com/diffplug/spotless/npm/NpmFormatterStepCommonTests.java index dff105108a..60dd42801a 100644 --- a/testlib/src/test/java/com/diffplug/spotless/npm/NpmFormatterStepCommonTests.java +++ b/testlib/src/test/java/com/diffplug/spotless/npm/NpmFormatterStepCommonTests.java @@ -17,17 +17,22 @@ import java.io.File; import java.io.IOException; +import java.util.Collections; import com.diffplug.spotless.ResourceHarness; public abstract class NpmFormatterStepCommonTests extends ResourceHarness { protected NpmPathResolver npmPathResolver() { - return new NpmPathResolver(npmExecutable(), npmrc()); + return new NpmPathResolver(npmExecutable(), nodeExecutable(), npmrc(), Collections.emptyList()); } private File npmExecutable() { - return NpmExecutableResolver.tryFind().orElseThrow(() -> new IllegalStateException("cannot detect node binary")); + return NpmExecutableResolver.tryFind().orElseThrow(() -> new IllegalStateException("cannot detect npm binary")); + } + + private File nodeExecutable() { + return NodeExecutableResolver.tryFindNextTo(npmExecutable()).orElseThrow(() -> new IllegalStateException("cannot detect node binary")); } private File npmrc() { From f4f8da7a259fbe55fdb66fc97b31f6d55c47a266 Mon Sep 17 00:00:00 2001 From: Simon Gamma Date: Tue, 17 Jan 2023 13:11:59 +0100 Subject: [PATCH 2/8] add test for supplying only npm when binary --- .../NpmTestsWithoutNpmInstallationTest.java | 42 +++++++++++++++++-- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/NpmTestsWithoutNpmInstallationTest.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/NpmTestsWithoutNpmInstallationTest.java index 93bc4b699b..621d953868 100644 --- a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/NpmTestsWithoutNpmInstallationTest.java +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/NpmTestsWithoutNpmInstallationTest.java @@ -22,7 +22,7 @@ class NpmTestsWithoutNpmInstallationTest extends GradleIntegrationHarness { @Test - void useNodeFromNodeGradlePlugin() throws Exception { + void useNodeAndNpmFromNodeGradlePlugin() throws Exception { setFile("build.gradle").toLines( "plugins {", " id 'com.diffplug.spotless'", @@ -39,12 +39,48 @@ void useNodeFromNodeGradlePlugin() throws Exception { "def prettierConfig = [:]", "prettierConfig['printWidth'] = 50", "prettierConfig['parser'] = 'typescript'", + "def npmExecExtension = System.getProperty('os.name').toLowerCase().contains('windows') ? '.cmd' : ''", + "def nodeExecExtension = System.getProperty('os.name').toLowerCase().contains('windows') ? '.exe' : ''", "spotless {", " format 'mytypescript', {", " target 'test.ts'", " prettier()", - " .npmExecutable(\"${tasks.named('npmSetup').get().npmDir.get()}/bin/npm\")", - " .nodeExecutable(\"${tasks.named('nodeSetup').get().nodeDir.get()}/bin/node\")", + " .npmExecutable(\"${tasks.named('npmSetup').get().npmDir.get()}/bin/npm${npmExecExtension}\")", + " .nodeExecutable(\"${tasks.named('nodeSetup').get().nodeDir.get()}/bin/node${nodeExecExtension}\")", + " .config(prettierConfig)", + " }", + "}"); + setFile("test.ts").toResource("npm/prettier/config/typescript.dirty"); + // make sure node binary is there + gradleRunner().withArguments("nodeSetup", "npmSetup").build(); + // then run spotless using that node installation + final BuildResult spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").build(); + Assertions.assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL"); + assertFile("test.ts").sameAsResource("npm/prettier/config/typescript.configfile.clean"); + } + + @Test + void useNpmFromNodeGradlePlugin() throws Exception { + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + " id 'com.github.node-gradle.node' version '3.5.1'", + "}", + "repositories { mavenCentral() }", + "node {", + " download = true", + " version = '18.13.0'", + " workDir = file(\"${buildDir}/nodejs\")", + "}", + "def prettierConfig = [:]", + "prettierConfig['printWidth'] = 50", + "prettierConfig['parser'] = 'typescript'", + "def npmExecExtension = System.getProperty('os.name').toLowerCase().contains('windows') ? '.cmd' : ''", + "spotless {", + " format 'mytypescript', {", + " target 'test.ts'", + " prettier()", + " .npmExecutable(\"${tasks.named('npmSetup').get().npmDir.get()}/bin/npm${npmExecExtension}\")", " .config(prettierConfig)", " }", "}"); From 27f9919da7cc5079cfb2823d6c65484c6e542bb9 Mon Sep 17 00:00:00 2001 From: Simon Gamma Date: Tue, 17 Jan 2023 19:51:54 +0100 Subject: [PATCH 3/8] also allow to configure node executable only --- .../spotless/npm/NodeExecutableResolver.java | 5 +++ .../spotless/npm/NpmPathResolver.java | 41 ++++++++++++++++--- .../NpmTestsWithoutNpmInstallationTest.java | 34 +++++++++++++++ 3 files changed, 74 insertions(+), 6 deletions(-) diff --git a/lib/src/main/java/com/diffplug/spotless/npm/NodeExecutableResolver.java b/lib/src/main/java/com/diffplug/spotless/npm/NodeExecutableResolver.java index c9e04da620..eb30ae78b8 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/NodeExecutableResolver.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/NodeExecutableResolver.java @@ -42,4 +42,9 @@ static Optional tryFindNextTo(File npmExecutable) { } return Optional.empty(); } + + public static String explainMessage() { + return "Spotless was unable to find a node executable.\n" + + "Either specify the node executable explicitly or make sure it can be found next to the npm executable."; + } } diff --git a/lib/src/main/java/com/diffplug/spotless/npm/NpmPathResolver.java b/lib/src/main/java/com/diffplug/spotless/npm/NpmPathResolver.java index d18146188d..4759d2f914 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/NpmPathResolver.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/NpmPathResolver.java @@ -38,16 +38,45 @@ public NpmPathResolver(File explicitNpmExecutable, File explicitNodeExecutable, this.additionalNpmrcLocations = Collections.unmodifiableList(new ArrayList<>(additionalNpmrcLocations)); } + /** + * Finds the npm executable to use. + *
+ * Either the explicit npm executable is returned, or - if an explicit node executable is configured - tries to find + * the npm executable relative to the node executable. + * Falls back to looking for npm on the user's system using {@link NpmExecutableResolver} + * + * @return the npm executable to use + * @throws IllegalStateException if no npm executable could be found + */ public File resolveNpmExecutable() { - return Optional.ofNullable(this.explicitNpmExecutable) - .orElseGet(() -> NpmExecutableResolver.tryFind() - .orElseThrow(() -> new IllegalStateException("Can't automatically determine npm executable and none was specifically supplied!\n\n" + NpmExecutableResolver.explainMessage()))); + if (this.explicitNpmExecutable != null) { + return this.explicitNpmExecutable; + } + if (this.explicitNodeExecutable != null) { + File nodeExecutableCandidate = new File(this.explicitNodeExecutable.getParentFile(), NpmExecutableResolver.npmExecutableName()); + if (nodeExecutableCandidate.canExecute()) { + return nodeExecutableCandidate; + } + } + return NpmExecutableResolver.tryFind() + .orElseThrow(() -> new IllegalStateException("Can't automatically determine npm executable and none was specifically supplied!\n\n" + NpmExecutableResolver.explainMessage())); } + /** + * Finds the node executable to use. + *
+ * Either the explicit node executable is returned, or tries to find the node executable relative to the npm executable + * found by {@link #resolveNpmExecutable()}. + * @return the node executable to use + * @throws IllegalStateException if no node executable could be found + */ public File resolveNodeExecutable() { - return Optional.ofNullable(this.explicitNodeExecutable) - .orElseGet(() -> NodeExecutableResolver.tryFindNextTo(resolveNpmExecutable()) - .orElseThrow(() -> new IllegalStateException("Can't automatically determine node executable and none was specifically supplied!\n\n" + NpmExecutableResolver.explainMessage()))); + if (this.explicitNodeExecutable != null) { + return this.explicitNodeExecutable; + } + File npmExecutable = resolveNpmExecutable(); + return NodeExecutableResolver.tryFindNextTo(npmExecutable) + .orElseThrow(() -> new IllegalStateException("Can't automatically determine node executable and none was specifically supplied!\n\n" + NodeExecutableResolver.explainMessage())); } public String resolveNpmrcContent() { diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/NpmTestsWithoutNpmInstallationTest.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/NpmTestsWithoutNpmInstallationTest.java index 621d953868..8147c79228 100644 --- a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/NpmTestsWithoutNpmInstallationTest.java +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/NpmTestsWithoutNpmInstallationTest.java @@ -92,4 +92,38 @@ void useNpmFromNodeGradlePlugin() throws Exception { Assertions.assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL"); assertFile("test.ts").sameAsResource("npm/prettier/config/typescript.configfile.clean"); } + + @Test + void useNpmNextToConfiguredNodePluginFromNodeGradlePlugin() throws Exception { + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + " id 'com.github.node-gradle.node' version '3.5.1'", + "}", + "repositories { mavenCentral() }", + "node {", + " download = true", + " version = '18.13.0'", + " workDir = file(\"${buildDir}/nodejs\")", + "}", + "def prettierConfig = [:]", + "prettierConfig['printWidth'] = 50", + "prettierConfig['parser'] = 'typescript'", + "def nodeExecExtension = System.getProperty('os.name').toLowerCase().contains('windows') ? '.exe' : ''", + "spotless {", + " format 'mytypescript', {", + " target 'test.ts'", + " prettier()", + " .nodeExecutable(\"${tasks.named('nodeSetup').get().nodeDir.get()}/bin/node${nodeExecExtension}\")", + " .config(prettierConfig)", + " }", + "}"); + setFile("test.ts").toResource("npm/prettier/config/typescript.dirty"); + // make sure node binary is there + gradleRunner().withArguments("nodeSetup", "npmSetup").build(); + // then run spotless using that node installation + final BuildResult spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").build(); + Assertions.assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL"); + assertFile("test.ts").sameAsResource("npm/prettier/config/typescript.configfile.clean"); + } } From a96d92c0f524f71fc73476ffed6ab8f6b0425308 Mon Sep 17 00:00:00 2001 From: Simon Gamma Date: Tue, 17 Jan 2023 20:46:32 +0100 Subject: [PATCH 4/8] add tests that verifies issue #1499 --- .../NpmTestsWithoutNpmInstallationTest.java | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/NpmTestsWithoutNpmInstallationTest.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/NpmTestsWithoutNpmInstallationTest.java index 8147c79228..d5c4e8f092 100644 --- a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/NpmTestsWithoutNpmInstallationTest.java +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/NpmTestsWithoutNpmInstallationTest.java @@ -17,6 +17,7 @@ import org.assertj.core.api.Assertions; import org.gradle.testkit.runner.BuildResult; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; class NpmTestsWithoutNpmInstallationTest extends GradleIntegrationHarness { @@ -59,6 +60,46 @@ void useNodeAndNpmFromNodeGradlePlugin() throws Exception { assertFile("test.ts").sameAsResource("npm/prettier/config/typescript.configfile.clean"); } + @Test + @Disabled("This test is disabled because we currently don't support using npm/node installed by the" + + "node-gradle-plugin as the installation takes place in the *execution* phase, but spotless needs it in the *configuration* phase.") + void useNodeAndNpmFromNodeGradlePluginInOneSweep() throws Exception { + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + " id 'com.github.node-gradle.node' version '3.5.1'", + "}", + "repositories { mavenCentral() }", + "node {", + " download = true", + " version = '18.13.0'", + " npmVersion = '8.19.2'", + " workDir = file(\"${buildDir}/nodejs\")", + " npmWorkDir = file(\"${buildDir}/npm\")", + "}", + "def prettierConfig = [:]", + "prettierConfig['printWidth'] = 50", + "prettierConfig['parser'] = 'typescript'", + "def npmExecExtension = System.getProperty('os.name').toLowerCase().contains('windows') ? '.cmd' : ''", + "def nodeExecExtension = System.getProperty('os.name').toLowerCase().contains('windows') ? '.exe' : ''", + "spotless {", + " format 'mytypescript', {", + " target 'test.ts'", + " prettier()", + " .npmExecutable(\"${tasks.named('npmSetup').get().npmDir.get()}/bin/npm${npmExecExtension}\")", + " .nodeExecutable(\"${tasks.named('nodeSetup').get().nodeDir.get()}/bin/node${nodeExecExtension}\")", + " .config(prettierConfig)", + " }", + "}", + "tasks.named('spotlessMytypescript').configure {", + " it.dependsOn('nodeSetup', 'npmSetup')", + "}"); + setFile("test.ts").toResource("npm/prettier/config/typescript.dirty"); + final BuildResult spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").build(); + Assertions.assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL"); + assertFile("test.ts").sameAsResource("npm/prettier/config/typescript.configfile.clean"); + } + @Test void useNpmFromNodeGradlePlugin() throws Exception { setFile("build.gradle").toLines( From b9437cd78b16d4ef2aeee942bc8d7b6f367068ef Mon Sep 17 00:00:00 2001 From: Simon Gamma Date: Wed, 18 Jan 2023 13:02:15 +0100 Subject: [PATCH 5/8] add maven test to verify dynamically installed npm works --- .../maven/MavenIntegrationHarness.java | 27 +++++--- .../maven/MultiModuleProjectTest.java | 2 +- .../spotless/maven/SpotlessCheckMojoTest.java | 1 + .../incremental/UpToDateCheckingTest.java | 3 +- .../maven/npm/NpmFrontendMavenPlugin.java | 66 +++++++++++++++++++ ...namicallyInstalledNpmInstallationTest.java | 48 ++++++++++++++ .../src/test/resources/pom-test.xml.mustache | 1 + 7 files changed, 137 insertions(+), 11 deletions(-) create mode 100644 plugin-maven/src/test/java/com/diffplug/spotless/maven/npm/NpmFrontendMavenPlugin.java create mode 100644 plugin-maven/src/test/java/com/diffplug/spotless/maven/npm/NpmTestsWithDynamicallyInstalledNpmInstallationTest.java diff --git a/plugin-maven/src/test/java/com/diffplug/spotless/maven/MavenIntegrationHarness.java b/plugin-maven/src/test/java/com/diffplug/spotless/maven/MavenIntegrationHarness.java index 5a724408c8..f4e94e573c 100644 --- a/plugin-maven/src/test/java/com/diffplug/spotless/maven/MavenIntegrationHarness.java +++ b/plugin-maven/src/test/java/com/diffplug/spotless/maven/MavenIntegrationHarness.java @@ -59,6 +59,7 @@ public class MavenIntegrationHarness extends ResourceHarness { private static final String EXECUTIONS = "executions"; private static final String MODULES = "modules"; private static final String DEPENDENCIES = "dependencies"; + private static final String PLUGINS = "plugins"; private static final String MODULE_NAME = "name"; private static final int REMOTE_DEBUG_PORT = 5005; @@ -151,6 +152,10 @@ protected void writePomWithPrettierSteps(String includes, String... steps) throw writePom(formats(groupWithSteps("format", including(includes), steps))); } + protected void writePomWithPrettierSteps(String[] plugins, String includes, String... steps) throws IOException { + writePom(null, formats(groupWithSteps("format", including(includes), steps)), null, plugins); + } + protected void writePomWithPomSteps(String... steps) throws IOException { writePom(groupWithSteps("pom", including("pom_test.xml"), steps)); } @@ -168,11 +173,11 @@ protected void writePomWithYamlSteps(String... steps) throws IOException { } protected void writePom(String... configuration) throws IOException { - writePom(null, configuration, null); + writePom(null, configuration, null, null); } - protected void writePom(String[] executions, String[] configuration, String[] dependencies) throws IOException { - String pomXmlContent = createPomXmlContent(null, executions, configuration, dependencies); + protected void writePom(String[] executions, String[] configuration, String[] dependencies, String[] plugins) throws IOException { + String pomXmlContent = createPomXmlContent(null, executions, configuration, dependencies, plugins); setFile("pom.xml").toContent(pomXmlContent); } @@ -203,17 +208,17 @@ protected MavenRunner mavenRunnerWithRemoteDebug() throws IOException { return mavenRunner().withRemoteDebug(REMOTE_DEBUG_PORT); } - protected String createPomXmlContent(String pluginVersion, String[] executions, String[] configuration, String[] dependencies) throws IOException { - return createPomXmlContent("/pom-test.xml.mustache", pluginVersion, executions, configuration, dependencies); + protected String createPomXmlContent(String pluginVersion, String[] executions, String[] configuration, String[] dependencies, String[] plugins) throws IOException { + return createPomXmlContent("/pom-test.xml.mustache", pluginVersion, executions, configuration, dependencies, plugins); } - protected String createPomXmlContent(String pomTemplate, String pluginVersion, String[] executions, String[] configuration, String[] dependencies) throws IOException { - Map params = buildPomXmlParams(pluginVersion, executions, configuration, null, dependencies); + protected String createPomXmlContent(String pomTemplate, String pluginVersion, String[] executions, String[] configuration, String[] dependencies, String[] plugins) throws IOException { + Map params = buildPomXmlParams(pluginVersion, executions, configuration, null, dependencies, plugins); return createPomXmlContent(pomTemplate, params); } protected String createPomXmlContent(String pluginVersion, String[] executions, String[] configuration) throws IOException { - return createPomXmlContent(pluginVersion, executions, configuration, null); + return createPomXmlContent(pluginVersion, executions, configuration, null, null); } protected String createPomXmlContent(String pomTemplate, Map params) throws IOException { @@ -226,7 +231,7 @@ protected String createPomXmlContent(String pomTemplate, Map par } } - protected static Map buildPomXmlParams(String pluginVersion, String[] executions, String[] configuration, String[] modules, String[] dependencies) { + protected static Map buildPomXmlParams(String pluginVersion, String[] executions, String[] configuration, String[] modules, String[] dependencies, String[] plugins) { Map params = new HashMap<>(); params.put(SPOTLESS_MAVEN_PLUGIN_VERSION, pluginVersion == null ? getSystemProperty(SPOTLESS_MAVEN_PLUGIN_VERSION) : pluginVersion); @@ -247,6 +252,10 @@ protected static Map buildPomXmlParams(String pluginVersion, Str params.put(DEPENDENCIES, String.join("\n", dependencies)); } + if (plugins != null) { + params.put(PLUGINS, String.join("\n", plugins)); + } + return params; } diff --git a/plugin-maven/src/test/java/com/diffplug/spotless/maven/MultiModuleProjectTest.java b/plugin-maven/src/test/java/com/diffplug/spotless/maven/MultiModuleProjectTest.java index ae8378c566..6206affc04 100644 --- a/plugin-maven/src/test/java/com/diffplug/spotless/maven/MultiModuleProjectTest.java +++ b/plugin-maven/src/test/java/com/diffplug/spotless/maven/MultiModuleProjectTest.java @@ -145,7 +145,7 @@ private void createRootPom() throws IOException { modulesList.addAll(subProjects.keySet()); String[] modules = modulesList.toArray(new String[0]); - Map rootPomParams = buildPomXmlParams(null, null, configuration, modules, null); + Map rootPomParams = buildPomXmlParams(null, null, configuration, modules, null, null); setFile("pom.xml").toContent(createPomXmlContent("/multi-module/pom-parent.xml.mustache", rootPomParams)); } diff --git a/plugin-maven/src/test/java/com/diffplug/spotless/maven/SpotlessCheckMojoTest.java b/plugin-maven/src/test/java/com/diffplug/spotless/maven/SpotlessCheckMojoTest.java index 566851c26c..92a78e3923 100644 --- a/plugin-maven/src/test/java/com/diffplug/spotless/maven/SpotlessCheckMojoTest.java +++ b/plugin-maven/src/test/java/com/diffplug/spotless/maven/SpotlessCheckMojoTest.java @@ -62,6 +62,7 @@ void testSpotlessCheckBindingToVerifyPhase() throws Exception { " ${basedir}/license.txt", " ", ""}, + null, null); testSpotlessCheck(UNFORMATTED_FILE, "verify", true); diff --git a/plugin-maven/src/test/java/com/diffplug/spotless/maven/incremental/UpToDateCheckingTest.java b/plugin-maven/src/test/java/com/diffplug/spotless/maven/incremental/UpToDateCheckingTest.java index 6afcba7430..0a9fe8f2d4 100644 --- a/plugin-maven/src/test/java/com/diffplug/spotless/maven/incremental/UpToDateCheckingTest.java +++ b/plugin-maven/src/test/java/com/diffplug/spotless/maven/incremental/UpToDateCheckingTest.java @@ -98,7 +98,8 @@ private void writePomWithPluginManagementAndDependency() throws IOException { " javax.inject", " 1", " ", - ""})); + ""}, + null)); } @Test diff --git a/plugin-maven/src/test/java/com/diffplug/spotless/maven/npm/NpmFrontendMavenPlugin.java b/plugin-maven/src/test/java/com/diffplug/spotless/maven/npm/NpmFrontendMavenPlugin.java new file mode 100644 index 0000000000..3d20170fab --- /dev/null +++ b/plugin-maven/src/test/java/com/diffplug/spotless/maven/npm/NpmFrontendMavenPlugin.java @@ -0,0 +1,66 @@ +/* + * Copyright 2023 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.maven.npm; + +/** + * Helper class to configure a maven pom to use frontend-maven-plugin. + * @see frontend-maven-plugin on github + */ +public final class NpmFrontendMavenPlugin { + + public static final String GROUP_ID = "com.github.eirslett"; + public static final String ARTIFACT_ID = "frontend-maven-plugin"; + public static final String VERSION = "1.11.3"; // for now, we stick with this version, as it is the last to support maven 3.1 (see pom-test.xml.mustache) + + public static final String GOAL_INSTALL_NODE_AND_NPM = "install-node-and-npm"; + + public static final String INSTALL_DIRECTORY = "target"; + + private NpmFrontendMavenPlugin() { + // prevent instantiation + } + + public static String[] pomPluginLines(String nodeVersion, String npmVersion) { + return new String[]{ + "", + String.format(" %s", GROUP_ID), + String.format(" %s", ARTIFACT_ID), + String.format(" %s", VERSION), + " ", + " ", + " install node and npm", + " ", + String.format(" %s", GOAL_INSTALL_NODE_AND_NPM), + " ", + " ", + " ", + " ", + (nodeVersion != null ? " " + nodeVersion + "" : ""), + (npmVersion != null ? " " + npmVersion + "" : ""), + String.format(" %s", INSTALL_DIRECTORY), + " ", + "" + }; + } + + public static String installNpmMavenGoal() { + return String.format("%s:%s:%s", GROUP_ID, ARTIFACT_ID, GOAL_INSTALL_NODE_AND_NPM); + } + + public static String installedNpmPath() { + return String.format("%s/node/npm", INSTALL_DIRECTORY); + } +} diff --git a/plugin-maven/src/test/java/com/diffplug/spotless/maven/npm/NpmTestsWithDynamicallyInstalledNpmInstallationTest.java b/plugin-maven/src/test/java/com/diffplug/spotless/maven/npm/NpmTestsWithDynamicallyInstalledNpmInstallationTest.java new file mode 100644 index 0000000000..78830bf09d --- /dev/null +++ b/plugin-maven/src/test/java/com/diffplug/spotless/maven/npm/NpmTestsWithDynamicallyInstalledNpmInstallationTest.java @@ -0,0 +1,48 @@ +/* + * Copyright 2023 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.maven.npm; + +import static com.diffplug.spotless.maven.npm.NpmFrontendMavenPlugin.installNpmMavenGoal; +import static com.diffplug.spotless.maven.npm.NpmFrontendMavenPlugin.installedNpmPath; +import static com.diffplug.spotless.maven.npm.NpmFrontendMavenPlugin.pomPluginLines; + +import org.junit.jupiter.api.Test; + +import com.diffplug.spotless.maven.MavenIntegrationHarness; + +public class NpmTestsWithDynamicallyInstalledNpmInstallationTest extends MavenIntegrationHarness { + + @Test + void useDownloadedNpmInstallation() throws Exception { + writePomWithPrettierSteps( + pomPluginLines("v18.13.0", null), + "src/main/typescript/test.ts", + "", + " " + installedNpmPath() + "", + ""); + + String kind = "typescript"; + String suffix = "ts"; + String configPath = ".prettierrc.yml"; + setFile(configPath).toResource("npm/prettier/filetypes/" + kind + "/" + ".prettierrc.yml"); + String path = "src/main/" + kind + "/test." + suffix; + setFile(path).toResource("npm/prettier/filetypes/" + kind + "/" + kind + ".dirty"); + + mavenRunner().withArguments(installNpmMavenGoal(), "spotless:apply").runNoError(); + assertFile(path).sameAsResource("npm/prettier/filetypes/" + kind + "/" + kind + ".clean"); + } + +} diff --git a/plugin-maven/src/test/resources/pom-test.xml.mustache b/plugin-maven/src/test/resources/pom-test.xml.mustache index 122f5d9218..6db3cb0c15 100644 --- a/plugin-maven/src/test/resources/pom-test.xml.mustache +++ b/plugin-maven/src/test/resources/pom-test.xml.mustache @@ -21,6 +21,7 @@ + {{{plugins}}} com.diffplug.spotless spotless-maven-plugin From 789b9d210a4b1555f0eee41d7e178798ecdd16f1 Mon Sep 17 00:00:00 2001 From: Simon Gamma Date: Wed, 18 Jan 2023 14:22:13 +0100 Subject: [PATCH 6/8] add nodeExecutable configuration to README --- plugin-gradle/README.md | 11 ++++++++--- plugin-maven/README.md | 10 ++++++++-- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/plugin-gradle/README.md b/plugin-gradle/README.md index f2567a5924..11f7866644 100644 --- a/plugin-gradle/README.md +++ b/plugin-gradle/README.md @@ -844,18 +844,23 @@ spotless { ### npm detection Prettier is based on NodeJS, so a working NodeJS installation (especially npm) is required on the host running spotless. -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. +Spotless will try to auto-discover an npm installation. If that is not working for you, it is possible to directly configure the npm +and/or node binary to use. ```gradle spotless { format 'javascript', { - prettier().npmExecutable('/usr/bin/npm').config(...) + prettier().npmExecutable('/usr/bin/npm').nodeExecutable('/usr/bin/node').config(...) ``` +If you provide both `npmExecutable` and `nodeExecutable`, spotless will use these paths. If you specify only one of the +two, spotless will assume the other one is in the same directory. + ### `.npmrc` detection Spotless picks up npm configuration stored in a `.npmrc` file either in the project directory or in your user home. -Alternatively you can supply spotless with a location of the `.npmrc` file to use. (This can be combined with `npmExecutable`, of course.) +Alternatively you can supply spotless with a location of the `.npmrc` file to use. (This can be combined with +`npmExecutable` and `nodeExecutable`, of course.) ```gradle spotless { diff --git a/plugin-maven/README.md b/plugin-maven/README.md index 2a455dfe07..dc3795927a 100644 --- a/plugin-maven/README.md +++ b/plugin-maven/README.md @@ -1050,17 +1050,23 @@ Since spotless uses the actual npm prettier package behind the scenes, it is pos ### npm detection Prettier is based on NodeJS, so to use it, a working NodeJS installation (especially npm) is required on the host running spotless. -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. +Spotless will try to auto-discover an npm installation. If that is not working for you, it is possible to directly configure the npm +and/or node binary to use. ```xml /usr/bin/npm + /usr/bin/node ``` +If you provide both `npmExecutable` and `nodeExecutable`, spotless will use these paths. If you specify only one of the +two, spotless will assume the other one is in the same directory. + ### `.npmrc` detection Spotless picks up npm configuration stored in a `.npmrc` file either in the project directory or in your user home. -Alternatively you can supply spotless with a location of the `.npmrc` file to use. (This can be combined with `npmExecutable`, of course.) +Alternatively you can supply spotless with a location of the `.npmrc` file to use. (This can be combined with +`npmExecutable` and `nodeExecutable`, of course.) ```xml From a9c79aee0929dc15ac1683cf64831d85f589e7d9 Mon Sep 17 00:00:00 2001 From: Simon Gamma Date: Wed, 18 Jan 2023 14:31:57 +0100 Subject: [PATCH 7/8] add `nodeExecutable` to changes.md --- CHANGES.md | 1 + plugin-gradle/CHANGES.md | 1 + plugin-maven/CHANGES.md | 1 + 3 files changed, 3 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 60c723f62d..dee5bb6175 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -12,6 +12,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format ( ## [Unreleased] ### Added * `ProcessRunner` has added some convenience methods so it can be used for maven testing. ([#1496](https://github.com/diffplug/spotless/pull/1496)) +* Allow to specify node executable for node-based formatters using `nodeExecutable` parameter ([#1500](https://github.com/diffplug/spotless/pull/1500)) ### Fixed * 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) ### Changes diff --git a/plugin-gradle/CHANGES.md b/plugin-gradle/CHANGES.md index 63e5b43d74..58142c6755 100644 --- a/plugin-gradle/CHANGES.md +++ b/plugin-gradle/CHANGES.md @@ -4,6 +4,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format ( ## [Unreleased] ### Added +* Allow to specify node executable for node-based formatters using `nodeExecutable` parameter ([#1500](https://github.com/diffplug/spotless/pull/1500)) ### Fixed * 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) ### Changes diff --git a/plugin-maven/CHANGES.md b/plugin-maven/CHANGES.md index 1bb49898f1..d93893f35b 100644 --- a/plugin-maven/CHANGES.md +++ b/plugin-maven/CHANGES.md @@ -4,6 +4,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format ( ## [Unreleased] ### Added +* Allow to specify node executable for node-based formatters using `nodeExecutable` parameter ([#1500](https://github.com/diffplug/spotless/pull/1500)) ### Fixed * 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) ### Changes From e8f0d2d27bb542987b8cc97b7a4c2b5a04880306 Mon Sep 17 00:00:00 2001 From: Simon Gamma Date: Wed, 18 Jan 2023 19:59:56 +0100 Subject: [PATCH 8/8] try fix windows build: set binary name for win --- .../spotless/GradleIntegrationHarness.java | 47 ++- .../NpmTestsWithoutNpmInstallationTest.java | 284 ++++++++++-------- .../maven/npm/NpmFrontendMavenPlugin.java | 2 +- 3 files changed, 193 insertions(+), 140 deletions(-) diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/GradleIntegrationHarness.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/GradleIntegrationHarness.java index 9e2f490429..1763fd088f 100644 --- a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/GradleIntegrationHarness.java +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/GradleIntegrationHarness.java @@ -21,6 +21,7 @@ import java.io.IOException; import java.util.List; import java.util.ListIterator; +import java.util.function.BiConsumer; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -117,11 +118,34 @@ protected GradleRunner gradleRunner() throws IOException { } /** Dumps the complete file contents of the folder to the console. */ - protected String getContents() throws IOException { + protected String getContents() { return getContents(subPath -> !subPath.startsWith(".gradle")); } - protected String getContents(Predicate subpathsToInclude) throws IOException { + protected String getContents(Predicate subpathsToInclude) { + return StringPrinter.buildString(printer -> Errors.rethrow().run(() -> iterateFiles(subpathsToInclude, (subpath, file) -> { + printer.println("### " + subpath + " ###"); + try { + printer.println(read(subpath)); + } catch (IOException e) { + throw new RuntimeException(e); + } + }))); + } + + /** Dumps the filtered file listing of the folder to the console. */ + protected String listFiles(Predicate subpathsToInclude) { + return StringPrinter.buildString(printer -> iterateFiles(subpathsToInclude, (subPath, file) -> { + printer.println(subPath + " [" + getFileAttributes(file) + "]"); + })); + } + + /** Dumps the file listing of the folder to the console. */ + protected String listFiles() { + return listFiles(subPath -> !subPath.startsWith(".gradle")); + } + + protected void iterateFiles(Predicate subpathsToInclude, BiConsumer consumer) { TreeDef treeDef = TreeDef.forFile(Errors.rethrow()); List files = TreeStream.depthFirst(treeDef, rootFolder()) .filter(File::isFile) @@ -129,16 +153,17 @@ protected String getContents(Predicate subpathsToInclude) throws IOExcep ListIterator iterator = files.listIterator(files.size()); int rootLength = rootFolder().getAbsolutePath().length() + 1; - return StringPrinter.buildString(printer -> Errors.rethrow().run(() -> { - while (iterator.hasPrevious()) { - File file = iterator.previous(); - String subPath = file.getAbsolutePath().substring(rootLength); - if (subpathsToInclude.test(subPath)) { - printer.println("### " + subPath + " ###"); - printer.println(read(subPath)); - } + while (iterator.hasPrevious()) { + File file = iterator.previous(); + String subPath = file.getAbsolutePath().substring(rootLength); + if (subpathsToInclude.test(subPath)) { + consumer.accept(subPath, file); } - })); + } + } + + protected String getFileAttributes(File file) { + return (file.canRead() ? "r" : "-") + (file.canWrite() ? "w" : "-") + (file.canExecute() ? "x" : "-"); } protected void checkRunsThenUpToDate() throws IOException { diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/NpmTestsWithoutNpmInstallationTest.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/NpmTestsWithoutNpmInstallationTest.java index d5c4e8f092..916d853920 100644 --- a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/NpmTestsWithoutNpmInstallationTest.java +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/NpmTestsWithoutNpmInstallationTest.java @@ -20,151 +20,179 @@ import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import com.diffplug.common.base.Predicates; + class NpmTestsWithoutNpmInstallationTest extends GradleIntegrationHarness { @Test void useNodeAndNpmFromNodeGradlePlugin() throws Exception { - setFile("build.gradle").toLines( - "plugins {", - " id 'com.diffplug.spotless'", - " id 'com.github.node-gradle.node' version '3.5.1'", - "}", - "repositories { mavenCentral() }", - "node {", - " download = true", - " version = '18.13.0'", - " npmVersion = '8.19.2'", - " workDir = file(\"${buildDir}/nodejs\")", - " npmWorkDir = file(\"${buildDir}/npm\")", - "}", - "def prettierConfig = [:]", - "prettierConfig['printWidth'] = 50", - "prettierConfig['parser'] = 'typescript'", - "def npmExecExtension = System.getProperty('os.name').toLowerCase().contains('windows') ? '.cmd' : ''", - "def nodeExecExtension = System.getProperty('os.name').toLowerCase().contains('windows') ? '.exe' : ''", - "spotless {", - " format 'mytypescript', {", - " target 'test.ts'", - " prettier()", - " .npmExecutable(\"${tasks.named('npmSetup').get().npmDir.get()}/bin/npm${npmExecExtension}\")", - " .nodeExecutable(\"${tasks.named('nodeSetup').get().nodeDir.get()}/bin/node${nodeExecExtension}\")", - " .config(prettierConfig)", - " }", - "}"); - setFile("test.ts").toResource("npm/prettier/config/typescript.dirty"); - // make sure node binary is there - gradleRunner().withArguments("nodeSetup", "npmSetup").build(); - // then run spotless using that node installation - final BuildResult spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").build(); - Assertions.assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL"); - assertFile("test.ts").sameAsResource("npm/prettier/config/typescript.configfile.clean"); + try { + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + " id 'com.github.node-gradle.node' version '3.5.1'", + "}", + "repositories { mavenCentral() }", + "node {", + " download = true", + " version = '18.13.0'", + " npmVersion = '8.19.2'", + " workDir = file(\"${buildDir}/nodejs\")", + " npmWorkDir = file(\"${buildDir}/npm\")", + "}", + "def prettierConfig = [:]", + "prettierConfig['printWidth'] = 50", + "prettierConfig['parser'] = 'typescript'", + "def npmExec = System.getProperty('os.name').toLowerCase().contains('windows') ? '/npm.cmd' : '/bin/npm'", + "def nodeExec = System.getProperty('os.name').toLowerCase().contains('windows') ? '/node.exe' : '/bin/node'", + "spotless {", + " format 'mytypescript', {", + " target 'test.ts'", + " prettier()", + " .npmExecutable(\"${tasks.named('npmSetup').get().npmDir.get()}${npmExec}\")", + " .nodeExecutable(\"${tasks.named('nodeSetup').get().nodeDir.get()}${nodeExec}\")", + " .config(prettierConfig)", + " }", + "}"); + setFile("test.ts").toResource("npm/prettier/config/typescript.dirty"); + // make sure node binary is there + gradleRunner().withArguments("nodeSetup", "npmSetup").build(); + // then run spotless using that node installation + final BuildResult spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").build(); + Assertions.assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL"); + assertFile("test.ts").sameAsResource("npm/prettier/config/typescript.configfile.clean"); + } catch (Exception e) { + printContents(); + throw e; + } + } + + private void printContents() { + System.out.println("************* Folder contents **************************"); + System.out.println(listFiles(Predicates.and(path -> !path.startsWith(".gradle"), path -> !path.contains("/node_modules/"), path -> !path.contains("/include/")))); + System.out.println("********************************************************"); } @Test @Disabled("This test is disabled because we currently don't support using npm/node installed by the" + "node-gradle-plugin as the installation takes place in the *execution* phase, but spotless needs it in the *configuration* phase.") void useNodeAndNpmFromNodeGradlePluginInOneSweep() throws Exception { - setFile("build.gradle").toLines( - "plugins {", - " id 'com.diffplug.spotless'", - " id 'com.github.node-gradle.node' version '3.5.1'", - "}", - "repositories { mavenCentral() }", - "node {", - " download = true", - " version = '18.13.0'", - " npmVersion = '8.19.2'", - " workDir = file(\"${buildDir}/nodejs\")", - " npmWorkDir = file(\"${buildDir}/npm\")", - "}", - "def prettierConfig = [:]", - "prettierConfig['printWidth'] = 50", - "prettierConfig['parser'] = 'typescript'", - "def npmExecExtension = System.getProperty('os.name').toLowerCase().contains('windows') ? '.cmd' : ''", - "def nodeExecExtension = System.getProperty('os.name').toLowerCase().contains('windows') ? '.exe' : ''", - "spotless {", - " format 'mytypescript', {", - " target 'test.ts'", - " prettier()", - " .npmExecutable(\"${tasks.named('npmSetup').get().npmDir.get()}/bin/npm${npmExecExtension}\")", - " .nodeExecutable(\"${tasks.named('nodeSetup').get().nodeDir.get()}/bin/node${nodeExecExtension}\")", - " .config(prettierConfig)", - " }", - "}", - "tasks.named('spotlessMytypescript').configure {", - " it.dependsOn('nodeSetup', 'npmSetup')", - "}"); - setFile("test.ts").toResource("npm/prettier/config/typescript.dirty"); - final BuildResult spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").build(); - Assertions.assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL"); - assertFile("test.ts").sameAsResource("npm/prettier/config/typescript.configfile.clean"); + try { + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + " id 'com.github.node-gradle.node' version '3.5.1'", + "}", + "repositories { mavenCentral() }", + "node {", + " download = true", + " version = '18.13.0'", + " npmVersion = '8.19.2'", + " workDir = file(\"${buildDir}/nodejs\")", + " npmWorkDir = file(\"${buildDir}/npm\")", + "}", + "def prettierConfig = [:]", + "prettierConfig['printWidth'] = 50", + "prettierConfig['parser'] = 'typescript'", + "def npmExec = System.getProperty('os.name').toLowerCase().contains('windows') ? '/npm.cmd' : '/bin/npm'", + "def nodeExec = System.getProperty('os.name').toLowerCase().contains('windows') ? '/node.exe' : '/bin/node'", + "spotless {", + " format 'mytypescript', {", + " target 'test.ts'", + " prettier()", + " .npmExecutable(\"${tasks.named('npmSetup').get().npmDir.get()}${npmExec}\")", + " .nodeExecutable(\"${tasks.named('nodeSetup').get().nodeDir.get()}${nodeExec}\")", + " .config(prettierConfig)", + " }", + "}", + "tasks.named('spotlessMytypescript').configure {", + " it.dependsOn('nodeSetup', 'npmSetup')", + "}"); + setFile("test.ts").toResource("npm/prettier/config/typescript.dirty"); + final BuildResult spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").build(); + Assertions.assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL"); + assertFile("test.ts").sameAsResource("npm/prettier/config/typescript.configfile.clean"); + } catch (Exception e) { + printContents(); + throw e; + } } @Test void useNpmFromNodeGradlePlugin() throws Exception { - setFile("build.gradle").toLines( - "plugins {", - " id 'com.diffplug.spotless'", - " id 'com.github.node-gradle.node' version '3.5.1'", - "}", - "repositories { mavenCentral() }", - "node {", - " download = true", - " version = '18.13.0'", - " workDir = file(\"${buildDir}/nodejs\")", - "}", - "def prettierConfig = [:]", - "prettierConfig['printWidth'] = 50", - "prettierConfig['parser'] = 'typescript'", - "def npmExecExtension = System.getProperty('os.name').toLowerCase().contains('windows') ? '.cmd' : ''", - "spotless {", - " format 'mytypescript', {", - " target 'test.ts'", - " prettier()", - " .npmExecutable(\"${tasks.named('npmSetup').get().npmDir.get()}/bin/npm${npmExecExtension}\")", - " .config(prettierConfig)", - " }", - "}"); - setFile("test.ts").toResource("npm/prettier/config/typescript.dirty"); - // make sure node binary is there - gradleRunner().withArguments("nodeSetup", "npmSetup").build(); - // then run spotless using that node installation - final BuildResult spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").build(); - Assertions.assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL"); - assertFile("test.ts").sameAsResource("npm/prettier/config/typescript.configfile.clean"); + try { + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + " id 'com.github.node-gradle.node' version '3.5.1'", + "}", + "repositories { mavenCentral() }", + "node {", + " download = true", + " version = '18.13.0'", + " workDir = file(\"${buildDir}/nodejs\")", + "}", + "def prettierConfig = [:]", + "prettierConfig['printWidth'] = 50", + "prettierConfig['parser'] = 'typescript'", + "def npmExec = System.getProperty('os.name').toLowerCase().contains('windows') ? '/npm.cmd' : '/bin/npm'", + "spotless {", + " format 'mytypescript', {", + " target 'test.ts'", + " prettier()", + " .npmExecutable(\"${tasks.named('npmSetup').get().npmDir.get()}${npmExec}\")", + " .config(prettierConfig)", + " }", + "}"); + setFile("test.ts").toResource("npm/prettier/config/typescript.dirty"); + // make sure node binary is there + gradleRunner().withArguments("nodeSetup", "npmSetup").build(); + // then run spotless using that node installation + final BuildResult spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").build(); + Assertions.assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL"); + assertFile("test.ts").sameAsResource("npm/prettier/config/typescript.configfile.clean"); + } catch (Exception e) { + printContents(); + throw e; + } } @Test void useNpmNextToConfiguredNodePluginFromNodeGradlePlugin() throws Exception { - setFile("build.gradle").toLines( - "plugins {", - " id 'com.diffplug.spotless'", - " id 'com.github.node-gradle.node' version '3.5.1'", - "}", - "repositories { mavenCentral() }", - "node {", - " download = true", - " version = '18.13.0'", - " workDir = file(\"${buildDir}/nodejs\")", - "}", - "def prettierConfig = [:]", - "prettierConfig['printWidth'] = 50", - "prettierConfig['parser'] = 'typescript'", - "def nodeExecExtension = System.getProperty('os.name').toLowerCase().contains('windows') ? '.exe' : ''", - "spotless {", - " format 'mytypescript', {", - " target 'test.ts'", - " prettier()", - " .nodeExecutable(\"${tasks.named('nodeSetup').get().nodeDir.get()}/bin/node${nodeExecExtension}\")", - " .config(prettierConfig)", - " }", - "}"); - setFile("test.ts").toResource("npm/prettier/config/typescript.dirty"); - // make sure node binary is there - gradleRunner().withArguments("nodeSetup", "npmSetup").build(); - // then run spotless using that node installation - final BuildResult spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").build(); - Assertions.assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL"); - assertFile("test.ts").sameAsResource("npm/prettier/config/typescript.configfile.clean"); + try { + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + " id 'com.github.node-gradle.node' version '3.5.1'", + "}", + "repositories { mavenCentral() }", + "node {", + " download = true", + " version = '18.13.0'", + " workDir = file(\"${buildDir}/nodejs\")", + "}", + "def prettierConfig = [:]", + "prettierConfig['printWidth'] = 50", + "prettierConfig['parser'] = 'typescript'", + "def nodeExec = System.getProperty('os.name').toLowerCase().contains('windows') ? '/node.exe' : '/bin/node'", + "spotless {", + " format 'mytypescript', {", + " target 'test.ts'", + " prettier()", + " .nodeExecutable(\"${tasks.named('nodeSetup').get().nodeDir.get()}${nodeExec}\")", + " .config(prettierConfig)", + " }", + "}"); + setFile("test.ts").toResource("npm/prettier/config/typescript.dirty"); + // make sure node binary is there + gradleRunner().withArguments("nodeSetup", "npmSetup").build(); + // then run spotless using that node installation + final BuildResult spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").build(); + Assertions.assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL"); + assertFile("test.ts").sameAsResource("npm/prettier/config/typescript.configfile.clean"); + } catch (Exception e) { + printContents(); + throw e; + } } } diff --git a/plugin-maven/src/test/java/com/diffplug/spotless/maven/npm/NpmFrontendMavenPlugin.java b/plugin-maven/src/test/java/com/diffplug/spotless/maven/npm/NpmFrontendMavenPlugin.java index 3d20170fab..de030ab81c 100644 --- a/plugin-maven/src/test/java/com/diffplug/spotless/maven/npm/NpmFrontendMavenPlugin.java +++ b/plugin-maven/src/test/java/com/diffplug/spotless/maven/npm/NpmFrontendMavenPlugin.java @@ -61,6 +61,6 @@ public static String installNpmMavenGoal() { } public static String installedNpmPath() { - return String.format("%s/node/npm", INSTALL_DIRECTORY); + return String.format("%s/node/npm%s", INSTALL_DIRECTORY, System.getProperty("os.name").toLowerCase().contains("win") ? ".cmd" : ""); } }