Skip to content

Commit 2fb9430

Browse files
author
FTMahringer
committed
feat(registry): add custom registry sources
1 parent 9d1c9cb commit 2fb9430

19 files changed

Lines changed: 864 additions & 37 deletions
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# v2.6.2-dev
2+
3+
Custom Registry Sources for the v2.7.0 plugin ecosystem milestone.
4+
5+
## Added
6+
7+
- Persistent custom registry source records for GitHub, GitLab, Forgejo, and Nexus/Maven-style sources.
8+
- Registry source API endpoints:
9+
- `GET /api/registry/sources`
10+
- `POST /api/registry/sources`
11+
- `PUT /api/registry/sources/{id}`
12+
- `DELETE /api/registry/sources/{id}`
13+
- `POST /api/registry/sources/{id}/sync`
14+
- Source visibility controls:
15+
- `store` entries appear in marketplace/store listings
16+
- `internal` entries stay available to registry/admin flows but are hidden from store listings
17+
- Per-source trust tier and sync interval fields.
18+
- CLI commands:
19+
- `synapse registry sources`
20+
- `synapse registry add-source`
21+
- `synapse registry remove-source`
22+
- `synapse registry sync-source`
23+
24+
## Validation
25+
26+
```bash
27+
cd packages/core
28+
mvn -q test
29+
30+
cd ../cli
31+
go test ./...
32+
```

CHANGELOG.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
---
1111

12+
## [v2.6.2-dev] - 2026-05-18
13+
14+
**Custom Registry Sources**
15+
16+
### Added
17+
- Persistent custom registry sources with GitHub, GitLab, Forgejo, and Nexus/Maven source types.
18+
- Source visibility controls for `internal` vs `store` marketplace exposure.
19+
- Per-source trust tier and sync interval configuration.
20+
- Registry source APIs for listing, adding, updating, removing, and syncing sources.
21+
- `synapse registry add-source`, `sources`, `remove-source`, and `sync-source` CLI commands.
22+
23+
### Changed
24+
- Store listings now hide entries from registry sources marked `internal`.
25+
- Core and dashboard versions are now `2.6.2-dev`.
26+
- The `v2.6.2-dev` roadmap step is now complete.
27+
28+
---
29+
1230
## [v2.6.1-dev] - 2026-05-18
1331

1432
**Plugin Registry Service Core**

docs/roadmaps/SYNAPSE_V3_IMPLEMENTATION_ROADMAP.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -450,7 +450,7 @@ security controls. Completes the plugin platform story.
450450
- `synapse registry sync` CLI command
451451
- **Exit**: Registry serves metadata + JARs; standalone starts as separate container
452452

453-
#### v2.6.2-dev: Custom Registry Sources
453+
#### v2.6.2-dev: Custom Registry Sources (completed)
454454
- Add/remove sources: GitHub, GitLab, Forgejo, Nexus/Maven
455455
- Visibility toggle: `internal` or `store`
456456
- Per-source trust tier and sync interval

packages/cli/cmd/registry.go

Lines changed: 151 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,134 @@ var registrySyncCmd = &cobra.Command{
131131
},
132132
}
133133

