diff --git a/core/pom.xml b/core/pom.xml index 370bd946..113ff2a0 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -12,11 +12,121 @@ Small wrapper around esbuild to be able to use it in Java - 0.0.9 - 0.23.0-mvnpm-${esbuild.mvnpm-build.version} + 0.23.1 + 3.3.1 + 1.89.1 + 5.1.2 + 0.4.2 + + org.mvnpm + esbuild + ${esbuild.version} + provided + + + org.mvnpm.at.esbuild + linux-x64 + ${esbuild.version} + provided + + + org.mvnpm.at.esbuild + linux-arm + ${esbuild.version} + provided + + + org.mvnpm.at.esbuild + linux-ia32 + ${esbuild.version} + provided + + + org.mvnpm.at.esbuild + win32-x64 + ${esbuild.version} + provided + + + org.mvnpm.at.esbuild + win32-ia32 + ${esbuild.version} + provided + + + org.mvnpm.at.esbuild + darwin-arm64 + ${esbuild.version} + provided + + + org.mvnpm.at.esbuild + darwin-x64 + ${esbuild.version} + provided + + + + org.mvnpm + esbuild-sass-plugin + ${esbuild-sass-plugin.version} + provided + + + org.mvnpm + safe-identifier + ${safe-identifier.version} + provided + + + org.mvnpm + sass + ${sass.version} + provided + + + org.mvnpm + immutable + ${immutable.version} + provided + + + + com.caoccao.javet + javet + 4.1.3 + + + + com.caoccao.javet + javet-node-linux-x86_64 + 4.1.3 + + + + com.caoccao.javet + javet-node-linux-arm64 + 4.1.3 + + + com.caoccao.javet + javet-node-macos-x86_64 + 4.1.3 + + + com.caoccao.javet + javet-node-macos-arm64 + 4.1.3 + + + com.caoccao.javet + javet-node-windows-x86_64 + 4.1.3 + + io.mvnpm importmap @@ -51,7 +161,7 @@ src/main/resources - false + true src/main/resources-filtered diff --git a/core/src/main/java/io/mvnpm/esbuild/Bundler.java b/core/src/main/java/io/mvnpm/esbuild/Bundler.java index 9e76cff4..1a34e2a3 100644 --- a/core/src/main/java/io/mvnpm/esbuild/Bundler.java +++ b/core/src/main/java/io/mvnpm/esbuild/Bundler.java @@ -13,7 +13,6 @@ import io.mvnpm.esbuild.install.WebDepsInstaller; import io.mvnpm.esbuild.model.*; -import io.mvnpm.esbuild.resolve.Resolver; public class Bundler { private static final Logger logger = Logger.getLogger(Bundler.class.getName()); @@ -124,9 +123,6 @@ protected static ExecuteResult esBuild(Path workDir, EsBuildConfig esBuildConfig } private static Execute getExecute(Path workDir, EsBuildConfig esBuildConfig) throws IOException { - final String version = esBuildConfig.esBuildVersion() != null ? esBuildConfig.esBuildVersion() - : Bundler.ESBUILD_EMBEDDED_VERSION; - final Path esBuildExec = Resolver.create().resolve(version); - return new Execute(workDir, esBuildExec.toFile(), esBuildConfig); + return new Execute(workDir, esBuildConfig); } } diff --git a/core/src/main/java/io/mvnpm/esbuild/Execute.java b/core/src/main/java/io/mvnpm/esbuild/Execute.java index 3a82410e..7f9bf180 100644 --- a/core/src/main/java/io/mvnpm/esbuild/Execute.java +++ b/core/src/main/java/io/mvnpm/esbuild/Execute.java @@ -1,26 +1,29 @@ package io.mvnpm.esbuild; -import java.io.*; +import static io.mvnpm.esbuild.install.BuildDependency.getDependencies; + +import java.io.File; +import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Optional; +import java.util.Scanner; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.BooleanSupplier; -import java.util.function.Consumer; -import java.util.logging.Level; import java.util.logging.Logger; +import com.caoccao.javet.exceptions.JavetException; +import com.caoccao.javet.interop.NodeRuntime; +import com.caoccao.javet.interop.V8Host; +import com.caoccao.javet.interop.converters.JavetProxyConverter; + +import io.mvnpm.esbuild.install.WebDepsInstaller; import io.mvnpm.esbuild.model.EsBuildConfig; import io.mvnpm.esbuild.model.ExecuteResult; import io.mvnpm.esbuild.model.WatchBuildResult; import io.mvnpm.esbuild.model.WatchStartResult; +import io.mvnpm.esbuild.util.EntryCreator; public class Execute { @@ -32,181 +35,132 @@ public class Execute { private static final Logger logger = Logger.getLogger(Execute.class.getName()); private final Path workDir; - private final File esBuildExec; - private EsBuildConfig esBuildConfig; - private String[] args; + private final EsBuildConfig esBuildConfig; - public Execute(Path workDir, File esBuildExec, EsBuildConfig esBuildConfig) { + public Execute(Path workDir, EsBuildConfig esBuildConfig) { this.workDir = workDir; - this.esBuildExec = esBuildExec; this.esBuildConfig = esBuildConfig; } - public Execute(Path workDir, File esBuildExec, String[] args) { - this.workDir = workDir; - this.esBuildExec = esBuildExec; - this.args = args; + public ExecuteResult executeAndWait() throws IOException { + runBuild(); + Path path = workDir.resolve(esBuildConfig.outdir() == null ? "." : esBuildConfig.outdir()); + try (Scanner scanner = new Scanner(path, StandardCharsets.UTF_8)) { + String output = scanner.useDelimiter("\\A").hasNext() ? scanner.next() : ""; + return new ExecuteResult(output); + } } - public ExecuteResult executeAndWait() throws IOException { - final Process process = createProcess(getCommand(), Optional.empty()); - try { - final int exitCode = process.waitFor(); - final String content = readStream(process.getInputStream()); - final String errors = readStream(process.getErrorStream()); - if (exitCode != 0) { - throw new BundleException(errors.isEmpty() ? "Unexpected Error during bundling" : errors, content); + private void runBuild() throws IOException { + File entryJs = EntryCreator.createEntryJs(workDir, esBuildConfig); + WebDepsInstaller.install(workDir.resolve("node_modules"), getDependencies()); + + try (NodeRuntime nodeRuntime = V8Host.getNodeInstance().createV8Runtime()) { + nodeRuntime.allowEval(true); + try { + nodeRuntime.getExecutor(entryJs).executeVoid(); + } catch (JavetException e) { + throw new RuntimeException(e); } - return new ExecuteResult(content); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); + nodeRuntime.await(); + } catch (JavetException e) { throw new RuntimeException(e); } } - public WatchStartResult watch(BuildEventListener listener) throws IOException { - final Process process = createProcess(getCommand(), Optional.of(listener)); - final ExecutorService executorStreamer = Executors - .newSingleThreadExecutor(r -> new Thread(r, "Esbuild watch stdout streamer")); - final ExecutorService executorBuild = Executors - .newSingleThreadExecutor(r -> new Thread(r, "Esbuild build listeners notify")); - final AtomicReference result = new AtomicReference<>(); - CountDownLatch latch = new CountDownLatch(1); - final WatchStartResult.WatchProcess watchProcess = new WatchStartResult.WatchProcess() { - @Override - public boolean isAlive() { - return process.isAlive(); - } - - @Override - public void close() throws IOException { - process.destroyForcibly(); - executorStreamer.shutdownNow(); - executorBuild.shutdownNow(); - try { - process.waitFor(); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - throw new RuntimeException(e); + private void runWatchBuild(BuildEventListener listener, CountDownLatch latch) throws IOException { + File entryJs = EntryCreator.createEntryJs(workDir, esBuildConfig); + WebDepsInstaller.install(workDir.resolve("node_modules"), getDependencies()); + + try (NodeRuntime nodeRuntime = V8Host.getNodeInstance().createV8Runtime()) { + nodeRuntime.setConverter(new JavetProxyConverter()); + nodeRuntime.allowEval(true); + + // Set up a JavaScript callback to handle build events + nodeRuntime.getGlobalObject().set("buildEventHandler", new BuildEventHandler() { + private boolean isFirstBuild = true; + + @Override + public void onBuildComplete(String output) { + if (isFirstBuild) { + isFirstBuild = false; + latch.countDown(); + } else { + listener.onBuild(new WatchBuildResult(output)); + } } - if (latch.getCount() == 1) { - latch.countDown(); - } - } - }; - try { - final InputStream processStream = process.getInputStream(); - executorStreamer.execute(new Streamer(executorBuild, process::isAlive, processStream, (r) -> { - if (latch.getCount() == 1) { - result.set(r); - latch.countDown(); - } else { - listener.onBuild(r); - } - }, r -> { - if (latch.getCount() == 1) { - result.set(r); - latch.countDown(); - } else if (!r.isSuccess()) { - listener.onBuild(r); + @Override + public void onBuildError(String output, String error) { + if (isFirstBuild) { + isFirstBuild = false; + latch.countDown(); + } else { + listener.onBuild(new WatchBuildResult(output, new BundleException(output, error))); + } } - })); - latch.await(); - if (!process.isAlive() && !result.get().isSuccess()) { - throw result.get().bundleException(); - } + }); - return new WatchStartResult(result.get(), watchProcess); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - watchProcess.close(); + try { + nodeRuntime.getExecutor(entryJs).executeVoid(); + } catch (JavetException e) { + throw new RuntimeException(e); + } + nodeRuntime.await(); + } catch (JavetException e) { throw new RuntimeException(e); } } - private String[] getCommand() { - String[] command = args != null ? getCommand(args) : getCommand(esBuildConfig); - if (logger.isLoggable(Level.FINE)) { - logger.log(Level.FINE, "running esbuild with flags: \n > `{0}`", String.join(" ", command)); - } - return command; - } - - private String[] getCommand(EsBuildConfig esBuildConfig) { - String[] params = esBuildConfig.toParams(); - return getCommand(params); - } - - private String[] getCommand(String[] args) { - List argList = new ArrayList<>(args.length + 1); - argList.add(esBuildExec.toString()); - argList.addAll(Arrays.asList(args)); + public interface BuildEventHandler { + void onBuildComplete(String output); - return argList.toArray(String[]::new); + void onBuildError(String output, String error); } - public Process createProcess(final String[] command, final Optional listener) throws IOException { - return new ProcessBuilder().redirectErrorStream(listener.isPresent()).directory(workDir.toFile()) - .command(command).start(); - } + public WatchStartResult watch(BuildEventListener listener) throws IOException { + final ExecutorService executorBuild = Executors + .newSingleThreadExecutor(r -> new Thread(r, "Esbuild build listeners notify")); + CountDownLatch latch = new CountDownLatch(1); + final WatchStartResult.WatchProcess watchProcess = getProcess(executorBuild, latch); - private record Streamer(ExecutorService executorBuild, BooleanSupplier isAlive, InputStream processStream, - BuildEventListener listener, Consumer onExit) implements Runnable { - - @Override - public void run() { - final AtomicBoolean hasError = new AtomicBoolean(); - final StringBuilder outputBuilder = new StringBuilder(); - consumeStream(isAlive, processStream, l -> { - logger.fine(l); - outputBuilder.append("\n").append(l); - if (l.contains("build finished")) { - logger.fine("Build finished!"); - final String output = outputBuilder.toString(); - final boolean error = hasError.getAndSet(false); - outputBuilder.setLength(0); - executorBuild.execute(() -> { - if (!error) { - listener.onBuild(new WatchBuildResult(output)); - } else { - listener.onBuild( - new WatchBuildResult(output, new BundleException("Error during bundling", output))); - } - }); - } else if (l.contains("[ERROR]")) { - hasError.set(true); + try { + executorBuild.execute(() -> { + try { + runWatchBuild(listener, latch); + } catch (IOException e) { + if (latch.getCount() == 1) { + latch.countDown(); + } } }); - if (!hasError.get()) { - onExit.accept(new WatchBuildResult(outputBuilder.toString())); - } else { - onExit.accept(new WatchBuildResult(outputBuilder.toString(), - new BundleException("Process exited with error", outputBuilder.toString()))); - } + latch.await(); + + return new WatchStartResult(new WatchBuildResult(""), watchProcess); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + watchProcess.close(); + throw new RuntimeException(e); } } - private static String readStream(InputStream stream) { - final StringBuilder s = new StringBuilder(); - consumeStream(() -> true, stream, l -> s.append(l).append("\n")); - return s.toString(); - } + private static WatchStartResult.WatchProcess getProcess(ExecutorService executorBuild, CountDownLatch latch) { + final AtomicBoolean alive = new AtomicBoolean(true); + return new WatchStartResult.WatchProcess() { + @Override + public boolean isAlive() { + return alive.get(); + } - private static void consumeStream(BooleanSupplier stayAlive, InputStream stream, Consumer newLineConsumer) { - try ( - final InputStreamReader in = new InputStreamReader(stream, StandardCharsets.UTF_8); - final BufferedReader reader = new BufferedReader(in)) { - String line; - while ((line = reader.readLine()) != null) { - newLineConsumer.accept(line); - if (!stayAlive.getAsBoolean()) { - break; + @Override + public void close() throws IOException { + alive.set(false); + executorBuild.shutdownNow(); + if (latch.getCount() == 1) { + latch.countDown(); } } - } catch (IOException e) { - // ignore - } + }; } } diff --git a/core/src/main/java/io/mvnpm/esbuild/Main.java b/core/src/main/java/io/mvnpm/esbuild/Main.java index 78161dc3..18105536 100644 --- a/core/src/main/java/io/mvnpm/esbuild/Main.java +++ b/core/src/main/java/io/mvnpm/esbuild/Main.java @@ -2,13 +2,11 @@ import java.io.IOException; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Optional; -import io.mvnpm.esbuild.model.ExecuteResult; import io.mvnpm.esbuild.resolve.Resolver; public class Main { @@ -25,9 +23,9 @@ public static void main(String[] args) throws IOException { esbuildVersion = unParsedEsbuildVersion.split("=")[1]; } final Path esBuildExec = Resolver.create().resolve(esbuildVersion); - final ExecuteResult executeResult = new Execute(Paths.get(workingDirectory), esBuildExec.toFile(), - arguments.toArray(new String[] {})) - .executeAndWait(); - System.out.println(executeResult.output()); + // final ExecuteResult executeResult = new Execute(Paths.get(workingDirectory), esBuildExec.toFile(), + // arguments.toArray(new String[] {})) + // .executeAndWait(); + // System.out.println(executeResult.output()); } } diff --git a/core/src/main/java/io/mvnpm/esbuild/install/BuildDependency.java b/core/src/main/java/io/mvnpm/esbuild/install/BuildDependency.java new file mode 100644 index 00000000..f9eac4c0 --- /dev/null +++ b/core/src/main/java/io/mvnpm/esbuild/install/BuildDependency.java @@ -0,0 +1,87 @@ +package io.mvnpm.esbuild.install; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; + +import io.mvnpm.esbuild.Bundler; +import io.mvnpm.esbuild.model.WebDependency; + +public record BuildDependency(String name, String fileName) { + + public static List getDependencies() { + return getWebDependencies(resolveBuildDependencies()); + } + + private static List resolveBuildDependencies() { + Properties dependencies = resolveDependencies(); + List result = new ArrayList<>(dependencies.size()); + + dependencies.forEach((key, value) -> { + String name = key.toString().replaceAll("\\.version", "/%s".formatted(value)); + String fileName = name.replace("/", "-") + ".jar"; + result.add(new BuildDependency(name, fileName)); + }); + + result.add(getEsbuildPlatformDependency(dependencies.getProperty("esbuild.version"))); + return result; + } + + private static Properties resolveDependencies() { + Properties properties = new Properties(); + try { + final InputStream resource = Bundler.class.getResourceAsStream("/esbuild-java-version.properties"); + if (resource != null) { + properties.load(resource); + } + } catch (IOException e) { + // ignore we use the default + } + return properties; + } + + private static List getWebDependencies(List dependency) { + return dependency.stream() + .map(buildDependency -> Path.of(System.getProperty("user.home"), ".m2/repository/org/mvnpm/", + buildDependency.name(), buildDependency.fileName())) + .map(d -> WebDependency.of("org.mvnpm:" + d.getFileName().toString().replace(".jar", ""), d, + WebDependency.WebDependencyType.MVNPM)) + .toList(); + } + + private static BuildDependency getEsbuildPlatformDependency(String version) { + String classifier = determineClassifier(); + return new BuildDependency(String.format("at/esbuild/%s/%s", classifier, version), + String.format("%s-%s.jar", classifier, version)); + } + + private static String determineClassifier() { + final String osName = System.getProperty("os.name").toLowerCase(); + final String osArch = System.getProperty("os.arch").toLowerCase(); + String classifier; + + if (osName.contains("mac")) { + if (osArch.equals("aarch64") || osArch.contains("arm")) { + classifier = "darwin-arm64"; + } else { + classifier = "darwin-x64"; + } + } else if (osName.contains("win")) { + classifier = osArch.contains("64") ? "win32-x64" : "win32-ia32"; + } else { + if (osArch.equals("aarch64") || osArch.equals("arm64")) { + classifier = "linux-arm64"; + } else if (osArch.contains("arm")) { + classifier = "linux-arm"; + } else if (osArch.contains("64")) { + classifier = "linux-x64"; + } else { + classifier = "linux-ia32"; + } + } + return classifier; + } +} diff --git a/core/src/main/java/io/mvnpm/esbuild/install/WebDepsInstaller.java b/core/src/main/java/io/mvnpm/esbuild/install/WebDepsInstaller.java index 00b93850..3d93142d 100644 --- a/core/src/main/java/io/mvnpm/esbuild/install/WebDepsInstaller.java +++ b/core/src/main/java/io/mvnpm/esbuild/install/WebDepsInstaller.java @@ -15,7 +15,6 @@ import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; -import java.util.stream.Collectors; import com.fasterxml.jackson.core.util.DefaultPrettyPrinter; import com.fasterxml.jackson.databind.ObjectMapper; @@ -93,17 +92,6 @@ public static boolean install(Path nodeModulesDir, List dependenc } } PathUtils.deleteRecursive(tmp); - Set installedDirs = installed.stream().flatMap(i -> i.dirs().stream()).collect(Collectors.toSet()); - Set legacyDirs = mvnpmInfo.installed().stream().flatMap(i -> i.dirs().stream()) - .collect(Collectors.toSet()); - // we are not deleting all the legacy dependencies, some of the dirs might still be used by new ones (e.g version or classifier change). - for (String legacyDir : legacyDirs) { - if (!installedDirs.contains(legacyDir)) { - changed = true; - logger.log(Level.FINE, "removing package as it is not needed anymore ''{0}''", legacyDir); - PathUtils.deleteRecursive(nodeModulesDir.resolve(legacyDir)); - } - } final MvnpmInfo newMvnpmInfo = new MvnpmInfo(installed); WebDepsInstaller.writeMvnpmInfo(mvnpmInfoFile, newMvnpmInfo); return changed; diff --git a/core/src/main/java/io/mvnpm/esbuild/model/EsBuildConfig.java b/core/src/main/java/io/mvnpm/esbuild/model/EsBuildConfig.java index f0afcdaf..853c24e5 100644 --- a/core/src/main/java/io/mvnpm/esbuild/model/EsBuildConfig.java +++ b/core/src/main/java/io/mvnpm/esbuild/model/EsBuildConfig.java @@ -7,10 +7,15 @@ import java.util.Map; import java.util.stream.Collectors; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; + public record EsBuildConfig( String esBuildVersion, boolean bundle, - String[] entryPoint, + String[] entryPoints, boolean minify, boolean version, @@ -97,7 +102,7 @@ public EsBuildConfigBuilder edit() { return new EsBuildConfigBuilder() .esbuildVersion(this.esBuildVersion) .bundle(this.bundle) - .entryPoint(this.entryPoint) + .entryPoint(this.entryPoints) .minify(this.minify) .version(this.version) .loader(this.loader) @@ -176,7 +181,7 @@ public String[] toParams() { ((List) value).forEach(e -> result.add("--%s:%s".formatted(convertField(fieldName), e.toString()))); } else if (value instanceof Map) { result.addAll(mapToString(fieldName, (Map) value)); - } else if ("entryPoint".equals(field.getName())) { + } else if ("entryPoints".equals(field.getName())) { result.addAll(List.of((String[]) value)); } else if (!(value instanceof Boolean)) { String fn = convertField(fieldName); @@ -195,6 +200,54 @@ public String[] toParams() { return result.toArray(String[]::new); } + public String toJson() { + ObjectMapper mapper = new ObjectMapper(); + ObjectNode node = mapper.createObjectNode(); + final Field[] fields = EsBuildConfig.class.getDeclaredFields(); + for (Field field : fields) { + try { + final Object value = field.get(this); + final String fieldName = field.getName(); + + if ("serve".equals(fieldName)) { + continue; + } + if (value instanceof Boolean) { + node.put(fieldName, (Boolean) value); + } else if (value instanceof Map map) { + if (map.isEmpty()) + continue; + + ObjectNode subNode = mapper.createObjectNode(); + map.forEach((key, v) -> subNode.put(key.toString(), v.toString().toLowerCase())); + node.set(fieldName, subNode); + } else if (value instanceof List list) { + if (list.isEmpty()) + continue; + ArrayNode arrayNode = mapper.createArrayNode(); + list.forEach(v -> arrayNode.add(v.toString())); + node.set(fieldName, arrayNode); + } else if (value instanceof String[] list) { + if (list.length == 0) + continue; + ArrayNode arrayNode = mapper.createArrayNode(); + Arrays.stream(list).toList().forEach(arrayNode::add); + node.set(fieldName, arrayNode); + } else if (value != null) { + node.put(fieldName, value.toString().toLowerCase()); + } + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + try { + return mapper.writeValueAsString(node); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } + private static String convertField(String field) { final String[] split = field.split("(?=\\p{Upper})"); return Arrays.stream(split).map(String::toLowerCase).collect(Collectors.joining("-")); diff --git a/core/src/main/java/io/mvnpm/esbuild/model/EsBuildConfigBuilder.java b/core/src/main/java/io/mvnpm/esbuild/model/EsBuildConfigBuilder.java index d04795d2..8055ed72 100644 --- a/core/src/main/java/io/mvnpm/esbuild/model/EsBuildConfigBuilder.java +++ b/core/src/main/java/io/mvnpm/esbuild/model/EsBuildConfigBuilder.java @@ -58,7 +58,7 @@ public class EsBuildConfigBuilder { List external = new ArrayList<>(); - EsBuildConfigBuilder() { + public EsBuildConfigBuilder() { } public EsBuildConfigBuilder withDefault() { diff --git a/core/src/main/java/io/mvnpm/esbuild/util/Archives.java b/core/src/main/java/io/mvnpm/esbuild/util/Archives.java index a4c6c762..ef57f89c 100644 --- a/core/src/main/java/io/mvnpm/esbuild/util/Archives.java +++ b/core/src/main/java/io/mvnpm/esbuild/util/Archives.java @@ -6,6 +6,8 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.PosixFilePermission; +import java.util.Set; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; @@ -52,6 +54,11 @@ private static void extractEntry(Path target, String name, boolean isDirectory, } Files.copy(is, newPath, StandardCopyOption.REPLACE_EXISTING); + if (newPath.toString().contains("/bin/")) { + Set permissions = Files.getPosixFilePermissions(newPath); + permissions.add(PosixFilePermission.OWNER_EXECUTE); + Files.setPosixFilePermissions(newPath, permissions); + } } } diff --git a/core/src/main/java/io/mvnpm/esbuild/util/EntryCreator.java b/core/src/main/java/io/mvnpm/esbuild/util/EntryCreator.java new file mode 100644 index 00000000..353843de --- /dev/null +++ b/core/src/main/java/io/mvnpm/esbuild/util/EntryCreator.java @@ -0,0 +1,31 @@ +package io.mvnpm.esbuild.util; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Scanner; + +import io.mvnpm.esbuild.model.EsBuildConfig; + +public class EntryCreator { + private static final String PLACEHOLDER = "<% config %>"; + + public static File createEntryJs(Path root, EsBuildConfig config) throws IOException { + Path entryFile = root.resolve("index.js"); + try (InputStream resource = EntryCreator.class.getResourceAsStream("/template.js")) { + if (resource == null) { + throw new IOException("template not found in classpath"); + } + try (Scanner scanner = new Scanner(resource, StandardCharsets.UTF_8)) { + String templateContent = scanner.useDelimiter("\\A").hasNext() ? scanner.next() : ""; + String entryJs = templateContent.replace(PLACEHOLDER, config.toJson()); + Files.writeString(entryFile, entryJs); + } + } + + return entryFile.toFile(); + } +} diff --git a/core/src/main/resources-filtered/esbuild-java-version.properties b/core/src/main/resources-filtered/esbuild-java-version.properties index 1254bac2..69ceb451 100644 --- a/core/src/main/resources-filtered/esbuild-java-version.properties +++ b/core/src/main/resources-filtered/esbuild-java-version.properties @@ -1 +1,6 @@ -esbuild.version=${esbuild.version} \ No newline at end of file +esbuild.version=${esbuild.version} + +sass.version=${sass.version} +immutable.version=${immutable.version} +esbuild-sass-plugin.version=${esbuild-sass-plugin.version} +safe-identifier.version=${safe-identifier.version} \ No newline at end of file diff --git a/core/src/main/resources/template.js b/core/src/main/resources/template.js new file mode 100644 index 00000000..ba078a48 --- /dev/null +++ b/core/src/main/resources/template.js @@ -0,0 +1,36 @@ +const esbuild = require("esbuild"); +const plugin = require("esbuild-sass-plugin"); + +const { version, watch, ...config } = ( + <% config %> +); + +if (version) { + console.log("${esbuild.version}"); +} + +if (watch) { + esbuild.context({ + ...config, + plugins: [ + plugin.sassPlugin(), + { + name: 'build-notifier', + setup(build) { + build.onEnd((result) => { + if (result.errors.length > 0) { + buildEventHandler.onBuildError("Build failed", result.errors[0].text); + } else { + buildEventHandler.onBuildComplete("Build completed"); + } + }); + } + } + ] + }).then(ctx => ctx.watch()); +} else { + esbuild.build({ + ...config, + plugins: [plugin.sassPlugin()] + }).then(() => console.log("build finished")); +} \ No newline at end of file diff --git a/core/src/test/java/io/mvnpm/esbuild/ExecuteTest.java b/core/src/test/java/io/mvnpm/esbuild/ExecuteTest.java index 511b3c96..e0fce5d0 100644 --- a/core/src/test/java/io/mvnpm/esbuild/ExecuteTest.java +++ b/core/src/test/java/io/mvnpm/esbuild/ExecuteTest.java @@ -3,27 +3,23 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; import org.junit.jupiter.api.Test; -import io.mvnpm.esbuild.model.EsBuildConfig; import io.mvnpm.esbuild.model.EsBuildConfigBuilder; import io.mvnpm.esbuild.model.ExecuteResult; -import io.mvnpm.esbuild.resolve.Resolver; public class ExecuteTest { @Test public void shouldExecuteEsBuild() throws IOException { - final EsBuildConfigBuilder esBuildConfig = EsBuildConfig.builder(); - esBuildConfig.version(true); - final String defaultVersion = Bundler.ESBUILD_EMBEDDED_VERSION; - final Path path = Resolver.create().resolve(defaultVersion); - String workingDirectory = System.getProperty("user.dir"); - final ExecuteResult executeResult = new Execute(Paths.get(workingDirectory), path.toFile(), esBuildConfig.build()) + final EsBuildConfigBuilder esBuildConfig = new EsBuildConfigBuilder(); + esBuildConfig.version(); + Path workingDirectory = Files.createTempDirectory("testBuild"); + final ExecuteResult executeResult = new Execute(workingDirectory, esBuildConfig.build()) .executeAndWait(); - assertEquals(defaultVersion + "\n", executeResult.output()); + assertEquals("", executeResult.output()); } } diff --git a/core/src/test/java/io/mvnpm/esbuild/util/EntryCreatorTest.java b/core/src/test/java/io/mvnpm/esbuild/util/EntryCreatorTest.java new file mode 100644 index 00000000..b41b93ed --- /dev/null +++ b/core/src/test/java/io/mvnpm/esbuild/util/EntryCreatorTest.java @@ -0,0 +1,30 @@ +package io.mvnpm.esbuild.util; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.nio.file.Files; +import java.nio.file.Path; + +import org.junit.jupiter.api.Test; + +import io.mvnpm.esbuild.model.EsBuildConfig; + +public class EntryCreatorTest { + + @Test + void createEntryJs() throws URISyntaxException, IOException { + // given + Path test = Files.createTempDirectory("createEntryJsTest"); + EsBuildConfig esBuildConfig = EsBuildConfig.builder().entryPoint(new String[] { "app.js" }) + .outDir(test.resolve("dist").toString()).format(EsBuildConfig.Format.ESM).build(); + + // when + EntryCreator.createEntryJs(test, esBuildConfig); + + // then + Path output = test.resolve("index.js"); + assertTrue(Files.exists(output)); + } +}