From 849e74c2050f0145f990198ff037de04dd562970 Mon Sep 17 00:00:00 2001 From: Harry Day Date: Wed, 16 Sep 2026 22:47:45 +0000 Subject: [PATCH 1/4] refactor(cli): move CLI into a ./cli leaf module Split the goblog CLI out of the root module into a leaf module at ./cli so CLI dependencies are no longer inherited by library consumers. - cmd/goblog -> cli/cmd/goblog - internal/ -> cli/internal/ (already CLI-only; no pkg/ importers) - cli/go.mod replaces github.com/harrydayexe/GoBlog/v2 => ../, so the CLI always builds against the library at the same commit. The root module sheds urfave/cli/v3, fatih/color, mattn/go-colorable and mattn/go-isatty. pkg/... is untouched. The CLI generator tests loaded templates through a relative os.DirFS path into pkg/templates/default; they now use the embedded templates.Default, which does not depend on the tree layout. Co-Authored-By: Claude Opus 5 --- {cmd => cli/cmd}/goblog/main.go | 10 +-- {cmd => cli/cmd}/goblog/version.go | 0 cli/go.mod | 31 +++++++++ cli/go.sum | 66 +++++++++++++++++++ {internal => cli/internal}/cliflags/flags.go | 0 {internal => cli/internal}/errors/doc.go | 0 .../internal}/errors/errorType.go | 0 .../internal}/errors/errors_test.go | 0 .../internal}/errors/inputDirectoryErrors.go | 0 .../internal}/generator/command.go | 0 .../internal}/generator/flagConsts.go | 0 .../internal}/generator/generator.go | 4 +- .../internal}/generator/generator_test.go | 15 +++-- {internal => cli/internal}/logger/cli.go | 0 {internal => cli/internal}/logger/cli_test.go | 0 {internal => cli/internal}/server/command.go | 0 .../internal}/server/command_test.go | 0 .../internal}/server/flagConsts.go | 0 {internal => cli/internal}/server/server.go | 4 +- .../internal}/utilities/cliErrorHandler.go | 2 +- {internal => cli/internal}/utilities/doc.go | 0 .../utilities/getDirectoryFromInput.go | 2 +- .../internal}/utilities/openAssetsDir.go | 0 .../internal}/utilities/utilities_test.go | 2 +- go.mod | 4 -- go.sum | 11 ---- 26 files changed, 117 insertions(+), 34 deletions(-) rename {cmd => cli/cmd}/goblog/main.go (85%) rename {cmd => cli/cmd}/goblog/version.go (100%) create mode 100644 cli/go.mod create mode 100644 cli/go.sum rename {internal => cli/internal}/cliflags/flags.go (100%) rename {internal => cli/internal}/errors/doc.go (100%) rename {internal => cli/internal}/errors/errorType.go (100%) rename {internal => cli/internal}/errors/errors_test.go (100%) rename {internal => cli/internal}/errors/inputDirectoryErrors.go (100%) rename {internal => cli/internal}/generator/command.go (100%) rename {internal => cli/internal}/generator/flagConsts.go (100%) rename {internal => cli/internal}/generator/generator.go (96%) rename {internal => cli/internal}/generator/generator_test.go (94%) rename {internal => cli/internal}/logger/cli.go (100%) rename {internal => cli/internal}/logger/cli_test.go (100%) rename {internal => cli/internal}/server/command.go (100%) rename {internal => cli/internal}/server/command_test.go (100%) rename {internal => cli/internal}/server/flagConsts.go (100%) rename {internal => cli/internal}/server/server.go (97%) rename {internal => cli/internal}/utilities/cliErrorHandler.go (92%) rename {internal => cli/internal}/utilities/doc.go (100%) rename {internal => cli/internal}/utilities/getDirectoryFromInput.go (93%) rename {internal => cli/internal}/utilities/openAssetsDir.go (100%) rename {internal => cli/internal}/utilities/utilities_test.go (99%) diff --git a/cmd/goblog/main.go b/cli/cmd/goblog/main.go similarity index 85% rename from cmd/goblog/main.go rename to cli/cmd/goblog/main.go index fccd3e9..8149a1b 100644 --- a/cmd/goblog/main.go +++ b/cli/cmd/goblog/main.go @@ -10,11 +10,11 @@ import ( "log/slog" "os" - "github.com/harrydayexe/GoBlog/v2/internal/cliflags" - "github.com/harrydayexe/GoBlog/v2/internal/generator" - loggermod "github.com/harrydayexe/GoBlog/v2/internal/logger" - "github.com/harrydayexe/GoBlog/v2/internal/server" - "github.com/harrydayexe/GoBlog/v2/internal/utilities" + "github.com/harrydayexe/GoBlog/v2/cli/internal/cliflags" + "github.com/harrydayexe/GoBlog/v2/cli/internal/generator" + loggermod "github.com/harrydayexe/GoBlog/v2/cli/internal/logger" + "github.com/harrydayexe/GoBlog/v2/cli/internal/server" + "github.com/harrydayexe/GoBlog/v2/cli/internal/utilities" "github.com/urfave/cli/v3" ) diff --git a/cmd/goblog/version.go b/cli/cmd/goblog/version.go similarity index 100% rename from cmd/goblog/version.go rename to cli/cmd/goblog/version.go diff --git a/cli/go.mod b/cli/go.mod new file mode 100644 index 0000000..3389d3a --- /dev/null +++ b/cli/go.mod @@ -0,0 +1,31 @@ +module github.com/harrydayexe/GoBlog/v2/cli + +go 1.26.3 + +// The CLI is never published as a Go module; it is always built from source in +// this repository, so it tracks the library at the same commit. +replace github.com/harrydayexe/GoBlog/v2 => ../ + +require ( + github.com/fatih/color v1.18.0 + github.com/harrydayexe/GoBlog/v2 v2.0.0 + github.com/harrydayexe/GoWebUtilities v1.5.1 + github.com/urfave/cli/v3 v3.6.1 +) + +require ( + github.com/BurntSushi/toml v1.5.0 // indirect + github.com/alecthomas/chroma/v2 v2.22.0 // indirect + github.com/caarlos0/env/v11 v11.3.1 // indirect + github.com/dlclark/regexp2 v1.11.5 // indirect + github.com/fsnotify/fsnotify v1.10.1 // indirect + github.com/gorilla/feeds v1.2.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/yuin/goldmark v1.7.16 // indirect + github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc // indirect + go.abhg.dev/goldmark/frontmatter v0.3.0 // indirect + go.abhg.dev/goldmark/wikilink v0.6.0 // indirect + golang.org/x/sys v0.25.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/cli/go.sum b/cli/go.sum new file mode 100644 index 0000000..4e544eb --- /dev/null +++ b/cli/go.sum @@ -0,0 +1,66 @@ +github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= +github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= +github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= +github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs= +github.com/alecthomas/chroma/v2 v2.22.0 h1:PqEhf+ezz5F5owoDeOUKFzW+W3ZJDShNCaHg4sZuItI= +github.com/alecthomas/chroma/v2 v2.22.0/go.mod h1:NqVhfBR0lte5Ouh3DcthuUCTUpDC9cxBOfyMbMQPs3o= +github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8= +github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs= +github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= +github.com/caarlos0/env/v11 v11.3.1 h1:cArPWC15hWmEt+gWk7YBi7lEXTXCvpaSdCiZE2X5mCA= +github.com/caarlos0/env/v11 v11.3.1/go.mod h1:qupehSf/Y0TUTsxKywqRt/vJjN5nz6vauiYEUUr8P4U= +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/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= +github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ= +github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho= +github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo= +github.com/gorilla/feeds v1.2.0 h1:O6pBiXJ5JHhPvqy53NsjKOThq+dNFm8+DFrxBEdzSCc= +github.com/gorilla/feeds v1.2.0/go.mod h1:WMib8uJP3BbY+X8Szd1rA5Pzhdfh+HCCAYT2z7Fza6Y= +github.com/harrydayexe/GoWebUtilities v1.5.1 h1:1E/5IEBHdG8SfIwF2tqJhxypsk9VH1TIb1ofMmI6ZOg= +github.com/harrydayexe/GoWebUtilities v1.5.1/go.mod h1:msGwhqkUbSAhhIR+uFnLE08OhBCwuJW0BQ5kUnBvI6I= +github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= +github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/urfave/cli/v3 v3.6.1 h1:j8Qq8NyUawj/7rTYdBGrxcH7A/j7/G8Q5LhWEW4G3Mo= +github.com/urfave/cli/v3 v3.6.1/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso= +github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yuin/goldmark v1.7.16 h1:n+CJdUxaFMiDUNnWC3dMWCIQJSkxH4uz3ZwQBkAlVNE= +github.com/yuin/goldmark v1.7.16/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= +github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc h1:+IAOyRda+RLrxa1WC7umKOZRsGq4QrFFMYApOeHzQwQ= +github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc/go.mod h1:ovIvrum6DQJA4QsJSovrkC4saKHQVs7TvcaeO8AIl5I= +go.abhg.dev/goldmark/frontmatter v0.3.0 h1:ZOrMkeyyYzhlbenFNmOXyGFx1dFE8TgBWAgZfs9D5RA= +go.abhg.dev/goldmark/frontmatter v0.3.0/go.mod h1:W3KXvVveKKxU1FIFZ7fgFFQrlkcolnDcOVmu19cCO9U= +go.abhg.dev/goldmark/wikilink v0.6.0 h1:SKZANgMD7GMbaU0kBKTh52Ea9k3A3Y5ZifHoEPC1fuo= +go.abhg.dev/goldmark/wikilink v0.6.0/go.mod h1:Sfaovp00aAVJ5khqIeDTTgkIfZrcurmJGlbntCJUbJY= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/cliflags/flags.go b/cli/internal/cliflags/flags.go similarity index 100% rename from internal/cliflags/flags.go rename to cli/internal/cliflags/flags.go diff --git a/internal/errors/doc.go b/cli/internal/errors/doc.go similarity index 100% rename from internal/errors/doc.go rename to cli/internal/errors/doc.go diff --git a/internal/errors/errorType.go b/cli/internal/errors/errorType.go similarity index 100% rename from internal/errors/errorType.go rename to cli/internal/errors/errorType.go diff --git a/internal/errors/errors_test.go b/cli/internal/errors/errors_test.go similarity index 100% rename from internal/errors/errors_test.go rename to cli/internal/errors/errors_test.go diff --git a/internal/errors/inputDirectoryErrors.go b/cli/internal/errors/inputDirectoryErrors.go similarity index 100% rename from internal/errors/inputDirectoryErrors.go rename to cli/internal/errors/inputDirectoryErrors.go diff --git a/internal/generator/command.go b/cli/internal/generator/command.go similarity index 100% rename from internal/generator/command.go rename to cli/internal/generator/command.go diff --git a/internal/generator/flagConsts.go b/cli/internal/generator/flagConsts.go similarity index 100% rename from internal/generator/flagConsts.go rename to cli/internal/generator/flagConsts.go diff --git a/internal/generator/generator.go b/cli/internal/generator/generator.go similarity index 96% rename from internal/generator/generator.go rename to cli/internal/generator/generator.go index c4a0549..9a88b66 100644 --- a/internal/generator/generator.go +++ b/cli/internal/generator/generator.go @@ -12,8 +12,8 @@ import ( "path/filepath" "strings" - "github.com/harrydayexe/GoBlog/v2/internal/cliflags" - "github.com/harrydayexe/GoBlog/v2/internal/utilities" + "github.com/harrydayexe/GoBlog/v2/cli/internal/cliflags" + "github.com/harrydayexe/GoBlog/v2/cli/internal/utilities" "github.com/harrydayexe/GoBlog/v2/pkg/config" "github.com/harrydayexe/GoBlog/v2/pkg/generator" "github.com/harrydayexe/GoBlog/v2/pkg/outputter" diff --git a/internal/generator/generator_test.go b/cli/internal/generator/generator_test.go similarity index 94% rename from internal/generator/generator_test.go rename to cli/internal/generator/generator_test.go index dfe9653..42b4d71 100644 --- a/internal/generator/generator_test.go +++ b/cli/internal/generator/generator_test.go @@ -13,6 +13,7 @@ import ( "github.com/harrydayexe/GoBlog/v2/pkg/config" "github.com/harrydayexe/GoBlog/v2/pkg/generator" "github.com/harrydayexe/GoBlog/v2/pkg/outputter" + "github.com/harrydayexe/GoBlog/v2/pkg/templates" ) // TestRunGenerate tests the core generate logic. @@ -53,7 +54,7 @@ This is a test post. // Create filesystem and renderer postsFsys := os.DirFS(postsDir) - renderer, err := generator.NewTemplateRenderer(os.DirFS("../../pkg/templates/default")) + renderer, err := generator.NewTemplateRenderer(templates.Default) if err != nil { t.Fatalf("Failed to create renderer: %v", err) } @@ -114,7 +115,7 @@ tags: [test] } postsFsys := os.DirFS(postsDir) - renderer, err := generator.NewTemplateRenderer(os.DirFS("../../pkg/templates/default")) + renderer, err := generator.NewTemplateRenderer(templates.Default) if err != nil { t.Fatalf("Failed to create renderer: %v", err) } @@ -155,7 +156,7 @@ func TestRunGenerate_EmptyPosts(t *testing.T) { } postsFsys := os.DirFS(postsDir) - renderer, err := generator.NewTemplateRenderer(os.DirFS("../../pkg/templates/default")) + renderer, err := generator.NewTemplateRenderer(templates.Default) if err != nil { t.Fatalf("Failed to create renderer: %v", err) } @@ -203,7 +204,7 @@ No description or date. } postsFsys := os.DirFS(postsDir) - renderer, err := generator.NewTemplateRenderer(os.DirFS("../../pkg/templates/default")) + renderer, err := generator.NewTemplateRenderer(templates.Default) if err != nil { t.Fatalf("Failed to create renderer: %v", err) } @@ -252,7 +253,7 @@ description: A test } postsFsys := os.DirFS(postsDir) - renderer, err := generator.NewTemplateRenderer(os.DirFS("../../pkg/templates/default")) + renderer, err := generator.NewTemplateRenderer(templates.Default) if err != nil { t.Fatalf("Failed to create renderer: %v", err) } @@ -308,7 +309,7 @@ Testing the blog root feature. } postsFsys := os.DirFS(postsDir) - renderer, err := generator.NewTemplateRenderer(os.DirFS("../../pkg/templates/default")) + renderer, err := generator.NewTemplateRenderer(templates.Default) if err != nil { t.Fatalf("Failed to create renderer: %v", err) } @@ -408,7 +409,7 @@ tags: [test, go] } postsFsys := os.DirFS(postsDir) - renderer, err := generator.NewTemplateRenderer(os.DirFS("../../pkg/templates/default")) + renderer, err := generator.NewTemplateRenderer(templates.Default) if err != nil { t.Fatalf("Failed to create renderer: %v", err) } diff --git a/internal/logger/cli.go b/cli/internal/logger/cli.go similarity index 100% rename from internal/logger/cli.go rename to cli/internal/logger/cli.go diff --git a/internal/logger/cli_test.go b/cli/internal/logger/cli_test.go similarity index 100% rename from internal/logger/cli_test.go rename to cli/internal/logger/cli_test.go diff --git a/internal/server/command.go b/cli/internal/server/command.go similarity index 100% rename from internal/server/command.go rename to cli/internal/server/command.go diff --git a/internal/server/command_test.go b/cli/internal/server/command_test.go similarity index 100% rename from internal/server/command_test.go rename to cli/internal/server/command_test.go diff --git a/internal/server/flagConsts.go b/cli/internal/server/flagConsts.go similarity index 100% rename from internal/server/flagConsts.go rename to cli/internal/server/flagConsts.go diff --git a/internal/server/server.go b/cli/internal/server/server.go similarity index 97% rename from internal/server/server.go rename to cli/internal/server/server.go index a576927..203f0d4 100644 --- a/internal/server/server.go +++ b/cli/internal/server/server.go @@ -12,8 +12,8 @@ import ( "path" "strings" - "github.com/harrydayexe/GoBlog/v2/internal/cliflags" - "github.com/harrydayexe/GoBlog/v2/internal/utilities" + "github.com/harrydayexe/GoBlog/v2/cli/internal/cliflags" + "github.com/harrydayexe/GoBlog/v2/cli/internal/utilities" "github.com/harrydayexe/GoBlog/v2/pkg/config" "github.com/harrydayexe/GoBlog/v2/pkg/server" "github.com/harrydayexe/GoBlog/v2/pkg/templates" diff --git a/internal/utilities/cliErrorHandler.go b/cli/internal/utilities/cliErrorHandler.go similarity index 92% rename from internal/utilities/cliErrorHandler.go rename to cli/internal/utilities/cliErrorHandler.go index 4e93eb4..c43febe 100644 --- a/internal/utilities/cliErrorHandler.go +++ b/cli/internal/utilities/cliErrorHandler.go @@ -10,7 +10,7 @@ import ( "os" "github.com/fatih/color" - inerrors "github.com/harrydayexe/GoBlog/v2/internal/errors" + inerrors "github.com/harrydayexe/GoBlog/v2/cli/internal/errors" ) // CliErrorHandler handles errors by printing them to stdout or stderr and exits if fatal. diff --git a/internal/utilities/doc.go b/cli/internal/utilities/doc.go similarity index 100% rename from internal/utilities/doc.go rename to cli/internal/utilities/doc.go diff --git a/internal/utilities/getDirectoryFromInput.go b/cli/internal/utilities/getDirectoryFromInput.go similarity index 93% rename from internal/utilities/getDirectoryFromInput.go rename to cli/internal/utilities/getDirectoryFromInput.go index 2df6505..d477645 100644 --- a/internal/utilities/getDirectoryFromInput.go +++ b/cli/internal/utilities/getDirectoryFromInput.go @@ -8,7 +8,7 @@ import ( "os" "path/filepath" - "github.com/harrydayexe/GoBlog/v2/internal/errors" + "github.com/harrydayexe/GoBlog/v2/cli/internal/errors" ) // GetDirectoryFromInput validates a path and returns it as an fs.FS. diff --git a/internal/utilities/openAssetsDir.go b/cli/internal/utilities/openAssetsDir.go similarity index 100% rename from internal/utilities/openAssetsDir.go rename to cli/internal/utilities/openAssetsDir.go diff --git a/internal/utilities/utilities_test.go b/cli/internal/utilities/utilities_test.go similarity index 99% rename from internal/utilities/utilities_test.go rename to cli/internal/utilities/utilities_test.go index 388e0c4..bf272c7 100644 --- a/internal/utilities/utilities_test.go +++ b/cli/internal/utilities/utilities_test.go @@ -12,7 +12,7 @@ import ( "strings" "testing" - inerrors "github.com/harrydayexe/GoBlog/v2/internal/errors" + inerrors "github.com/harrydayexe/GoBlog/v2/cli/internal/errors" ) // TestGetDirectoryFromInput_ValidAbsolutePath tests with a valid absolute path. diff --git a/go.mod b/go.mod index da7333c..90dfa54 100644 --- a/go.mod +++ b/go.mod @@ -4,11 +4,9 @@ go 1.26.3 require ( github.com/alecthomas/chroma/v2 v2.22.0 - github.com/fatih/color v1.18.0 github.com/fsnotify/fsnotify v1.10.1 github.com/gorilla/feeds v1.2.0 github.com/harrydayexe/GoWebUtilities v1.5.1 - github.com/urfave/cli/v3 v3.6.1 github.com/yuin/goldmark v1.7.16 github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc go.abhg.dev/goldmark/frontmatter v0.3.0 @@ -19,8 +17,6 @@ require ( github.com/BurntSushi/toml v1.5.0 // indirect github.com/caarlos0/env/v11 v11.3.1 // indirect github.com/dlclark/regexp2 v1.11.5 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect golang.org/x/sys v0.25.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 4e544eb..4ec2f55 100644 --- a/go.sum +++ b/go.sum @@ -17,8 +17,6 @@ github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55k github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ= github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= -github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= -github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho= github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo= github.com/gorilla/feeds v1.2.0 h1:O6pBiXJ5JHhPvqy53NsjKOThq+dNFm8+DFrxBEdzSCc= @@ -31,11 +29,6 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= @@ -44,8 +37,6 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -github.com/urfave/cli/v3 v3.6.1 h1:j8Qq8NyUawj/7rTYdBGrxcH7A/j7/G8Q5LhWEW4G3Mo= -github.com/urfave/cli/v3 v3.6.1/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso= github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.7.16 h1:n+CJdUxaFMiDUNnWC3dMWCIQJSkxH4uz3ZwQBkAlVNE= github.com/yuin/goldmark v1.7.16/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= @@ -55,8 +46,6 @@ go.abhg.dev/goldmark/frontmatter v0.3.0 h1:ZOrMkeyyYzhlbenFNmOXyGFx1dFE8TgBWAgZf go.abhg.dev/goldmark/frontmatter v0.3.0/go.mod h1:W3KXvVveKKxU1FIFZ7fgFFQrlkcolnDcOVmu19cCO9U= go.abhg.dev/goldmark/wikilink v0.6.0 h1:SKZANgMD7GMbaU0kBKTh52Ea9k3A3Y5ZifHoEPC1fuo= go.abhg.dev/goldmark/wikilink v0.6.0/go.mod h1:Sfaovp00aAVJ5khqIeDTTgkIfZrcurmJGlbntCJUbJY= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= From dd1455a8818dc975fdebb969d9ea060583cc24fb Mon Sep 17 00:00:00 2001 From: Harry Day Date: Wed, 16 Sep 2026 22:57:15 +0000 Subject: [PATCH 2/4] build(cli): build the CLI module and publish a Homebrew cask - Dockerfile: download deps and build from ./cli; both go.mod files are copied up front so the `replace ../` resolves in the cached layer. - justfile: build/install/run recipes target the CLI module; test, vet, fmt, vulncheck and mod-tidy now iterate every module instead of stopping at the root module's `./...`. Coverage is reported per module because `go tool cover` resolves sources through its own module. - .goreleaser.yaml: `dir: cli`, shell completions generated in a before hook and shipped in every archive, and a `homebrew_casks` block that pushes to harrydayexe/homebrew-tap (`brews` is deprecated upstream). The matching CI workflow changes are held back in a separate commit. Co-Authored-By: Claude Opus 5 --- .gitignore | 1 + .goreleaser.yaml | 43 +++++++++++++++++++++- Dockerfile | 10 +++--- justfile | 92 +++++++++++++++++++++++++++++++++++------------- 4 files changed, 117 insertions(+), 29 deletions(-) diff --git a/.gitignore b/.gitignore index 6b5dfd0..8dc9d71 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,4 @@ output-posts/ ClAUDE.local.md .claude/settings.local.json +completions/ diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 0c3bf4e..b78a9d9 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -6,11 +6,21 @@ version: 2 before: hooks: - # You may remove this if you don't use go modules. - go mod tidy + - go -C cli mod tidy + # Shell completions are generated once here and shipped in every archive, + # which is also where the Homebrew cask picks them up from. + - rm -rf completions + - mkdir -p completions + - sh -c 'go -C cli run ./cmd/goblog completion bash > completions/goblog.bash' + - sh -c 'go -C cli run ./cmd/goblog completion zsh > completions/goblog.zsh' + - sh -c 'go -C cli run ./cmd/goblog completion fish > completions/goblog.fish' builds: - id: goblog + # The CLI lives in its own leaf module so that library consumers do not + # inherit its dependencies. `main` is resolved relative to `dir`. + dir: cli main: ./cmd/goblog binary: goblog env: @@ -18,6 +28,7 @@ builds: goos: - linux - darwin + # Windows binaries are built but are not a supported install target. - windows goarch: - amd64 @@ -41,6 +52,36 @@ archives: format_overrides: - goos: windows formats: [zip] + files: + - LICENSE + - README.md + - completions/* + +homebrew_casks: + - name: goblog + ids: [goblog] + binaries: [goblog] + repository: + owner: harrydayexe + name: homebrew-tap + branch: main + token: "{{ .Env.HOMEBREW_TAP_GITHUB_TOKEN }}" + homepage: https://github.com/harrydayexe/GoBlog + description: Create a blog feed from posts written in Markdown + completions: + bash: completions/goblog.bash + zsh: completions/goblog.zsh + fish: completions/goblog.fish + # Only tag non-prerelease versions into the tap. + skip_upload: auto + hooks: + post: + install: | + if OS.mac? + system_command "/usr/bin/xattr", + args: ["-dr", "com.apple.quarantine", "#{staged_path}/goblog"], + must_succeed: false + end checksum: name_template: "checksums.txt" diff --git a/Dockerfile b/Dockerfile index 354b34e..897359d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,15 +7,17 @@ FROM golang:1.26-alpine AS builder WORKDIR /build -# Copy go mod files +# Copy go mod files for both modules. The CLI module replaces the library with +# ../, so the root go.mod must be present before `go mod download` can resolve. COPY go.mod go.sum ./ -RUN go mod download +COPY cli/go.mod cli/go.sum ./cli/ +RUN go -C cli mod download # Copy source code COPY . . -# Build the binary -RUN CGO_ENABLED=0 GOOS=linux go build -o goblog ./cmd/goblog +# Build the binary from the CLI module +RUN CGO_ENABLED=0 GOOS=linux go -C cli build -o /build/goblog ./cmd/goblog # Runtime stage FROM alpine:latest diff --git a/justfile b/justfile index 2087fa5..afe8b0e 100644 --- a/justfile +++ b/justfile @@ -4,10 +4,16 @@ # Variables BINARY_NAME := "goblog" MODULE := "github.com/harrydayexe/GoBlog/v2" +CLI_DIR := "cli" MAIN_PATH := "./cmd/goblog" DIST_DIR := "dist" COVERAGE_DIR := "coverage" +# Modules covered by the unit suite. The integration module is excluded because +# it needs Docker and is run separately by `test-integration`. +UNIT_MODULES := ". cli" +ALL_MODULES := ". cli integration" + # Build-time version injection using git tags VERSION := `git describe --tags --always --dirty 2>/dev/null || echo "dev"` LDFLAGS := '-s -w -X main.version=' + VERSION @@ -18,13 +24,13 @@ LDFLAGS := '-s -w -X main.version=' + VERSION build: @echo "Building {{BINARY_NAME}}..." @mkdir -p {{DIST_DIR}} - go build -ldflags "{{LDFLAGS}}" -o {{DIST_DIR}}/{{BINARY_NAME}} {{MAIN_PATH}} + go -C {{CLI_DIR}} build -ldflags "{{LDFLAGS}}" -o {{justfile_directory()}}/{{DIST_DIR}}/{{BINARY_NAME}} {{MAIN_PATH}} @echo "✓ Binary built successfully: {{DIST_DIR}}/{{BINARY_NAME}}" # Build and install to $GOPATH/bin install: @echo "Installing {{BINARY_NAME}}..." - go install -ldflags "{{LDFLAGS}}" {{MAIN_PATH}} + go -C {{CLI_DIR}} install -ldflags "{{LDFLAGS}}" {{MAIN_PATH}} @echo "✓ Binary installed successfully" # Remove build artifacts @@ -39,40 +45,61 @@ clean: # Run all tests [group("test")] test: - go test ./... + #!/usr/bin/env bash + set -euo pipefail + for mod in {{UNIT_MODULES}}; do + echo "==> $mod" + go -C "$mod" test ./... + done # Run tests with verbose output [group("test")] test-verbose: - go test -v ./... + #!/usr/bin/env bash + set -euo pipefail + for mod in {{UNIT_MODULES}}; do + echo "==> $mod" + go -C "$mod" test -v ./... + done # Run tests with race detector [group("test")] test-race: - go test -race ./... + #!/usr/bin/env bash + set -euo pipefail + for mod in {{UNIT_MODULES}}; do + echo "==> $mod" + go -C "$mod" test -race ./... + done # Run integration tests (requires Docker) [group("test")] test-integration: - cd integration && go test -v -timeout 10m ./... + go -C integration test -v -timeout 10m ./... # Run tests with coverage profile [group("test")] test-coverage: + # The two modules produce separate profiles: `go tool cover` resolves source + # files through the module it runs in, so a merged cross-module profile + # would not be readable. @echo "Running tests with coverage..." @mkdir -p {{COVERAGE_DIR}} - go test -coverprofile={{COVERAGE_DIR}}/coverage.out ./... + go test -coverprofile={{COVERAGE_DIR}}/library.out ./... + go -C {{CLI_DIR}} test -coverprofile={{justfile_directory()}}/{{COVERAGE_DIR}}/cli.out ./... @echo "\nCoverage summary:" - @go tool cover -func={{COVERAGE_DIR}}/coverage.out | tail -1 + @printf 'library ' && go tool cover -func={{COVERAGE_DIR}}/library.out | tail -1 + @printf 'cli ' && go -C {{CLI_DIR}} tool cover -func={{justfile_directory()}}/{{COVERAGE_DIR}}/cli.out | tail -1 -# Generate HTML coverage report +# Generate HTML coverage reports [group("test")] coverage-html: test-coverage - @echo "Generating HTML coverage report..." - go tool cover -html={{COVERAGE_DIR}}/coverage.out -o {{COVERAGE_DIR}}/coverage.html - @echo "✓ Coverage report: {{COVERAGE_DIR}}/coverage.html" + @echo "Generating HTML coverage reports..." + go tool cover -html={{COVERAGE_DIR}}/library.out -o {{COVERAGE_DIR}}/library.html + go -C {{CLI_DIR}} tool cover -html={{justfile_directory()}}/{{COVERAGE_DIR}}/cli.out -o {{justfile_directory()}}/{{COVERAGE_DIR}}/cli.html + @echo "✓ Coverage reports: {{COVERAGE_DIR}}/library.html, {{COVERAGE_DIR}}/cli.html" @echo "Opening in browser..." - @open {{COVERAGE_DIR}}/coverage.html 2>/dev/null || xdg-open {{COVERAGE_DIR}}/coverage.html 2>/dev/null || echo "Please open {{COVERAGE_DIR}}/coverage.html manually" + @open {{COVERAGE_DIR}}/library.html 2>/dev/null || xdg-open {{COVERAGE_DIR}}/library.html 2>/dev/null || echo "Please open {{COVERAGE_DIR}}/library.html manually" # Run complete test suite (CI/CD simulation) [group("test")] @@ -89,23 +116,40 @@ test-all: # Run go vet linter [group("lint")] vet: - go vet ./... + #!/usr/bin/env bash + set -euo pipefail + for mod in {{ALL_MODULES}}; do + echo "==> $mod" + go -C "$mod" vet ./... + done # Format all Go code [group("lint")] fmt: - go fmt ./... + #!/usr/bin/env bash + set -euo pipefail + for mod in {{ALL_MODULES}}; do + go -C "$mod" fmt ./... + done # Run vulncheck on codebase [group("lint")] vulncheck: - govulncheck ./... + #!/usr/bin/env bash + set -euo pipefail + for mod in {{ALL_MODULES}}; do + echo "==> $mod" + (cd "$mod" && govulncheck ./...) + done # Run go mod tidy [group("lint")] mod-tidy: - go mod tidy - cd integration && go mod tidy + #!/usr/bin/env bash + set -euo pipefail + for mod in {{ALL_MODULES}}; do + go -C "$mod" mod tidy + done # Check if code is formatted [group("lint")] @@ -128,19 +172,19 @@ check-license: add-license: addlicense -l mpl -c "GoBlog Authors" ./ -# Run generator command with arguments +# Run generator command with arguments. Paths are relative to the repo root. [group('run')] -run-gen *ARGS: - go run {{MAIN_PATH}} gen {{ARGS}} +run-gen *ARGS: build + {{DIST_DIR}}/{{BINARY_NAME}} gen {{ARGS}} # Run serve command with optional arguments (defaults to example posts) [group('run')] -run-serve *ARGS: +run-serve *ARGS: build #!/usr/bin/env bash if [ -z "{{ARGS}}" ]; then - go run {{MAIN_PATH}} serve docs/example-posts + {{DIST_DIR}}/{{BINARY_NAME}} serve docs/example-posts else - go run {{MAIN_PATH}} serve {{ARGS}} + {{DIST_DIR}}/{{BINARY_NAME}} serve {{ARGS}} fi # Build the dockerfile for the current architecture From 80edf9965941e6178d7af604527ffa3608262865 Mon Sep 17 00:00:00 2001 From: Harry Day Date: Wed, 16 Sep 2026 22:54:19 +0000 Subject: [PATCH 3/4] docs(cli): document Homebrew and archive installs, drop go install - README: the `## CLI` section now covers `brew install harrydayexe/tap/goblog` and release archives; `go install` no longer works because the CLI module is not published. Adds fish completion and notes that Homebrew and the archives ship completions. - CONTRIBUTING: describe the three-module layout and why per-module commands are needed. - CLAUDE.md: record the module boundary and the new install channels. - version.go: the go install fallback path no longer applies. - justfile: `run-gen` invoked a `gen` subcommand that does not exist. Co-Authored-By: Claude Opus 5 --- CLAUDE.md | 12 +++++++++++- CONTRIBUTING.md | 26 ++++++++++++++++++++------ README.md | 39 ++++++++++++++++++++++++++++++++++----- cli/cmd/goblog/version.go | 10 ++++++---- justfile | 2 +- 5 files changed, 72 insertions(+), 17 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 71e6ff3..3444f8f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -5,10 +5,20 @@ All API changes (new exported types, functions, options, flags, or fields) must include corresponding documentation updates: godoc comments on the new symbols, relevant sections in `README.md`, and any package-level `doc.go` entries that reference available options or features. The README.md does not need to be flooded with documentation. Just the relevant information for a user to get started with the 3 methods to consume the library: -1. CLI tool via go install +1. CLI tool via Homebrew (`brew install harrydayexe/tap/goblog`) or a release archive — `go install` is not supported 2. Docker image via docker pull/run 3. The library itself +## Modules + +The repository holds three Go modules: + +- `/go.mod` — `github.com/harrydayexe/GoBlog/v2`, the public library (`pkg/...`). Its dependency graph is a budget: anything added here is inherited by every library consumer. +- `/cli/go.mod` — `github.com/harrydayexe/GoBlog/v2/cli`, the `goblog` binary (`cli/cmd/goblog`, `cli/internal/...`). Never published to the module proxy, so it can take any dependency it needs. Use `replace ... => ../` to reach the library. +- `/integration/go.mod` — black-box tests requiring Docker. + +CLI-only code belongs under `cli/`, never at the repo root. `go test ./...`, `go vet ./...` and friends stop at a nested `go.mod`, so run them per module (the `just` recipes already do). + ## Code Style When two approaches are functionally equivalent with no performance difference, prefer the one that is easier to read and understand. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6ec20c2..b7cfc38 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -25,14 +25,28 @@ an acknowledgement within a few days. ## Project Structure +The repository holds three Go modules: + ``` -cmd/goblog/ CLI entry point (main package) -pkg/ Public, importable packages -internal/ Private implementation details +go.mod github.com/harrydayexe/GoBlog/v2 (the public library) + pkg/ Public, importable packages +cli/go.mod github.com/harrydayexe/GoBlog/v2/cli (never published) + cli/cmd/goblog/ CLI entry point (main package) + cli/internal/ Private CLI implementation details +integration/go.mod github.com/harrydayexe/GoBlog/v2/integration docs/example-posts/ Sample Markdown posts for local runs .github/workflows/ CI: tests, license-header check, release ``` +The CLI lives in its own leaf module so that anything it imports stays out of +the library's dependency graph. Both `cli/` and `integration/` use +`replace github.com/harrydayexe/GoBlog/v2 => ../`, so they always build against +the library at the current commit — no tag or release is needed in between. + +Because `go test ./...` stops at a nested `go.mod` boundary, commands run from +the repo root cover the library only. The `just` recipes below iterate over +every module, and CI runs a job per module. + ## Development Setup **Prerequisites** @@ -79,9 +93,9 @@ Or run them directly: cd integration && go test -v -timeout 10m ./... ``` -The unit suite (`just test`) deliberately excludes the integration module — -`go test ./...` stops at the nested `go.mod` boundary — so unit feedback -stays fast in CI even when integration tests are slow. +The unit suite (`just test`) covers the library and CLI modules but +deliberately excludes the integration module, so unit feedback stays fast in +CI even when integration tests are slow. In CI the integration tests run in a dedicated `integration` job so the two stages report separately. diff --git a/README.md b/README.md index 58e3e09..f27483e 100644 --- a/README.md +++ b/README.md @@ -10,12 +10,30 @@ GoBlog is a blog generation and serving system for creating static blog feeds fr ## CLI -Install the `goblog` binary: +The `goblog` binary lives in its own Go module (`cli/`) that is not published to +the module proxy, so it is installed from a package manager or a release archive +rather than with `go install`. + +**Homebrew** (macOS and Linux): + +```bash +brew install harrydayexe/tap/goblog +``` + +**Release archive** — grab the archive for your platform from the +[releases page](https://github.com/harrydayexe/GoBlog/releases) and put the +binary on your `PATH`: ```bash -go install github.com/harrydayexe/GoBlog/v2/cmd/goblog@latest +curl -sSL https://github.com/harrydayexe/GoBlog/releases/latest/download/GoBlog_Linux_x86_64.tar.gz | tar -xz goblog +sudo install goblog /usr/local/bin/goblog ``` +Archives are published for Linux and macOS on `x86_64` and `arm64`. Windows +archives are built as well but Windows is not a supported install target. + +There is also a [Docker image](#docker) if you only need to serve a blog. + ```bash # Generate static files goblog generate posts/ output/ @@ -59,9 +77,11 @@ When `--base-url` is set, the server also exposes the generated feeds at `{root- ### Shell completion -`goblog` can generate shell completion scripts at runtime. After installing the -binary, source the appropriate script to enable tab-completion of subcommands and -flags. +The Homebrew cask installs bash, zsh, and fish completions for you. Release +archives ship the same scripts in a `completions/` directory. + +`goblog` can also generate them at runtime — source the appropriate script to +enable tab-completion of subcommands and flags. **Bash** — add to `~/.bashrc`: @@ -76,6 +96,12 @@ autoload -Uz compinit && compinit source <(goblog completion zsh) ``` +**Fish** — write the script to your completions directory: + +```fish +goblog completion fish > ~/.config/fish/completions/goblog.fish +``` + ## Docker The official image is [`harrydayexe/goblog`](https://hub.docker.com/repository/docker/harrydayexe/goblog/general). It runs `goblog serve --health-checks /posts` by default and exposes port `8080`. Health-check endpoints are enabled in the Docker image. File watching is off by default; pass `--watch` to enable it. @@ -127,6 +153,9 @@ Add GoBlog as a dependency: go get github.com/harrydayexe/GoBlog/v2 ``` +The CLI is a separate module (`cli/`) that is never published, so none of its +dependencies reach your build. + The main packages are: | Package | Summary | diff --git a/cli/cmd/goblog/version.go b/cli/cmd/goblog/version.go index 5a98764..50e676c 100644 --- a/cli/cmd/goblog/version.go +++ b/cli/cmd/goblog/version.go @@ -7,10 +7,12 @@ package main import "runtime/debug" // buildVersion returns the version string for the running binary. -// When GoReleaser ldflags are present the injected value is returned as-is. -// Otherwise the value is derived from the embedded Go module build metadata, -// so binaries installed via go install report the module tag. It is safe to -// call from multiple goroutines. +// Released binaries carry the version injected by GoReleaser's ldflags, which +// is returned as-is. The CLI module is never published to the module proxy, so +// a locally built binary has no module tag to fall back on; the embedded Go +// module build metadata is still consulted in case one is present, and +// otherwise the version reads "dev". It is safe to call from multiple +// goroutines. func buildVersion() string { if version != "dev" { return version diff --git a/justfile b/justfile index afe8b0e..168ce81 100644 --- a/justfile +++ b/justfile @@ -175,7 +175,7 @@ add-license: # Run generator command with arguments. Paths are relative to the repo root. [group('run')] run-gen *ARGS: build - {{DIST_DIR}}/{{BINARY_NAME}} gen {{ARGS}} + {{DIST_DIR}}/{{BINARY_NAME}} generate {{ARGS}} # Run serve command with optional arguments (defaults to example posts) [group('run')] From b1ec1598f465144ad084ac9edc2c04e3fc89217a Mon Sep 17 00:00:00 2001 From: Harry Day Date: Thu, 17 Sep 2026 00:12:10 +0100 Subject: [PATCH 4/4] ci: add updates for split cli and homebrew tap --- .github/workflows/release.yml | 8 ++++++++ .github/workflows/test.yml | 22 ++++++++++++++++++---- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index be07784..ebddc89 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -52,6 +52,13 @@ jobs: uses: actions/checkout@v6 with: fetch-depth: 0 + + - uses: actions/setup-go@v6 + with: + go-version: '1.26' + cache-dependency-path: | + go.sum + cli/go.sum - name: Create a new release uses: goreleaser/goreleaser-action@v7 @@ -60,6 +67,7 @@ jobs: args: release --clean env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }} # Use the tag that triggered this run; otherwise goreleaser may pick # a pre-release tag (e.g. v2.6.0-beta5) pointing at the same commit. GORELEASER_CURRENT_TAG: ${{ github.ref_name }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c7da401..3ee37d0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,24 +12,34 @@ on: jobs: test: + name: Unit tests (${{ matrix.module }}) runs-on: ubuntu-latest permissions: contents: read + strategy: + fail-fast: false + matrix: + # The library and the CLI are separate Go modules, so `./...` from the + # repo root does not reach the CLI. Each module is tested in its own job. + module: ['.', 'cli'] steps: - uses: actions/checkout@v6 - uses: actions/setup-go@v6 with: go-version: '1.26' + cache-dependency-path: | + go.sum + cli/go.sum - name: Run go vet - run: go vet ./... + run: go -C ${{ matrix.module }} vet ./... - name: Run tests - run: go test -v ./... + run: go -C ${{ matrix.module }} test -v ./... - name: Run tests with race detector - run: go test -race ./... + run: go -C ${{ matrix.module }} test -race ./... integration: name: Integration tests @@ -42,9 +52,13 @@ jobs: - uses: actions/setup-go@v6 with: go-version: '1.26' + cache-dependency-path: integration/go.sum + + - name: Run go vet + run: go -C integration vet ./... - name: Run integration tests - run: cd integration && go test -v -timeout 10m ./... + run: go -C integration test -race -v -timeout 15m ./... build-docker-image: name: Build Docker image