From 601e89becefafa88f616f5f71b531b61c458fbe4 Mon Sep 17 00:00:00 2001 From: vickydotbat Date: Thu, 7 May 2026 07:41:22 +0200 Subject: [PATCH] Packaging Fix --- internal/app/app.go | 6 ++- internal/app/app_test.go | 81 ++++++++++++++++++++++++++++++++ internal/topdata/top_package.go | 10 ++-- internal/topdata/topdata_test.go | 30 +++++++++++- 4 files changed, 122 insertions(+), 5 deletions(-) create mode 100644 internal/app/app_test.go diff --git a/internal/app/app.go b/internal/app/app.go index 5183fb3..a0dffd6 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -565,7 +565,11 @@ func runBuildTopPackage(ctx context) error { return err } - result, err := topdata.BuildAndPackageWithOptions(p, opts, func(message string) { + if opts.BuildWiki { + return fmt.Errorf("--wiki is not supported with build-top-package; use build-topdata when wiki generation is required") + } + + result, err := topdata.BuildPackage(p, func(message string) { fmt.Fprintf(ctx.stdout, "[build-top-package] %s\n", message) }) if err != nil { diff --git a/internal/app/app_test.go b/internal/app/app_test.go new file mode 100644 index 0000000..3c45b43 --- /dev/null +++ b/internal/app/app_test.go @@ -0,0 +1,81 @@ +package app + +import ( + "bytes" + "os" + "path/filepath" + "strings" + "testing" + "time" +) + +func TestRunBuildTopPackageUsesCachedCompiledOutputs(t *testing.T) { + root := t.TempDir() + mkdirAll(t, filepath.Join(root, "build")) + mkdirAll(t, filepath.Join(root, ".cache", "2da")) + mkdirAll(t, filepath.Join(root, "topdata", "assets", "gui")) + mkdirAll(t, filepath.Join(root, "topdata", "data", "repadjust")) + + writeFile(t, filepath.Join(root, "nwn-tool.json"), `{ + "module": { + "name": "Test Module", + "resref": "test_module" + }, + "topdata": { + "source": "topdata", + "build": ".cache", + "package_hak": "sow_top.hak", + "package_tlk": "sow_tlk.tlk" + } +}`+"\n") + writeFile(t, filepath.Join(root, ".cache", "2da", "repadjust.2da"), "2DA V2.0\n\n Label\n0 TEST_LABEL\n") + writeFile(t, filepath.Join(root, "build", "sow_tlk.tlk"), "compiled tlk") + writeFile(t, filepath.Join(root, "topdata", "assets", "gui", "testicon.png"), "icon-data") + writeFile(t, filepath.Join(root, "topdata", "data", "repadjust", "base.json"), "{ this is intentionally invalid json }\n") + + sourceTime := time.Now().Add(-2 * time.Hour) + outputTime := time.Now().Add(-1 * time.Hour) + setFileTime(t, filepath.Join(root, "topdata", "assets", "gui", "testicon.png"), sourceTime) + setFileTime(t, filepath.Join(root, "topdata", "data", "repadjust", "base.json"), sourceTime) + setFileTime(t, filepath.Join(root, ".cache", "2da", "repadjust.2da"), outputTime) + setFileTime(t, filepath.Join(root, "build", "sow_tlk.tlk"), outputTime) + + var stdout bytes.Buffer + ctx := context{ + stdout: &stdout, + stderr: &bytes.Buffer{}, + cwd: root, + args: []string{"build-top-package"}, + } + + if err := runBuildTopPackage(ctx); err != nil { + t.Fatalf("runBuildTopPackage failed: %v", err) + } + if !strings.Contains(stdout.String(), "top package hak:") { + t.Fatalf("expected build-top-package output, got %q", stdout.String()) + } + if _, err := os.Stat(filepath.Join(root, "build", "sow_top.hak")); err != nil { + t.Fatalf("expected packaged hak output: %v", err) + } +} + +func mkdirAll(t *testing.T, path string) { + t.Helper() + if err := os.MkdirAll(path, 0o755); err != nil { + t.Fatalf("mkdir %s: %v", path, err) + } +} + +func writeFile(t *testing.T, path, content string) { + t.Helper() + if err := os.WriteFile(path, []byte(content), 0o644); err != nil { + t.Fatalf("write %s: %v", path, err) + } +} + +func setFileTime(t *testing.T, path string, modTime time.Time) { + t.Helper() + if err := os.Chtimes(path, modTime, modTime); err != nil { + t.Fatalf("set file time %s: %v", path, err) + } +} diff --git a/internal/topdata/top_package.go b/internal/topdata/top_package.go index fb2c6b8..d171bf9 100644 --- a/internal/topdata/top_package.go +++ b/internal/topdata/top_package.go @@ -165,6 +165,7 @@ func packageBuiltTopData(p *project.Project, nativeResult BuildResult, progress func collectTopPackageResources(p *project.Project, compiled2DADir string) ([]erf.Resource, int, error) { resourceByKey := map[string]erf.Resource{} + resourceSourceByKey := map[string]string{} entries, err := os.ReadDir(compiled2DADir) if err != nil { @@ -179,7 +180,9 @@ func collectTopPackageResources(p *project.Project, compiled2DADir string) ([]er if err != nil { return nil, 0, err } - resourceByKey[topPackageResourceKey(resource)] = resource + key := topPackageResourceKey(resource) + resourceByKey[key] = resource + resourceSourceByKey[key] = path } assetFiles := 0 @@ -210,10 +213,11 @@ func collectTopPackageResources(p *project.Project, compiled2DADir string) ([]er return err } key := topPackageResourceKey(resource) - if _, ok := resourceByKey[key]; ok { - return fmt.Errorf("topdata asset %s collides with generated top package resource %s", path, key) + if existing, ok := resourceSourceByKey[key]; ok { + return fmt.Errorf("topdata asset %s collides with %s for top package resource %s", path, existing, key) } resourceByKey[key] = resource + resourceSourceByKey[key] = path assetFiles++ return nil }) diff --git a/internal/topdata/topdata_test.go b/internal/topdata/topdata_test.go index 6c51da1..c3e8755 100644 --- a/internal/topdata/topdata_test.go +++ b/internal/topdata/topdata_test.go @@ -1,6 +1,7 @@ package topdata import ( + "bytes" "encoding/json" "net/http" "net/http/httptest" @@ -9670,7 +9671,8 @@ func TestBuildAndPackageIncludesCompiled2DAAndTopAssets(t *testing.T) { "columns": ["Label"], "rows": [{"id": 0, "Label": "TEST_LABEL"}] }`+"\n") - writeFile(t, filepath.Join(root, "topdata", "assets", "gui", "testicon.png"), "icon-data") + pngPayload := bytes.Repeat([]byte("png-payload-"), 4096) + writeBytes(t, filepath.Join(root, "topdata", "assets", "gui", "testicon.png"), pngPayload) proj := testProject(root) proj.Config.TopData.ReferenceBuilder = "" @@ -9708,6 +9710,9 @@ func TestBuildAndPackageIncludesCompiled2DAAndTopAssets(t *testing.T) { found2DA = true } if key == "testicon.png" { + if !bytes.Equal(resource.Data, pngPayload) { + t.Fatalf("expected testicon.png payload to be preserved, got %d bytes", len(resource.Data)) + } foundAsset = true } if ext == "tlk" { @@ -9728,6 +9733,22 @@ func TestBuildAndPackageIncludesCompiled2DAAndTopAssets(t *testing.T) { } } +func TestCollectTopPackageResourcesRejectsDuplicateTopAssetKeys(t *testing.T) { + root := testProjectRoot(t) + mkdirAll(t, filepath.Join(root, ".cache", "2da")) + mkdirAll(t, filepath.Join(root, "topdata", "assets", "gui", "icons")) + mkdirAll(t, filepath.Join(root, "topdata", "assets", "gui", "portraits")) + writeFile(t, filepath.Join(root, "topdata", "base_dialog.json"), "{}\n") + writeFile(t, filepath.Join(root, ".cache", "2da", "repadjust.2da"), "2DA V2.0\n\n Label\n0 TEST_LABEL\n") + writeFile(t, filepath.Join(root, "topdata", "assets", "gui", "icons", "duplicate.png"), "icon-a") + writeFile(t, filepath.Join(root, "topdata", "assets", "gui", "portraits", "duplicate.png"), "icon-b") + + proj := testProject(root) + if _, _, err := collectTopPackageResources(proj, filepath.Join(root, ".cache", "2da")); err == nil || !strings.Contains(err.Error(), "collides with") { + t.Fatalf("expected duplicate topdata asset collision, got %v", err) + } +} + func TestBuildAndPackageHonorsConfiguredTopPackageOutputNames(t *testing.T) { root := testProjectRoot(t) mkdirAll(t, filepath.Join(root, "topdata", "data", "repadjust")) @@ -10214,3 +10235,10 @@ func writeFile(t *testing.T, path, content string) { t.Fatalf("write %s: %v", path, err) } } + +func writeBytes(t *testing.T, path string, content []byte) { + t.Helper() + if err := os.WriteFile(path, content, 0o755); err != nil { + t.Fatalf("write %s: %v", path, err) + } +}