From 777f5a8858fa2aa7188c572607f48acc3fe84347 Mon Sep 17 00:00:00 2001 From: GedMarc Date: Mon, 14 Sep 2026 17:17:45 +0200 Subject: [PATCH 1/2] Fail startup reliably and order terminal process cleanup --- pom.xml | 5 +- .../guicedinjection/GuiceApplication.java | 101 +++++++++++++ .../guicedinjection/GuiceContext.java | 143 +++++++++++------- .../tests/FrameworkLifecycleProbe.java | 86 +++++++++++ .../tests/FrameworkLifecycleTest.java | 41 +++++ src/test/java/module-info.java | 4 +- 6 files changed, 325 insertions(+), 55 deletions(-) create mode 100644 src/main/java/com/guicedee/guicedinjection/GuiceApplication.java create mode 100644 src/test/java/com/guicedee/tests/FrameworkLifecycleProbe.java create mode 100644 src/test/java/com/guicedee/tests/FrameworkLifecycleTest.java diff --git a/pom.xml b/pom.xml index 308e5cf..2977826 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ com.guicedee inject jar - 2.2.3 + 2.2.3-SNAPSHOT Guiced Injector GuicedEE Inject is an open-source Guice integration library that discovers binders/modules across JARs via SPI and classpath scanning, bootstraps logging, job services, and JRT URL handling, and keeps optional adapters (e.g., Vert.x) isolated. Requires Java 25 LTS. https://guicedee.com @@ -40,6 +40,9 @@ + + com.guicedeeclient2.2.3-SNAPSHOT + com.guicedee fasterxml-bom diff --git a/src/main/java/com/guicedee/guicedinjection/GuiceApplication.java b/src/main/java/com/guicedee/guicedinjection/GuiceApplication.java new file mode 100644 index 0000000..3e37cc3 --- /dev/null +++ b/src/main/java/com/guicedee/guicedinjection/GuiceApplication.java @@ -0,0 +1,101 @@ +package com.guicedee.guicedinjection; + +import com.guicedee.client.Environment; +import com.guicedee.client.IGuiceContext; +import org.apache.logging.log4j.LogManager; + +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicReference; + +/** Process entry boundary. Returns after startup; failed startup cleans up and exits unsuccessfully. */ +public final class GuiceApplication { + private GuiceApplication() {} + + /** + * Runs preparation and injection within GUICEDEE_STARTUP_TIMEOUT_SECONDS (default 300). + * Failure has GUICEDEE_FAILED_STARTUP_CLEANUP_SECONDS (default 45) to stop the process, + * including cleanup and JVM shutdown hooks. This method is for main entry points only. + */ + public static void run(Runnable preparation) { + Objects.requireNonNull(preparation); + var context = new AtomicReference(); + var phase = new AtomicReference<>("deadline configuration"); + Thread startup = null; + int cleanupSeconds = 45; + boolean interrupted = false; + try { + cleanupSeconds = seconds("GUICEDEE_FAILED_STARTUP_CLEANUP_SECONDS", 45, 120); + int startupSeconds = seconds("GUICEDEE_STARTUP_TIMEOUT_SECONDS", 300, 1800); + var ready = new CompletableFuture(); + // Preserve normal non-daemon inheritance for resources created by startup hooks. + startup = Thread.ofPlatform().daemon(false).name("guicedee-application-startup").start(() -> { + try { + phase.set("context discovery"); + var active = IGuiceContext.instance(); + if (active instanceof GuiceContext guice) guice.manageProcessLifecycle(); + context.set(active); + phase.set("application preparation"); + preparation.run(); + phase.set("injector initialization"); + active.inject(); + phase.set("asynchronous startup"); + active.getLoadingFinished().onComplete(result -> { + if (result.succeeded()) ready.complete(null); + else ready.completeExceptionally(result.cause()); + }); + } catch (Throwable failed) { + ready.completeExceptionally(failed); + } + }); + ready.get(startupSeconds, TimeUnit.SECONDS); + return; + } catch (InterruptedException cancelled) { + interrupted = true; + phase.set("interrupted startup"); + } catch (TimeoutException expired) { + phase.set("startup deadline exceeded"); + } catch (Throwable failed) { + // Retain the fixed phase without repeating configuration values or raw causes. + } + + // A stuck cleanup hook or JVM shutdown hook must not leave a failed process serving traffic. + final int cleanupBudget = cleanupSeconds; + Thread.ofPlatform().daemon().name("guicedee-failed-startup-deadline").start(() -> { + long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(cleanupBudget); + while (System.nanoTime() < deadline) { + try { + TimeUnit.NANOSECONDS.sleep(Math.max(1, deadline - System.nanoTime())); + } catch (InterruptedException ignored) { + // Preserve the absolute failure deadline. + } + } + Runtime.getRuntime().halt(1); + }); + LogManager.getLogger(GuiceApplication.class).error("Application startup failed during {}; shutting down", phase.get()); + if (startup != null) startup.interrupt(); + try { + var active = context.get(); + if (active != null) active.destroy(); + } catch (Throwable failedCleanup) { + LogManager.getLogger(GuiceApplication.class).error("Application startup cleanup failed"); + } finally { + if (interrupted) Thread.currentThread().interrupt(); + System.exit(1); + } + } + + private static int seconds(String name, int fallback, int maximum) { + try { + String value = Environment.getSystemPropertyOrEnvironment(name, Integer.toString(fallback)); + if (!value.matches("[1-9][0-9]{0,3}")) throw new IllegalArgumentException(); + int seconds = Integer.parseInt(value); + if (seconds > maximum) throw new IllegalArgumentException(); + return seconds; + } catch (Exception invalid) { + throw new IllegalArgumentException("Invalid application startup deadline"); + } + } +} diff --git a/src/main/java/com/guicedee/guicedinjection/GuiceContext.java b/src/main/java/com/guicedee/guicedinjection/GuiceContext.java index 73938f7..4fba232 100644 --- a/src/main/java/com/guicedee/guicedinjection/GuiceContext.java +++ b/src/main/java/com/guicedee/guicedinjection/GuiceContext.java @@ -432,7 +432,7 @@ private static org.apache.logging.log4j.core.Layout buildConsoleLayout(Consol /** * The physical injector for the JVM container */ - private Injector injector; + private volatile Injector injector; /** * The actual scanner */ @@ -451,6 +451,15 @@ private static org.apache.logging.log4j.core.Layout buildConsoleLayout(Consol private static boolean configured; private final CompletableFuture loadingFinished = new CompletableFuture<>(); + private final java.util.concurrent.atomic.AtomicBoolean shutdownStarted = new java.util.concurrent.atomic.AtomicBoolean(); + private final CompletableFuture shutdownFinished = new CompletableFuture<>(); + private volatile Thread shutdownOwner; + private volatile boolean processLifecycle; + + /** Opts a process launcher into terminal, exactly-once shutdown. Embedded callers retain their lifecycle. */ + void manageProcessLifecycle() { + processLifecycle = true; + } /** * Creates a new Guice context. Not necessary @@ -459,13 +468,18 @@ private GuiceContext() { } + /** Returns the already-created injector without starting application services. */ + @Override + public Optional existingInjector() { + return Optional.ofNullable(injector); + } + /** - * Reference the Injector Directly - * - * @return The global Guice Injector Object, Never Null, Instantiates the Injector if not configured + * Returns the global injector, creating it and starting lifecycle work when needed. + * Await {@link #getLoadingFinished()} to observe asynchronous startup success or failure. */ - public Injector inject() { + if (processLifecycle && shutdownStarted.get() && injector == null) throw new IllegalStateException("Guice context is stopped"); if (GuiceContext.buildingInjector) { log.error("๐Ÿ’ฅ The injector is being called recursively during build. Place such actions in a IGuicePostStartup or use the IGuicePreStartup Service Loader."); new IllegalStateException("The injector is being called recursively during build. Place such actions in a IGuicePostStartup or use the IGuicePreStartup Service Loader.").printStackTrace(); @@ -521,7 +535,10 @@ public Injector inject() { GuiceContext.instance() .loadPreDestroyServices(); }).subscribe().with(a -> { - log.trace("Subcription for post startups completed - " + a); + log.trace("Subscription for post startups completed - " + a); + }, failure -> { + loadingFinished.completeExceptionally(failure); + log.error("Post-startup initialization failed", failure); }); Runtime .getRuntime() @@ -535,6 +552,7 @@ public void run() { } catch (Throwable e) { GuiceContext.buildingInjector = false; log.error("๐Ÿ’ฅ Critical failure during dependency injection system initialization: {}", e.getMessage(), e); + loadingFinished.completeExceptionally(e); throw new RuntimeException("Unable to boot Guice Injector", e); } } @@ -547,54 +565,62 @@ public void run() { */ @SuppressWarnings("unused") public void destroy() { - log.info("๐Ÿ›‘ Starting Guice Context shutdown and resource cleanup"); - Stopwatch shutdownStopwatch = Stopwatch.createStarted(); - + if (processLifecycle && !shutdownStarted.compareAndSet(false, true)) { + if (shutdownOwner != Thread.currentThread()) shutdownFinished.join(); + return; + } + shutdownOwner = Thread.currentThread(); + if (processLifecycle) loadingFinished.completeExceptionally(new IllegalStateException("Guice context is stopping")); try { - Set destroyers = loadPreDestroyServices(); - log.debug("๐Ÿ—‘๏ธ Executing {} pre-destroy services for cleanup", destroyers.size()); + log.info("๐Ÿ›‘ Starting Guice Context shutdown and resource cleanup"); + Stopwatch shutdownStopwatch = Stopwatch.createStarted(); - int successCount = 0; - int failureCount = 0; - - for (IGuicePreDestroy destroyer : destroyers) { - String destroyerName = destroyer.getClass().getCanonicalName(); - log.debug("๐Ÿ—‘๏ธ Running pre-destroy service: {}", destroyerName); - - try { - destroyer.onDestroy(); - successCount++; - log.debug("โœ… Successfully executed pre-destroy service: {}", destroyerName); - } catch (Throwable T) { - failureCount++; - log.error("โŒ Failed to run destroyer '{}': {}", destroyerName, T.getMessage(), T); + try { + Set destroyers = loadPreDestroyServices(); + log.debug("๐Ÿ—‘๏ธ Executing {} pre-destroy services for cleanup", destroyers.size()); + + int successCount = 0; + int failureCount = 0; + + for (IGuicePreDestroy destroyer : destroyers) { + String destroyerName = destroyer.getClass().getCanonicalName(); + log.debug("๐Ÿ—‘๏ธ Running pre-destroy service: {}", destroyerName); + + try { + destroyer.onDestroy(); + successCount++; + log.debug("โœ… Successfully executed pre-destroy service: {}", destroyerName); + } catch (Throwable T) { + failureCount++; + log.error("โŒ Failed to run destroyer '{}': {}", destroyerName, T.getMessage(), T); + } } - } - log.info("๐Ÿ“Š Pre-destroy services execution completed - Success: {}, Failed: {}", - successCount, failureCount); + log.info("๐Ÿ“Š Pre-destroy services execution completed - Success: {}, Failed: {}", + successCount, failureCount); - } catch (Throwable T) { - log.error("๐Ÿ’ฅ Failed to run destroyers: {}", T.getMessage(), T); - } + } catch (Throwable T) { + log.error("๐Ÿ’ฅ Failed to run destroyers: {}", T.getMessage(), T); + } - log.debug("๐Ÿงน Cleaning up scanner resources"); - if (GuiceContext.instance().scanResult != null) { - GuiceContext.instance().scanResult.close(); - log.debug("โœ… Scan result resources released"); - } + log.debug("๐Ÿงน Cleaning up scanner resources"); + if (GuiceContext.instance().scanResult != null) { + GuiceContext.instance().scanResult.close(); + log.debug("โœ… Scan result resources released"); + } - // Clear all references - GuiceContext.instance().scanResult = null; - GuiceContext.instance().scanner = null; - GuiceContext.instance().injector = null; - GuiceContext.configured = false; - GuiceContext.config.reset(); - IGuiceContext.getAllLoadedServices().clear(); - - shutdownStopwatch.stop(); - log.info("๐ŸŽ‰ Guice Context shutdown completed in {}ms", - shutdownStopwatch.elapsed(TimeUnit.MILLISECONDS)); + // Clear all references + GuiceContext.instance().scanResult = null; + GuiceContext.instance().scanner = null; + GuiceContext.instance().injector = null; + GuiceContext.configured = false; + GuiceContext.config.reset(); + IGuiceContext.getAllLoadedServices().clear(); + + shutdownStopwatch.stop(); + log.info("๐ŸŽ‰ Guice Context shutdown completed in {}ms", + shutdownStopwatch.elapsed(TimeUnit.MILLISECONDS)); + } finally { shutdownFinished.complete(null); } } /** @@ -1230,6 +1256,7 @@ private Uni loadPostStartups() { return Multi.createFrom().iterable(groupedStartups.entrySet()) .onItem() .transformToUniAndConcatenate(entry -> { + if (shutdownStarted.get()) return Uni.createFrom().failure(new IllegalStateException("Guice context is stopping")); int sortOrder = entry.getKey(); List> group = entry.getValue(); // group.sort(Comparator.comparing(IGuicePostStartup::sortOrder)); @@ -1243,12 +1270,14 @@ private Uni loadPostStartups() { if (postLoadResults != null) { for (Uni postLoadResult : postLoadResults) { Uni onContext = Uni.createFrom().emitter(em -> - vertx.runOnContext(v -> + vertx.runOnContext(v -> { + try { postLoadResult .invoke(a -> log.trace("โœ… Completed postload : " + startup.getClass().getCanonicalName())) .onItem().transform(a -> true) - .subscribe().with(em::complete, em::fail) - ) + .subscribe().with(em::complete, em::fail); + } catch (Throwable failure) { em.fail(failure); } + }) ); startupsInGroup.add(onContext); } @@ -1265,9 +1294,13 @@ private Uni loadPostStartups() { }) .collect().asList() .replaceWith(true) + .onItem().invoke(ignored -> { + if (shutdownStarted.get()) throw new IllegalStateException("Guice context is stopping"); + loadingFinished.complete(null); + }) + .onFailure().invoke(loadingFinished::completeExceptionally) .eventually(() -> { totalStopwatch.stop(); - loadingFinished.complete(null); log.info("๐ŸŽ‰ Post-startup initialization setup completed in {}ms", totalStopwatch.elapsed(TimeUnit.MILLISECONDS)); }); @@ -1337,7 +1370,13 @@ public Set loadPreStartupServices() { * @return the set of pre-destroy services */ public Set loadPreDestroyServices() { - return new LinkedHashSet<>(getLoader(IGuicePreDestroy.class, true, ServiceLoader.load(IGuicePreDestroy.class))); + // IDefaultService.compareTo is not a valid equality comparator for equal priorities. + // Use an explicit total ordering and retain every distinct provider. + Set hooks = getLoader(IGuicePreDestroy.class, true, ServiceLoader.load(IGuicePreDestroy.class)); + return hooks.stream() + .sorted(Comparator.comparingInt((IGuicePreDestroy hook) -> hook.shutdownSortOrder()) + .thenComparing(hook -> hook.getClass().getName())) + .collect(Collectors.toCollection(LinkedHashSet::new)); } /** diff --git a/src/test/java/com/guicedee/tests/FrameworkLifecycleProbe.java b/src/test/java/com/guicedee/tests/FrameworkLifecycleProbe.java new file mode 100644 index 0000000..1233fda --- /dev/null +++ b/src/test/java/com/guicedee/tests/FrameworkLifecycleProbe.java @@ -0,0 +1,86 @@ +package com.guicedee.tests; + +import com.google.inject.AbstractModule; +import com.guicedee.client.IGuiceContext; +import com.guicedee.client.services.lifecycle.*; +import com.guicedee.guicedinjection.GuiceContext; +import io.smallrye.mutiny.Uni; +import io.vertx.core.*; +import java.util.*; +import java.util.concurrent.*; + +/** Fresh-JVM framework runner with explicit fixture services; never starts application/database modules. */ +public final class FrameworkLifecycleProbe { + static String mode; + static final List stopped=new CopyOnWriteArrayList<>(); + static final Promise pending=Promise.promise(); + static boolean resourceClosed; + public static void main(String[] args) throws Exception { + mode=args[0]; + Class.forName("com.guicedee.client.scopes.CallScoper");var vertx=Vertx.vertx().exceptionHandler(Throwable::printStackTrace);var context=GuiceContext.instance(); + IGuiceContext.contexts.put("default",context); + context.getConfig().setClasspathScanning(false).setServiceLoadWithClassPath(false); + var cache=IGuiceContext.getAllLoadedServices(); + cache.put(IGuiceConfigurator.class,Set.of());cache.put(IGuicePreStartup.class,Set.of()); + cache.put(IGuiceModule.class,Set.of()); + cache.put(IGuicePostStartup.class,Set.of(new Startup())); + cache.put(IGuicePreDestroy.class,new LinkedHashSet<>(List.of(new Late(),new SameB(),new Early(),new SameA()))); + IGuiceContext.modules.add(new AbstractModule(){protected void configure(){ + bind(Vertx.class).toInstance(vertx); + if(mode.equals("module-failure"))addError("fixture-module-failure"); + }}); + try { + if(mode.equals("module-failure")) { + try {context.inject();throw new AssertionError("Failed module was accepted");}catch(RuntimeException expected){} + requireFailed(context.getLoadingFinished());System.out.println("PROBE_OK module-failure");return; + } + context.inject(); + if(context.existingInjector().isEmpty())throw new AssertionError("Existing injector unavailable"); + if(mode.equals("failure") || mode.equals("subscription-failure")) { + pending.fail("fixture-startup-failure");requireFailed(context.getLoadingFinished()); + } else { + if(context.getLoadingFinished().isComplete())throw new AssertionError("Startup completed before hook"); + pending.complete(true);context.getLoadingFinished().toCompletionStage().toCompletableFuture().get(5,TimeUnit.SECONDS); + } + context.destroy(); + if(!stopped.equals(List.of("early","same-a","same-b","late")))throw new AssertionError("Wrong shutdown order: "+stopped); + System.out.println("PROBE_OK "+mode); + // Framework registers a JVM shutdown hook; do not execute fixture hooks twice on exit. + cache.put(IGuicePreDestroy.class,Set.of()); + } finally {vertx.close().toCompletionStage().toCompletableFuture().get(5,TimeUnit.SECONDS);} + } + static void requireFailed(io.vertx.core.Future future) throws Exception { + try {future.toCompletionStage().toCompletableFuture().get(5,TimeUnit.SECONDS);throw new AssertionError("Startup failure reported success");} + catch(ExecutionException expected) { + String required = switch(mode) { + case "module-failure" -> "fixture-module-failure"; + case "subscription-failure" -> "fixture-subscription-failure"; + default -> "fixture-startup-failure"; + }; + var trace = new java.io.StringWriter(); + expected.printStackTrace(new java.io.PrintWriter(trace)); + if(!trace.toString().contains(required))throw new AssertionError("Wrong startup failure",expected); + } + } + public static final class Startup implements IGuicePostStartup { + public List> postLoad(){ + if(mode.equals("subscription-failure"))return List.of(new io.smallrye.mutiny.operators.AbstractUni() { + public void subscribe(io.smallrye.mutiny.subscription.UniSubscriber subscriber) {throw new IllegalStateException("fixture-subscription-failure");} + }); + return List.of(Uni.createFrom().completionStage(pending.future().toCompletionStage())); + } + } + public static final class Early implements IGuicePreDestroy { + public Integer sortOrder(){return -100;} + public void onDestroy(){if(resourceClosed)throw new AssertionError("Resource closed before drain");stopped.add("early");} + } + public static final class SameA implements IGuicePreDestroy { + public Integer sortOrder(){return 0;}public void onDestroy(){stopped.add("same-a");} + } + public static final class SameB implements IGuicePreDestroy { + public Integer sortOrder(){return 0;}public void onDestroy(){stopped.add("same-b");} + } + public static final class Late implements IGuicePreDestroy { + public Integer sortOrder(){return -200;}public Integer shutdownSortOrder(){return 100;}public void onDestroy(){resourceClosed=true;stopped.add("late");} + } +} diff --git a/src/test/java/com/guicedee/tests/FrameworkLifecycleTest.java b/src/test/java/com/guicedee/tests/FrameworkLifecycleTest.java new file mode 100644 index 0000000..519ccb8 --- /dev/null +++ b/src/test/java/com/guicedee/tests/FrameworkLifecycleTest.java @@ -0,0 +1,41 @@ +package com.guicedee.tests; + +import com.guicedee.client.Environment; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import java.nio.file.*; +import java.util.*; +import java.util.concurrent.TimeUnit; +import static org.junit.jupiter.api.Assertions.*; + +class FrameworkLifecycleTest { + @TempDir Path temporary; + @Test void readinessWaitsAndShutdownRetainsTiedProvidersWithItsOwnPriority() throws Exception {probe("success");} + @Test void asynchronousFailureRejectsReadiness() throws Exception {probe("failure");} + @Test void moduleFailureRejectsReadiness() throws Exception {probe("module-failure");} + @Test void synchronousSubscriptionFailureRejectsReadiness() throws Exception {probe("subscription-failure");} + + void probe(String mode) throws Exception { + Path output=Path.of("target/framework-lifecycle-"+mode+".log").toAbsolutePath(); + Files.writeString(temporary.resolve(".env"),"GUICEDEE_LIFECYCLE_FIXTURE=true\n"); + Files.writeString(temporary.resolve(".env.local"),"GUICEDEE_LIFECYCLE_FIXTURE=true\n"); + Path logging=temporary.resolve("log4j2.xml"); + Files.writeString(logging,""" + + + """); + var builder=new ProcessBuilder(List.of(Path.of(Environment.getSystemPropertyOrEnvironment("java.home",null),"bin/java").toString(), + "-Dlog4j2.configurationFile="+logging.toUri(),"--module-path",Environment.getSystemPropertyOrEnvironment("jdk.module.path",null), + "--add-modules","ALL-MODULE-PATH","--module","guice.injection.tests/"+FrameworkLifecycleProbe.class.getName(),mode)) + .directory(temporary.toFile()).redirectErrorStream(true).redirectOutput(output.toFile()); + builder.environment().keySet().removeIf(key -> !Set.of("SYSTEMROOT","WINDIR","TEMP","TMP").contains(key.toUpperCase(Locale.ROOT))); + var process=builder.start(); + try { + assertTrue(process.waitFor(20,TimeUnit.SECONDS),"Probe timed out: "+output); + assertEquals(0,process.exitValue(),"Probe failed: "+output); + String log=Files.readString(output); + assertTrue(log.contains("PROBE_OK "+mode),"Probe never reached its assertions: "+output); + if(mode.equals("success"))assertFalse(log.contains("ERROR"),"Unexpected runtime error: "+output); + } finally {if(process.isAlive()){process.destroyForcibly();process.waitFor(5,TimeUnit.SECONDS);}} + } +} diff --git a/src/test/java/module-info.java b/src/test/java/module-info.java index e5e8d96..a7fda94 100644 --- a/src/test/java/module-info.java +++ b/src/test/java/module-info.java @@ -7,6 +7,6 @@ //requires org.slf4j; //requires org.apache.logging.log4j.slf4j2.impl; - opens com.guicedee.tests to org.junit.platform.commons; + opens com.guicedee.tests to org.junit.platform.commons, com.google.guice; -} \ No newline at end of file +} From aea1b12945cb127278ecc39b43746169ad210899 Mon Sep 17 00:00:00 2001 From: GedMarc Date: Mon, 14 Sep 2026 17:27:41 +0200 Subject: [PATCH 2/2] Restrict feature reviews to verification without deployment --- .github/workflows/maven-publish.yml | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/.github/workflows/maven-publish.yml b/.github/workflows/maven-publish.yml index 942b430..d6aebd5 100644 --- a/.github/workflows/maven-publish.yml +++ b/.github/workflows/maven-publish.yml @@ -7,6 +7,14 @@ on: description: Run a release to maven central default: false push: + branches: + - main + - master + pull_request: + types: [opened, synchronize, reopened, closed] + branches: + - main + - master permissions: @@ -16,11 +24,26 @@ permissions: pull-requests: write jobs: - GuicedInjection: + verify: + if: ${{ github.event_name == 'pull_request' && github.event.action != 'closed' }} uses: GuicedEE/Workflows/.github/workflows/projects.yml@master with: baseDir: '' name: 'Guiced Injection' sonarProjectName: 'GuicedEE_GuicedInjection' sonarOrganization: 'guicedee' - secrets: inherit \ No newline at end of file + skipDeploy: 'true' + secrets: inherit + deploy: + if: >- + github.event_name == 'push' || + github.event_name == 'workflow_dispatch' || + (github.event_name == 'pull_request' && github.event.action == 'closed' && github.event.pull_request.merged == true) + uses: GuicedEE/Workflows/.github/workflows/projects.yml@master + with: + baseDir: '' + name: 'Guiced Injection' + sonarProjectName: 'GuicedEE_GuicedInjection' + sonarOrganization: 'guicedee' + publishToCentral: ${{inputs.centralRelease}} + secrets: inherit