Add "Task" build and development process with Taskfile integration
Some checks failed
CI / build (push) Failing after 30s
renovate / renovate (push) Successful in 1m25s

- Added Taskfile.yml to streamline build, development, and testing tasks.
- Updated README to reflect new build instructions and development environment setup using `go-task`.
- Included `.taskfile.env` and `.task` in .dockerignore and .gitignore for better environment management.
- Modified asset loading in verifier.ts to include integrity and cross-origin attributes for security.
- Updated SRI generation script to handle both directory and single file inputs for improved flexibility.
This commit is contained in:
2025-12-27 22:35:12 -06:00
parent 187c2e53d9
commit e2c80671fa
9 changed files with 360 additions and 231 deletions

View File

@@ -12,23 +12,34 @@ import (
)
func main() {
buildDir := "frontend/build"
// Default behavior: process HTML in frontend/build
target := "frontend/build"
if len(os.Args) > 1 {
buildDir = os.Args[1]
target = os.Args[1]
}
fmt.Printf("Generating SRI hashes for assets in %s...\n", buildDir)
info, err := os.Stat(target)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
err := filepath.Walk(buildDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() && strings.HasSuffix(path, ".html") {
return processHTML(path, buildDir)
}
return nil
})
if info.IsDir() {
fmt.Printf("Generating SRI hashes for assets in %s...\n", target)
err = filepath.Walk(target, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() && strings.HasSuffix(path, ".html") {
return processHTML(path, target)
}
return nil
})
} else {
// If a single file is provided (like verifier.ts), process it specially
fmt.Printf("Updating SRI hashes in %s...\n", target)
err = processSourceFile(target)
}
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
@@ -37,16 +48,12 @@ func main() {
}
func processHTML(htmlPath, buildDir string) error {
content, err := os.ReadFile(filepath.Clean(htmlPath)) // #nosec G304
content, err := os.ReadFile(filepath.Clean(htmlPath))
if err != nil {
return err
}
updated := string(content)
// Regex to find script and link tags that don't have integrity yet
// Matches: <script src="/_app/..."
// Matches: <link rel="stylesheet" href="/_app/..."
scriptRegex := regexp.MustCompile(`<(script|link)\s+([^>]*)(src|href)=["'](/[^"']+)["']([^>]*)>`)
matches := scriptRegex.FindAllStringSubmatch(updated, -1)
@@ -55,12 +62,10 @@ func processHTML(htmlPath, buildDir string) error {
attr := match[3]
url := match[4]
// Only process local assets starting with /
if !strings.HasPrefix(url, "/") || strings.HasPrefix(url, "//") {
continue
}
// Skip if integrity already exists
if strings.Contains(tag, "integrity=") {
continue
}
@@ -68,7 +73,6 @@ func processHTML(htmlPath, buildDir string) error {
filePath := filepath.Join(buildDir, url)
hash, err := calculateSHA384(filePath)
if err != nil {
// Asset might not exist (e.g. dynamic URL or external-ish local path)
fmt.Printf(" Skipping %s: %v\n", url, err)
continue
}
@@ -80,14 +84,58 @@ func processHTML(htmlPath, buildDir string) error {
}
if updated != string(content) {
return os.WriteFile(filepath.Clean(htmlPath), []byte(updated), 0644) // #nosec G306
return os.WriteFile(filepath.Clean(htmlPath), []byte(updated), 0644)
}
return nil
}
func processSourceFile(sourcePath string) error {
content, err := os.ReadFile(filepath.Clean(sourcePath))
if err != nil {
return err
}
updated := string(content)
// We need a way to map the asset to the SRI. For verifier.ts, we know the assets.
assets := map[string]string{
"wasm_exec.js": "frontend/static/verifier/wasm_exec.js",
"verifier.wasm": "frontend/static/verifier/verifier.wasm",
}
for assetName, assetPath := range assets {
hash, err := calculateSHA384(assetPath)
if err != nil {
fmt.Printf(" Warning: could not calculate hash for %s: %v\n", assetName, err)
continue
}
// Find the line that mentions the asset and update the NEXT integrity string
assetEscaped := strings.ReplaceAll(assetName, ".", "\\.")
assetPattern := regexp.MustCompile(`['"](/verifier/)?` + assetEscaped + `['"][\s\S]*?sha384-([^'"]+)`)
matches := assetPattern.FindAllStringSubmatchIndex(updated, -1)
// Process from end to start to not mess up indices
for i := len(matches) - 1; i >= 0; i-- {
match := matches[i]
oldHashStart, oldHashEnd := match[4], match[5]
oldHash := updated[oldHashStart:oldHashEnd]
if oldHash != hash {
updated = updated[:oldHashStart] + hash + updated[oldHashEnd:]
fmt.Printf(" Updated SRI for %s in %s\n", assetName, sourcePath)
}
}
}
if updated != string(content) {
return os.WriteFile(filepath.Clean(sourcePath), []byte(updated), 0644)
}
return nil
}
func calculateSHA384(path string) (string, error) {
f, err := os.Open(filepath.Clean(path)) // #nosec G304
f, err := os.Open(filepath.Clean(path))
if err != nil {
return "", err
}