Repository: quarkusio/quarkus
Issue: Qute: extension tags not registered

### Describe the bug

I just tried the latest Quarkus/Qute to see if it would pick up my Vixen extension tags, declared in `extensions/vixen/runtime/src/main/resources/templates/tags` (in the runtime module) and they're not visible to the user application, I'm getting errors.

Perhaps I'm doing it wrong and they need to be in the deployment module?

### Expected behavior

_No response_

### Actual behavior

_No response_

### How to Reproduce?

_No response_

### Output of `uname -a` or `ver`

_No response_

### Output of `java -version`

_No response_

### GraalVM version (if different from Java)

_No response_

### Quarkus version or git rev

_No response_

### Build tool (ie. output of `mvnw --version` or `gradlew --version`)

_No response_

### Additional information

_No response_

Changed files:
extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/devconsole/QuteDevConsoleProcessor.java
extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/TemplatePathBuildItem.java
extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java

diff --git a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java
index 3594df36ea8..1b91ef972fe 100644
--- a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java
+++ b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java
@@ -9,8 +9,9 @@
 import java.io.IOException;
 import java.io.Reader;
 import java.io.StringReader;
+import java.io.UncheckedIOException;
 import java.lang.reflect.Modifier;
-import java.nio.charset.StandardCharsets;
+import java.nio.file.FileSystem;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.ArrayList;
@@ -80,6 +81,7 @@
 import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
 import io.quarkus.deployment.pkg.builditem.CurateOutcomeBuildItem;
 import io.quarkus.deployment.util.JandexUtil;
+import io.quarkus.fs.util.ZipUtils;
 import io.quarkus.gizmo.ClassOutput;
 import io.quarkus.maven.dependency.Dependency;
 import io.quarkus.maven.dependency.ResolvedDependency;
@@ -383,22 +385,17 @@ public CompletionStage<ResultNode> resolve(SectionResolutionContext context) {
             public Optional<TemplateLocation> locate(String id) {
                 TemplatePathBuildItem found = templatePaths.stream().filter(p -> p.getPath().equals(id)).findAny().orElse(null);
                 if (found != null) {
-                    try {
-                        byte[] content = Files.readAllBytes(found.getFullPath());
-                        return Optional.of(new TemplateLocation() {
-                            @Override
-                            public Reader read() {
-                                return new StringReader(new String(content, StandardCharsets.UTF_8));
-                            }
+                    return Optional.of(new TemplateLocation() {
+                        @Override
+                        public Reader read() {
+                            return new StringReader(found.getContent());
+                        }
 
-                            @Override
-                            public Optional<Variant> getVariant() {
-                                return Optional.empty();
-                            }
-                        });
-                    } catch (IOException e) {
-                        LOGGER.warn("Unable to read the template from path: " + found.getFullPath(), e);
-                    }
+                        @Override
+                        public Optional<Variant> getVariant() {
+                            return Optional.empty();
+                        }
+                    });
                 }
                 return Optional.empty();
             }
@@ -1234,15 +1231,27 @@ void collectTemplates(ApplicationArchivesBuildItem applicationArchives,
                 // Skip extension archives that are also application archives
                 continue;
             }
-            for (Path p : artifact.getResolvedPaths()) {
-                if (Files.isDirectory(p)) {
+            for (Path path : artifact.getResolvedPaths()) {
+                if (Files.isDirectory(path)) {
                     // Try to find the templates in the root dir
-                    Path basePath = Files.list(p).filter(QuteProcessor::isBasePath).findFirst().orElse(null);
+                    Path basePath = Files.list(path).filter(QuteProcessor::isBasePath).findFirst().orElse(null);
                     if (basePath != null) {
-                        LOGGER.debugf("Found extension templates dir: %s", p);
-                        basePaths.add(basePath);
+                        LOGGER.debugf("Found extension templates dir: %s", path);
+                        scan(basePath, basePath, BASE_PATH + "/", watchedPaths, templatePaths, nativeImageResources,
+                                config.templatePathExclude);
                         break;
                     }
+                } else {
+                    try (FileSystem artifactFs = ZipUtils.newFileSystem(path)) {
+                        Path basePath = artifactFs.getPath(BASE_PATH);
+                        if (Files.exists(basePath)) {
+                            LOGGER.debugf("Found extension templates in: %s", path);
+                            scan(basePath, basePath, BASE_PATH + "/", watchedPaths, templatePaths, nativeImageResources,
+                                    config.templatePathExclude);
+                        }
+                    } catch (IOException e) {
+                        LOGGER.warnf(e, "Unable to create the file system from the path: %s", path);
+                    }
                 }
             }
         }
@@ -1254,14 +1263,12 @@ void collectTemplates(ApplicationArchivesBuildItem applicationArchives,
                 if (basePath != null) {
                     LOGGER.debugf("Found templates dir: %s", basePath);
                     basePaths.add(basePath);
+                    scan(basePath, basePath, BASE_PATH + "/", watchedPaths, templatePaths, nativeImageResources,
+                            config.templatePathExclude);
                     break;
                 }
             }
         }
-        for (Path base : basePaths) {
-            scan(base, base, BASE_PATH + "/", watchedPaths, templatePaths, nativeImageResources,
-                    config.templatePathExclude);
-        }
     }
 
     @BuildStep
