feat(build): support nested MkDocs configs

This commit is contained in:
Virgil 2026-04-01 18:23:19 +00:00
parent 303ec418e9
commit 7da8d7e843
4 changed files with 87 additions and 9 deletions

View file

@ -62,7 +62,8 @@ func (b *DocsBuilder) Build(ctx context.Context, cfg *build.Config, targets []bu
return nil, coreerr.E("DocsBuilder.Build", "failed to create output directory", err)
}
if !b.hasMkDocsConfig(cfg.FS, cfg.ProjectDir) {
configPath := b.resolveMkDocsConfigPath(cfg.FS, cfg.ProjectDir)
if configPath == "" {
return nil, coreerr.E("DocsBuilder.Build", "mkdocs.yml or mkdocs.yaml not found", nil)
}
@ -83,7 +84,7 @@ func (b *DocsBuilder) Build(ctx context.Context, cfg *build.Config, targets []bu
return artifacts, coreerr.E("DocsBuilder.Build", "failed to create site directory", err)
}
args := []string{"build", "--clean", "--site-dir", siteDir}
args := []string{"build", "--clean", "--site-dir", siteDir, "--config-file", configPath}
output, err := ax.CombinedOutput(ctx, cfg.ProjectDir, cfg.Env, mkdocsCommand, args...)
if err != nil {
return artifacts, coreerr.E("DocsBuilder.Build", "mkdocs build failed: "+output, err)
@ -104,9 +105,9 @@ func (b *DocsBuilder) Build(ctx context.Context, cfg *build.Config, targets []bu
return artifacts, nil
}
// hasMkDocsConfig reports whether the project contains a MkDocs config file.
func (b *DocsBuilder) hasMkDocsConfig(fs io.Medium, projectDir string) bool {
return fs.IsFile(ax.Join(projectDir, "mkdocs.yml")) || fs.IsFile(ax.Join(projectDir, "mkdocs.yaml"))
// resolveMkDocsConfigPath returns the MkDocs config file path if present.
func (b *DocsBuilder) resolveMkDocsConfigPath(fs io.Medium, projectDir string) string {
return build.ResolveMkDocsConfigPath(fs, projectDir)
}
// resolveMkDocsCli returns the executable path for the mkdocs CLI.

View file

@ -106,6 +106,41 @@ func TestDocs_DocsBuilderBuild_Good(t *testing.T) {
assert.Contains(t, string(content), "FOO=bar")
}
func TestDocs_DocsBuilderBuild_Good_NestedConfig(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("mkdocs test fixture uses a shell script")
}
dir := t.TempDir()
require.NoError(t, ax.MkdirAll(ax.Join(dir, "docs"), 0o755))
require.NoError(t, ax.WriteFile(ax.Join(dir, "docs", "mkdocs.yaml"), []byte("site_name: Demo\n"), 0o644))
binDir := t.TempDir()
mkdocsPath := ax.Join(binDir, "mkdocs")
script := "#!/bin/sh\nset -eu\nif [ -n \"${DOCS_BUILD_LOG_FILE:-}\" ]; then\n printf '%s\\n' \"$@\" >> \"${DOCS_BUILD_LOG_FILE}\"\nfi\nsite_dir=\"\"\nwhile [ $# -gt 0 ]; do\n if [ \"$1\" = \"--site-dir\" ]; then\n shift\n site_dir=\"$1\"\n fi\n shift\ndone\nmkdir -p \"$site_dir\"\nprintf '%s' 'demo docs' > \"$site_dir/index.html\"\n"
require.NoError(t, ax.WriteFile(mkdocsPath, []byte(script), 0o755))
t.Setenv("PATH", binDir+string(os.PathListSeparator)+os.Getenv("PATH"))
logPath := ax.Join(t.TempDir(), "docs.args")
cfg := &build.Config{
FS: io.Local,
ProjectDir: dir,
OutputDir: ax.Join(dir, "dist"),
Name: "demo-site",
Env: []string{"DOCS_BUILD_LOG_FILE=" + logPath},
}
builder := NewDocsBuilder()
artifacts, err := builder.Build(context.Background(), cfg, []build.Target{{OS: "linux", Arch: "amd64"}})
require.NoError(t, err)
require.Len(t, artifacts, 1)
content, err := ax.ReadFile(logPath)
require.NoError(t, err)
assert.Contains(t, string(content), "--config-file")
assert.Contains(t, string(content), "docs/mkdocs.yaml")
}
func TestDocs_DocsBuilderBuild_Bad(t *testing.T) {
builder := NewDocsBuilder()

View file

@ -15,6 +15,8 @@ const (
markerComposer = "composer.json"
markerMkDocs = "mkdocs.yml"
markerMkDocsYAML = "mkdocs.yaml"
markerDocsMkDocs = "docs/mkdocs.yml"
markerDocsMkDocsYAML = "docs/mkdocs.yaml"
markerPyProject = "pyproject.toml"
markerRequirements = "requirements.txt"
markerCargo = "Cargo.toml"
@ -72,6 +74,7 @@ func Discover(fs io.Medium, dir string) ([]ProjectType, error) {
projectType ProjectType
detected bool
}{
{ProjectTypeDocs, IsMkDocsProject(fs, dir)},
{ProjectTypeDocker, IsDockerProject(fs, dir)},
{ProjectTypeLinuxKit, IsLinuxKitProject(fs, dir)},
{ProjectTypeCPP, IsCPPProject(fs, dir)},
@ -137,12 +140,28 @@ func IsCPPProject(fs io.Medium, dir string) bool {
return fileExists(fs, ax.Join(dir, "CMakeLists.txt"))
}
// IsMkDocsProject checks for MkDocs config at the project root.
// IsMkDocsProject checks for MkDocs config at the project root or in docs/.
//
// ok := build.IsMkDocsProject(io.Local, ".")
func IsMkDocsProject(fs io.Medium, dir string) bool {
return fileExists(fs, ax.Join(dir, markerMkDocs)) ||
fileExists(fs, ax.Join(dir, markerMkDocsYAML))
return ResolveMkDocsConfigPath(fs, dir) != ""
}
// ResolveMkDocsConfigPath returns the first MkDocs config path that exists.
//
// configPath := build.ResolveMkDocsConfigPath(io.Local, ".")
func ResolveMkDocsConfigPath(fs io.Medium, dir string) string {
for _, path := range []string{
ax.Join(dir, markerMkDocs),
ax.Join(dir, markerMkDocsYAML),
ax.Join(dir, "docs", "mkdocs.yml"),
ax.Join(dir, "docs", "mkdocs.yaml"),
} {
if fileExists(fs, path) {
return path
}
}
return ""
}
// HasSubtreeNpm checks for package.json within depth 2 subdirectories.
@ -248,7 +267,8 @@ func DiscoverFull(fs io.Medium, dir string) (*DiscoveryResult, error) {
// Record raw marker presence
allMarkers := []string{
markerGoMod, markerWails, markerNodePackage, markerComposer,
markerMkDocs, markerMkDocsYAML, markerPyProject, markerRequirements, markerCargo,
markerMkDocs, markerMkDocsYAML, markerDocsMkDocs, markerDocsMkDocsYAML,
markerPyProject, markerRequirements, markerCargo,
"CMakeLists.txt", markerDockerfile, markerLinuxKitYAML, markerLinuxKitYAMLAlt,
markerTaskfileYML, markerTaskfileYAML, markerTaskfileBare,
markerTaskfileLowerYML, markerTaskfileLowerYAML,

View file

@ -66,6 +66,16 @@ func TestDiscovery_Discover_Good(t *testing.T) {
assert.Equal(t, []ProjectType{ProjectTypeDocs}, types)
})
t.Run("detects docs project in docs directory", func(t *testing.T) {
dir := t.TempDir()
require.NoError(t, ax.MkdirAll(ax.Join(dir, "docs"), 0755))
require.NoError(t, ax.WriteFile(ax.Join(dir, "docs", "mkdocs.yml"), []byte("site_name: Demo\n"), 0644))
types, err := Discover(fs, dir)
assert.NoError(t, err)
assert.Equal(t, []ProjectType{ProjectTypeDocs}, types)
})
t.Run("detects Python project with pyproject.toml", func(t *testing.T) {
dir := setupTestDir(t, "pyproject.toml")
types, err := Discover(fs, dir)
@ -609,6 +619,18 @@ func TestDiscovery_DiscoverFull_Good(t *testing.T) {
assert.True(t, result.Markers["mkdocs.yaml"])
})
t.Run("detects docs project markers in docs directory", func(t *testing.T) {
dir := t.TempDir()
require.NoError(t, ax.MkdirAll(ax.Join(dir, "docs"), 0755))
require.NoError(t, ax.WriteFile(ax.Join(dir, "docs", "mkdocs.yaml"), []byte("site_name: Demo\n"), 0644))
result, err := DiscoverFull(fs, dir)
require.NoError(t, err)
assert.Equal(t, []ProjectType{ProjectTypeDocs}, result.Types)
assert.Equal(t, "docs", result.PrimaryStack)
assert.True(t, result.Markers["docs/mkdocs.yaml"])
})
t.Run("detects Rust project markers", func(t *testing.T) {
dir := setupTestDir(t, "Cargo.toml")
result, err := DiscoverFull(fs, dir)