Added site previewing
This will generate a local version of the Hugo site and serve it via the server
This commit is contained in:
parent
3cf4294e87
commit
68aa9c0e13
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
6
main.go
6
main.go
|
@ -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)
|
||||||
|
|
|
@ -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"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -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")
|
||||||
if err != nil {
|
|
||||||
return "", nil, err
|
|
||||||
}
|
|
||||||
clean = func() {
|
|
||||||
os.RemoveAll(outDir)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
outDir, err = filepath.Abs(outDir)
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
outDir, err = filepath.Abs(filepath.Join(dir, site.Name))
|
||||||
|
if err != nil {
|
||||||
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue