Skip to content

Commit 809a45b

Browse files
author
ntwigg
committed
All the fixes we have done for npm run blah are now backported to npm ci also.
1 parent fe63aa9 commit 809a45b

File tree

3 files changed

+100
-75
lines changed

3 files changed

+100
-75
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# Webtools releases
22

33
## [Unreleased]
4+
### Fixed
5+
- All the fixes we have done for `npm run blah` are now backported to `npm ci` also.
46

57
## [1.2.4] - 2025-08-16
68
### Fixed

src/main/java/com/diffplug/webtools/node/NodePlugin.java

Lines changed: 5 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,14 @@
1515
*/
1616
package com.diffplug.webtools.node;
1717

18-
import java.io.BufferedReader;
1918
import java.io.File;
2019
import java.io.IOException;
21-
import java.io.InputStream;
22-
import java.io.InputStreamReader;
2320
import java.nio.charset.StandardCharsets;
2421
import java.nio.file.Files;
2522
import java.util.ArrayList;
2623
import java.util.List;
2724
import java.util.Objects;
2825
import java.util.TreeMap;
29-
import java.util.concurrent.CompletableFuture;
3026
import org.gradle.api.*;
3127
import org.gradle.api.file.DirectoryProperty;
3228
import org.gradle.api.provider.Property;
@@ -95,68 +91,18 @@ public TreeMap<String, String> getEnvironment() {
9591
@Internal
9692
public abstract DirectoryProperty getProjectDir();
9793

98-
private static CompletableFuture<Void> readStream(InputStream inputStream, List<String> outputLines, String streamName) {
99-
return CompletableFuture.runAsync(() -> {
100-
try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
101-
String line;
102-
while ((line = reader.readLine()) != null) {
103-
synchronized (outputLines) {
104-
outputLines.add(line);
105-
}
106-
}
107-
} catch (IOException e) {
108-
synchronized (outputLines) {
109-
outputLines.add("Error reading " + streamName + ": " + e.getMessage());
110-
}
111-
}
112-
});
113-
}
114-
11594
@TaskAction
11695
public void npmCiRunTask() throws Exception {
11796
SetupCleanupNode setup = getSetup().get();
11897
File projectDir = getProjectDir().get().getAsFile();
11998
// install node, npm, and package-lock.json
12099
setup.start(projectDir);
121100

122-
// Use ProcessBuilder for direct console output instead of NpmRunner
123-
File installDir = new File(projectDir, "build/node-install");
124-
File npmExe;
125-
if (System.getProperty("os.name").toLowerCase().contains("win")) {
126-
npmExe = new File(installDir, "node/npm.cmd");
127-
} else {
128-
npmExe = new File(installDir, "node/npm");
129-
}
130-
131-
ProcessBuilder processBuilder = new ProcessBuilder(npmExe.getAbsolutePath(), "run", npmTaskName);
132-
processBuilder.directory(projectDir);
133-
134-
addNodeToPath(processBuilder, installDir);
135-
processBuilder.environment().putAll(environment);
136-
Process process = processBuilder.start();
137-
138-
// Buffer output to only show on failure
139-
List<String> stdoutLines = new ArrayList<>();
140-
List<String> stderrLines = new ArrayList<>();
141-
142-
// Create threads to read stdout and stderr concurrently
143-
CompletableFuture<Void> stdoutFuture = readStream(process.getInputStream(), stdoutLines, "stdout");
144-
CompletableFuture<Void> stderrFuture = readStream(process.getErrorStream(), stderrLines, "stderr");
145-
int exitCode = process.waitFor();
146-
CompletableFuture.allOf(stdoutFuture, stderrFuture).join();
147-
if (exitCode == 0) {
148-
return;
149-
}
150-
151-
var cmd = new StringBuilder().append("> npm run ").append(npmTaskName).append(" FAILED\n");
152-
environment.forEach((key, value) -> cmd.append(" env ").append(key).append("=").append(value).append("\n"));
153-
for (String line : stdoutLines) {
154-
cmd.append(line).append("\n");
155-
}
156-
for (String line : stderrLines) {
157-
cmd.append(line).append("\n");
158-
}
159-
throw new GradleException(cmd.toString());
101+
// Use the new executeNpmCommand method
102+
List<String> commandArgs = new ArrayList<>();
103+
commandArgs.add("run");
104+
commandArgs.add(npmTaskName);
105+
setup.executeNpmCommand(commandArgs, environment);
160106
}
161107
}
162108

@@ -165,19 +111,6 @@ public void apply(Project project) {
165111
project.getExtensions().create(EXTENSION_NAME, Extension.class, project);
166112
}
167113

