All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.quarkiverse.web.bundler.deployment.WebDependenciesProcessor Maven / Gradle / Ivy

package io.quarkiverse.web.bundler.deployment;

import static io.mvnpm.esbuild.model.WebDependency.WebDependencyType.resolveType;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Instant;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

import org.jboss.logging.Logger;

import io.mvnpm.esbuild.install.WebDepsInstaller;
import io.mvnpm.esbuild.model.BundleOptions;
import io.mvnpm.esbuild.model.WebDependency;
import io.mvnpm.esbuild.model.WebDependency.WebDependencyType;
import io.quarkiverse.web.bundler.deployment.items.EntryPointBuildItem;
import io.quarkiverse.web.bundler.deployment.items.InstalledWebDependenciesBuildItem;
import io.quarkiverse.web.bundler.deployment.items.WebDependenciesBuildItem;
import io.quarkiverse.web.bundler.deployment.items.WebDependenciesBuildItem.Dependency;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.builditem.LaunchModeBuildItem;
import io.quarkus.deployment.builditem.LiveReloadBuildItem;
import io.quarkus.deployment.pkg.builditem.CurateOutcomeBuildItem;
import io.quarkus.deployment.pkg.builditem.OutputTargetBuildItem;
import io.quarkus.maven.dependency.DependencyFlags;
import io.quarkus.maven.dependency.ResolvedDependency;
import io.quarkus.runtime.configuration.ConfigurationException;

class WebDependenciesProcessor {

    private static final Logger LOGGER = Logger.getLogger(WebDependenciesProcessor.class);

    @BuildStep
    WebDependenciesBuildItem collectDependencies(LaunchModeBuildItem launchMode,
            CurateOutcomeBuildItem curateOutcome,
            List entryPoints,
            WebBundlerConfig config) {
        if (entryPoints.isEmpty() && !config.dependencies().autoImport().isEnabled()) {
            return new WebDependenciesBuildItem(List.of());
        }
        final List dependencies = StreamSupport.stream(curateOutcome.getApplicationModel()
                .getDependenciesWithAnyFlag(DependencyFlags.COMPILE_ONLY, DependencyFlags.RUNTIME_CP).spliterator(), false)
                .filter(io.quarkus.maven.dependency.Dependency::isJar)
                .filter(d -> WebDependencyType.anyMatch(d.toCompactCoords()))
                .peek(d -> checkScope(launchMode, d, config))
                .map(WebDependenciesProcessor::toWebDep)
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
        return new WebDependenciesBuildItem(dependencies);
    }

    @BuildStep
    InstalledWebDependenciesBuildItem installDependencies(LaunchModeBuildItem launchMode,
            LiveReloadBuildItem liveReload,
            OutputTargetBuildItem outputTarget,
            WebDependenciesBuildItem webDependencies,
            WebBundlerConfig config) {
        final InstalledWebDependenciesContext installedWebDependenciesContext = liveReload
                .getContextObject(InstalledWebDependenciesContext.class);
        final Path nodeModulesDir = resolveNodeModulesDir(config, outputTarget);
        if (liveReload.isLiveReload() && installedWebDependenciesContext != null
                && WebBundlerConfig.WebDependenciesConfig.isEqual(config.dependencies(),
                        installedWebDependenciesContext.config().dependencies())
                && nodeModulesDir.equals(installedWebDependenciesContext.nodeModulesDir())
                && installedWebDependenciesContext.dependencies().equals(webDependencies.list())) {
            return new InstalledWebDependenciesBuildItem(nodeModulesDir, webDependencies.list());
        }
        long startedInstall = Instant.now().toEpochMilli();
        try {
            final List toInstall = webDependencies.toEsBuildWebDependencies();
            if (WebDepsInstaller.install(nodeModulesDir, toInstall)) {
                final long duration = Instant.now().minusMillis(startedInstall).toEpochMilli();
                if (LOGGER.isDebugEnabled()) {
                    String deps = webDependencies.list().stream().map(Dependency::id)
                            .collect(
                                    Collectors.joining(", "));
                    LOGGER.infof("%d web dependencies installed in %sms: %s", webDependencies.list().size(),
                            duration, deps);
                } else {
                    LOGGER.infof("%d web Dependencies installed in %sms.", webDependencies.list().size(),
                            duration);
                }
            } else if (webDependencies.isEmpty()) {
                LOGGER.info("No web dependencies to install.");
            } else {
                LOGGER.info("All web dependencies are already installed.");
            }
            liveReload.setContextObject(InstalledWebDependenciesContext.class,
                    new InstalledWebDependenciesContext(config, nodeModulesDir, webDependencies.list()));
            return new InstalledWebDependenciesBuildItem(nodeModulesDir, webDependencies.list());
        } catch (IOException e) {
            liveReload.setContextObject(InstalledWebDependenciesContext.class, new InstalledWebDependenciesContext());
            throw new RuntimeException(e);
        }
    }

    private void checkScope(LaunchModeBuildItem launchMode, ResolvedDependency d, WebBundlerConfig config) {
        if (!launchMode.getLaunchMode().isDevOrTest() && config.dependencies().compileOnly() && d.isRuntimeCp()) {
            throw new ConfigurationException(
                    ("The Web Bundler is configured to only include compileOnly web dependencies, but %s is set as runtime." +
                            " Use a compile only scope (e.g. provided) or set quarkus.web-bundler.dependencies.compile-only=false to allow runtime web dependencies.")
                            .formatted(d.toCompactCoords()));
        }
    }

    private static Dependency toWebDep(ResolvedDependency d) {
        return d.getResolvedPaths().stream().filter(p -> p.getFileName().toString().endsWith(".jar")).findFirst()
                .map(j -> new Dependency(d, d.toCompactCoords(), j, resolveType(d.toCompactCoords()).orElseThrow(),
                        d.isDirect()))
                .orElse(null);
    }

    private static Path resolveNodeModulesDir(WebBundlerConfig config, OutputTargetBuildItem outputTarget) {
        if (config.dependencies().nodeModules().isEmpty()) {
            return outputTarget.getOutputDirectory().resolve(BundleOptions.NODE_MODULES);
        }
        final Path projectRoot = findProjectRoot(outputTarget.getOutputDirectory());
        final Path nodeModulesDir = Path.of(config.dependencies().nodeModules().get().trim());
        if (nodeModulesDir.isAbsolute() && Files.isDirectory(nodeModulesDir.getParent())) {
            return nodeModulesDir;
        }
        if (projectRoot == null || !Files.isDirectory(projectRoot)) {
            throw new IllegalStateException(
                    "If not absolute, the node_modules directory is resolved relative to the project root, but Web Bundler was not able to find the project root.");
        }
        return projectRoot.resolve(nodeModulesDir);
    }

    static Path findProjectRoot(Path outputDirectory) {
        Path currentPath = outputDirectory;
        do {
            if (Files.exists(currentPath.resolve(Paths.get("src", "main")))
                    || Files.exists(currentPath.resolve(Paths.get("config", "application.properties")))
                    || Files.exists(currentPath.resolve(Paths.get("config", "application.yaml")))
                    || Files.exists(currentPath.resolve(Paths.get("config", "application.yml")))) {
                return currentPath.normalize();
            }
            if (currentPath.getParent() != null && Files.exists(currentPath.getParent())) {
                currentPath = currentPath.getParent();
            } else {
                return null;
            }
        } while (true);
    }

    record InstalledWebDependenciesContext(WebBundlerConfig config, Path nodeModulesDir, List dependencies) {
        InstalledWebDependenciesContext() {
            this(null, null, List.of());
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy