From fb02573ff328acc4fabde0d45b47052d9663b09d Mon Sep 17 00:00:00 2001 From: Amen <2668333+amenophis1er@users.noreply.github.com> Date: Sun, 13 Sep 2026 22:29:48 -0400 Subject: [PATCH] feat: scaffold studio.url; migrate warns on the dead studio.settings/fields keys The generated config sets studio.url from STUDIO_URL, which since Trokky 3.1 is the one place the server learns where the Studio lives. The studio-mount warning gains a case for studio.settings/fields, ignored since 3.1, and its patterns now tolerate a nested branding block ahead of the key they look for, which FUCEC's real config has. --- internal/migrate/warnings.go | 6 ++++-- internal/migrate/warnings_test.go | 10 +++++++++- internal/scaffold/scaffold_test.go | 3 +++ internal/scaffold/templates.go | 2 ++ 4 files changed, 18 insertions(+), 3 deletions(-) diff --git a/internal/migrate/warnings.go b/internal/migrate/warnings.go index 633e180..8b5001f 100644 --- a/internal/migrate/warnings.go +++ b/internal/migrate/warnings.go @@ -518,10 +518,12 @@ var studioMountPatterns = []struct { "getMountedStudioPath() is gone; the Studio path is wherever you app.use() studioRouter()"}, {regexp.MustCompile(`\.\s*studioPath\b`), "getInfo()/getMountedPaths() no longer report studioPath; keep your own constant for the mount path"}, - {regexp.MustCompile(`studio\s*:\s*\{[^}]*\b(enabled|path|requireAuth|apiUrl)\s*:`), + {regexp.MustCompile(`studio\s*:\s*\{(?:[^{}]|\{[^{}]*\})*\b(enabled|path|requireAuth|apiUrl)\s*:`), "the studio config block may only hold branding, fields, settings and session; remove enabled/path/requireAuth/apiUrl (serving the Studio is server.ts's job now)"}, - {regexp.MustCompile(`studio\s*:\s*\{[^}]*\bstructure\b`), + {regexp.MustCompile(`studio\s*:\s*\{(?:[^{}]|\{[^{}]*\})*\bstructure\b`), "move structure out of the studio block to the top level of the config: the server enforces singletons from it"}, + {regexp.MustCompile(`studio\s*:\s*\{(?:[^{}]|\{[^{}]*\})*\b(settings|fields)\s*:`), + "studio.settings and studio.fields are ignored since 3.1 (nothing ever read them); delete them. The studio block is url, branding and session"}, {regexp.MustCompile(`@trokky/studio/dist/server/assets\.js`), "'@trokky/studio/dist/server/assets.js' is no longer exported; use studioRouter() from '@trokky/studio/express'"}, } diff --git a/internal/migrate/warnings_test.go b/internal/migrate/warnings_test.go index 9041b07..82028b0 100644 --- a/internal/migrate/warnings_test.go +++ b/internal/migrate/warnings_test.go @@ -424,6 +424,14 @@ func TestScanStudioMountFlagsOldConfigKeys(t *testing.T) { if len(got) != 2 { t.Fatalf("expected the removed-keys warning and the structure warning, got %d: %+v", len(got), got) } + nested := ScanStudioMount("trokky.config.ts", "studio: {\n enabled: true,\n branding: { title: 'X', theme: 'light' },\n structure: fucecStructure,\n}") + if len(nested) != 2 { + t.Errorf("a branding block before structure must not hide it: %+v", nested) + } + dead := ScanStudioMount("trokky.config.ts", "studio: {\n branding: {},\n settings: { pageSize: 20 },\n}") + if len(dead) != 1 || !strings.Contains(dead[0].Message, "ignored since 3.1") { + t.Errorf("expected the dead-keys warning alone, got %+v", dead) + } if !strings.Contains(got[0].Message, "enabled/path/requireAuth/apiUrl") || !strings.Contains(got[1].Message, "top level") { t.Errorf("unexpected messages: %+v", got) } @@ -432,7 +440,7 @@ func TestScanStudioMountFlagsOldConfigKeys(t *testing.T) { func TestScanStudioMountIsQuietOnTheNewShape(t *testing.T) { src := `import { studioRouter } from '@trokky/studio/express' server.app.use('/studio', studioRouter({ apiPath: '/api' })) -export default { structure, studio: { branding: { title: 'CMS' } } } +export default { structure, studio: { url: process.env.STUDIO_URL, branding: { title: 'CMS' } } } ` if got := ScanStudioMount("server.ts", src); len(got) != 0 { t.Errorf("new shape must not warn: %+v", got) diff --git a/internal/scaffold/scaffold_test.go b/internal/scaffold/scaffold_test.go index 5259545..ef6078b 100644 --- a/internal/scaffold/scaffold_test.go +++ b/internal/scaffold/scaffold_test.go @@ -169,6 +169,9 @@ func TestScaffoldTemplates(t *testing.T) { if cfg.Studio != StudioEmbedded && hasRouter { t.Errorf("server.ts mounts studioRouter for studio=%s:\n%s", cfg.Studio, server) } + if cfg.Studio != StudioNone && !strings.Contains(files["scaffold:trokky.config.ts"], "url: process.env.STUDIO_URL") { + t.Errorf("trokky.config.ts studio block lacks url: process.env.STUDIO_URL:\n%s", files["scaffold:trokky.config.ts"]) + } if cfg.Studio != StudioNone && !strings.Contains(server, "structure: trokkyConfig.structure") { t.Errorf("server.ts does not forward the top-level structure:\n%s", server) } diff --git a/internal/scaffold/templates.go b/internal/scaffold/templates.go index 1e565fe..2e7259c 100644 --- a/internal/scaffold/templates.go +++ b/internal/scaffold/templates.go @@ -282,6 +282,8 @@ const mailFromName = process.env.EMAIL_FROM_NAME || '%s' studioConfig = fmt.Sprintf(` structure, studio: { + // Public Studio URL: the device-flow approval page and email links derive from it. + url: process.env.STUDIO_URL, branding: { title: %q }, },`, cfg.Name) }