168-
private static void addNodeToPath(ProcessBuilder processBuilder, File installDir) {
169-
// Add node binary directory to PATH for npm to find node executable
170-
File nodeDir = new File(installDir, "node");
171-
String currentPath = processBuilder.environment().get("PATH");
172-
if (currentPath == null) {
173-
currentPath = processBuilder.environment().get("Path"); // Windows
174-
}
175-
String nodeBinPath = nodeDir.getAbsolutePath();
176-
String pathSeparator = System.getProperty("path.separator");
177-
String newPath = nodeBinPath + pathSeparator + (currentPath != null ? currentPath : "");
178-
processBuilder.environment().put("PATH", newPath);
179-
}
180-
181114
private static String nvmRc(File file) throws IOException {
182115
String str = new String(Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8).trim();
183116
return "v" + str;

src/main/java/com/diffplug/webtools/node/SetupCleanupNode.java

Lines changed: 93 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,20 @@
2020
import com.github.eirslett.maven.plugins.frontend.lib.InstallationException;
2121
import com.github.eirslett.maven.plugins.frontend.lib.ProxyConfig;
2222
import com.github.eirslett.maven.plugins.frontend.lib.TaskRunnerException;
23+
import java.io.BufferedReader;
2324
import java.io.File;
2425
import java.io.IOException;
26+
import java.io.InputStream;
27+
import java.io.InputStreamReader;
2528
import java.io.Serializable;
2629
import java.nio.file.Files;
2730
import java.nio.file.StandardCopyOption;
31+
import java.util.ArrayList;
2832
import java.util.Collections;
33+
import java.util.List;
34+
import java.util.Map;
35+
import java.util.concurrent.CompletableFuture;
36+
import org.gradle.api.GradleException;
2937

3038
class SetupCleanupNode implements Serializable {
3139
public String nodeVersion;
@@ -49,9 +57,92 @@ private static File keyFile(File projectDir) {
4957
return new File(projectDir, "build/node_modules/.gradle-state");
5058
}
5159

60+
public void executeNpmCommand(String command, String... args) throws Exception {
61+
List<String> commandArgs = new ArrayList<>();
62+
commandArgs.add(command);
63+
for (String arg : args) {
64+
commandArgs.add(arg);
65+
}
66+
executeNpmCommand(commandArgs, Collections.emptyMap());
67+
}
68+
69+
public void executeNpmCommand(List<String> commandArgs, Map<String, String> environment) throws Exception {
70+
// Use ProcessBuilder for direct console output instead of NpmRunner
71+
File npmExe;
72+
if (System.getProperty("os.name").toLowerCase().contains("win")) {
73+
npmExe = new File(installDir, "node/npm.cmd");
74+
} else {
75+
npmExe = new File(installDir, "node/npm");
76+
}
77+
78+
List<String> fullCommand = new ArrayList<>();
79+
fullCommand.add(npmExe.getAbsolutePath());
80+
fullCommand.addAll(commandArgs);
81+
82+
ProcessBuilder processBuilder = new ProcessBuilder(fullCommand);
83+
processBuilder.directory(workingDir);
84+
85+
addNodeToPath(processBuilder, installDir);
86+
processBuilder.environment().putAll(environment);
87+
Process process = processBuilder.start();
88+
89+
// Buffer output to only show on failure
90+
List<String> stdoutLines = new ArrayList<>();
91+
List<String> stderrLines = new ArrayList<>();
92+
93+
// Create threads to read stdout and stderr concurrently
94+
CompletableFuture<Void> stdoutFuture = readStream(process.getInputStream(), stdoutLines, "stdout");
95+
CompletableFuture<Void> stderrFuture = readStream(process.getErrorStream(), stderrLines, "stderr");
96+
int exitCode = process.waitFor();
97+
CompletableFuture.allOf(stdoutFuture, stderrFuture).join();
98+
if (exitCode == 0) {
99+
return;
100+
}
101+
102+
var cmd = new StringBuilder().append("> npm ").append(String.join(" ", commandArgs)).append(" FAILED\n");
103+
environment.forEach((key, value) -> cmd.append(" env ").append(key).append("=").append(value).append("\n"));
104+
for (String line : stdoutLines) {
105+
cmd.append(line).append("\n");
106+
}
107+
for (String line : stderrLines) {
108+
cmd.append(line).append("\n");
109+
}
110+
throw new GradleException(cmd.toString());
111+
}
112+
113+
private static CompletableFuture<Void> readStream(InputStream inputStream, List<String> outputLines, String streamName) {
114+
return CompletableFuture.runAsync(() -> {
115+
try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
116+
String line;
117+
while ((line = reader.readLine()) != null) {
118+
synchronized (outputLines) {
119+
outputLines.add(line);
120+
}
121+
}
122+
} catch (IOException e) {
123+
synchronized (outputLines) {
124+
outputLines.add("Error reading " + streamName + ": " + e.getMessage());
125+
}
126+
}
127+
});
128+
}
129+
130+
private static void addNodeToPath(ProcessBuilder processBuilder, File installDir) {
131+
// Add node binary directory to PATH for npm to find node executable
132+
File nodeDir = new File(installDir, "node");
133+
String currentPath = processBuilder.environment().get("PATH");
134+
if (currentPath == null) {
135+
currentPath = processBuilder.environment().get("Path"); // Windows
136+
}
137+
String nodeBinPath = nodeDir.getAbsolutePath();
138+
String pathSeparator = System.getProperty("path.separator");
139+
String newPath = nodeBinPath + pathSeparator + (currentPath != null ? currentPath : "");
140+
processBuilder.environment().put("PATH", newPath);
141+
}
142+
52143
private static class Impl extends SetupCleanup<SetupCleanupNode> {
53144
@Override
54-
protected void doStart(SetupCleanupNode key) throws TaskRunnerException, InstallationException {
145+
protected void doStart(SetupCleanupNode key) throws TaskRunnerException, InstallationException, Exception {
55146
ProxyConfig proxyConfig = new ProxyConfig(Collections.emptyList());
56147
FrontendPluginFactory factory = key.factory();
57148
factory.getNodeInstaller(proxyConfig)
@@ -68,8 +159,7 @@ protected void doStart(SetupCleanupNode key) throws TaskRunnerException, Install
68159
throw new RuntimeException(e);
69160
}
70161
}
71-
factory.getNpmRunner(proxyConfig, null)
72-
.execute("ci", null);
162+
key.executeNpmCommand("ci");
73163
}
74164

75165
@Override

0 commit comments

Comments
 (0)