@@ -2114,7 +2121,7 @@ private static void produceTemplateBuildItems(BuildProducer<TemplatePathBuildIte
         // NOTE: we cannot just drop the template because a template param can be added 
         watchedPaths.produce(new HotDeploymentWatchedFileBuildItem(fullPath, true));
         nativeImageResources.produce(new NativeImageResourceBuildItem(fullPath));
-        templatePaths.produce(new TemplatePathBuildItem(filePath, originalPath));
+        templatePaths.produce(new TemplatePathBuildItem(filePath, originalPath, readTemplateContent(originalPath)));
     }
 
     private void scan(Path root, Path directory, String basePath, BuildProducer<HotDeploymentWatchedFileBuildItem> watchedPaths,
@@ -2191,6 +2198,14 @@ private boolean isApplicationArchive(ResolvedDependency dependency, Set<Applicat
         return false;
     }
 
+    static String readTemplateContent(Path path) {
+        try {
+            return Files.readString(path);
+        } catch (IOException e) {
+            throw new UncheckedIOException("Unable to read the template content from path: " + path, e);
+        }
+    }
+
     /**
      * Java members lookup config.
      * 
diff --git a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/TemplatePathBuildItem.java b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/TemplatePathBuildItem.java
index f10580ae2bd..c8dedb9539a 100644
--- a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/TemplatePathBuildItem.java
+++ b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/TemplatePathBuildItem.java
@@ -13,10 +13,12 @@ public final class TemplatePathBuildItem extends MultiBuildItem {
 
     private final String path;
     private final Path fullPath;
+    private final String content;
 
-    public TemplatePathBuildItem(String path, Path fullPath) {
+    public TemplatePathBuildItem(String path, Path fullPath, String content) {
         this.path = path;
         this.fullPath = fullPath;
+        this.content = content;
     }
 
     /**
@@ -49,4 +51,8 @@ public boolean isRegular() {
         return !isTag();
     }
 
+    public String getContent() {
+        return content;
+    }
+
 }
diff --git a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/devconsole/QuteDevConsoleProcessor.java b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/devconsole/QuteDevConsoleProcessor.java
index 9dee337d5a1..2a1f8c67f0d 100644
--- a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/devconsole/QuteDevConsoleProcessor.java
+++ b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/devconsole/QuteDevConsoleProcessor.java
@@ -3,11 +3,7 @@
 import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE;
 import static io.quarkus.deployment.annotations.ExecutionTime.STATIC_INIT;
 
-import java.io.IOException;
 import java.net.URLConnection;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -124,18 +120,9 @@ private CheckedTemplateBuildItem findCheckedTemplate(String basePath, List<Check
     private Map<String, String> processVariants(List<TemplatePathBuildItem> templatePaths, List<String> variants) {
         Map<String, String> variantsMap = new HashMap<>();
         for (String variant : variants) {
-            String source = "";
-            Path sourcePath = templatePaths.stream().filter(p -> p.getPath().equals(variant))
-                    .map(TemplatePathBuildItem::getFullPath).findFirst()
+            String source = templatePaths.stream().filter(p -> p.getPath().equals(variant))
+                    .map(TemplatePathBuildItem::getContent).findFirst()
                     .orElse(null);
-            if (sourcePath != null) {
-                try {
-                    byte[] content = Files.readAllBytes(sourcePath);
-                    source = new String(content, StandardCharsets.UTF_8);
-                } catch (IOException e) {
-                    LOG.warn("Unable to read the template from path: " + sourcePath, e);
-                }
-            }
             source = source.replace("\\n", "\\\\n");
             variantsMap.put(variant, source);
         }