Skip to content

Commit 1d00ee7

Browse files
committed
fix: rewrite Foundry module manifest URLs via JSON parsing instead of string replace
The old string-replace approach silently failed when the on-disk module.json had been edited (e.g. by the admin version update handler), leaving the original GitHub URLs in place — causing Foundry to report "no manifest found" since those URLs don't resolve. Now parses module.json as JSON and always overwrites the manifest and download fields with the instance's BaseURL. Also rewrites module.json inside the served zip so the installed module checks the correct URL for updates. https://claude.ai/code/session_01XMwxFR8BCi5XvgaSVMSBZB
1 parent 767a6b3 commit 1d00ee7

1 file changed

Lines changed: 36 additions & 10 deletions

File tree

internal/app/app.go

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package app
66
import (
77
"archive/zip"
88
"database/sql"
9+
"encoding/json"
910
"errors"
1011
"fmt"
1112
"io/fs"
@@ -288,30 +289,33 @@ func (a *App) serveFoundryModuleManifest(c echo.Context) error {
288289
return echo.NewHTTPError(http.StatusNotFound, "module.json not found")
289290
}
290291

291-
baseURL := strings.TrimRight(a.Config.BaseURL, "/")
292-
content := string(data)
292+
var manifest map[string]any
293+
if err := json.Unmarshal(data, &manifest); err != nil {
294+
return echo.NewHTTPError(http.StatusInternalServerError, "invalid module.json")
295+
}
293296

294-
// Replace manifest and download URLs with this Chronicle instance's URLs.
295-
content = strings.Replace(content,
296-
`"manifest": "https://raw.githubusercontent.com/keyxmakerx/Chronicle/main/foundry-module/module.json"`,
297-
fmt.Sprintf(`"manifest": "%s/foundry-module/module.json"`, baseURL), 1)
298-
content = strings.Replace(content,
299-
`"download": "https://github.com/keyxmakerx/Chronicle/releases/download/foundry-v0.1.0/chronicle-sync.zip"`,
300-
fmt.Sprintf(`"download": "%s/foundry-module/chronicle-sync.zip"`, baseURL), 1)
297+
// Always rewrite manifest and download URLs to point at this instance.
298+
baseURL := strings.TrimRight(a.Config.BaseURL, "/")
299+
manifest["manifest"] = baseURL + "/foundry-module/module.json"
300+
manifest["download"] = baseURL + "/foundry-module/chronicle-sync.zip"
301301

302-
return c.JSONBlob(http.StatusOK, []byte(content))
302+
return c.JSON(http.StatusOK, manifest)
303303
}
304304

305305
// serveFoundryModuleZip dynamically zips the foundry-module/ directory and
306306
// serves it as chronicle-sync.zip. Foundry VTT downloads this during module
307307
// installation. The zip contains all files under a chronicle-sync/ root
308308
// directory, which is the expected structure for Foundry module archives.
309+
// The module.json inside the zip gets its manifest/download URLs rewritten
310+
// to point at this Chronicle instance.
309311
func (a *App) serveFoundryModuleZip(c echo.Context) error {
310312
moduleDir := "foundry-module"
311313
if _, err := os.Stat(moduleDir); err != nil {
312314
return echo.NewHTTPError(http.StatusNotFound, "foundry module directory not found")
313315
}
314316

317+
baseURL := strings.TrimRight(a.Config.BaseURL, "/")
318+
315319
c.Response().Header().Set("Content-Type", "application/zip")
316320
c.Response().Header().Set("Content-Disposition", "attachment; filename=chronicle-sync.zip")
317321
c.Response().WriteHeader(http.StatusOK)
@@ -343,6 +347,16 @@ func (a *App) serveFoundryModuleZip(c echo.Context) error {
343347
if err != nil {
344348
return err
345349
}
350+
351+
// Rewrite module.json URLs inside the zip so the installed module
352+
// points back at this Chronicle instance for updates.
353+
if name == "module.json" {
354+
data, err = a.rewriteModuleManifest(data, baseURL)
355+
if err != nil {
356+
return err
357+
}
358+
}
359+
346360
_, err = w.Write(data)
347361
return err
348362
})
@@ -352,6 +366,18 @@ func (a *App) serveFoundryModuleZip(c echo.Context) error {
352366
return zw.Close()
353367
}
354368

369+
// rewriteModuleManifest parses module.json and rewrites the manifest and
370+
// download URLs to point at the given baseURL.
371+
func (a *App) rewriteModuleManifest(data []byte, baseURL string) ([]byte, error) {
372+
var manifest map[string]any
373+
if err := json.Unmarshal(data, &manifest); err != nil {
374+
return data, err
375+
}
376+
manifest["manifest"] = baseURL + "/foundry-module/module.json"
377+
manifest["download"] = baseURL + "/foundry-module/chronicle-sync.zip"
378+
return json.MarshalIndent(manifest, "", " ")
379+
}
380+
355381
// Start begins listening for HTTP requests on the configured port.
356382
func (a *App) Start() error {
357383
addr := fmt.Sprintf(":%d", a.Config.Port)

0 commit comments

Comments
 (0)