PrettierRunner.java
/*******************************************************************************
* Copyright (c) 2026 Carsten Hammer.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Carsten Hammer
*******************************************************************************/
package org.sandbox.jdt.internal.css.core;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.concurrent.TimeUnit;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.ILog;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
/**
* Runs Prettier to format CSS files.
*/
public class PrettierRunner {
private static final ILog LOG = Platform.getLog(PrettierRunner.class);
/**
* Format a CSS file using Prettier.
*
* @param file the CSS file to format
* @return the formatted content, or null if formatting failed
* @throws IOException if an I/O error occurs
* @throws InterruptedException if the thread is interrupted
*/
public static String format(IFile file) throws IOException, InterruptedException {
if (!NodeExecutor.isNpxAvailable()) {
throw new IllegalStateException("npx is not available. Please install Node.js."); //$NON-NLS-1$
}
Path filePath = file.getLocation().toFile().toPath();
String originalContent = Files.readString(filePath, StandardCharsets.UTF_8);
// Use prettier with stdin/stdout to avoid file modification during processing
ProcessBuilder pb = new ProcessBuilder(
"npx", "prettier", //$NON-NLS-1$ //$NON-NLS-2$
"--parser", "css", //$NON-NLS-1$ //$NON-NLS-2$
"--stdin-filepath", file.getName() //$NON-NLS-1$
);
Process process = pb.start();
// Write content to stdin
try (OutputStream os = process.getOutputStream()) {
os.write(originalContent.getBytes(StandardCharsets.UTF_8));
}
// Read formatted output
String formatted;
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8))) {
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
sb.append(line).append("\n"); //$NON-NLS-1$
}
formatted = sb.toString();
}
// Read any errors
String errors;
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getErrorStream(), StandardCharsets.UTF_8))) {
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
sb.append(line).append("\n"); //$NON-NLS-1$
}
errors = sb.toString();
}
boolean finished = process.waitFor(30, TimeUnit.SECONDS);
if (!finished) {
process.destroyForcibly();
LOG.log(new Status(IStatus.WARNING, "sandbox_css_cleanup", //$NON-NLS-1$
"Prettier timed out after 30 seconds")); //$NON-NLS-1$
return null;
}
int exitCode = process.exitValue();
if (exitCode != 0) {
LOG.log(new Status(IStatus.WARNING, "sandbox_css_cleanup", //$NON-NLS-1$
"Prettier failed: " + errors)); //$NON-NLS-1$
return null;
}
return formatted;
}
/**
* Check if Prettier is available (installed globally or via npx).
*/
public static boolean isPrettierAvailable() {
try {
NodeExecutor.ExecutionResult result = NodeExecutor.executeNpx("prettier", "--version"); //$NON-NLS-1$ //$NON-NLS-2$
return result.isSuccess();
} catch (Exception e) {
return false;
}
}
}