fix(pipeline): make the full module build work for module-only projects
build-binaries / build-binaries (pull_request) Successful in 2m13s
test-image / build-image (pull_request) Successful in 46s
test / test (pull_request) Successful in 1m23s

`crucible module build` runs the full pipeline (module + HAK producer +
topdata). A module-only project consumes prebuilt HAKs and has no
`paths.assets`, so the HAK stage crashed on the unset assets dir: the
music-credits walk hit `filepath.WalkDir("")` (`lstat : no such file or
directory`) and asset collection hit `filepath.Rel(root, "")`.

Guard both on an unset `AssetsDir()`: an absent assets tree simply means
no authored credits and no asset files to collect, so the HAK stage
no-ops cleanly while topdata generated-2DA assets still build. Honors
spec Decision 3 (canonical `crucible module build`, no `build-module`).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-24 17:21:59 +02:00
co-authored by Claude Opus 4.8
parent 5c46824ebd
commit e8e8ba421a
3 changed files with 99 additions and 29 deletions
+30 -25
View File
@@ -435,33 +435,38 @@ func writeCreditsArtifacts(p *project.Project, generated []musicCreditGroup) (cr
}
}
err := filepath.WalkDir(p.AssetsDir(), func(path string, d os.DirEntry, walkErr error) error {
if walkErr != nil {
return walkErr
}
if d.IsDir() {
// Authored credits.md files live under the assets tree. A project with no
// configured assets dir (e.g. a module that consumes prebuilt HAKs) has none
// to collect — walking "" would fail with `lstat : no such file or directory`.
if assetsDir := p.AssetsDir(); assetsDir != "" {
err := filepath.WalkDir(assetsDir, func(path string, d os.DirEntry, walkErr error) error {
if walkErr != nil {
return walkErr
}
if d.IsDir() {
return nil
}
if strings.ToLower(d.Name()) != "credits.md" {
return nil
}
entries, err := music.ParseCreditsMarkdown(path)
if err != nil {
return fmt.Errorf("parse credits %s: %w", path, err)
}
rel, err := filepath.Rel(p.Root, path)
if err != nil {
return err
}
sources = append(sources, music.CreditsSource{
Path: filepath.ToSlash(rel),
Kind: "authored",
Entries: entries,
})
return nil
}
if strings.ToLower(d.Name()) != "credits.md" {
return nil
}
entries, err := music.ParseCreditsMarkdown(path)
if err != nil {
return fmt.Errorf("parse credits %s: %w", path, err)
}
rel, err := filepath.Rel(p.Root, path)
if err != nil {
return err
}
sources = append(sources, music.CreditsSource{
Path: filepath.ToSlash(rel),
Kind: "authored",
Entries: entries,
})
return nil
})
if err != nil {
return creditsArtifactWriteResult{}, err
if err != nil {
return creditsArtifactWriteResult{}, err
}
}
sort.Slice(sources, func(i, j int) bool { return sources[i].Path < sources[j].Path })