From ad6710f83f338bfbb1bcde623690dbdeaade7c70 Mon Sep 17 00:00:00 2001 From: deemkeen Date: Thu, 30 Nov 2023 22:19:36 +0100 Subject: [PATCH] Basic bun integration (#1108) * initial bun integration * fixing integration tests * fixing integration tests * fixing integration tests * adding invoker properties * bun install * fix log output * update bun integration test to version 1.0.10 --- README.md | 1 + .../src/it/bun-integration/.gitignore | 1 + .../src/it/bun-integration/invoker.properties | 1 + .../src/it/bun-integration/package-lock.json | 13 ++ .../src/it/bun-integration/package.json | 10 + .../src/it/bun-integration/pom.xml | 50 ++++ .../src/it/bun-integration/verify.groovy | 7 + .../maven/plugins/frontend/mojo/BunMojo.java | 84 +++++++ .../plugins/frontend/mojo/InstallBunMojo.java | 59 +++++ .../m2e/lifecycle-mapping-metadata.xml | 2 + .../plugins/frontend/lib/BunExecutor.java | 31 +++ .../frontend/lib/BunExecutorConfig.java | 46 ++++ .../plugins/frontend/lib/BunInstaller.java | 215 ++++++++++++++++++ .../maven/plugins/frontend/lib/BunRunner.java | 47 ++++ .../plugins/frontend/lib/BunTaskExecutor.java | 112 +++++++++ .../frontend/lib/FrontendPluginFactory.java | 7 + 16 files changed, 686 insertions(+) create mode 100644 frontend-maven-plugin/src/it/bun-integration/.gitignore create mode 100644 frontend-maven-plugin/src/it/bun-integration/invoker.properties create mode 100644 frontend-maven-plugin/src/it/bun-integration/package-lock.json create mode 100644 frontend-maven-plugin/src/it/bun-integration/package.json create mode 100644 frontend-maven-plugin/src/it/bun-integration/pom.xml create mode 100644 frontend-maven-plugin/src/it/bun-integration/verify.groovy create mode 100644 frontend-maven-plugin/src/main/java/com/github/eirslett/maven/plugins/frontend/mojo/BunMojo.java create mode 100644 frontend-maven-plugin/src/main/java/com/github/eirslett/maven/plugins/frontend/mojo/InstallBunMojo.java create mode 100644 frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/BunExecutor.java create mode 100644 frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/BunExecutorConfig.java create mode 100644 frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/BunInstaller.java create mode 100644 frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/BunRunner.java create mode 100644 frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/BunTaskExecutor.java diff --git a/README.md b/README.md index 922648821..51cfba120 100644 --- a/README.md +++ b/README.md @@ -547,6 +547,7 @@ Tools and property to enable skipping * npm `-Dskip.npm` * yarn `-Dskip.yarn` * bower `-Dskip.bower` +* bun `-Dskip.bun` * grunt `-Dskip.grunt` * gulp `-Dskip.gulp` * jspm `-Dskip.jspm` diff --git a/frontend-maven-plugin/src/it/bun-integration/.gitignore b/frontend-maven-plugin/src/it/bun-integration/.gitignore new file mode 100644 index 000000000..3c3629e64 --- /dev/null +++ b/frontend-maven-plugin/src/it/bun-integration/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/frontend-maven-plugin/src/it/bun-integration/invoker.properties b/frontend-maven-plugin/src/it/bun-integration/invoker.properties new file mode 100644 index 000000000..93fc8f46c --- /dev/null +++ b/frontend-maven-plugin/src/it/bun-integration/invoker.properties @@ -0,0 +1 @@ +invoker.os.family = !windows, unix, mac diff --git a/frontend-maven-plugin/src/it/bun-integration/package-lock.json b/frontend-maven-plugin/src/it/bun-integration/package-lock.json new file mode 100644 index 000000000..d3cf1f3d9 --- /dev/null +++ b/frontend-maven-plugin/src/it/bun-integration/package-lock.json @@ -0,0 +1,13 @@ +{ + "name": "example", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "dependencies": { + "classnames": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz", + "integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==" + } + } +} diff --git a/frontend-maven-plugin/src/it/bun-integration/package.json b/frontend-maven-plugin/src/it/bun-integration/package.json new file mode 100644 index 000000000..e36bdbfca --- /dev/null +++ b/frontend-maven-plugin/src/it/bun-integration/package.json @@ -0,0 +1,10 @@ +{ + "name": "example", + "version": "0.0.1", + "dependencies": { + "classnames": "^2.3.2" + }, + "scripts": { + "prebuild": "npm install" + } +} diff --git a/frontend-maven-plugin/src/it/bun-integration/pom.xml b/frontend-maven-plugin/src/it/bun-integration/pom.xml new file mode 100644 index 000000000..95122ece6 --- /dev/null +++ b/frontend-maven-plugin/src/it/bun-integration/pom.xml @@ -0,0 +1,50 @@ + + + 4.0.0 + + com.github.eirslett + example + 0 + pom + + + + + com.github.eirslett + frontend-maven-plugin + + @project.version@ + + + target + + + + + + install bun runtime + + install-bun + + + v1.0.10 + + + + + bun install + + bun + + + + install + + + + + + + + + diff --git a/frontend-maven-plugin/src/it/bun-integration/verify.groovy b/frontend-maven-plugin/src/it/bun-integration/verify.groovy new file mode 100644 index 000000000..228a1e9d4 --- /dev/null +++ b/frontend-maven-plugin/src/it/bun-integration/verify.groovy @@ -0,0 +1,7 @@ +assert new File(basedir, 'target/bun').exists(): "Bun was not installed in the custom install directory"; + +import org.codehaus.plexus.util.FileUtils; + +String buildLog = FileUtils.fileRead(new File(basedir, 'build.log')); + +assert buildLog.contains('BUILD SUCCESS'): 'build was not successful' diff --git a/frontend-maven-plugin/src/main/java/com/github/eirslett/maven/plugins/frontend/mojo/BunMojo.java b/frontend-maven-plugin/src/main/java/com/github/eirslett/maven/plugins/frontend/mojo/BunMojo.java new file mode 100644 index 000000000..997df3bac --- /dev/null +++ b/frontend-maven-plugin/src/main/java/com/github/eirslett/maven/plugins/frontend/mojo/BunMojo.java @@ -0,0 +1,84 @@ +package com.github.eirslett.maven.plugins.frontend.mojo; + +import com.github.eirslett.maven.plugins.frontend.lib.FrontendPluginFactory; +import com.github.eirslett.maven.plugins.frontend.lib.ProxyConfig; +import com.github.eirslett.maven.plugins.frontend.lib.TaskRunnerException; +import org.apache.maven.execution.MavenSession; +import org.apache.maven.plugins.annotations.Component; +import org.apache.maven.plugins.annotations.LifecyclePhase; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.settings.crypto.SettingsDecrypter; +import org.sonatype.plexus.build.incremental.BuildContext; + +import java.io.File; +import java.util.Collections; + +@Mojo(name = "bun", defaultPhase = LifecyclePhase.GENERATE_RESOURCES, threadSafe = true) +public final class BunMojo extends AbstractFrontendMojo { + + private static final String NPM_REGISTRY_URL = "npmRegistryURL"; + + /** + * bun arguments. Default is "install". + */ + @Parameter(defaultValue = "", property = "frontend.bun.arguments", required = false) + private String arguments; + + @Parameter(property = "frontend.bun.bunInheritsProxyConfigFromMaven", required = false, + defaultValue = "true") + private boolean bunInheritsProxyConfigFromMaven; + + /** + * Registry override, passed as the registry option during npm install if set. + */ + @Parameter(property = NPM_REGISTRY_URL, required = false, defaultValue = "") + private String npmRegistryURL; + + @Parameter(property = "session", defaultValue = "${session}", readonly = true) + private MavenSession session; + + @Component + private BuildContext buildContext; + + @Component(role = SettingsDecrypter.class) + private SettingsDecrypter decrypter; + + /** + * Skips execution of this mojo. + */ + @Parameter(property = "skip.bun", defaultValue = "${skip.bun}") + private boolean skip; + + @Override + protected boolean skipExecution() { + return this.skip; + } + + @Override + public synchronized void execute(FrontendPluginFactory factory) throws TaskRunnerException { + File packageJson = new File(this.workingDirectory, "package.json"); + if (this.buildContext == null || this.buildContext.hasDelta(packageJson) + || !this.buildContext.isIncremental()) { + ProxyConfig proxyConfig = getProxyConfig(); + factory.getBunRunner(proxyConfig, getRegistryUrl()).execute(this.arguments, + this.environmentVariables); + } else { + getLog().info("Skipping bun install as package.json unchanged"); + } + } + + private ProxyConfig getProxyConfig() { + if (this.bunInheritsProxyConfigFromMaven) { + return MojoUtils.getProxyConfig(this.session, this.decrypter); + } else { + getLog().info("bun not inheriting proxy config from Maven"); + return new ProxyConfig(Collections.emptyList()); + } + } + + private String getRegistryUrl() { + // check to see if overridden via `-D`, otherwise fallback to pom value + return System.getProperty(NPM_REGISTRY_URL, this.npmRegistryURL); + } +} diff --git a/frontend-maven-plugin/src/main/java/com/github/eirslett/maven/plugins/frontend/mojo/InstallBunMojo.java b/frontend-maven-plugin/src/main/java/com/github/eirslett/maven/plugins/frontend/mojo/InstallBunMojo.java new file mode 100644 index 000000000..005c800e2 --- /dev/null +++ b/frontend-maven-plugin/src/main/java/com/github/eirslett/maven/plugins/frontend/mojo/InstallBunMojo.java @@ -0,0 +1,59 @@ +package com.github.eirslett.maven.plugins.frontend.mojo; + +import com.github.eirslett.maven.plugins.frontend.lib.FrontendPluginFactory; +import com.github.eirslett.maven.plugins.frontend.lib.InstallationException; +import com.github.eirslett.maven.plugins.frontend.lib.ProxyConfig; +import org.apache.maven.execution.MavenSession; +import org.apache.maven.plugins.annotations.Component; +import org.apache.maven.plugins.annotations.LifecyclePhase; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.settings.Server; +import org.apache.maven.settings.crypto.SettingsDecrypter; + +@Mojo(name = "install-bun", defaultPhase = LifecyclePhase.GENERATE_RESOURCES, threadSafe = true) +public final class InstallBunMojo extends AbstractFrontendMojo { + + /** + * The version of Bun to install. IMPORTANT! Most Bun version names start with 'v', for example + * 'v1.0.0' + */ + @Parameter(property = "bunVersion", required = true) + private String bunVersion; + + /** + * Server Id for download username and password + */ + @Parameter(property = "serverId", defaultValue = "") + private String serverId; + + @Parameter(property = "session", defaultValue = "${session}", readonly = true) + private MavenSession session; + + /** + * Skips execution of this mojo. + */ + @Parameter(property = "skip.installbun", alias = "skip.installbun", defaultValue = "${skip.installbun}") + private boolean skip; + + @Component(role = SettingsDecrypter.class) + private SettingsDecrypter decrypter; + + @Override + protected boolean skipExecution() { + return this.skip; + } + + @Override + public void execute(FrontendPluginFactory factory) throws InstallationException { + ProxyConfig proxyConfig = MojoUtils.getProxyConfig(this.session, this.decrypter); + Server server = MojoUtils.decryptServer(this.serverId, this.session, this.decrypter); + if (null != server) { + factory.getBunInstaller(proxyConfig).setBunVersion(this.bunVersion).setUserName(server.getUsername()) + .setPassword(server.getPassword()).install(); + } else { + factory.getBunInstaller(proxyConfig).setBunVersion(this.bunVersion).install(); + } + } + +} diff --git a/frontend-maven-plugin/src/main/resources/META-INF/m2e/lifecycle-mapping-metadata.xml b/frontend-maven-plugin/src/main/resources/META-INF/m2e/lifecycle-mapping-metadata.xml index 106a4663b..546ab3330 100644 --- a/frontend-maven-plugin/src/main/resources/META-INF/m2e/lifecycle-mapping-metadata.xml +++ b/frontend-maven-plugin/src/main/resources/META-INF/m2e/lifecycle-mapping-metadata.xml @@ -7,6 +7,7 @@ install-node-and-npm install-node-and-pnpm install-node-and-yarn + install-bun @@ -25,6 +26,7 @@ gulp grunt bower + bun jspm ember webpack diff --git a/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/BunExecutor.java b/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/BunExecutor.java new file mode 100644 index 000000000..271eed5b0 --- /dev/null +++ b/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/BunExecutor.java @@ -0,0 +1,31 @@ +package com.github.eirslett.maven.plugins.frontend.lib; + +import org.slf4j.Logger; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +final class BunExecutor { + private final ProcessExecutor executor; + + public BunExecutor(BunExecutorConfig config, List arguments, Map additionalEnvironment) { + final String bun = config.getBunPath().getAbsolutePath(); + List localPaths = new ArrayList(); + localPaths.add(config.getBunPath().getParent()); + this.executor = new ProcessExecutor( + config.getWorkingDirectory(), + localPaths, + Utils.prepend(bun, arguments), + config.getPlatform(), + additionalEnvironment); + } + + public String executeAndGetResult(final Logger logger) throws ProcessExecutionException { + return executor.executeAndGetResult(logger); + } + + public int executeAndRedirectOutput(final Logger logger) throws ProcessExecutionException { + return executor.executeAndRedirectOutput(logger); + } +} diff --git a/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/BunExecutorConfig.java b/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/BunExecutorConfig.java new file mode 100644 index 000000000..614839cbe --- /dev/null +++ b/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/BunExecutorConfig.java @@ -0,0 +1,46 @@ +package com.github.eirslett.maven.plugins.frontend.lib; + +import java.io.File; + +public interface BunExecutorConfig { + + File getNodePath(); + + File getBunPath(); + + File getWorkingDirectory(); + + Platform getPlatform(); +} + +final class InstallBunExecutorConfig implements BunExecutorConfig { + + private File nodePath; + + private final InstallConfig installConfig; + + public InstallBunExecutorConfig(InstallConfig installConfig) { + this.installConfig = installConfig; + nodePath = new InstallNodeExecutorConfig(installConfig).getNodePath(); + } + + @Override + public File getNodePath() { + return nodePath; + } + + @Override + public File getBunPath() { + return new File(installConfig.getInstallDirectory() + BunInstaller.INSTALL_PATH); + } + + @Override + public File getWorkingDirectory() { + return installConfig.getWorkingDirectory(); + } + + @Override + public Platform getPlatform() { + return installConfig.getPlatform(); + } +} \ No newline at end of file diff --git a/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/BunInstaller.java b/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/BunInstaller.java new file mode 100644 index 000000000..ad1becc69 --- /dev/null +++ b/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/BunInstaller.java @@ -0,0 +1,215 @@ +package com.github.eirslett.maven.plugins.frontend.lib; + +import org.apache.commons.io.FileUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.EOFException; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; +import java.util.Arrays; + +public class BunInstaller { + + public static final String INSTALL_PATH = "/bun"; + + public static final String DEFAULT_BUN_DOWNLOAD_ROOT = + "https://github.com/oven-sh/bun/releases/download/"; + private static final Object LOCK = new Object(); + + private String bunVersion, userName, password; + + private final Logger logger; + + private final InstallConfig config; + + private final ArchiveExtractor archiveExtractor; + + private final FileDownloader fileDownloader; + + BunInstaller(InstallConfig config, ArchiveExtractor archiveExtractor, FileDownloader fileDownloader) { + this.logger = LoggerFactory.getLogger(getClass()); + this.config = config; + this.archiveExtractor = archiveExtractor; + this.fileDownloader = fileDownloader; + } + + public BunInstaller setBunVersion(String bunVersion) { + this.bunVersion = bunVersion; + return this; + } + + public BunInstaller setUserName(String userName) { + this.userName = userName; + return this; + } + + public BunInstaller setPassword(String password) { + this.password = password; + return this; + } + + public void install() throws InstallationException { + // use static lock object for a synchronized block + synchronized (LOCK) { + if (!bunIsAlreadyInstalled()) { + if (!this.bunVersion.startsWith("v")) { + this.logger.warn("Bun version does not start with naming convention 'v'."); + } + if (this.config.getPlatform().isWindows()) { + throw new InstallationException("Unable to install bun on windows!"); + } else { + installBunDefault(); + } + } + } + } + + private boolean bunIsAlreadyInstalled() { + try { + BunExecutorConfig executorConfig = new InstallBunExecutorConfig(config); + File bunFile = executorConfig.getBunPath(); + if (bunFile.exists()) { + final String version = + new BunExecutor(executorConfig, Arrays.asList("--version"), null).executeAndGetResult(logger); + + if (version.equals(this.bunVersion.replaceFirst("^v", ""))) { + this.logger.info("Bun {} is already installed.", version); + return true; + } else { + this.logger.info("Bun {} was installed, but we need version {}", version, + this.bunVersion); + return false; + } + } else { + return false; + } + } catch (ProcessExecutionException e) { + this.logger.warn("Unable to determine current bun version: {}", e.getMessage()); + return false; + } + } + + private void installBunDefault() throws InstallationException { + try { + + logger.info("Installing Bun version {}", bunVersion); + + String downloadUrl = createDownloadUrl(); + + CacheDescriptor cacheDescriptor = new CacheDescriptor("bun", this.bunVersion, + "zip"); + + File archive = this.config.getCacheResolver().resolve(cacheDescriptor); + + downloadFileIfMissing(downloadUrl, archive, this.userName, this.password); + + File installDirectory = getInstallDirectory(); + + // We need to delete the existing bun directory first so we clean out any old files, and + // so we can rename the package directory below. + try { + if (installDirectory.isDirectory()) { + FileUtils.deleteDirectory(installDirectory); + } + } catch (IOException e) { + logger.warn("Failed to delete existing Bun installation."); + } + + try { + extractFile(archive, installDirectory); + } catch (ArchiveExtractionException e) { + if (e.getCause() instanceof EOFException) { + this.logger.error("The archive file {} is corrupted and will be deleted. " + + "Please try the build again.", archive.getPath()); + archive.delete(); + } + + throw e; + } + + // Search for the bun binary + File bunBinary = + new File(getInstallDirectory(), File.separator + createBunTargetArchitecturePath() + File.separator + "bun"); + if (!bunBinary.exists()) { + throw new FileNotFoundException( + "Could not find the downloaded bun binary in " + bunBinary); + } else { + File destinationDirectory = getInstallDirectory(); + + File destination = new File(destinationDirectory, "bun"); + this.logger.info("Copying bun binary from {} to {}", bunBinary, destination); + if (destination.exists() && !destination.delete()) { + throw new InstallationException("Could not install Bun: Was not allowed to delete " + destination); + } + try { + Files.move(bunBinary.toPath(), destination.toPath(), StandardCopyOption.REPLACE_EXISTING); + } catch (IOException e) { + throw new InstallationException("Could not install Bun: Was not allowed to rename " + + bunBinary + " to " + destination); + } + + if (!destination.setExecutable(true, false)) { + throw new InstallationException( + "Could not install Bun: Was not allowed to make " + destination + " executable."); + } + + this.logger.info("Installed bun locally."); + } + } catch (IOException e) { + throw new InstallationException("Could not install bun", e); + } catch (DownloadException e) { + throw new InstallationException("Could not download bun", e); + } catch (ArchiveExtractionException e) { + throw new InstallationException("Could not extract the bun archive", e); + } + } + + private String createDownloadUrl() { + String downloadUrl = String.format("%sbun-%s", DEFAULT_BUN_DOWNLOAD_ROOT, bunVersion); + String extension = "zip"; + String fileending = String.format("%s.%s", createBunTargetArchitecturePath(), extension); + + downloadUrl += fileending; + return downloadUrl; + } + + private String createBunTargetArchitecturePath() { + OS os = OS.guess(); + Architecture architecture = Architecture.guess(); + String destOs = os.equals(OS.Linux) ? "linux" : os.equals(OS.Mac) ? "darwin" : null; + String destArc = architecture.equals(Architecture.x64) ? "x64" : architecture.equals( + Architecture.arm64) ? "aarch64" : null; + return String.format("%s-%s-%s", INSTALL_PATH, destOs, destArc); + } + + private File getInstallDirectory() { + File installDirectory = new File(this.config.getInstallDirectory(), "/"); + if (!installDirectory.exists()) { + this.logger.info("Creating install directory {}", installDirectory); + installDirectory.mkdirs(); + } + return installDirectory; + } + + private void extractFile(File archive, File destinationDirectory) throws ArchiveExtractionException { + this.logger.info("Unpacking {} into {}", archive, destinationDirectory); + this.archiveExtractor.extract(archive.getPath(), destinationDirectory.getPath()); + } + + private void downloadFileIfMissing(String downloadUrl, File destination, String userName, String password) + throws DownloadException { + if (!destination.exists()) { + downloadFile(downloadUrl, destination, userName, password); + } + } + + private void downloadFile(String downloadUrl, File destination, String userName, String password) + throws DownloadException { + this.logger.info("Downloading {} to {}", downloadUrl, destination); + this.fileDownloader.download(downloadUrl, destination.getPath(), userName, password); + } +} diff --git a/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/BunRunner.java b/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/BunRunner.java new file mode 100644 index 000000000..5954b56d4 --- /dev/null +++ b/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/BunRunner.java @@ -0,0 +1,47 @@ +package com.github.eirslett.maven.plugins.frontend.lib; + +import com.github.eirslett.maven.plugins.frontend.lib.ProxyConfig.Proxy; + +import java.util.ArrayList; +import java.util.List; + +public interface BunRunner extends NodeTaskRunner { +} + +final class DefaultBunRunner extends BunTaskExecutor implements BunRunner { + + private static final String TASK_NAME = "bun"; + + public DefaultBunRunner(BunExecutorConfig config, ProxyConfig proxyConfig, String npmRegistryURL) { + super(config, TASK_NAME, config.getBunPath().getAbsolutePath(), + buildArguments(proxyConfig, npmRegistryURL)); + } + + private static List buildArguments(ProxyConfig proxyConfig, String npmRegistryURL) { + List arguments = new ArrayList<>(); + + if (npmRegistryURL != null && !npmRegistryURL.isEmpty()) { + arguments.add("--registry=" + npmRegistryURL); + } + + if (!proxyConfig.isEmpty()) { + Proxy proxy = null; + if (npmRegistryURL != null && !npmRegistryURL.isEmpty()) { + proxy = proxyConfig.getProxyForUrl(npmRegistryURL); + } + + if (proxy == null) { + proxy = proxyConfig.getSecureProxy(); + } + + if (proxy == null) { + proxy = proxyConfig.getInsecureProxy(); + } + + arguments.add("--https-proxy=" + proxy.getUri().toString()); + arguments.add("--proxy=" + proxy.getUri().toString()); + } + + return arguments; + } +} diff --git a/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/BunTaskExecutor.java b/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/BunTaskExecutor.java new file mode 100644 index 000000000..7ba02aae7 --- /dev/null +++ b/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/BunTaskExecutor.java @@ -0,0 +1,112 @@ +package com.github.eirslett.maven.plugins.frontend.lib; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static com.github.eirslett.maven.plugins.frontend.lib.Utils.implode; + +abstract class BunTaskExecutor { + private static final String DS = "//"; + + private static final String AT = "@"; + + private final Logger logger; + + private final String taskName; + + private final ArgumentsParser argumentsParser; + + private final BunExecutorConfig config; + + public BunTaskExecutor(BunExecutorConfig config, String taskLocation) { + this(config, taskLocation, Collections.emptyList()); + } + + public BunTaskExecutor(BunExecutorConfig config, String taskName, String taskLocation) { + this(config, taskName, taskLocation, Collections.emptyList()); + } + + public BunTaskExecutor(BunExecutorConfig config, String taskLocation, + List additionalArguments) { + this(config, getTaskNameFromLocation(taskLocation), taskLocation, additionalArguments); + } + + public BunTaskExecutor(BunExecutorConfig config, String taskName, String taskLocation, + List additionalArguments) { + logger = LoggerFactory.getLogger(getClass()); + this.config = config; + this.taskName = taskName; + this.argumentsParser = new ArgumentsParser(additionalArguments); + } + + private static String getTaskNameFromLocation(String taskLocation) { + return taskLocation.replaceAll("^.*/([^/]+)(?:\\.js)?$", "$1"); + } + + public final void execute(String args, Map environment) throws TaskRunnerException { + final List arguments = getArguments(args); + logger.info("Running " + taskToString(taskName, arguments) + " in " + config.getWorkingDirectory()); + + try { + final int result = + new BunExecutor(config, arguments, environment).executeAndRedirectOutput(logger); + if (result != 0) { + throw new TaskRunnerException( + taskToString(taskName, arguments) + " failed. (error code " + result + ")"); + } + } catch (ProcessExecutionException e) { + throw new TaskRunnerException(taskToString(taskName, arguments) + " failed.", e); + } + } + + private List getArguments(String args) { + return argumentsParser.parse(args); + } + + private static String taskToString(String taskName, List arguments) { + List clonedArguments = new ArrayList<>(arguments); + for (int i = 0; i < clonedArguments.size(); i++) { + final String s = clonedArguments.get(i); + final boolean maskMavenProxyPassword = s.contains("proxy="); + if (maskMavenProxyPassword) { + final String bestEffortMaskedPassword = maskPassword(s); + clonedArguments.set(i, bestEffortMaskedPassword); + } + } + return "'" + taskName + " " + implode(" ", clonedArguments) + "'"; + } + + private static String maskPassword(String proxyString) { + String retVal = proxyString; + if (proxyString != null && !"".equals(proxyString.trim())) { + boolean hasSchemeDefined = proxyString.contains("http:") || proxyString.contains("https:"); + boolean hasProtocolDefined = proxyString.contains(DS); + boolean hasAtCharacterDefined = proxyString.contains(AT); + if (hasSchemeDefined && hasProtocolDefined && hasAtCharacterDefined) { + final int firstDoubleSlashIndex = proxyString.indexOf(DS); + final int lastAtCharIndex = proxyString.lastIndexOf(AT); + boolean hasPossibleURIUserInfo = firstDoubleSlashIndex < lastAtCharIndex; + if (hasPossibleURIUserInfo) { + final String userInfo = + proxyString.substring(firstDoubleSlashIndex + DS.length(), lastAtCharIndex); + final String[] userParts = userInfo.split(":"); + if (userParts.length > 0) { + final int startOfUserNameIndex = firstDoubleSlashIndex + DS.length(); + final int firstColonInUsernameOrEndOfUserNameIndex = + startOfUserNameIndex + userParts[0].length(); + final String leftPart = + proxyString.substring(0, firstColonInUsernameOrEndOfUserNameIndex); + final String rightPart = proxyString.substring(lastAtCharIndex); + retVal = leftPart + ":***" + rightPart; + } + } + } + } + return retVal; + } +} diff --git a/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/FrontendPluginFactory.java b/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/FrontendPluginFactory.java index 799cd36aa..1b6c398a1 100644 --- a/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/FrontendPluginFactory.java +++ b/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/FrontendPluginFactory.java @@ -21,6 +21,9 @@ public FrontendPluginFactory(File workingDirectory, File installDirectory, Cache this.cacheResolver = cacheResolver; } + public BunInstaller getBunInstaller(ProxyConfig proxy) { + return new BunInstaller(getInstallConfig(), new DefaultArchiveExtractor(), new DefaultFileDownloader(proxy)); + } public NodeInstaller getNodeInstaller(ProxyConfig proxy) { return new NodeInstaller(getInstallConfig(), new DefaultArchiveExtractor(), new DefaultFileDownloader(proxy)); } @@ -41,6 +44,10 @@ public BowerRunner getBowerRunner(ProxyConfig proxy) { return new DefaultBowerRunner(getExecutorConfig(), proxy); } + public BunRunner getBunRunner(ProxyConfig proxy, String npmRegistryURL) { + return new DefaultBunRunner(new InstallBunExecutorConfig(getInstallConfig()), proxy, npmRegistryURL); + } + public JspmRunner getJspmRunner() { return new DefaultJspmRunner(getExecutorConfig()); }