name: Generate Template Hashes on: push: branches: - main - hashs paths: - 'src/main/resources/templates/**' - 'src/main/resources/static/**' workflow_dispatch: # Allow manual triggering jobs: generate-hash: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v3 - name: Set up JDK uses: actions/setup-java@v3 with: distribution: 'temurin' java-version: '17' - name: Calculate template hashes id: hash run: | # Create a Java program to calculate hashes cat > HashGenerator.java << 'EOF' import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.zip.CRC32; public class HashGenerator { private static final String TEMPLATE_DIR = "src/main/resources/templates"; private static final String STATIC_DIR = "src/main/resources/static"; private static final String OUTPUT_FILE = "src/main/resources/reference-hash.json"; // Text file extensions that need normalization private static final Set TEXT_EXTENSIONS = new HashSet<>(Arrays.asList( "html", "htm", "css", "js", "txt", "md", "xml", "json", "csv", "properties" )); public static void main(String[] args) throws IOException { List entries = new ArrayList<>(); // Process templates directory processDirectory(new File(TEMPLATE_DIR), entries, "templates"); // Process static directory processDirectory(new File(STATIC_DIR), entries, "static"); // Sort entries for consistent output Collections.sort(entries); // Write JSON output writeJsonOutput(entries); System.out.println("Generated hashes for " + entries.size() + " files"); } private static void processDirectory(File dir, List entries, String basePath) throws IOException { if (!dir.exists() || !dir.isDirectory()) { System.out.println("Directory not found: " + dir); return; } processFilesRecursively(dir, dir, entries, basePath); } private static void processFilesRecursively(File baseDir, File currentDir, List entries, String basePath) throws IOException { File[] files = currentDir.listFiles(); if (files == null) return; for (File file : files) { if (file.isDirectory()) { processFilesRecursively(baseDir, file, entries, basePath); } else { // Get relative path String relativePath = baseDir.toPath().relativize(file.toPath()).toString() .replace('\\', '/'); String fullPath = basePath + "/" + relativePath; // Calculate hash String hash = calculateFileHash(file.toPath()); entries.add(new FileEntry(fullPath, hash)); System.out.println("Processed: " + fullPath + " => " + hash); } } } private static String calculateFileHash(Path filePath) throws IOException { String extension = getFileExtension(filePath.toString()).toLowerCase(); boolean isTextFile = TEXT_EXTENSIONS.contains(extension); if (isTextFile) { return calculateNormalizedTextFileHash(filePath); } else { return calculateBinaryFileHash(filePath); } } private static String calculateNormalizedTextFileHash(Path filePath) throws IOException { byte[] content = Files.readAllBytes(filePath); String text = new String(content, StandardCharsets.UTF_8); // Normalize line endings to LF (remove CRs) text = text.replace("\r", ""); byte[] normalizedBytes = text.getBytes(StandardCharsets.UTF_8); CRC32 checksum = new CRC32(); checksum.update(normalizedBytes, 0, normalizedBytes.length); return String.valueOf(checksum.getValue()); } private static String calculateBinaryFileHash(Path filePath) throws IOException { byte[] content = Files.readAllBytes(filePath); CRC32 checksum = new CRC32(); checksum.update(content, 0, content.length); return String.valueOf(checksum.getValue()); } private static String getFileExtension(String filename) { int lastDot = filename.lastIndexOf('.'); if (lastDot == -1 || lastDot == filename.length() - 1) { return ""; } return filename.substring(lastDot + 1); } private static void writeJsonOutput(List entries) throws IOException { File outputFile = new File(OUTPUT_FILE); outputFile.getParentFile().mkdirs(); try (FileWriter writer = new FileWriter(outputFile)) { writer.write("{\n"); for (int i = 0; i < entries.size(); i++) { FileEntry entry = entries.get(i); writer.write(" \"" + entry.path + "\": \"" + entry.hash + "\""); if (i < entries.size() - 1) { writer.write(","); } writer.write("\n"); } writer.write("}\n"); } } // Class to represent a file and its hash private static class FileEntry implements Comparable { final String path; final String hash; FileEntry(String path, String hash) { this.path = path; this.hash = hash; } @Override public int compareTo(FileEntry other) { return path.compareTo(other.path); } } } EOF # Compile and run the Java program javac HashGenerator.java java HashGenerator - name: Commit and push if changed run: | git config --local user.email "github-actions[bot]@users.noreply.github.com" git config --local user.name "GitHub Actions" git add src/main/resources/reference-hash.json # Only commit if there are changes if git diff --staged --quiet; then echo "No changes to commit" else git commit -m "Update template reference hashes [skip ci]" git push fi