Skip to content

Commit

Permalink
A few improvements for a perfect install management
Browse files Browse the repository at this point in the history
  • Loading branch information
ia3andy committed Sep 29, 2023
1 parent 40230b6 commit 8ea53f7
Show file tree
Hide file tree
Showing 8 changed files with 9,739 additions and 129 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,8 @@ public ForwardedDevServerBuildItem prepareDevService(
final PackageManagerRunner packageManagerRunner = installedPackageManager.getPackageManager();
final String checkPath = resolvedConfig.devServer().checkPath().orElse(null);
if (devService != null) {
boolean shouldShutdownTheBroker = !resolvedConfig.equals(oldConfig);
boolean shouldShutdownTheBroker = !resolvedConfig.equals(oldConfig)
|| QuinoaProcessor.isPackageJsonLiveReloadChanged(configuredQuinoa, liveReload);
if (!shouldShutdownTheBroker) {
if (devServerConfig.port().isEmpty()) {
throw new IllegalStateException(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
import static io.quarkus.deployment.annotations.ExecutionTime.RUNTIME_INIT;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
Expand Down Expand Up @@ -40,7 +39,6 @@
import io.quarkiverse.quinoa.deployment.items.TargetDirBuildItem;
import io.quarkiverse.quinoa.deployment.packagemanager.PackageManagerInstall;
import io.quarkiverse.quinoa.deployment.packagemanager.PackageManagerRunner;
import io.quarkiverse.quinoa.deployment.packagemanager.types.PackageManagerType;
import io.quarkus.deployment.IsDevelopment;
import io.quarkus.deployment.IsNormal;
import io.quarkus.deployment.annotations.BuildProducer;
Expand Down Expand Up @@ -129,25 +127,16 @@ public InstalledPackageManagerBuildItem install(

final PackageManagerRunner packageManagerRunner = autoDetectPackageManager(packageManagerBinary,
resolvedConfig.packageManagerCommand(), configuredQuinoa.uiDir(), paths);
final boolean alreadyInstalled = Files.isDirectory(packageManagerRunner.getDirectory().resolve("node_modules"));
boolean packageFileModified = liveReload.isLiveReload()
&& liveReload.getChangedResources().stream()
.anyMatch(r -> r.equals(configuredQuinoa.packageJson().toString()));

final Path targetPackageJson = outputTarget.getOutputDirectory().resolve(TARGET_DIR_NAME).resolve(BUILD_FILE);
final Path currentPackageJson = configuredQuinoa.packageJson();
if (!packageFileModified) {
// check for manual changes in package.json and trigger and automatic install
packageFileModified = !compareTextFiles(targetPackageJson, currentPackageJson);
}
if (resolvedConfig.forceInstall() || !alreadyInstalled || packageFileModified) {
if (resolvedConfig.forceInstall()
|| shouldInstallPackages(configuredQuinoa, liveReload, targetPackageJson, currentPackageJson)) {
final boolean ci = resolvedConfig.ci().orElseGet(QuinoaProcessor::isCI);
if (ci) {
packageManagerRunner.ci();
} else {
packageManagerRunner.install();
}

// copy the package.json to build, so we can compare for next time
Files.copy(currentPackageJson, targetPackageJson, StandardCopyOption.REPLACE_EXISTING);
}
Expand Down Expand Up @@ -228,11 +217,19 @@ public BuiltResourcesBuildItem prepareResourcesForOtherMode(
return new BuiltResourcesBuildItem(targetDir.get().getBuildDirectory(), entries);
}

@BuildStep
@BuildStep(onlyIf = IsDevelopment.class)
void watchChanges(
Optional<ConfiguredQuinoaBuildItem> quinoaDir,
BuildProducer<HotDeploymentWatchedFileBuildItem> watchedPaths) throws IOException {
if (quinoaDir.isEmpty() || isDevServerMode(quinoaDir.get().resolvedConfig())) {
if (quinoaDir.isEmpty()) {
return;
}
if (isDevServerMode(quinoaDir.get().resolvedConfig())) {
final HotDeploymentWatchedFileBuildItem watchPackageJson = HotDeploymentWatchedFileBuildItem.builder()
.setLocation(quinoaDir.get().packageJson().toString())
.setRestartNeeded(true)
.build();
watchedPaths.produce(watchPackageJson);
return;
}
scan(quinoaDir.get().uiDir(), watchedPaths);
Expand Down Expand Up @@ -273,21 +270,6 @@ public void runtimeInit(
}
}

@BuildStep(onlyIf = IsDevelopment.class)
List<HotDeploymentWatchedFileBuildItem> hotDeploymentWatchedFiles(Optional<ConfiguredQuinoaBuildItem> configuredQuinoa,
OutputTargetBuildItem outputTarget) {
final List<HotDeploymentWatchedFileBuildItem> watchedFiles = new ArrayList<>(PackageManagerType.values().length);
if (configuredQuinoa.isEmpty()) {
return watchedFiles;
}

for (PackageManagerType pm : PackageManagerType.values()) {
final String watchFile = configuredQuinoa.get().uiDir().resolve(pm.getLockFile()).toString();
watchedFiles.add(new HotDeploymentWatchedFileBuildItem(watchFile));
}
return watchedFiles;
}

private HashSet<BuiltResourcesBuildItem.BuiltResource> prepareBuiltResources(
BuildProducer<GeneratedResourceBuildItem> generatedResources,
BuildProducer<NativeImageResourceBuildItem> nativeImageResources,
Expand Down Expand Up @@ -326,6 +308,41 @@ private void scan(Path directory, BuildProducer<HotDeploymentWatchedFileBuildIte
}
}

private static boolean shouldInstallPackages(ConfiguredQuinoaBuildItem configuredQuinoa,
LiveReloadBuildItem liveReload,
Path targetPackageJson,
Path currentPackageJson) throws IOException {

if (!Files.isDirectory(configuredQuinoa.uiDir().resolve("node_modules"))) {
LOG.info("Quinoa didn't detect a node_modules directory, let's install packages...");
return true;
}

if (isPackageJsonLiveReloadChanged(configuredQuinoa, liveReload)) {
return true;
}

if (!Files.exists(targetPackageJson)) {
LOG.info("Fresh Quinoa build, let's install packages...");
return true;
}
// Check for size then content
if (Files.size(currentPackageJson) != Files.size(targetPackageJson)
|| !Arrays.equals(Files.readAllBytes(currentPackageJson), Files.readAllBytes(targetPackageJson))) {
LOG.info("Quinoa detected a change in package.json since the previous install, let's install packages again...");
return true;
}

LOG.debug("package.json seems to be the same as previous Quinoa install, skipping packages install");
return false;
}

static boolean isPackageJsonLiveReloadChanged(ConfiguredQuinoaBuildItem configuredQuinoa, LiveReloadBuildItem liveReload) {
return liveReload.isLiveReload()
&& liveReload.getChangedResources().stream()
.anyMatch(r -> r.equals(configuredQuinoa.packageJson().toString()));
}

/**
* Check whether this path should be scanned for changes by comparing against known directories that should be ignored.
* Ignored directories include any that start with DOT "." like ".next" or ".svelte", also "node_modules" and any
Expand Down Expand Up @@ -405,23 +422,6 @@ public static Path initializeTargetDirectory(OutputTargetBuildItem outputTarget)
return targetBuildDir;
}

public static boolean compareTextFiles(Path file1, Path file2) throws IOException {
if (!Files.exists(file1) || !Files.exists(file2)) {
LOG.info("Clean build forcing automatic install...");
return false;
}
// Read the contents of both files into strings
String content1 = new String(Files.readAllBytes(file1), StandardCharsets.UTF_8);
String content2 = new String(Files.readAllBytes(file2), StandardCharsets.UTF_8);

// Compare the contents
boolean equals = Objects.equals(content1, content2);
if (!equals) {
LOG.infof("'%s' has been modified forcing automatic install!", BUILD_FILE);
}
return equals;
}

private static class QuinoaLiveContext {
private final Path location;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,13 @@
package io.quarkiverse.quinoa.deployment.devui;

import java.util.Map;
import java.util.Optional;
import java.util.function.Function;

import io.quarkiverse.quinoa.deployment.config.PackageManagerInstallConfig;
import io.quarkiverse.quinoa.deployment.config.QuinoaConfig;
import io.quarkiverse.quinoa.deployment.items.ConfiguredQuinoaBuildItem;
import io.quarkiverse.quinoa.deployment.items.InstalledPackageManagerBuildItem;
import io.quarkiverse.quinoa.deployment.packagemanager.PackageManagerRunner;
import io.quarkiverse.quinoa.devui.QuinoaJsonRpcService;
import io.quarkus.deployment.IsDevelopment;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.dev.console.DevConsoleManager;
import io.quarkus.devui.spi.JsonRPCProvidersBuildItem;
import io.quarkus.devui.spi.page.CardPageBuildItem;
import io.quarkus.devui.spi.page.ExternalPageBuilder;
import io.quarkus.devui.spi.page.FooterPageBuildItem;
Expand Down Expand Up @@ -100,27 +93,4 @@ void createCard(BuildProducer<CardPageBuildItem> cardPageBuildItemBuildProducer,
footerProducer.produce(new FooterPageBuildItem(nodeLogPageBuilder));
}

@BuildStep(onlyIf = IsDevelopment.class)
JsonRPCProvidersBuildItem registerJsonRpcBackend(InstalledPackageManagerBuildItem installedPackageManager,
QuinoaConfig quinoaConfig) {
DevConsoleManager.register("quinoa-install-action",
install(installedPackageManager, quinoaConfig));
return new JsonRPCProvidersBuildItem(QuinoaJsonRpcService.class);
}

private Function<Map<String, String>, String> install(InstalledPackageManagerBuildItem installedPackageManager,
QuinoaConfig quinoaConfig) {
return (map -> {
try {
final PackageManagerRunner packageManagerRunner = installedPackageManager.getPackageManager();

// install or update packages
packageManagerRunner.install();

return "installed";
} catch (Exception e) {
return e.getMessage();
}
});
}
}
42 changes: 1 addition & 41 deletions deployment/src/main/resources/dev-ui/qwc-quinoa-card.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,7 @@ export class QwcQuinoaCard extends LitElement {
.button {
cursor: pointer;
}
.installIcon {
color: var(--lumo-primary-text-colo);
}
.restartIcon {
color: var(--lumo-primary-text-colo);
Expand Down Expand Up @@ -109,48 +106,11 @@ export class QwcQuinoaCard extends LitElement {
<div class="description">${DESCRIPTION}</div>
</div>
${this._renderCardLinks()}
${this._renderActions()}
${progress}
</div>
`;
}

_renderActions() {
return html`
<vaadin-button theme="small" @click=${() => this._install()} class="button"
title="Run package manager 'install' command to update packages and restart the Node server">
<vaadin-icon class="installIcon" icon="font-awesome-solid:cloud-arrow-down"></vaadin-icon>
Install/Update Packages
</vaadin-button>
`;
}

_install() {
this.build_complete = false;
this.build_in_progress = true;
this.build_error = false;
this.result = "";
this.jsonRpc.install()
.onNext(jsonRpcResponse => {
const msg = jsonRpcResponse.result;
if (msg === "started") {
this.build_complete = false;
this.build_in_progress = true;
this.build_error = false;
} else if (msg.includes("installed")) {
this.build_complete = true;
this.build_in_progress = false;
this.result = msg;
this._success('Packages updated or installed successfully.');
} else {
this.build_complete = true;
this.build_in_progress = false;
this.build_error = true;
this._error(msg)
}
});
}

_success(message) {
notifier.showSuccessMessage(message, "top-center");
}
Expand Down
4 changes: 2 additions & 2 deletions docs/modules/ROOT/pages/includes/quarkus-quinoa.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ a|icon:lock[title=Fixed at build time] [[quarkus-quinoa_quarkus.quinoa.just-buil

[.description]
--
Indicate if Quinoa should just do the build part. If true, Quinoa will NOT serve the Web UI built resources. This is handy when the output of the build is used to be served via something else (nginx, cdn, ...) Quinoa put the built files in 'target/quinoa-build' (or 'build/quinoa-build with Gradle).
Indicate if Quinoa should just do the build part. If true, Quinoa will NOT serve the Web UI built resources. This is handy when the output of the build is used to be served via something else (nginx, cdn, ...) Quinoa put the built files in 'target/quinoa/build' (or 'build/quinoa/build with Gradle).
ifdef::add-copy-button-to-env-var[]
Environment variable: env_var_with_copy_button:+++QUARKUS_QUINOA_JUST_BUILD+++[]
Expand Down Expand Up @@ -66,7 +66,7 @@ a|icon:lock[title=Fixed at build time] [[quarkus-quinoa_quarkus.quinoa.build-dir
[.description]
--
This the Web UI internal build system (webpack, ...) output directory. After the build, Quinoa will take the files from this directory, move them to 'target/quinoa-build' (or build/quinoa-build with Gradle) and serve them at runtime. The path is relative to the Web UI path.
This the Web UI internal build system (webpack, ...) output directory. After the build, Quinoa will take the files from this directory, move them to 'target/quinoa/build' (or build/quinoa/build with Gradle) and serve them at runtime. The path is relative to the Web UI path.

ifdef::add-copy-button-to-env-var[]
Environment variable: env_var_with_copy_button:+++QUARKUS_QUINOA_BUILD_DIR+++[]
Expand Down
26 changes: 19 additions & 7 deletions integration-tests/src/main/ui-angular/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion integration-tests/src/main/ui-angular/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"@angular/platform-browser-dynamic": "~13.3.0",
"@angular/router": "~13.3.0",
"rxjs": "~7.5.0",
"tslib": "^2.3.0",
"tslib": "2.6.2",
"zone.js": "~0.11.4"
},
"devDependencies": {
Expand Down
Loading

0 comments on commit 8ea53f7

Please sign in to comment.