Skip to content

Commit a0366b7

Browse files
committed
feat: PRSDM-10268 embed themes in binary
1 parent b1cc45e commit a0366b7

File tree

11 files changed

+539
-16
lines changed

11 files changed

+539
-16
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,5 @@ resources/
1212

1313
dist/
1414
docs/resources
15-
reports/
15+
reports/
16+
_embedded_themes/

Dockerfile.test

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# Multi-stage Dockerfile to test presidium with embedded themes
2+
# This tests that themes work without network access
3+
4+
# Stage 1: Build presidium binary for Linux
5+
FROM golang:1.23-alpine AS builder
6+
7+
# Install build dependencies
8+
RUN apk add --no-cache git gcc g++ musl-dev
9+
10+
# Copy source code
11+
WORKDIR /build
12+
COPY . .
13+
14+
# Set GOTOOLCHAIN to auto to allow Go to download required version
15+
ENV GOTOOLCHAIN=auto
16+
17+
# Build presidium binary
18+
RUN go build -tags extended -o presidium .
19+
20+
# Stage 2: Test without network access
21+
FROM golang:1.23-alpine AS test
22+
23+
# Install runtime dependencies (C++ libraries required by Hugo/presidium)
24+
RUN apk add --no-cache curl libstdc++ libgcc
25+
26+
# Copy the test site
27+
COPY ./.tmp/presidium-test-validation /workspace/test-site
28+
29+
# Copy the built presidium binary from builder stage
30+
COPY --from=builder /build/presidium /usr/local/bin/presidium
31+
RUN chmod +x /usr/local/bin/presidium
32+
33+
# Set working directory to the test site
34+
WORKDIR /workspace/test-site
35+
36+
# Verify presidium binary
37+
RUN presidium --version || echo "Presidium version check completed"
38+
39+
# Run hugo build using presidium (this will use embedded themes)
40+
# This should work without network access, proving themes are embedded
41+
RUN echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" && \
42+
echo "Building site with embedded themes..." && \
43+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" && \
44+
presidium hugo && \
45+
echo "" && \
46+
echo "✓ Build successful! Themes were loaded from embedded binary." && \
47+
echo "" && \
48+
echo "Generated files:" && \
49+
ls -lh public/ | head -20
50+
51+
# Verify the build output exists and contains expected files
52+
RUN test -d public && \
53+
test -f public/index.html && \
54+
test -f public/sitemap.xml && \
55+
echo "" && \
56+
echo "✓ Hugo site built successfully!" && \
57+
echo "✓ All expected files generated" && \
58+
echo "✓ Themes loaded from binary (no GitHub fetch required)"
59+
60+
# Create a test script that will run with --network=none
61+
RUN echo '#!/bin/sh' > /test.sh && \
62+
echo 'echo "╔═══════════════════════════════════════════════════════════╗"' >> /test.sh && \
63+
echo 'echo "║ Network Isolation Test ║"' >> /test.sh && \
64+
echo 'echo "╚═══════════════════════════════════════════════════════════╝"' >> /test.sh && \
65+
echo 'echo ""' >> /test.sh && \
66+
echo 'echo "Testing network isolation..."' >> /test.sh && \
67+
echo 'if curl -s --max-time 2 https://github.com >/dev/null 2>&1; then' >> /test.sh && \
68+
echo ' echo "❌ FAIL: Network access detected!"' >> /test.sh && \
69+
echo ' exit 1' >> /test.sh && \
70+
echo 'else' >> /test.sh && \
71+
echo ' echo "✓ Confirmed: No network access"' >> /test.sh && \
72+
echo 'fi' >> /test.sh && \
73+
echo 'echo ""' >> /test.sh && \
74+
echo 'echo "Build artifacts:"' >> /test.sh && \
75+
echo 'ls -lh public/ | head -15' >> /test.sh && \
76+
echo 'echo ""' >> /test.sh && \
77+
echo 'echo "╔═══════════════════════════════════════════════════════════╗"' >> /test.sh && \
78+
echo 'echo "║ ✅ SUCCESS: Embedded themes work without network! ║"' >> /test.sh && \
79+
echo 'echo "╚═══════════════════════════════════════════════════════════╝"' >> /test.sh && \
80+
chmod +x /test.sh
81+
82+
# Default command runs the test script
83+
CMD ["/test.sh"]

