Added site previewing

This will generate a local version of the Hugo site and serve it via the server
This commit is contained in:
Leon Mika 2025-02-17 21:41:36 +11:00
parent 3cf4294e87
commit 68aa9c0e13
9 changed files with 61 additions and 33 deletions

View file

@ -13,6 +13,7 @@ type Config struct {
DataStagingDir string `env:"DATA_STAGING_DIR,default=staging"` DataStagingDir string `env:"DATA_STAGING_DIR,default=staging"`
DataScratchDir string `env:"DATA_SCRATCH_DIR,default=scratch"` DataScratchDir string `env:"DATA_SCRATCH_DIR,default=scratch"`
DataPreviewDir string `env:"DATA_PREVIEW_DIR,default=preview"`
} }
func Load() (cfg Config, err error) { func Load() (cfg Config, err error) {
@ -27,6 +28,10 @@ func (c Config) StagingDir() string {
return filepath.Join(c.DataDir, c.DataStagingDir) return filepath.Join(c.DataDir, c.DataStagingDir)
} }
func (c Config) PreviewDir() string {
return filepath.Join(c.DataDir, c.DataPreviewDir)
}
func (c Config) ScratchDir() string { func (c Config) ScratchDir() string {
return filepath.Join(c.DataDir, c.DataScratchDir) return filepath.Join(c.DataDir, c.DataScratchDir)
} }

View file

@ -72,7 +72,7 @@ func main() {
return return
} }
hugoProvider, err := hugo.New(cfg.StagingDir(), cfg.ScratchDir()) hugoProvider, err := hugo.New(cfg.StagingDir(), cfg.PreviewDir(), cfg.ScratchDir())
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
@ -129,6 +129,10 @@ func main() {
app.Post("/sites", siteHandlers.Create) app.Post("/sites", siteHandlers.Create)
app.Get("/sites/:siteId", siteHandlers.Show) app.Get("/sites/:siteId", siteHandlers.Show)
app.Use("/preview", static.New(cfg.PreviewDir(), static.Config{
Browse: true,
}))
sr := app.Group("/sites/:siteId") sr := app.Group("/sites/:siteId")
sr.Use(siteHandlers.WithSite()) sr.Use(siteHandlers.WithSite())
sr.Post("/rebuild", siteHandlers.Rebuild) sr.Post("/rebuild", siteHandlers.Rebuild)

View file

@ -8,6 +8,9 @@ type ThemeMeta struct {
// Indicates that this theme prefers posts have titles. // Indicates that this theme prefers posts have titles.
PreferTitle bool PreferTitle bool
// Indicates that the theme doesn't automatically put titles on pages
AddTitleToPages bool
// Page bundle for "blog" posts // Page bundle for "blog" posts
BlogPostBundle string `json:"post_dir"` BlogPostBundle string `json:"post_dir"`
} }

View file

@ -5,6 +5,7 @@ type hugoConfig struct {
LanguageCode string `yaml:"languageCode"` LanguageCode string `yaml:"languageCode"`
Title string `yaml:"title"` Title string `yaml:"title"`
Theme string `yaml:"theme"` Theme string `yaml:"theme"`
CanonifyURLs bool `yaml:"canonifyURLs,omitempty"`
Markup hugoConfigMarkup `yaml:"markup"` Markup hugoConfigMarkup `yaml:"markup"`
} }

View file

