forked from eclipse-wildwebdeveloper/wildwebdeveloper
-
Notifications
You must be signed in to change notification settings - Fork 0
/
NodeJSManager.java
368 lines (324 loc) · 12.5 KB
/
NodeJSManager.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
/*******************************************************************************
* Copyright (c) 2019, 2022 Red Hat Inc. and others.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Mickael Istria (Red Hat Inc.) - initial implementation
* Gautier de Saint Martin Lacaze - Issue #55 Warn missing or incompatible node.js
* Pierre-Yves B. - Issue #196 NullPointerException when validating Node.js version
* Pierre-Yves B. - Issue #238 Why does wildweb do "/bin/bash -c which node" ?
* Pierre-Yves B. - Issue #268 Incorrect default Node.js location for macOS
*******************************************************************************/
package org.eclipse.wildwebdeveloper.embedder.node;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
import org.eclipse.core.internal.runtime.InternalPlatform;
import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.osgi.service.datalocation.Location;
import org.eclipse.swt.widgets.Display;
@SuppressWarnings("restriction")
public class NodeJSManager {
public static final String NODE_ROOT_DIRECTORY = ".node";
private static final String MACOS_DSCL_SHELL_PREFIX = "UserShell: ";
private static boolean alreadyWarned;
private static Properties cachedNodeJsInfoProperties;
private static final Object EXPAND_LOCK = new Object();
/**
* Finds Node.js executable installed in following list of locations:
* - Location, specified in `org.eclipse.wildwebdeveloper.nodeJSLocation` system property
* - Platform Install Location
* - Platform User Location
* - WWD Node bundle configuration location
* - OS dependent default installation path
* In case of Node.js cannot be found installs the embedded version into the first
* available location of platform install/user/workspace locations
*
* @return The file for Node.js executable or null if it cannot be installed
*/
public static File getNodeJsLocation() {
String nodeJsLocation = System.getProperty("org.eclipse.wildwebdeveloper.nodeJSLocation");
if (nodeJsLocation != null) {
File nodejs = new File(nodeJsLocation);
if (nodejs.exists()) {
validateNodeVersion(nodejs);
return new File(nodeJsLocation);
}
}
Properties properties = getNodeJsInfoProperties();
if (properties != null) {
try {
File nodePath = probeNodeJsExacutable(properties);
if (nodePath != null) {
return nodePath;
}
File installationPath = probeNodeJsInstallLocationn();
if (installationPath != null) {
nodePath = new File(installationPath, properties.getProperty("nodePath"));
synchronized (EXPAND_LOCK) {
if (!nodePath.exists() || !nodePath.canRead() || !nodePath.canExecute()) {
CompressUtils.unarchive(FileLocator.find(Activator.getDefault().getBundle(),
new Path(properties.getProperty("archiveFile"))), installationPath);
}
}
return nodePath;
}
} catch (IOException e) {
Activator.getDefault().getLog().log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, e.getMessage(), e));
}
}
File res = which("node");
if (res == null && getDefaultNodePath().exists()) {
res = getDefaultNodePath();
}
if (res != null) {
validateNodeVersion(res);
return res;
} else if (!alreadyWarned) {
warnNodeJSMissing();
alreadyWarned = true;
}
return null;
}
/**
* Finds NPM executable installed in Node.js bundle location
*
* @return The file for NPM executable or null if it cannot be found
* @since 0.2
*/
public static File getNpmLocation() {
String npmFileName = Platform.getOS().equals(Platform.OS_WIN32) ? "npm.cmd" : "npm";
File nodeJsLocation = getNodeJsLocation();
if (nodeJsLocation != null) {
File res = new File(nodeJsLocation.getParentFile(), npmFileName);
if (res.exists()) {
return res;
}
}
return which(npmFileName);
}
public static File getNpmJSLocation() {
try {
File npmLocation = getNpmLocation().getCanonicalFile();
if (npmLocation.getAbsolutePath().endsWith(".js")) {
return npmLocation;
}
String path = "node_modules/npm/bin/npm-cli.js";
if (new File(npmLocation.getParentFile(), "node_modules").exists()) {
return new File(npmLocation.getParentFile(), path);
}
File target = new File( npmLocation.getParentFile().getParentFile(), path);
if (target.exists()) {
return target;
}
return new File( npmLocation.getParentFile(), "lib/cli.js");
} catch (IOException e) {
}
return null;
}
public static ProcessBuilder prepareNodeProcessBuilder(String... commands)
{
return prepareNodeProcessBuilder(Arrays.asList(commands));
}
public static ProcessBuilder prepareNodeProcessBuilder(List<String> commands)
{
List<String> tmp = new ArrayList<>();
tmp.add(getNodeJsLocation().getAbsolutePath());
tmp.addAll(commands);
return new ProcessBuilder(tmp);
}
public static ProcessBuilder prepareNPMProcessBuilder(String... commands)
{
return prepareNPMProcessBuilder(Arrays.asList(commands));
}
public static ProcessBuilder prepareNPMProcessBuilder(List<String> commands)
{
List<String> tmp = new ArrayList<>();
tmp.add(getNpmJSLocation().getAbsolutePath());
tmp.addAll(commands);
return prepareNodeProcessBuilder(tmp);
}
public static File which(String program) {
Properties properties = getNodeJsInfoProperties();
if (properties != null) {
File nodePath = probeNodeJsExacutable(properties);
if (nodePath != null && nodePath.exists() && nodePath.canRead() && nodePath.canExecute()) {
File exe = new File(nodePath.getParent(), program);
if (exe.canExecute()) {
return exe;
} else if (Platform.OS_WIN32.equals(Platform.getOS())) {
exe = new File(nodePath.getParent(), program + ".exe");
if (exe.canExecute()) {
return exe;
}
}
}
}
String[] paths = System.getenv("PATH").split(System.getProperty("path.separator"));
for (String path : paths) {
File exe = new File(path, program);
if (exe.canExecute())
return exe;
}
String res = null;
String[] command = new String[] { "/bin/bash", "-c", "-l", "which " + program };
if (Platform.getOS().equals(Platform.OS_WIN32)) {
command = new String[] { "cmd", "/c", "where " + program };
} else if (Platform.getOS().equals(Platform.OS_MACOSX)) {
command = new String[] { getDefaultShellMacOS(), "-c", "-li", "which " + program };
}
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(Runtime.getRuntime().exec(command).getInputStream()));) {
res = reader.readLine();
} catch (IOException e) {
Activator.getDefault().getLog().log(
new Status(IStatus.ERROR, Activator.getDefault().getBundle().getSymbolicName(), e.getMessage(), e));
}
return res != null ? new File(res) : null;
}
private static Properties getNodeJsInfoProperties() {
if (cachedNodeJsInfoProperties == null) {
URL nodeJsInfo = FileLocator.find(Activator.getDefault().getBundle(), new Path("nodejs-info.properties"));
if (nodeJsInfo != null) {
try (InputStream infoStream = nodeJsInfo.openStream()) {
Properties properties = new Properties();
properties.load(infoStream);
cachedNodeJsInfoProperties = properties;
} catch (IOException e) {
Activator.getDefault().getLog()
.log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, e.getMessage(), e));
}
}
}
return cachedNodeJsInfoProperties;
}
private static final File probeNodeJsInstallLocationn() {
File[] nodeJsLocations = getOrderedInstallationLocations();
for (File installationPath : nodeJsLocations) {
if (probeDirectoryForInstallation(installationPath)) {
return installationPath;
}
}
return null;
}
private static final boolean probeDirectoryForInstallation(File directory) {
if (directory == null) {
return false;
}
if (directory.exists() && directory.isDirectory()
&& directory.canWrite() && directory.canExecute()) {
return true;
}
return probeDirectoryForInstallation(directory.getParentFile());
}
private static final File probeNodeJsExacutable(Properties properties) {
File[] nodeJsLocations = getOrderedInstallationLocations();
for (File installationPath : nodeJsLocations) {
File nodePath = getNodeJsExecutablen(installationPath, properties);
if (nodePath != null) {
return nodePath;
}
}
return null;
}
private static final File[] getOrderedInstallationLocations() {
return new File[] {
toFile(Platform.getInstallLocation(), NODE_ROOT_DIRECTORY), // Platform Install Location
toFile(Platform.getUserLocation(), NODE_ROOT_DIRECTORY), // Platform User Location
toFile(Platform.getStateLocation(Activator.getDefault().getBundle())) // Default
};
}
private static final File toFile(Location location, String binDirectory) {
File installLocation = location != null && location.getURL() != null ? new File(location.getURL().getFile()) : null;
if (installLocation != null && binDirectory != null) {
installLocation = new File(installLocation, binDirectory);
}
return installLocation;
}
private static final File toFile(IPath locationPath) {
return locationPath != null ? locationPath.toFile() : null;
}
private static final File getNodeJsExecutablen(File installationLocation, Properties properties) {
if (installationLocation != null) {
File nodePath = new File(installationLocation, properties.getProperty("nodePath"));
if (nodePath.exists() && nodePath.canRead() && nodePath.canExecute()) {
return nodePath;
}
}
return null;
}
private static String getDefaultShellMacOS() {
String res = null;
String[] command = { "/bin/bash", "-c", "-l", "dscl . -read ~/ UserShell" };
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(Runtime.getRuntime().exec(command).getInputStream()));) {
res = reader.readLine();
if (!res.startsWith(MACOS_DSCL_SHELL_PREFIX)) {
Activator.getDefault().getLog()
.log(new Status(IStatus.ERROR, Activator.getDefault().getBundle().getSymbolicName(),
"Cannot find default shell. Use '/bin/zsh' instead."));
return "/bin/zsh"; // Default shell since macOS 10.15
}
res = res.substring(MACOS_DSCL_SHELL_PREFIX.length());
} catch (IOException e) {
Activator.getDefault().getLog().log(
new Status(IStatus.ERROR, Activator.getDefault().getBundle().getSymbolicName(), e.getMessage(), e));
}
return res;
}
private static File getDefaultNodePath() {
return new File(switch (Platform.getOS()) {
case Platform.OS_MACOSX -> "/usr/local/bin/node";
case Platform.OS_WIN32 -> "C:\\Program Files\\nodejs\\node.exe";
default -> "/usr/bin/node";
});
}
private static void validateNodeVersion(File nodeJsLocation) {
String nodeVersion = null;
String[] nodeVersionCommand = { nodeJsLocation.getAbsolutePath(), "-v" };
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(Runtime.getRuntime().exec(nodeVersionCommand).getInputStream()));) {
nodeVersion = reader.readLine();
} catch (IOException e) {
Activator.getDefault().getLog().log(
new Status(IStatus.ERROR, Activator.getDefault().getBundle().getSymbolicName(), e.getMessage(), e));
}
if (nodeVersion == null) {
warnNodeJSVersionCouldNotBeDetermined();
}
}
private static void warnNodeJSMissing() {
if (!alreadyWarned) {
Display.getDefault().asyncExec(() -> MessageDialog.openWarning(Display.getCurrent().getActiveShell(),
"Missing node.js", "Could not find node.js. This will result in editors missing key features.\n"
+ "Please make sure node.js is installed and that your PATH environment variable contains the location to the `node` executable."));
}
alreadyWarned = true;
}
private static void warnNodeJSVersionCouldNotBeDetermined() {
if (!alreadyWarned) {
Display.getDefault().asyncExec(() -> MessageDialog.openWarning(Display.getCurrent().getActiveShell(),
"Node.js version could not be determined",
"Node.js version could not be determined. Please make sure a recent version of node.js is installed, editors may be missing key features otherwise.\n"));
}
alreadyWarned = true;
}
}