Makefile

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,22 @@ DOCSDIR=docs
66
help: ## Display available targets
77
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " %-18s %s\n", $$1, $$2}'
88

9-
build: ## Build the presidium binary
9+
stage-themes: ## Copy themes into embeddable staging directory
10+
@rm -rf _embedded_themes
11+
@mkdir -p _embedded_themes
12+
@for dir in themes/presidium-*; do \
13+
name=$$(basename "$$dir"); \
14+
cp -R "$$dir" "_embedded_themes/$$name"; \
15+
if [ -f "_embedded_themes/$$name/go.mod" ]; then \
16+
mv "_embedded_themes/$$name/go.mod" "_embedded_themes/$$name/gomod.txt"; \
17+
fi; \
18+
if [ -f "_embedded_themes/$$name/go.sum" ]; then \
19+
mv "_embedded_themes/$$name/go.sum" "_embedded_themes/$$name/gosum.txt"; \
20+
fi; \
21+
rm -rf "_embedded_themes/$$name/.git"; \
22+
done
23+
24+
build: stage-themes ## Build the presidium binary
1025
go build -tags extended -o $(FILENAME) .
1126

1227
test: ## Run tests with coverage
@@ -23,16 +38,16 @@ tidy: ## Tidy and verify module dependencies
2338
go mod tidy && go mod verify
2439

2540
clean: ## Remove build artifacts
26-
rm -fr "dist"
41+
rm -fr "dist" "_embedded_themes"
2742

2843
coverage_report: ## Open coverage report in browser
2944
@go tool cover -html=reports/tests-cov.out
3045

31-
dist: ## Build distribution binary
46+
dist: stage-themes ## Build distribution binary
3247
mkdir -p "dist"
3348
go build -trimpath -o "dist/presidium" --tags extended
3449

35-
checks: tidy fmt vet lint test build
50+
checks: clean tidy fmt vet lint test build
3651

3752
serve-docs:
3853
cd $(DOCSDIR) && make serve

cmd/hugo.go

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,28 @@
11
package cmd
22

33
import (
4+
"os"
5+
46
"github.com/SPANDigital/presidium-hugo/pkg/domain/service/hugo"
7+
"github.com/SPANDigital/presidium-hugo/pkg/log"
58
"github.com/spf13/cobra"
69
)
710

811
var (
912
// hugoCommand wraps hugo into Presidium. This allows you to run hugo
1013
// in Presidium, and makes it easier to debug etc.
14+
// All arguments and flags are passed through to Hugo unchanged.
1115
hugoCommand = &cobra.Command{
12-
Use: "hugo",
13-
Short: "Runs hugo against your presidium site",
16+
Use: "hugo",
17+
Short: "Runs hugo with full access to all Hugo commands and flags",
18+
DisableFlagParsing: true, // Don't parse flags - let Hugo handle them
1419
Run: func(cmd *cobra.Command, args []string) {
15-
hugo := hugo.New()
16-
hugo.Execute(args...)
20+
hugoService := hugo.New()
21+
err := hugoService.Execute(args...)
22+
if err != nil {
23+
log.Error(err)
24+
os.Exit(1)
25+
}
1726
},
1827
}
1928
)

embedded.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,6 @@ import "embed"
44

55
//go:embed all:templates
66
var templatesFS embed.FS
7+
8+
//go:embed all:themes
9+
var themesFS embed.FS