134+
var registrySourcesCmd = &cobra.Command{
135+
Use: "sources",
136+
Short: "List registered registry sources",
137+
RunE: func(cmd *cobra.Command, args []string) error {
138+
client := clientFromCmd(cmd)
139+
rawJSON, _ := cmd.Flags().GetBool("json")
140+
141+
var resp []map[string]any
142+
if err := client.Get("/api/registry/sources", &resp); err != nil {
143+
return err
144+
}
145+
146+
if rawJSON {
147+
tuioutput.JSON(resp)
148+
return nil
149+
}
150+
151+
tuioutput.Header(fmt.Sprintf("Registry Sources (%d)", len(resp)))
152+
tuioutput.Separator()
153+
for _, source := range resp {
154+
tuioutput.Row(
155+
fmt.Sprint(source["id"]),
156+
fmt.Sprint(source["type"]),
157+
fmt.Sprint(source["visibility"]),
158+
fmt.Sprint(source["trustTier"]),
159+
fmt.Sprint(source["url"]),
160+
)
161+
}
162+
return nil
163+
},
164+
}
165+
166+
var registryAddSourceCmd = &cobra.Command{
167+
Use: "add-source",
168+
Short: "Register a custom plugin registry source",
169+
RunE: func(cmd *cobra.Command, args []string) error {
170+
client := clientFromCmd(cmd)
171+
rawJSON, _ := cmd.Flags().GetBool("json")
172+
id, _ := cmd.Flags().GetString("id")
173+
name, _ := cmd.Flags().GetString("name")
174+
sourceType, _ := cmd.Flags().GetString("type")
175+
sourceURL, _ := cmd.Flags().GetString("url")
176+
visibility, _ := cmd.Flags().GetString("visibility")
177+
trustTier, _ := cmd.Flags().GetString("trust-tier")
178+
syncInterval, _ := cmd.Flags().GetString("sync-interval")
179+
token, _ := cmd.Flags().GetString("token")
180+
enabled, _ := cmd.Flags().GetBool("enabled")
181+
182+
body := map[string]any{
183+
"id": id,
184+
"name": name,
185+
"type": sourceType,
186+
"url": sourceURL,
187+
"visibility": visibility,
188+
"trustTier": trustTier,
189+
"syncInterval": syncInterval,
190+
"enabled": enabled,
191+
}
192+
if token != "" {
193+
body["token"] = token
194+
}
195+
196+
var resp map[string]any
197+
if err := client.Post("/api/registry/sources", body, &resp); err != nil {
198+
return err
199+
}
200+
201+
if rawJSON {
202+
tuioutput.JSON(resp)
203+
return nil
204+
}
205+
206+
tuioutput.OK(fmt.Sprintf("Registry source registered: %v (%v)", resp["id"], resp["type"]))
207+
return nil
208+
},
209+
}
210+
211+
var registryRemoveSourceCmd = &cobra.Command{
212+
Use: "remove-source <sourceId>",
213+
Short: "Remove a custom plugin registry source",
214+
Args: cobra.ExactArgs(1),
215+
RunE: func(cmd *cobra.Command, args []string) error {
216+
client := clientFromCmd(cmd)
217+
rawJSON, _ := cmd.Flags().GetBool("json")
218+
219+
if err := client.Delete("/api/registry/sources/" + args[0]); err != nil {
220+
return err
221+
}
222+
223+
if rawJSON {
224+
tuioutput.JSON(map[string]any{"id": args[0], "removed": true})
225+
return nil
226+
}
227+
228+
tuioutput.OK("Registry source removed: " + args[0])
229+
return nil
230+
},
231+
}
232+
233+
var registrySyncSourceCmd = &cobra.Command{
234+
Use: "sync-source <sourceId>",
235+
Short: "Sync one custom registry source now",
236+
Args: cobra.ExactArgs(1),
237+
RunE: func(cmd *cobra.Command, args []string) error {
238+
client := clientFromCmd(cmd)
239+
rawJSON, _ := cmd.Flags().GetBool("json")
240+
241+
var resp map[string]any
242+
if err := client.Post("/api/registry/sources/"+args[0]+"/sync", map[string]any{}, &resp); err != nil {
243+
return err
244+
}
245+
246+
if rawJSON {
247+
tuioutput.JSON(resp)
248+
return nil
249+
}
250+
251+
tuioutput.OK(
252+
fmt.Sprintf(
253+
"Registry source synced: %v entries from %v",
254+
resp["entriesSynced"],
255+
resp["source"],
256+
),
257+
)
258+
return nil
259+
},
260+
}
261+
134262
var registryStatusCmd = &cobra.Command{
135263
Use: "status",
136264
Short: "Show registry service status",
@@ -166,7 +294,29 @@ func init() {
166294
registryListCmd.Flags().String("type", "", "Filter by type")
167295
registryListCmd.Flags().Bool("compatible", false, "Only show entries compatible with this SYNAPSE version")
168296

169-
registryCmd.AddCommand(registryListCmd, registryVersionsCmd, registrySyncCmd, registryStatusCmd)
297+
registryAddSourceCmd.Flags().String("id", "", "Source ID (defaults to a slug from name)")
298+
registryAddSourceCmd.Flags().String("name", "", "Source display name")
299+
registryAddSourceCmd.Flags().String("type", "", "Source type: github, gitlab, forgejo, or maven")
300+
registryAddSourceCmd.Flags().String("url", "", "Registry source URL")
301+
registryAddSourceCmd.Flags().String("visibility", "store", "Visibility: store or internal")
302+
registryAddSourceCmd.Flags().String("trust-tier", "community", "Trust tier: official, community, or private")
303+
registryAddSourceCmd.Flags().String("sync-interval", "PT1H", "Sync interval as an ISO-8601 duration")
304+
registryAddSourceCmd.Flags().String("token", "", "Bearer token for private registry sources")
305+
registryAddSourceCmd.Flags().Bool("enabled", true, "Enable source after registration")
306+
registryAddSourceCmd.MarkFlagRequired("name")
307+
registryAddSourceCmd.MarkFlagRequired("type")
308+
registryAddSourceCmd.MarkFlagRequired("url")
309+
310+
registryCmd.AddCommand(
311+
registryListCmd,
312+
registryVersionsCmd,
313+
registrySyncCmd,
314+
registryStatusCmd,
315+
registrySourcesCmd,
316+
registryAddSourceCmd,
317+
registryRemoveSourceCmd,
318+
registrySyncSourceCmd,
319+
)
170320
commandfeatures.BindSubcommandListing(registryCmd)
171321
rootCmd.AddCommand(registryCmd)
172322
}

packages/cli/cmd/registry_test.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,61 @@ func TestRegistryListCommand(t *testing.T) {
7979
}
8080
}
8181