@ -2,6 +2,7 @@ package hugo
import ( import (
"context" "context"
"fmt"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"lmika.dev/lmika/hugo-cms/models" "lmika.dev/lmika/hugo-cms/models"
"log" "log"
@ -12,12 +13,14 @@ import (
type Provider struct { type Provider struct {
stagingDir string stagingDir string
previewDir string
scratchDir string scratchDir string
} }
func New(stagingDir, scratchDir string) (*Provider, error) { func New(stagingDir, previewDir, scratchDir string) (*Provider, error) {
return &Provider{ return &Provider{
stagingDir: stagingDir, stagingDir: stagingDir,
previewDir: previewDir,
scratchDir: scratchDir, scratchDir: scratchDir,
}, nil }, nil
} }
@ -47,42 +50,53 @@ func (p *Provider) NewSite(ctx context.Context, site models.Site) error {
return nil return nil
} }
func (p *Provider) PublishSite(ctx context.Context, site models.Site, target models.PublishTarget) (outDir string, clean func(), err error) { func (p *Provider) PreviewSite(ctx context.Context, site models.Site) (outDir string, err error) {
if err := os.MkdirAll(p.scratchDir, 0755); err != nil { previewTarget := models.PublishTarget{
return "", nil, err URL: fmt.Sprintf("http://localhost:3000/preview/%s", site.Name),
} }
outDir, err = os.MkdirTemp(p.scratchDir, site.Name+"-*") return p.publishSiteAt(ctx, p.previewDir, site, previewTarget, "hugoPreview.yaml")
}
func (p *Provider) PublishSite(ctx context.Context, site models.Site, target models.PublishTarget) (outDir string, err error) {
return p.publishSiteAt(ctx, p.scratchDir, site, target, "hugo.yaml")
}
func (p *Provider) publishSiteAt(ctx context.Context, dir string, site models.Site, target models.PublishTarget, configFile string) (outDir string, err error) {
baseSiteDir, err := filepath.Abs(p.SiteStagingDir(site, BaseSiteDir))
if err != nil { if err != nil {
return "", nil, err return "", err
}
clean = func() {
os.RemoveAll(outDir)
} }
outDir, err = filepath.Abs(outDir) outDir, err = filepath.Abs(filepath.Join(dir, site.Name))
if err != nil { if err != nil {
return "", nil, err return "", err
}
if err := os.MkdirAll(outDir, 0755); err != nil {
return "", err
} }
cmd := exec.CommandContext(ctx, "hugo", cmd := exec.CommandContext(ctx, "hugo",
"--source", p.SiteStagingDir(site, BaseSiteDir), "--source", baseSiteDir,
"--destination", outDir, "--destination", outDir,
"--config", filepath.Join(baseSiteDir, configFile),
"--baseURL", target.URL) "--baseURL", target.URL)
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
if err := cmd.Run(); err != nil { if err := cmd.Run(); err != nil {
return "", clean, err return "", err
} }
return outDir, clean, nil return outDir, nil
} }
func (p *Provider) ReconfigureSite(ctx context.Context, site models.Site) error { func (p *Provider) ReconfigureSite(ctx context.Context, configBase string, site models.Site) error {
hugoCfg := hugoConfig{ hugoCfg := hugoConfig{
Title: site.Title, Title: site.Title,
LanguageCode: "en", LanguageCode: "en",
Theme: site.Theme, Theme: site.Theme,
CanonifyURLs: configBase == "hugoPreview",
Markup: hugoConfigMarkup{ Markup: hugoConfigMarkup{
Goldmark: hugoGoldmarkConfig{ Goldmark: hugoGoldmarkConfig{
Renderer: hugoGoldmarkRendererConfig{ Renderer: hugoGoldmarkRendererConfig{
@ -97,12 +111,12 @@ func (p *Provider) ReconfigureSite(ctx context.Context, site models.Site) error
return err return err
} }
if err := os.WriteFile(filepath.Join(p.SiteStagingDir(site, BaseSiteDir), "hugo.yaml"), ymlBytes, 0644); err != nil { if err := os.WriteFile(filepath.Join(p.SiteStagingDir(site, BaseSiteDir), configBase+".yaml"), ymlBytes, 0644); err != nil {
return err return err
} }
if _, err := os.Stat(filepath.Join(p.SiteStagingDir(site, BaseSiteDir), "hugo.toml")); err == nil { if _, err := os.Stat(filepath.Join(p.SiteStagingDir(site, BaseSiteDir), configBase+".toml")); err == nil {
if err := os.Remove(filepath.Join(p.SiteStagingDir(site, BaseSiteDir), "hugo.toml")); err != nil { if err := os.Remove(filepath.Join(p.SiteStagingDir(site, BaseSiteDir), configBase+".toml")); err != nil {
return err return err
} }
} }

View file

@ -130,10 +130,9 @@ func (s *Service) writePage(bi pageBuildInfo, page models.Page) error {
frontMatter := map[string]any{ frontMatter := map[string]any{
"date": page.PublishDate.Format(time.RFC3339), "date": page.PublishDate.Format(time.RFC3339),
} }
if page.Title != "" { if page.Title != "" {
frontMatter["title"] = page.Title frontMatter["title"] = page.Title
} else if bi.themeMeta.PreferTitle {
frontMatter["title"] = page.PublishDate.Format(time.ANSIC)
} }
return s.writeMarkdownFile(postFilename, frontMatter, page.Body) return s.writeMarkdownFile(postFilename, frontMatter, page.Body)

View file

@ -156,7 +156,7 @@ func (s *Service) pageFilename(bi pageBuildInfo, page models.Page) string {
isLeafBundle := true isLeafBundle := true
thisBundleInfo := bi.bundleInfo[bi.bundle.ID] thisBundleInfo := bi.bundleInfo[bi.bundle.ID]
if thisBundleInfo.PageCount > 1 { if thisBundleInfo.PageCount > 1 || bi.bundle.Name == models.RootBundleName {
isLeafBundle = false isLeafBundle = false
isIndex = thisBundleInfo.IndexPageID == page.ID isIndex = thisBundleInfo.IndexPageID == page.ID
} else { } else {

View file

@ -17,6 +17,10 @@ func (s *Service) Publish(site models.Site) models.Job {
} }
func (s *Service) publish(ctx context.Context, site models.Site) error { func (s *Service) publish(ctx context.Context, site models.Site) error {
if _, err := s.hugo.PreviewSite(ctx, site); err != nil {
return err
}
targets, err := s.db.GetPublishTargets(ctx, site.ID) targets, err := s.db.GetPublishTargets(ctx, site.ID)
if err != nil { if err != nil {
return err return err
@ -31,14 +35,7 @@ func (s *Service) publish(ctx context.Context, site models.Site) error {
} }
func (s *Service) publishTarget(ctx context.Context, site models.Site, target models.PublishTarget) error { func (s *Service) publishTarget(ctx context.Context, site models.Site, target models.PublishTarget) error {
outDir, cleanFn, err := s.hugo.PublishSite(ctx, site, target) outDir, err := s.hugo.PublishSite(ctx, site, target)
//defer func() {
// if cleanFn != nil {
// cleanFn()
// }
//}()
_ = cleanFn
if err != nil { if err != nil {
return err return err
} }

View file

@ -172,9 +172,14 @@ func (s *Service) createSite(ctx context.Context, site models.Site) error {
return err return err
} }
if err := s.hugo.ReconfigureSite(ctx, site); err != nil { if err := s.hugo.ReconfigureSite(ctx, "hugo", site); err != nil {
return err return err
} }
if err := s.hugo.ReconfigureSite(ctx, "hugoPreview", site); err != nil {
return err
}
return nil return nil
} }