main.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@ package main
33
import (
44
"github.com/SPANDigital/presidium-hugo/cmd"
55
"github.com/SPANDigital/presidium-hugo/pkg/domain/service/template"
6+
"github.com/SPANDigital/presidium-hugo/pkg/domain/service/themes"
67
)
78

89
func main() {
910
template.SetFS(templatesFS)
11+
themes.SetFS(themesFS)
1012
cmd.Execute()
1113
}

pkg/domain/service/conversion/conversion.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,17 @@ package conversion
33
import (
44
"errors"
55
"fmt"
6-
"github.com/SPANDigital/presidium-hugo/pkg/config"
7-
"github.com/SPANDigital/presidium-hugo/pkg/domain/service/hugo"
8-
"github.com/SPANDigital/presidium-hugo/pkg/utils"
96
"io"
107
"io/fs"
118
"log"
129
"os"
1310
"path/filepath"
1411
"strings"
1512

13+
"github.com/SPANDigital/presidium-hugo/pkg/config"
14+
"github.com/SPANDigital/presidium-hugo/pkg/domain/service/hugo"
15+
"github.com/SPANDigital/presidium-hugo/pkg/utils"
16+
1617
"github.com/SPANDigital/presidium-hugo/pkg/configtranslation"
1718
"github.com/SPANDigital/presidium-hugo/pkg/domain/service/conversion/fileactions"
1819
"github.com/SPANDigital/presidium-hugo/pkg/domain/service/conversion/resources"
@@ -408,7 +409,7 @@ func (c *Converter) generateHugoModule() {
408409
}
409410

410411
c.messageUser(infoMessage("Adding Hugo GO module to site").withContentStyle(colors.Labels.Wanted))
411-
hugo.New().Execute("--source", c.stagingDir, "mod", "init", c.moduleName())
412+
_ = hugo.New().Execute("--source", c.stagingDir, "mod", "init", c.moduleName())
412413
srcModFile := filepath.Join(c.stagingDir, "go.mod")
413414
dstModFile := filepath.Join(c.destinationRepoDir, "go.mod")
414415
_ = c.fs.Copy(srcModFile, dstModFile, fs.ModePerm)

pkg/domain/service/hugo/hugo.go

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
package hugo
22

3-
import "github.com/gohugoio/hugo/commands"
3+
import (
4+
"fmt"
5+
"os"
6+
7+
"github.com/SPANDigital/presidium-hugo/pkg/domain/service/themes"
8+
"github.com/SPANDigital/presidium-hugo/pkg/log"
9+
"github.com/gohugoio/hugo/commands"
10+
)
411

512
type Service struct {
613
}
@@ -9,6 +16,30 @@ func New() Service {
916
return Service{}
1017
}
1118

12-
func (s Service) Execute(args ...string) {
13-
_ = commands.Execute(args)
19+
func (s Service) Execute(args ...string) error {
20+
// Initialize themes service
21+
themesService, err := themes.New()
22+
if err != nil {
23+
log.Warn(fmt.Sprintf("Failed to initialize themes service, falling back to remote theme fetch: %v", err))
24+
return commands.Execute(args)
25+
}
26+
27+
// Extract embedded themes to temp directory
28+
tmpDir, replacements, err := themesService.Extract()
29+
if err != nil {
30+
log.Warn(fmt.Sprintf("Failed to extract embedded themes, falling back to remote theme fetch: %v", err))
31+
return commands.Execute(args)
32+
}
33+
34+
// Ensure cleanup happens regardless of how Hugo execution ends
35+
defer func() {
36+
os.RemoveAll(tmpDir)
37+
os.Unsetenv("HUGO_MODULE_REPLACEMENTS")
38+
}()
39+
40+
// Set environment variable to point Hugo to local themes
41+
os.Setenv("HUGO_MODULE_REPLACEMENTS", replacements)
42+
43+
// Execute Hugo with local themes - return the error to preserve Hugo's exit behavior
44+
return commands.Execute(args)
1445
}

0 commit comments

Comments
 (0)