82+
func TestRegistryAddSourceCommand(t *testing.T) {
83+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
84+
if r.URL.Path != "/api/registry/sources" {
85+
t.Fatalf("unexpected path: %s", r.URL.Path)
86+
}
87+
if r.Method != http.MethodPost {
88+
t.Fatalf("unexpected method: %s", r.Method)
89+
}
90+
var body map[string]any
91+
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
92+
t.Fatalf("decode body: %v", err)
93+
}
94+
if body["type"] != "gitlab" || body["visibility"] != "store" || body["trustTier"] != "private" {
95+
t.Fatalf("unexpected body: %#v", body)
96+
}
97+
_ = json.NewEncoder(w).Encode(map[string]any{
98+
"id": "company-gitlab",
99+
"type": "gitlab",
100+
"visibility": "store",
101+
"trustTier": "private",
102+
})
103+
}))
104+
defer server.Close()
105+
106+
cmd, _, err := newIsolatedRootCommand()
107+
if err != nil {
108+
t.Fatalf("newIsolatedRootCommand: %v", err)
109+
}
110+
111+
got := captureStdout(t, func() {
112+
cmd.SetArgs([]string{
113+
"registry",
114+
"add-source",
115+
"--host",
116+
server.URL,
117+
"--name",
118+
"Company GitLab",
119+
"--type",
120+
"gitlab",
121+
"--url",
122+
"https://gitlab.example.com/plugins/registry.yml",
123+
"--visibility",
124+
"store",
125+
"--trust-tier",
126+
"private",
127+
})
128+
if err := cmd.Execute(); err != nil {
129+
t.Fatalf("execute: %v", err)
130+
}
131+
})
132+
if !strings.Contains(got, "Registry source registered: company-gitlab") {
133+
t.Fatalf("unexpected output: %q", got)
134+
}
135+
}
136+
82137
func captureStdout(t *testing.T, fn func()) string {
83138
t.Helper()
84139

packages/core/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
<groupId>dev.synapse</groupId>
1717
<artifactId>synapse-core</artifactId>
18-
<version>2.6.1-dev</version>
18+
<version>2.6.2-dev</version>
1919
<name>synapse-core</name>
2020
<description>SYNAPSE Spring Boot backend</description>
2121

0 commit comments

Comments
 (0)