This is an automated email from the ASF dual-hosted git repository.

matrei pushed a commit to branch 15186-zip-date
in repository https://gitbox.apache.org/repos/asf/grails-core.git

commit fa79873fda5bf24a87c56f63f8c952049c212b15
Author: Mattias Reichel <[email protected]>
AuthorDate: Mon Nov 3 10:47:48 2025 +0100

    fix(forge): explicit zip directories and entry metadata
    
    Fixes gh-15186
---
 .../java/org/grails/forge/io/ZipOutputHandler.java | 45 ++++++++++++++++++++--
 1 file changed, 41 insertions(+), 4 deletions(-)

diff --git 
a/grails-forge/grails-forge-core/src/main/java/org/grails/forge/io/ZipOutputHandler.java
 
b/grails-forge/grails-forge-core/src/main/java/org/grails/forge/io/ZipOutputHandler.java
index 17775db48c..0173cf6ea1 100644
--- 
a/grails-forge/grails-forge-core/src/main/java/org/grails/forge/io/ZipOutputHandler.java
+++ 
b/grails-forge/grails-forge-core/src/main/java/org/grails/forge/io/ZipOutputHandler.java
@@ -31,12 +31,17 @@ import java.io.OutputStream;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.nio.file.attribute.FileTime;
+import java.time.Instant;
+import java.util.HashSet;
+import java.util.Set;
 
 public class ZipOutputHandler implements OutputHandler {
 
     private final ZipArchiveOutputStream zipOutputStream;
     private final File zip;
     private final String directory;
+    private final Set<String> createdDirs = new HashSet<>();
 
     public ZipOutputHandler(Project project) throws IOException {
         File baseDirectory = new File(".").getCanonicalFile();
@@ -78,10 +83,19 @@ public class ZipOutputHandler implements OutputHandler {
 
     @Override
     public void write(String path, Template contents) throws IOException {
-        ZipArchiveEntry zipEntry = new ZipArchiveEntry(directory != null ? 
StringUtils.prependUri(directory, path) : path);
-        if (contents.isExecutable()) {
-            zipEntry.setUnixMode(UnixStat.FILE_FLAG | 0755);
-        }
+        String entryName = (directory != null ? 
StringUtils.prependUri(directory, path) : path);
+        FileTime lastModified = FileTime.from(Instant.now());
+
+        // ensure parent directories exist as explicit dir entries
+        // https://github.com/apache/grails-core/issues/15186
+        createParentDirs(entryName, lastModified);
+
+        ZipArchiveEntry zipEntry = new ZipArchiveEntry(entryName);
+        setZipEntryMetadata(
+                zipEntry,
+                lastModified,
+                UnixStat.FILE_FLAG | (contents.isExecutable() ? 0755 : 0644)
+        );
         zipOutputStream.putArchiveEntry(zipEntry);
         contents.write(zipOutputStream);
         zipOutputStream.closeArchiveEntry();
@@ -92,4 +106,27 @@ public class ZipOutputHandler implements OutputHandler {
         zipOutputStream.finish();
         zipOutputStream.close();
     }
+
+    private void createParentDirs(String entryName, FileTime lastModified) 
throws IOException {
+        int slash = entryName.lastIndexOf('/');
+        if (slash < 0) return;
+
+        int i = 0;
+        while ((i = entryName.indexOf('/', i)) >= 0) {
+            String dir = entryName.substring(0, i + 1);
+            if (createdDirs.add(dir)) {
+                ZipArchiveEntry directoryEntry = new ZipArchiveEntry(dir);
+                setZipEntryMetadata(directoryEntry, lastModified, 
UnixStat.DIR_FLAG | 0755);
+                zipOutputStream.putArchiveEntry(directoryEntry);
+                zipOutputStream.closeArchiveEntry();
+            }
+            i++;
+        }
+    }
+
+    private void setZipEntryMetadata(ZipArchiveEntry zipEntry, FileTime 
lastModified, int unixMode) {
+        zipEntry.setLastModifiedTime(lastModified);
+        zipEntry.setTime(lastModified.toMillis());
+        zipEntry.setUnixMode(unixMode);
+    }
 }

Reply via email to