diff --git a/lib/src/main/java/com/diffplug/spotless/npm/NodeApp.java b/lib/src/main/java/com/diffplug/spotless/npm/NodeApp.java index edac59bbfd..932a148626 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/NodeApp.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/NodeApp.java @@ -15,6 +15,9 @@ */ package com.diffplug.spotless.npm; +import static com.diffplug.spotless.npm.NpmProcessFactory.OnlinePreferrence.PREFER_OFFLINE; +import static com.diffplug.spotless.npm.NpmProcessFactory.OnlinePreferrence.PREFER_ONLINE; + import java.util.Objects; import javax.annotation.Nonnull; @@ -22,6 +25,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.diffplug.spotless.ProcessRunner; + public class NodeApp { private static final Logger logger = LoggerFactory.getLogger(NodeApp.class); @@ -83,6 +88,28 @@ void prepareNodeAppLayout() { void npmInstall() { timedLogger.withInfo("Installing npm dependencies for {} with {}.", this.nodeServerLayout, this.npmProcessFactory.describe()) - .run(() -> npmProcessFactory.createNpmInstallProcess(nodeServerLayout, formatterStepLocations).waitFor()); + .run(this::optimizedNpmInstall); + } + + private void optimizedNpmInstall() { + try { + npmProcessFactory.createNpmInstallProcess(nodeServerLayout, formatterStepLocations, PREFER_OFFLINE).waitFor(); + } catch (NpmProcessException e) { + if (!offlineInstallFailed(e.getResult())) { + throw e; // pass through + } + // if the npm install fails with message "No matching version found for @", we try again without the offline flag + npmProcessFactory.createNpmInstallProcess(nodeServerLayout, formatterStepLocations, PREFER_ONLINE).waitFor(); + } + } + + private boolean offlineInstallFailed(ProcessRunner.Result result) { + if (result == null) { + return false; // no result, something else must have happened + } + if (result.exitCode() == 0) { + return false; // all is well + } + return result.stdOutUtf8().contains("code ETARGET") && result.stdOutUtf8().contains("No matching version found for"); // offline install failed, needs online install } } diff --git a/lib/src/main/java/com/diffplug/spotless/npm/NodeModulesCachingNpmProcessFactory.java b/lib/src/main/java/com/diffplug/spotless/npm/NodeModulesCachingNpmProcessFactory.java index 0086064194..8a3af5fdf2 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/NodeModulesCachingNpmProcessFactory.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/NodeModulesCachingNpmProcessFactory.java @@ -58,8 +58,8 @@ public static NodeModulesCachingNpmProcessFactory create(@Nonnull File cacheDir) } @Override - public NpmProcess createNpmInstallProcess(NodeServerLayout nodeServerLayout, NpmFormatterStepLocations formatterStepLocations) { - NpmProcess actualNpmInstallProcess = StandardNpmProcessFactory.INSTANCE.createNpmInstallProcess(nodeServerLayout, formatterStepLocations); + public NpmProcess createNpmInstallProcess(NodeServerLayout nodeServerLayout, NpmFormatterStepLocations formatterStepLocations, OnlinePreferrence onlinePreferrence) { + NpmProcess actualNpmInstallProcess = StandardNpmProcessFactory.INSTANCE.createNpmInstallProcess(nodeServerLayout, formatterStepLocations, onlinePreferrence); return new CachingNmpInstall(actualNpmInstallProcess, nodeServerLayout); } diff --git a/lib/src/main/java/com/diffplug/spotless/npm/NpmProcessException.java b/lib/src/main/java/com/diffplug/spotless/npm/NpmProcessException.java index bff621df07..33c6fb8d28 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/NpmProcessException.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/NpmProcessException.java @@ -15,14 +15,26 @@ */ package com.diffplug.spotless.npm; +import javax.annotation.CheckForNull; + +import com.diffplug.spotless.ProcessRunner; + public class NpmProcessException extends RuntimeException { private static final long serialVersionUID = 6424331316676759525L; + private final ProcessRunner.Result result; - public NpmProcessException(String message) { + public NpmProcessException(String message, ProcessRunner.Result result) { super(message); + this.result = result; } public NpmProcessException(String message, Throwable cause) { super(message, cause); + this.result = null; + } + + @CheckForNull + public ProcessRunner.Result getResult() { + return result; } } diff --git a/lib/src/main/java/com/diffplug/spotless/npm/NpmProcessFactory.java b/lib/src/main/java/com/diffplug/spotless/npm/NpmProcessFactory.java index 41543a2099..dbc9a807d5 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/NpmProcessFactory.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/NpmProcessFactory.java @@ -16,7 +16,22 @@ package com.diffplug.spotless.npm; public interface NpmProcessFactory { - NpmProcess createNpmInstallProcess(NodeServerLayout nodeServerLayout, NpmFormatterStepLocations formatterStepLocations); + + enum OnlinePreferrence { + PREFER_OFFLINE("--prefer-offline"), PREFER_ONLINE("--prefer-online"); + + private final String option; + + OnlinePreferrence(String option) { + this.option = option; + } + + public String option() { + return option; + } + } + + NpmProcess createNpmInstallProcess(NodeServerLayout nodeServerLayout, NpmFormatterStepLocations formatterStepLocations, OnlinePreferrence onlinePreferrence); NpmLongRunningProcess createNpmServeProcess(NodeServerLayout nodeServerLayout, NpmFormatterStepLocations formatterStepLocations); diff --git a/lib/src/main/java/com/diffplug/spotless/npm/StandardNpmProcessFactory.java b/lib/src/main/java/com/diffplug/spotless/npm/StandardNpmProcessFactory.java index 2c82768da2..91cf80ad69 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/StandardNpmProcessFactory.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/StandardNpmProcessFactory.java @@ -32,8 +32,8 @@ private StandardNpmProcessFactory() { } @Override - public NpmProcess createNpmInstallProcess(NodeServerLayout nodeServerLayout, NpmFormatterStepLocations formatterStepLocations) { - return new NpmInstall(nodeServerLayout.nodeModulesDir(), formatterStepLocations); + public NpmProcess createNpmInstallProcess(NodeServerLayout nodeServerLayout, NpmFormatterStepLocations formatterStepLocations, OnlinePreferrence onlinePreferrence) { + return new NpmInstall(nodeServerLayout.nodeModulesDir(), formatterStepLocations, onlinePreferrence); } @Override @@ -80,8 +80,11 @@ public String doDescribe() { private static class NpmInstall extends AbstractStandardNpmProcess implements NpmProcess { - public NpmInstall(File workingDir, NpmFormatterStepLocations formatterStepLocations) { + private final OnlinePreferrence onlinePreferrence; + + public NpmInstall(File workingDir, NpmFormatterStepLocations formatterStepLocations, OnlinePreferrence onlinePreferrence) { super(workingDir, formatterStepLocations); + this.onlinePreferrence = onlinePreferrence; } @Override @@ -91,7 +94,7 @@ protected List commandLine() { "install", "--no-audit", "--no-fund", - "--prefer-offline"); + onlinePreferrence.option()); } @Override @@ -103,7 +106,7 @@ public String describe() { public ProcessRunner.Result waitFor() { try (ProcessRunner.LongRunningProcess npmProcess = doStart()) { if (npmProcess.waitFor() != 0) { - throw new NpmProcessException("Running npm command '" + describe() + "' failed with exit code: " + npmProcess.exitValue() + "\n\n" + npmProcess.result()); + throw new NpmProcessException("Running npm command '" + describe() + "' failed with exit code: " + npmProcess.exitValue() + "\n\n" + npmProcess.result(), npmProcess.result()); } return npmProcess.result(); } catch (InterruptedException e) {