From 79fe9559ac6de5f4f1e3629345695d7b1368278f Mon Sep 17 00:00:00 2001 From: Steve Troetti Date: Thu, 19 Feb 2026 01:51:09 -0500 Subject: [PATCH 01/14] add go-webp and go-avif dependencies --- go.mod | 2 ++ go.sum | 3 +++ 2 files changed, 5 insertions(+) diff --git a/go.mod b/go.mod index 5233bf1..97b31e6 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,9 @@ module github.com/stevexciv/scad-server go 1.23.0 require ( + github.com/Kagami/go-avif v0.1.0 github.com/gin-gonic/gin v1.11.0 + github.com/kolesa-team/go-webp v1.0.5 github.com/swaggo/files v1.0.1 github.com/swaggo/gin-swagger v1.6.1 github.com/swaggo/swag v1.16.6 diff --git a/go.sum b/go.sum index 8ed4ca5..fb64514 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,4 @@ +github.com/Kagami/go-avif v0.1.0/go.mod h1:OPmPqzNdQq3+sXm0HqaUJQ9W/4k+Elbc3RSfJUemDKA= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= @@ -53,6 +54,7 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/kolesa-team/go-webp v1.0.5/go.mod h1:QmJu0YHXT3ex+4SgUvs+a+1SFCDcCqyZg+LbIuNNTnE= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= @@ -86,6 +88,7 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= From e3517df3de82e0b356a1e54af650af7753336806 Mon Sep 17 00:00:00 2001 From: Steve Troetti Date: Thu, 19 Feb 2026 01:52:39 -0500 Subject: [PATCH 02/14] add PNG to WebP/AVIF conversion functions --- go.sum | 2 ++ services/convert.go | 53 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 services/convert.go diff --git a/go.sum b/go.sum index fb64514..d65334c 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,4 @@ +github.com/Kagami/go-avif v0.1.0 h1:8GHAGLxCdFfhpd4Zg8j1EqO7rtcQNenxIDerC/uu68w= github.com/Kagami/go-avif v0.1.0/go.mod h1:OPmPqzNdQq3+sXm0HqaUJQ9W/4k+Elbc3RSfJUemDKA= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= @@ -54,6 +55,7 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/kolesa-team/go-webp v1.0.5 h1:GZQHJBaE8dsNKZltfwqsL0qVJ7vqHXsfA+4AHrQW3pE= github.com/kolesa-team/go-webp v1.0.5/go.mod h1:QmJu0YHXT3ex+4SgUvs+a+1SFCDcCqyZg+LbIuNNTnE= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= diff --git a/services/convert.go b/services/convert.go new file mode 100644 index 0000000..b54b3b5 --- /dev/null +++ b/services/convert.go @@ -0,0 +1,53 @@ +package services + +import ( + "bytes" + "fmt" + "image/png" + "log" + + avif "github.com/Kagami/go-avif" + "github.com/kolesa-team/go-webp/encoder" + "github.com/kolesa-team/go-webp/webp" +) + +const ( + defaultWebPQuality float32 = 80 +) + +// convertPNGToWebP takes raw PNG bytes and returns WebP-encoded bytes. +func convertPNGToWebP(pngData []byte) ([]byte, error) { + img, err := png.Decode(bytes.NewReader(pngData)) + if err != nil { + return nil, fmt.Errorf("failed to decode PNG: %w", err) + } + + options, err := encoder.NewLossyEncoderOptions(encoder.PresetDefault, defaultWebPQuality) + if err != nil { + return nil, fmt.Errorf("failed to create WebP encoder options: %w", err) + } + + var buf bytes.Buffer + if err := webp.Encode(&buf, img, options); err != nil { + return nil, fmt.Errorf("failed to encode WebP: %w", err) + } + + log.Printf("[Convert] PNG (%d bytes) -> WebP (%d bytes)", len(pngData), buf.Len()) + return buf.Bytes(), nil +} + +// convertPNGToAVIF takes raw PNG bytes and returns AVIF-encoded bytes. +func convertPNGToAVIF(pngData []byte) ([]byte, error) { + img, err := png.Decode(bytes.NewReader(pngData)) + if err != nil { + return nil, fmt.Errorf("failed to decode PNG: %w", err) + } + + var buf bytes.Buffer + if err := avif.Encode(&buf, img, nil); err != nil { + return nil, fmt.Errorf("failed to encode AVIF: %w", err) + } + + log.Printf("[Convert] PNG (%d bytes) -> AVIF (%d bytes)", len(pngData), buf.Len()) + return buf.Bytes(), nil +} From 8e71d5c99df0a40b5c3168d1541b43e9b2569375 Mon Sep 17 00:00:00 2001 From: Steve Troetti Date: Thu, 19 Feb 2026 01:53:04 -0500 Subject: [PATCH 03/14] document PNG options reuse for webp/avif --- models/models.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/models/models.go b/models/models.go index f41111f..1617e4a 100644 --- a/models/models.go +++ b/models/models.go @@ -16,7 +16,8 @@ type ExportOptions struct { ThreeMF *ThreeMFOptions `json:"3mf,omitempty"` } -// PNGOptions contains PNG export options +// PNGOptions contains PNG export options. +// Also used for webp and avif formats (which render via PNG internally). type PNGOptions struct { Width *int `json:"width,omitempty" example:"800"` Height *int `json:"height,omitempty" example:"600"` From be73767a11dffeca8d9ced66825ed12cc59dd94f Mon Sep 17 00:00:00 2001 From: Steve Troetti Date: Thu, 19 Feb 2026 01:54:12 -0500 Subject: [PATCH 04/14] add webp and avif format support to service --- services/openscad.go | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/services/openscad.go b/services/openscad.go index ac6044b..2d787e3 100644 --- a/services/openscad.go +++ b/services/openscad.go @@ -106,6 +106,22 @@ func (s *OpenSCADService) Export(req *models.ExportRequest) ([]byte, string, err } log.Printf("[OpenSCAD Export] Output file read successfully, size: %d bytes", len(data)) + // Post-process: convert PNG to target format if needed + switch req.Format { + case "webp": + data, err = convertPNGToWebP(data) + if err != nil { + log.Printf("[OpenSCAD Export] Failed to convert to WebP: %v", err) + return nil, "", fmt.Errorf("failed to convert to WebP: %w", err) + } + case "avif": + data, err = convertPNGToAVIF(data) + if err != nil { + log.Printf("[OpenSCAD Export] Failed to convert to AVIF: %v", err) + return nil, "", fmt.Errorf("failed to convert to AVIF: %w", err) + } + } + // Get content type contentType := s.getContentType(req.Format) @@ -176,6 +192,8 @@ func (s *OpenSCADService) validateFormat(format string) error { "svg": true, "pdf": true, "3mf": true, + "webp": true, + "avif": true, } if !validFormats[format] { @@ -199,6 +217,9 @@ func (s *OpenSCADService) getOutputExtension(format string) (string, string) { return "pdf", "" case "3mf": return "3mf", "" + case "webp", "avif": + // Render as PNG first, then convert to target format + return "png", "" default: return "", "" } @@ -208,7 +229,7 @@ func (s *OpenSCADService) buildExportOptions(req *models.ExportRequest) []string var args []string switch req.Format { - case "png": + case "png", "webp", "avif": if req.Options.PNG != nil { if req.Options.PNG.Width != nil || req.Options.PNG.Height != nil { width := 800 @@ -334,6 +355,10 @@ func (s *OpenSCADService) getContentType(format string) string { return "application/pdf" case "3mf": return "application/vnd.ms-package.3dmodel+xml" + case "webp": + return "image/webp" + case "avif": + return "image/avif" default: return "application/octet-stream" } From a1374a0779c74f09709372c7a8725d6a3332e613 Mon Sep 17 00:00:00 2001 From: Steve Troetti Date: Thu, 19 Feb 2026 01:56:32 -0500 Subject: [PATCH 05/14] update Dockerfile for CGO image conversion --- Dockerfile | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/Dockerfile b/Dockerfile index e3eedde..ea12bf1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,10 +1,12 @@ # Build stage -FROM golang:1.23-alpine AS builder +FROM golang:1.23-bookworm AS builder WORKDIR /app -# Install git for version info -RUN apk add --no-cache git +# Install git for version info, and C libraries for image conversion (CGO) +RUN apt-get update && apt-get install -y --no-install-recommends \ + git gcc libc-dev libwebp-dev libaom-dev && \ + rm -rf /var/lib/apt/lists/* # Copy go mod files COPY go.mod go.sum ./ @@ -25,15 +27,16 @@ RUN swag init RUN set -e && \ COMMIT=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown") && \ TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "unknown") && \ - CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo \ + CGO_ENABLED=1 GOOS=linux go build \ -ldflags "-X github.com/stevexciv/scad-server/version.commit=$COMMIT -X github.com/stevexciv/scad-server/version.tag=$TAG" \ -o scad-server . # Final stage FROM openscad/openscad:trixie -# Install ca-certificates and wget for health checks -RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates wget && \ +# Install ca-certificates, wget for health checks, and runtime libraries for image conversion +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates wget libwebp7 libaom3 && \ rm -rf /var/lib/apt/lists/* WORKDIR /app From 9dea329e80204a91699739e9176a88847570116b Mon Sep 17 00:00:00 2001 From: Steve Troetti Date: Thu, 19 Feb 2026 01:57:13 -0500 Subject: [PATCH 06/14] add image library installs to CI pipeline --- .github/workflows/ci.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3661039..297fd76 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,6 +20,9 @@ jobs: with: go-version: "1.23" + - name: Install image conversion libraries + run: sudo apt-get update && sudo apt-get install -y libwebp-dev libaom-dev + - name: Install swag run: go install github.com/swaggo/swag/cmd/swag@latest @@ -45,6 +48,9 @@ jobs: with: go-version: "1.23" + - name: Install image conversion libraries + run: sudo apt-get update && sudo apt-get install -y libwebp-dev libaom-dev + - name: Install swag run: go install github.com/swaggo/swag/cmd/swag@latest @@ -73,6 +79,9 @@ jobs: with: go-version: "1.23" + - name: Install image conversion libraries + run: sudo apt-get update && sudo apt-get install -y libwebp-dev libaom-dev + - name: Cache Go modules uses: actions/cache@v3 with: From 0c94d91f74a181d486d728cca6fb2f33f262de13 Mon Sep 17 00:00:00 2001 From: Steve Troetti Date: Thu, 19 Feb 2026 01:57:36 -0500 Subject: [PATCH 07/14] add webp and avif to justfile clean recipe --- justfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/justfile b/justfile index 69373d9..911e147 100644 --- a/justfile +++ b/justfile @@ -44,7 +44,7 @@ clean: rm -rf bin/ rm -rf tmp/ rm -f coverage.out coverage.html - rm -f *.stl *.png *.svg *.pdf *.scad + rm -f *.stl *.png *.svg *.pdf *.scad *.webp *.avif # Build Docker image docker-build: From 61d0b16a6892467ed10adf740eacc9e5b40bb604 Mon Sep 17 00:00:00 2001 From: Steve Troetti Date: Thu, 19 Feb 2026 02:00:17 -0500 Subject: [PATCH 08/14] add webp and avif test coverage --- handlers/handlers_test.go | 6 ++- services/convert_test.go | 93 +++++++++++++++++++++++++++++++++++++++ services/openscad_test.go | 54 +++++++++++++++++++++++ 3 files changed, 152 insertions(+), 1 deletion(-) create mode 100644 services/convert_test.go diff --git a/handlers/handlers_test.go b/handlers/handlers_test.go index 2e090a2..e97be85 100644 --- a/handlers/handlers_test.go +++ b/handlers/handlers_test.go @@ -351,13 +351,17 @@ func TestExportEndpoint_ValidFormats(t *testing.T) { contentType = "application/pdf" case "stl_binary", "stl_ascii": contentType = "application/octet-stream" + case "webp": + contentType = "image/webp" + case "avif": + contentType = "image/avif" } return []byte("mock export data"), contentType, nil }, } router := setupRouterWithMock(mock) - formats := []string{"png", "stl_binary", "stl_ascii", "svg", "pdf"} + formats := []string{"png", "stl_binary", "stl_ascii", "svg", "pdf", "webp", "avif"} for _, format := range formats { t.Run(format, func(t *testing.T) { diff --git a/services/convert_test.go b/services/convert_test.go new file mode 100644 index 0000000..0481cdd --- /dev/null +++ b/services/convert_test.go @@ -0,0 +1,93 @@ +package services + +import ( + "bytes" + "image" + "image/color" + "image/png" + "testing" +) + +// createTestPNG creates a minimal valid PNG image for testing. +func createTestPNG(width, height int) ([]byte, error) { + img := image.NewRGBA(image.Rect(0, 0, width, height)) + for y := 0; y < height; y++ { + for x := 0; x < width; x++ { + img.Set(x, y, color.RGBA{R: 255, G: 0, B: 0, A: 255}) + } + } + var buf bytes.Buffer + if err := png.Encode(&buf, img); err != nil { + return nil, err + } + return buf.Bytes(), nil +} + +func TestConvertPNGToWebP(t *testing.T) { + t.Run("Valid PNG", func(t *testing.T) { + pngData, err := createTestPNG(4, 4) + if err != nil { + t.Fatalf("Failed to create test PNG: %v", err) + } + + webpData, err := convertPNGToWebP(pngData) + if err != nil { + t.Fatalf("convertPNGToWebP() error = %v", err) + } + + if len(webpData) == 0 { + t.Error("Expected non-empty WebP output") + } + + // WebP files start with "RIFF" magic bytes + if len(webpData) < 12 { + t.Fatalf("WebP output too short: %d bytes", len(webpData)) + } + if string(webpData[0:4]) != "RIFF" { + t.Errorf("Expected RIFF magic bytes, got %q", webpData[0:4]) + } + if string(webpData[8:12]) != "WEBP" { + t.Errorf("Expected WEBP signature, got %q", webpData[8:12]) + } + }) + + t.Run("Invalid input", func(t *testing.T) { + _, err := convertPNGToWebP([]byte("not a png")) + if err == nil { + t.Error("Expected error for invalid PNG input") + } + }) +} + +func TestConvertPNGToAVIF(t *testing.T) { + t.Run("Valid PNG", func(t *testing.T) { + pngData, err := createTestPNG(4, 4) + if err != nil { + t.Fatalf("Failed to create test PNG: %v", err) + } + + avifData, err := convertPNGToAVIF(pngData) + if err != nil { + t.Fatalf("convertPNGToAVIF() error = %v", err) + } + + if len(avifData) == 0 { + t.Error("Expected non-empty AVIF output") + } + + // AVIF files contain an "ftyp" box near the start + if len(avifData) < 12 { + t.Fatalf("AVIF output too short: %d bytes", len(avifData)) + } + if string(avifData[4:8]) != "ftyp" { + t.Errorf("Expected ftyp box, got %q", avifData[4:8]) + } + }) + + t.Run("Invalid input", func(t *testing.T) { + _, err := convertPNGToAVIF([]byte("not a png")) + if err == nil { + t.Error("Expected error for invalid PNG input") + } + }) +} diff --git a/services/openscad_test.go b/services/openscad_test.go index a59c19e..0a8be1a 100644 --- a/services/openscad_test.go +++ b/services/openscad_test.go @@ -20,6 +20,8 @@ func TestValidateFormat(t *testing.T) { {"Valid SVG", "svg", false}, {"Valid PDF", "pdf", false}, {"Valid 3MF", "3mf", false}, + {"Valid WebP", "webp", false}, + {"Valid AVIF", "avif", false}, {"Invalid format", "invalid", true}, {"Empty format", "", true}, } @@ -49,6 +51,8 @@ func TestGetOutputExtension(t *testing.T) { {"SVG", "svg", "svg", ""}, {"PDF", "pdf", "pdf", ""}, {"3MF", "3mf", "3mf", ""}, + {"WebP", "webp", "png", ""}, + {"AVIF", "avif", "png", ""}, } for _, tt := range tests { @@ -78,6 +82,8 @@ func TestGetContentType(t *testing.T) { {"SVG", "svg", "image/svg+xml"}, {"PDF", "pdf", "application/pdf"}, {"3MF", "3mf", "application/vnd.ms-package.3dmodel+xml"}, + {"WebP", "webp", "image/webp"}, + {"AVIF", "avif", "image/avif"}, {"Unknown", "unknown", "application/octet-stream"}, } @@ -156,6 +162,54 @@ func TestBuildExportOptions(t *testing.T) { } }) + t.Run("WebP options (reuses PNG)", func(t *testing.T) { + width := 800 + height := 600 + req := &models.ExportRequest{ + Format: "webp", + Options: models.ExportOptions{ + PNG: &models.PNGOptions{ + Width: &width, + Height: &height, + }, + }, + } + args := service.buildExportOptions(req) + if len(args) != 2 { + t.Errorf("Expected 2 args, got %d", len(args)) + } + if args[0] != "--imgsize" { + t.Errorf("Expected --imgsize, got %s", args[0]) + } + if args[1] != "800,600" { + t.Errorf("Expected 800,600, got %s", args[1]) + } + }) + + t.Run("AVIF options (reuses PNG)", func(t *testing.T) { + width := 640 + height := 480 + req := &models.ExportRequest{ + Format: "avif", + Options: models.ExportOptions{ + PNG: &models.PNGOptions{ + Width: &width, + Height: &height, + }, + }, + } + args := service.buildExportOptions(req) + if len(args) != 2 { + t.Errorf("Expected 2 args, got %d", len(args)) + } + if args[0] != "--imgsize" { + t.Errorf("Expected --imgsize, got %s", args[0]) + } + if args[1] != "640,480" { + t.Errorf("Expected 640,480, got %s", args[1]) + } + }) + t.Run("No options", func(t *testing.T) { req := &models.ExportRequest{ Format: "png", From bf1c60d4da877a2b64a933e36463e5f14e985000 Mon Sep 17 00:00:00 2001 From: Steve Troetti Date: Thu, 19 Feb 2026 02:02:05 -0500 Subject: [PATCH 09/14] update documentation for webp and avif support --- API.md | 42 ++++++++++++++++++++++++++++++++++- README.md | 46 ++++++++++++++++++++++++++++++++++++--- examples/README.md | 16 ++++++++++++++ examples/export-avif.json | 10 +++++++++ examples/export-webp.json | 10 +++++++++ handlers/handlers.go | 2 +- 6 files changed, 121 insertions(+), 5 deletions(-) create mode 100644 examples/export-avif.json create mode 100644 examples/export-webp.json diff --git a/API.md b/API.md index 301570f..8c4e685 100644 --- a/API.md +++ b/API.md @@ -47,7 +47,7 @@ Export OpenSCAD content to various formats. | Field | Type | Required | Description | |-------|------|----------|-------------| | scad_content | string | Yes | The OpenSCAD code to export | -| format | string | Yes | Output format: `png`, `stl_binary`, `stl_ascii`, `svg`, `pdf`, `3mf` | +| format | string | Yes | Output format: `png`, `stl_binary`, `stl_ascii`, `svg`, `pdf`, `3mf`, `webp`, `avif` | | options | object | No | Format-specific options (see below) | #### Format-Specific Options @@ -59,6 +59,8 @@ Export OpenSCAD content to various formats. | width | integer | No | 800 | Image width in pixels | | height | integer | No | 600 | Image height in pixels | +> **Note:** WebP and AVIF formats reuse `options.png` for dimension customization. OpenSCAD renders to PNG first, then the server converts to the requested format. + ##### STL Options (`options.stl`) | Field | Type | Required | Default | Range | Description | @@ -297,6 +299,42 @@ curl -X POST http://localhost:8000/openscad/v1/export \ --output cube.3mf ``` +### Export a Cube to WebP + +```bash +curl -X POST http://localhost:8000/openscad/v1/export \ + -H "Content-Type: application/json" \ + -d '{ + "scad_content": "cube([10,10,10]);", + "format": "webp", + "options": { + "png": { + "width": 800, + "height": 600 + } + } + }' \ + --output cube.webp +``` + +### Export a Cube to AVIF + +```bash +curl -X POST http://localhost:8000/openscad/v1/export \ + -H "Content-Type: application/json" \ + -d '{ + "scad_content": "cube([10,10,10]);", + "format": "avif", + "options": { + "png": { + "width": 800, + "height": 600 + } + } + }' \ + --output cube.avif +``` + ### Generate Complete Summary ```bash @@ -366,6 +404,8 @@ Currently, no rate limiting is implemented. Consider adding rate limiting in pro | svg | `image/svg+xml` | | pdf | `application/pdf` | | 3mf | `application/vnd.ms-package.3dmodel+xml` | +| webp | `image/webp` | +| avif | `image/avif` | --- diff --git a/README.md b/README.md index 9e1f66b..3d85c60 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Hopefully others will find this useful as well. ## Features -- **Export to Multiple Formats**: PNG, STL (binary + ASCII), SVG, PDF, and 3MF +- **Export to Multiple Formats**: PNG, STL (binary + ASCII), SVG, PDF, 3MF, WebP, and AVIF - **Summary Generation**: Get diagnostics about SCAD models - **Format-Specific Options**: Supports a subset of format-specific parameters from the OpenSCAD CLI - **OpenAPI Documentation**: Interactive API docs @@ -33,7 +33,7 @@ http://localhost:8000/openscad/v1 POST /openscad/v1/export ``` -Exports OpenSCAD content to PNG, STL (binary/ASCII), SVG, PDF, or 3MF format. +Exports OpenSCAD content to PNG, STL (binary/ASCII), SVG, PDF, 3MF, WebP, or AVIF format. **Supported Formats:** @@ -43,6 +43,8 @@ Exports OpenSCAD content to PNG, STL (binary/ASCII), SVG, PDF, or 3MF format. - `svg` - Vector graphics - `pdf` - Document export - `3mf` - 3D Manufacturing Format (good option for more modern slicers) +- `webp` - WebP image (smaller file size than PNG) +- `avif` - AVIF image (modern format with excellent compression) #### 2. Generate Summary Information @@ -211,6 +213,42 @@ curl -X POST http://localhost:8000/openscad/v1/export \ --output square.pdf ``` +### Export to WebP + +```bash +curl -X POST http://localhost:8000/openscad/v1/export \ + -H "Content-Type: application/json" \ + -d '{ + "scad_content": "cube([10,10,10]);", + "format": "webp", + "options": { + "png": { + "width": 800, + "height": 600 + } + } + }' \ + --output cube.webp +``` + +### Export to AVIF + +```bash +curl -X POST http://localhost:8000/openscad/v1/export \ + -H "Content-Type: application/json" \ + -d '{ + "scad_content": "cube([10,10,10]);", + "format": "avif", + "options": { + "png": { + "width": 800, + "height": 600 + } + } + }' \ + --output cube.avif +``` + ### Generate Summary ```bash @@ -298,7 +336,9 @@ Common commands: │ └── handlers_test.go ├── services/ # Business logic │ ├── openscad.go -│ └── openscad_test.go +│ ├── openscad_test.go +│ ├── convert.go +│ └── convert_test.go ├── docs/ # Swagger documentation (generated) ├── Dockerfile # Docker configuration ├── justfile # Task runner configuration diff --git a/examples/README.md b/examples/README.md index 73335e6..a99816d 100644 --- a/examples/README.md +++ b/examples/README.md @@ -36,6 +36,22 @@ curl -X POST http://localhost:8000/openscad/v1/export \ --output square.pdf ``` +### Export to WebP +```bash +curl -X POST http://localhost:8000/openscad/v1/export \ + -H "Content-Type: application/json" \ + -d @examples/export-webp.json \ + --output cube.webp +``` + +### Export to AVIF +```bash +curl -X POST http://localhost:8000/openscad/v1/export \ + -H "Content-Type: application/json" \ + -d @examples/export-avif.json \ + --output cube.avif +``` + ### Generate Summary ```bash curl -X POST http://localhost:8000/openscad/v1/summary \ diff --git a/examples/export-avif.json b/examples/export-avif.json new file mode 100644 index 0000000..649e64e --- /dev/null +++ b/examples/export-avif.json @@ -0,0 +1,10 @@ +{ + "scad_content": "cube([10,10,10]);", + "format": "avif", + "options": { + "png": { + "width": 800, + "height": 600 + } + } +} diff --git a/examples/export-webp.json b/examples/export-webp.json new file mode 100644 index 0000000..791c0f6 --- /dev/null +++ b/examples/export-webp.json @@ -0,0 +1,10 @@ +{ + "scad_content": "cube([10,10,10]);", + "format": "webp", + "options": { + "png": { + "width": 800, + "height": 600 + } + } +} diff --git a/handlers/handlers.go b/handlers/handlers.go index 343a961..71c2948 100644 --- a/handlers/handlers.go +++ b/handlers/handlers.go @@ -31,7 +31,7 @@ func NewHandlerWithService(exporter services.OpenSCADExporter) *Handler { // Export handles the export endpoint // @Summary Export SCAD to various formats -// @Description Exports OpenSCAD content to PNG, STL (binary/ASCII), SVG, or PDF format +// @Description Exports OpenSCAD content to PNG, STL (binary/ASCII), SVG, PDF, 3MF, WebP, or AVIF format // @Tags export // @Accept json // @Produce octet-stream From 8e24a15b696b3e961c0346a1ae6c016bcc931ed0 Mon Sep 17 00:00:00 2001 From: Steve Troetti Date: Thu, 19 Feb 2026 19:45:02 -0500 Subject: [PATCH 10/14] replace cgo image libs with gen2brain (go1.26) --- .github/workflows/ci.yml | 15 +++------------ Dockerfile | 13 ++++++------- go.mod | 10 ++++++---- go.sum | 17 ++++++++++------- handlers/handlers_test.go | 5 +++-- services/convert.go | 18 ++++-------------- 6 files changed, 32 insertions(+), 46 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 297fd76..c81a052 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,10 +18,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v4 with: - go-version: "1.23" - - - name: Install image conversion libraries - run: sudo apt-get update && sudo apt-get install -y libwebp-dev libaom-dev + go-version: "1.26" - name: Install swag run: go install github.com/swaggo/swag/cmd/swag@latest @@ -46,10 +43,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v4 with: - go-version: "1.23" - - - name: Install image conversion libraries - run: sudo apt-get update && sudo apt-get install -y libwebp-dev libaom-dev + go-version: "1.26" - name: Install swag run: go install github.com/swaggo/swag/cmd/swag@latest @@ -77,10 +71,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v4 with: - go-version: "1.23" - - - name: Install image conversion libraries - run: sudo apt-get update && sudo apt-get install -y libwebp-dev libaom-dev + go-version: "1.26" - name: Cache Go modules uses: actions/cache@v3 diff --git a/Dockerfile b/Dockerfile index ea12bf1..351c5b2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,11 +1,10 @@ # Build stage -FROM golang:1.23-bookworm AS builder +FROM golang:1.26-trixie AS builder WORKDIR /app -# Install git for version info, and C libraries for image conversion (CGO) -RUN apt-get update && apt-get install -y --no-install-recommends \ - git gcc libc-dev libwebp-dev libaom-dev && \ +# Install git for version info +RUN apt-get update && apt-get install -y --no-install-recommends git && \ rm -rf /var/lib/apt/lists/* # Copy go mod files @@ -27,16 +26,16 @@ RUN swag init RUN set -e && \ COMMIT=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown") && \ TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "unknown") && \ - CGO_ENABLED=1 GOOS=linux go build \ + CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo \ -ldflags "-X github.com/stevexciv/scad-server/version.commit=$COMMIT -X github.com/stevexciv/scad-server/version.tag=$TAG" \ -o scad-server . # Final stage FROM openscad/openscad:trixie -# Install ca-certificates, wget for health checks, and runtime libraries for image conversion +# Install ca-certificates and wget for health checks RUN apt-get update && apt-get install -y --no-install-recommends \ - ca-certificates wget libwebp7 libaom3 && \ + ca-certificates wget && \ rm -rf /var/lib/apt/lists/* WORKDIR /app diff --git a/go.mod b/go.mod index 97b31e6..94c0943 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,11 @@ module github.com/stevexciv/scad-server -go 1.23.0 +go 1.26 require ( - github.com/Kagami/go-avif v0.1.0 + github.com/gen2brain/avif v0.4.4 + github.com/gen2brain/webp v0.5.5 github.com/gin-gonic/gin v1.11.0 - github.com/kolesa-team/go-webp v1.0.5 github.com/swaggo/files v1.0.1 github.com/swaggo/gin-swagger v1.6.1 github.com/swaggo/swag v1.16.6 @@ -18,6 +18,7 @@ require ( github.com/bytedance/sonic v1.14.0 // indirect github.com/bytedance/sonic/loader v0.3.0 // indirect github.com/cloudwego/base64x v0.1.6 // indirect + github.com/ebitengine/purego v0.9.1 // indirect github.com/gabriel-vasile/mimetype v1.4.8 // indirect github.com/gin-contrib/sse v1.1.0 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect @@ -40,6 +41,7 @@ require ( github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/quic-go/qpack v0.5.1 // indirect github.com/quic-go/quic-go v0.54.0 // indirect + github.com/tetratelabs/wazero v1.11.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.3.0 // indirect go.uber.org/mock v0.5.0 // indirect @@ -48,7 +50,7 @@ require ( golang.org/x/mod v0.25.0 // indirect golang.org/x/net v0.42.0 // indirect golang.org/x/sync v0.16.0 // indirect - golang.org/x/sys v0.35.0 // indirect + golang.org/x/sys v0.41.0 // indirect golang.org/x/text v0.27.0 // indirect golang.org/x/tools v0.34.0 // indirect google.golang.org/protobuf v1.36.9 // indirect diff --git a/go.sum b/go.sum index d65334c..35d6623 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,3 @@ -github.com/Kagami/go-avif v0.1.0 h1:8GHAGLxCdFfhpd4Zg8j1EqO7rtcQNenxIDerC/uu68w= -github.com/Kagami/go-avif v0.1.0/go.mod h1:OPmPqzNdQq3+sXm0HqaUJQ9W/4k+Elbc3RSfJUemDKA= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= @@ -16,8 +14,14 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A= +github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= +github.com/gen2brain/avif v0.4.4 h1:Ga/ss7qcWWQm2bxFpnjYjhJsNfZrWs5RsyklgFjKRSE= +github.com/gen2brain/avif v0.4.4/go.mod h1:/XCaJcjZraQwKVhpu9aEd9aLOssYOawLvhMBtmHVGqk= +github.com/gen2brain/webp v0.5.5 h1:MvQR75yIPU/9nSqYT5h13k4URaJK3gf9tgz/ksRbyEg= +github.com/gen2brain/webp v0.5.5/go.mod h1:xOSMzp4aROt2KFW++9qcK/RBTOVC2S9tJG66ip/9Oc0= github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4= github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk= github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= @@ -55,8 +59,6 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= -github.com/kolesa-team/go-webp v1.0.5 h1:GZQHJBaE8dsNKZltfwqsL0qVJ7vqHXsfA+4AHrQW3pE= -github.com/kolesa-team/go-webp v1.0.5/go.mod h1:QmJu0YHXT3ex+4SgUvs+a+1SFCDcCqyZg+LbIuNNTnE= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= @@ -90,7 +92,6 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= @@ -102,6 +103,8 @@ github.com/swaggo/gin-swagger v1.6.1 h1:Ri06G4gc9N4t4k8hekMigJ9zKTFSlqj/9paAQCQs github.com/swaggo/gin-swagger v1.6.1/go.mod h1:LQ+hJStHakCWRiK/YNYtJOu4mR2FP+pxLnILT/qNiTw= github.com/swaggo/swag v1.16.6 h1:qBNcx53ZaX+M5dxVyTrgQ0PJ/ACK+NzhwcbieTt+9yI= github.com/swaggo/swag v1.16.6/go.mod h1:ngP2etMK5a0P3QBizic5MEwpRmluJZPHjXcMoj4Xesg= +github.com/tetratelabs/wazero v1.11.0 h1:+gKemEuKCTevU4d7ZTzlsvgd1uaToIDtlQlmNbwqYhA= +github.com/tetratelabs/wazero v1.11.0/go.mod h1:eV28rsN8Q+xwjogd7f4/Pp4xFxO7uOGbLcD/LzB1wiU= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA= @@ -137,8 +140,8 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= -golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= +golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= diff --git a/handlers/handlers_test.go b/handlers/handlers_test.go index e97be85..fc84b24 100644 --- a/handlers/handlers_test.go +++ b/handlers/handlers_test.go @@ -3,6 +3,7 @@ package handlers import ( "bytes" "encoding/json" + "errors" "fmt" "net/http" "net/http/httptest" @@ -300,7 +301,7 @@ func TestSummaryEndpoint_SummaryTypeErrors(t *testing.T) { t.Run(tt.name, func(t *testing.T) { mock := &MockOpenSCADExporter{ SummaryFunc: func(req *models.SummaryRequest) (*models.SummaryResponse, error) { - return nil, fmt.Errorf(tt.errMsg) + return nil, errors.New(tt.errMsg) }, } router := setupRouterWithMock(mock) @@ -447,7 +448,7 @@ func TestExportEndpoint_FormatSpecificErrors(t *testing.T) { t.Run(tt.name, func(t *testing.T) { mock := &MockOpenSCADExporter{ ExportFunc: func(req *models.ExportRequest) ([]byte, string, error) { - return nil, "", fmt.Errorf(tt.errMsg) + return nil, "", errors.New(tt.errMsg) }, } router := setupRouterWithMock(mock) diff --git a/services/convert.go b/services/convert.go index b54b3b5..51a96a3 100644 --- a/services/convert.go +++ b/services/convert.go @@ -6,13 +6,8 @@ import ( "image/png" "log" - avif "github.com/Kagami/go-avif" - "github.com/kolesa-team/go-webp/encoder" - "github.com/kolesa-team/go-webp/webp" -) - -const ( - defaultWebPQuality float32 = 80 + "github.com/gen2brain/avif" + "github.com/gen2brain/webp" ) // convertPNGToWebP takes raw PNG bytes and returns WebP-encoded bytes. @@ -22,13 +17,8 @@ func convertPNGToWebP(pngData []byte) ([]byte, error) { return nil, fmt.Errorf("failed to decode PNG: %w", err) } - options, err := encoder.NewLossyEncoderOptions(encoder.PresetDefault, defaultWebPQuality) - if err != nil { - return nil, fmt.Errorf("failed to create WebP encoder options: %w", err) - } - var buf bytes.Buffer - if err := webp.Encode(&buf, img, options); err != nil { + if err := webp.Encode(&buf, img, webp.Options{Quality: 80}); err != nil { return nil, fmt.Errorf("failed to encode WebP: %w", err) } @@ -44,7 +34,7 @@ func convertPNGToAVIF(pngData []byte) ([]byte, error) { } var buf bytes.Buffer - if err := avif.Encode(&buf, img, nil); err != nil { + if err := avif.Encode(&buf, img); err != nil { return nil, fmt.Errorf("failed to encode AVIF: %w", err) } From a8b7e0f1a8484387ab17a9b6f5792dc005b51dfd Mon Sep 17 00:00:00 2001 From: Steve Troetti Date: Thu, 19 Feb 2026 20:00:20 -0500 Subject: [PATCH 11/14] update golangci-lint action to v4 for go1.26 support --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c81a052..c00e6e7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,7 +27,7 @@ jobs: run: swag init - name: Run golangci-lint - uses: golangci/golangci-lint-action@v3 + uses: golangci/golangci-lint-action@v4 with: version: latest args: --timeout=5m From 9498acad93575c8e6cc6f784eb2f383635c86ef5 Mon Sep 17 00:00:00 2001 From: Steve Troetti Date: Thu, 19 Feb 2026 20:05:50 -0500 Subject: [PATCH 12/14] upgrade golangci-lint action to v6, pin to v2.10 --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c00e6e7..955b1b4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,9 +27,9 @@ jobs: run: swag init - name: Run golangci-lint - uses: golangci/golangci-lint-action@v4 + uses: golangci/golangci-lint-action@v6 with: - version: latest + version: v2.10 args: --timeout=5m build: From 1e529e6138c728406291c64d73971fb5b60167ee Mon Sep 17 00:00:00 2001 From: Steve Troetti Date: Thu, 19 Feb 2026 20:07:14 -0500 Subject: [PATCH 13/14] pls just lint k thnx --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 955b1b4..bea7669 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,7 +27,7 @@ jobs: run: swag init - name: Run golangci-lint - uses: golangci/golangci-lint-action@v6 + uses: golangci/golangci-lint-action@v7 with: version: v2.10 args: --timeout=5m From ab58102929f5e1415e597f6c330d46401b0c8bdc Mon Sep 17 00:00:00 2001 From: Steve Troetti Date: Thu, 19 Feb 2026 20:10:26 -0500 Subject: [PATCH 14/14] ok try fixing linter config --- .golangci.yml | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index a1c6263..ede6edc 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,21 +1,22 @@ +version: "2" + linters: enable: - - gofmt - govet - errcheck - staticcheck - unused - - gosimple - ineffassign - - typecheck + settings: + errcheck: + check-type-assertions: true + check-blank: true -linters-settings: - errcheck: - check-type-assertions: true - check-blank: true +formatters: + enable: + - gofmt issues: - exclude-use-default: false max-issues-per-linter: 0 max-same-issues: 0