feat(core): private-plugin SDK, PluginVisibility enum, and Go 1.26.4 bump
Add private-plugin RPCs (ListPrivatePlugins, DeletePrivatePlugin, DeletePrivatePluginVersion, ListPrivatePluginInstallSites) and ListMyAccounts to the proto/generated stubs; introduce PluginVisibility enum replacing the loose string field; add ModPlugin.Private + Coords() routing to @private/<name>@<version>; update ninja CLI to use VisibilityLabel helper; bump go directive to 1.26.4 for ABI alignment. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
06cabd6eb9
commit
264116f44e
@ -483,7 +483,7 @@ func newPluginStatusCmd() *cobra.Command {
|
||||
continue
|
||||
}
|
||||
for _, p := range gs.Msg.Plugins {
|
||||
fmt.Printf(" @%s/%s [%s]\n", s.Slug, p.Name, p.Visibility)
|
||||
fmt.Printf(" @%s/%s [%s]\n", s.Slug, p.Name, core.VisibilityLabel(p.Visibility))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
2
go.mod
2
go.mod
@ -1,6 +1,6 @@
|
||||
module git.dev.alexdunmow.com/block/core
|
||||
|
||||
go 1.26
|
||||
go 1.26.4
|
||||
|
||||
require (
|
||||
connectrpc.com/connect v1.20.0
|
||||
|
||||
@ -66,6 +66,18 @@ const (
|
||||
// PluginRegistryServiceListCategoriesProcedure is the fully-qualified name of the
|
||||
// PluginRegistryService's ListCategories RPC.
|
||||
PluginRegistryServiceListCategoriesProcedure = "/orchestrator.v1.PluginRegistryService/ListCategories"
|
||||
// PluginRegistryServiceListPrivatePluginsProcedure is the fully-qualified name of the
|
||||
// PluginRegistryService's ListPrivatePlugins RPC.
|
||||
PluginRegistryServiceListPrivatePluginsProcedure = "/orchestrator.v1.PluginRegistryService/ListPrivatePlugins"
|
||||
// PluginRegistryServiceDeletePrivatePluginProcedure is the fully-qualified name of the
|
||||
// PluginRegistryService's DeletePrivatePlugin RPC.
|
||||
PluginRegistryServiceDeletePrivatePluginProcedure = "/orchestrator.v1.PluginRegistryService/DeletePrivatePlugin"
|
||||
// PluginRegistryServiceDeletePrivatePluginVersionProcedure is the fully-qualified name of the
|
||||
// PluginRegistryService's DeletePrivatePluginVersion RPC.
|
||||
PluginRegistryServiceDeletePrivatePluginVersionProcedure = "/orchestrator.v1.PluginRegistryService/DeletePrivatePluginVersion"
|
||||
// PluginRegistryServiceListPrivatePluginInstallSitesProcedure is the fully-qualified name of the
|
||||
// PluginRegistryService's ListPrivatePluginInstallSites RPC.
|
||||
PluginRegistryServiceListPrivatePluginInstallSitesProcedure = "/orchestrator.v1.PluginRegistryService/ListPrivatePluginInstallSites"
|
||||
// PluginPublishServicePublishVersionProcedure is the fully-qualified name of the
|
||||
// PluginPublishService's PublishVersion RPC.
|
||||
PluginPublishServicePublishVersionProcedure = "/orchestrator.v1.PluginPublishService/PublishVersion"
|
||||
@ -87,6 +99,9 @@ const (
|
||||
// PluginAuthServiceWhoamiProcedure is the fully-qualified name of the PluginAuthService's Whoami
|
||||
// RPC.
|
||||
PluginAuthServiceWhoamiProcedure = "/orchestrator.v1.PluginAuthService/Whoami"
|
||||
// PluginAuthServiceListMyAccountsProcedure is the fully-qualified name of the PluginAuthService's
|
||||
// ListMyAccounts RPC.
|
||||
PluginAuthServiceListMyAccountsProcedure = "/orchestrator.v1.PluginAuthService/ListMyAccounts"
|
||||
)
|
||||
|
||||
// PluginScopeServiceClient is a client for the orchestrator.v1.PluginScopeService service.
|
||||
@ -219,6 +234,12 @@ type PluginRegistryServiceClient interface {
|
||||
GetVersion(context.Context, *connect.Request[v1.GetVersionRequest]) (*connect.Response[v1.GetVersionResponse], error)
|
||||
ResolveInstall(context.Context, *connect.Request[v1.ResolveInstallRequest]) (*connect.Response[v1.ResolveInstallResponse], error)
|
||||
ListCategories(context.Context, *connect.Request[v1.ListCategoriesRequest]) (*connect.Response[v1.ListCategoriesResponse], error)
|
||||
// Private-plugin RPCs. All require the caller to be a member of the target
|
||||
// account; the server resolves account membership from the bearer token.
|
||||
ListPrivatePlugins(context.Context, *connect.Request[v1.ListPrivatePluginsRequest]) (*connect.Response[v1.ListPrivatePluginsResponse], error)
|
||||
DeletePrivatePlugin(context.Context, *connect.Request[v1.DeletePrivatePluginRequest]) (*connect.Response[v1.DeletePrivatePluginResponse], error)
|
||||
DeletePrivatePluginVersion(context.Context, *connect.Request[v1.DeletePrivatePluginVersionRequest]) (*connect.Response[v1.DeletePrivatePluginVersionResponse], error)
|
||||
ListPrivatePluginInstallSites(context.Context, *connect.Request[v1.ListPrivatePluginInstallSitesRequest]) (*connect.Response[v1.ListPrivatePluginInstallSitesResponse], error)
|
||||
}
|
||||
|
||||
// NewPluginRegistryServiceClient constructs a client for the orchestrator.v1.PluginRegistryService
|
||||
@ -268,6 +289,30 @@ func NewPluginRegistryServiceClient(httpClient connect.HTTPClient, baseURL strin
|
||||
connect.WithSchema(pluginRegistryServiceMethods.ByName("ListCategories")),
|
||||
connect.WithClientOptions(opts...),
|
||||
),
|
||||
listPrivatePlugins: connect.NewClient[v1.ListPrivatePluginsRequest, v1.ListPrivatePluginsResponse](
|
||||
httpClient,
|
||||
baseURL+PluginRegistryServiceListPrivatePluginsProcedure,
|
||||
connect.WithSchema(pluginRegistryServiceMethods.ByName("ListPrivatePlugins")),
|
||||
connect.WithClientOptions(opts...),
|
||||
),
|
||||
deletePrivatePlugin: connect.NewClient[v1.DeletePrivatePluginRequest, v1.DeletePrivatePluginResponse](
|
||||
httpClient,
|
||||
baseURL+PluginRegistryServiceDeletePrivatePluginProcedure,
|
||||
connect.WithSchema(pluginRegistryServiceMethods.ByName("DeletePrivatePlugin")),
|
||||
connect.WithClientOptions(opts...),
|
||||
),
|
||||
deletePrivatePluginVersion: connect.NewClient[v1.DeletePrivatePluginVersionRequest, v1.DeletePrivatePluginVersionResponse](
|
||||
httpClient,
|
||||
baseURL+PluginRegistryServiceDeletePrivatePluginVersionProcedure,
|
||||
connect.WithSchema(pluginRegistryServiceMethods.ByName("DeletePrivatePluginVersion")),
|
||||
connect.WithClientOptions(opts...),
|
||||
),
|
||||
listPrivatePluginInstallSites: connect.NewClient[v1.ListPrivatePluginInstallSitesRequest, v1.ListPrivatePluginInstallSitesResponse](
|
||||
httpClient,
|
||||
baseURL+PluginRegistryServiceListPrivatePluginInstallSitesProcedure,
|
||||
connect.WithSchema(pluginRegistryServiceMethods.ByName("ListPrivatePluginInstallSites")),
|
||||
connect.WithClientOptions(opts...),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
@ -279,6 +324,10 @@ type pluginRegistryServiceClient struct {
|
||||
getVersion *connect.Client[v1.GetVersionRequest, v1.GetVersionResponse]
|
||||
resolveInstall *connect.Client[v1.ResolveInstallRequest, v1.ResolveInstallResponse]
|
||||
listCategories *connect.Client[v1.ListCategoriesRequest, v1.ListCategoriesResponse]
|
||||
listPrivatePlugins *connect.Client[v1.ListPrivatePluginsRequest, v1.ListPrivatePluginsResponse]
|
||||
deletePrivatePlugin *connect.Client[v1.DeletePrivatePluginRequest, v1.DeletePrivatePluginResponse]
|
||||
deletePrivatePluginVersion *connect.Client[v1.DeletePrivatePluginVersionRequest, v1.DeletePrivatePluginVersionResponse]
|
||||
listPrivatePluginInstallSites *connect.Client[v1.ListPrivatePluginInstallSitesRequest, v1.ListPrivatePluginInstallSitesResponse]
|
||||
}
|
||||
|
||||
// CreatePlugin calls orchestrator.v1.PluginRegistryService.CreatePlugin.
|
||||
@ -311,6 +360,28 @@ func (c *pluginRegistryServiceClient) ListCategories(ctx context.Context, req *c
|
||||
return c.listCategories.CallUnary(ctx, req)
|
||||
}
|
||||
|
||||
// ListPrivatePlugins calls orchestrator.v1.PluginRegistryService.ListPrivatePlugins.
|
||||
func (c *pluginRegistryServiceClient) ListPrivatePlugins(ctx context.Context, req *connect.Request[v1.ListPrivatePluginsRequest]) (*connect.Response[v1.ListPrivatePluginsResponse], error) {
|
||||
return c.listPrivatePlugins.CallUnary(ctx, req)
|
||||
}
|
||||
|
||||
// DeletePrivatePlugin calls orchestrator.v1.PluginRegistryService.DeletePrivatePlugin.
|
||||
func (c *pluginRegistryServiceClient) DeletePrivatePlugin(ctx context.Context, req *connect.Request[v1.DeletePrivatePluginRequest]) (*connect.Response[v1.DeletePrivatePluginResponse], error) {
|
||||
return c.deletePrivatePlugin.CallUnary(ctx, req)
|
||||
}
|
||||
|
||||
// DeletePrivatePluginVersion calls
|
||||
// orchestrator.v1.PluginRegistryService.DeletePrivatePluginVersion.
|
||||
func (c *pluginRegistryServiceClient) DeletePrivatePluginVersion(ctx context.Context, req *connect.Request[v1.DeletePrivatePluginVersionRequest]) (*connect.Response[v1.DeletePrivatePluginVersionResponse], error) {
|
||||
return c.deletePrivatePluginVersion.CallUnary(ctx, req)
|
||||
}
|
||||
|
||||
// ListPrivatePluginInstallSites calls
|
||||
// orchestrator.v1.PluginRegistryService.ListPrivatePluginInstallSites.
|
||||
func (c *pluginRegistryServiceClient) ListPrivatePluginInstallSites(ctx context.Context, req *connect.Request[v1.ListPrivatePluginInstallSitesRequest]) (*connect.Response[v1.ListPrivatePluginInstallSitesResponse], error) {
|
||||
return c.listPrivatePluginInstallSites.CallUnary(ctx, req)
|
||||
}
|
||||
|
||||
// PluginRegistryServiceHandler is an implementation of the orchestrator.v1.PluginRegistryService
|
||||
// service.
|
||||
type PluginRegistryServiceHandler interface {
|
||||
@ -320,6 +391,12 @@ type PluginRegistryServiceHandler interface {
|
||||
GetVersion(context.Context, *connect.Request[v1.GetVersionRequest]) (*connect.Response[v1.GetVersionResponse], error)
|
||||
ResolveInstall(context.Context, *connect.Request[v1.ResolveInstallRequest]) (*connect.Response[v1.ResolveInstallResponse], error)
|
||||
ListCategories(context.Context, *connect.Request[v1.ListCategoriesRequest]) (*connect.Response[v1.ListCategoriesResponse], error)
|
||||
// Private-plugin RPCs. All require the caller to be a member of the target
|
||||
// account; the server resolves account membership from the bearer token.
|
||||
ListPrivatePlugins(context.Context, *connect.Request[v1.ListPrivatePluginsRequest]) (*connect.Response[v1.ListPrivatePluginsResponse], error)
|
||||
DeletePrivatePlugin(context.Context, *connect.Request[v1.DeletePrivatePluginRequest]) (*connect.Response[v1.DeletePrivatePluginResponse], error)
|
||||
DeletePrivatePluginVersion(context.Context, *connect.Request[v1.DeletePrivatePluginVersionRequest]) (*connect.Response[v1.DeletePrivatePluginVersionResponse], error)
|
||||
ListPrivatePluginInstallSites(context.Context, *connect.Request[v1.ListPrivatePluginInstallSitesRequest]) (*connect.Response[v1.ListPrivatePluginInstallSitesResponse], error)
|
||||
}
|
||||
|
||||
// NewPluginRegistryServiceHandler builds an HTTP handler from the service implementation. It
|
||||
@ -365,6 +442,30 @@ func NewPluginRegistryServiceHandler(svc PluginRegistryServiceHandler, opts ...c
|
||||
connect.WithSchema(pluginRegistryServiceMethods.ByName("ListCategories")),
|
||||
connect.WithHandlerOptions(opts...),
|
||||
)
|
||||
pluginRegistryServiceListPrivatePluginsHandler := connect.NewUnaryHandler(
|
||||
PluginRegistryServiceListPrivatePluginsProcedure,
|
||||
svc.ListPrivatePlugins,
|
||||
connect.WithSchema(pluginRegistryServiceMethods.ByName("ListPrivatePlugins")),
|
||||
connect.WithHandlerOptions(opts...),
|
||||
)
|
||||
pluginRegistryServiceDeletePrivatePluginHandler := connect.NewUnaryHandler(
|
||||
PluginRegistryServiceDeletePrivatePluginProcedure,
|
||||
svc.DeletePrivatePlugin,
|
||||
connect.WithSchema(pluginRegistryServiceMethods.ByName("DeletePrivatePlugin")),
|
||||
connect.WithHandlerOptions(opts...),
|
||||
)
|
||||
pluginRegistryServiceDeletePrivatePluginVersionHandler := connect.NewUnaryHandler(
|
||||
PluginRegistryServiceDeletePrivatePluginVersionProcedure,
|
||||
svc.DeletePrivatePluginVersion,
|
||||
connect.WithSchema(pluginRegistryServiceMethods.ByName("DeletePrivatePluginVersion")),
|
||||
connect.WithHandlerOptions(opts...),
|
||||
)
|
||||
pluginRegistryServiceListPrivatePluginInstallSitesHandler := connect.NewUnaryHandler(
|
||||
PluginRegistryServiceListPrivatePluginInstallSitesProcedure,
|
||||
svc.ListPrivatePluginInstallSites,
|
||||
connect.WithSchema(pluginRegistryServiceMethods.ByName("ListPrivatePluginInstallSites")),
|
||||
connect.WithHandlerOptions(opts...),
|
||||
)
|
||||
return "/orchestrator.v1.PluginRegistryService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case PluginRegistryServiceCreatePluginProcedure:
|
||||
@ -379,6 +480,14 @@ func NewPluginRegistryServiceHandler(svc PluginRegistryServiceHandler, opts ...c
|
||||
pluginRegistryServiceResolveInstallHandler.ServeHTTP(w, r)
|
||||
case PluginRegistryServiceListCategoriesProcedure:
|
||||
pluginRegistryServiceListCategoriesHandler.ServeHTTP(w, r)
|
||||
case PluginRegistryServiceListPrivatePluginsProcedure:
|
||||
pluginRegistryServiceListPrivatePluginsHandler.ServeHTTP(w, r)
|
||||
case PluginRegistryServiceDeletePrivatePluginProcedure:
|
||||
pluginRegistryServiceDeletePrivatePluginHandler.ServeHTTP(w, r)
|
||||
case PluginRegistryServiceDeletePrivatePluginVersionProcedure:
|
||||
pluginRegistryServiceDeletePrivatePluginVersionHandler.ServeHTTP(w, r)
|
||||
case PluginRegistryServiceListPrivatePluginInstallSitesProcedure:
|
||||
pluginRegistryServiceListPrivatePluginInstallSitesHandler.ServeHTTP(w, r)
|
||||
default:
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
@ -412,6 +521,22 @@ func (UnimplementedPluginRegistryServiceHandler) ListCategories(context.Context,
|
||||
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("orchestrator.v1.PluginRegistryService.ListCategories is not implemented"))
|
||||
}
|
||||
|
||||
func (UnimplementedPluginRegistryServiceHandler) ListPrivatePlugins(context.Context, *connect.Request[v1.ListPrivatePluginsRequest]) (*connect.Response[v1.ListPrivatePluginsResponse], error) {
|
||||
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("orchestrator.v1.PluginRegistryService.ListPrivatePlugins is not implemented"))
|
||||
}
|
||||
|
||||
func (UnimplementedPluginRegistryServiceHandler) DeletePrivatePlugin(context.Context, *connect.Request[v1.DeletePrivatePluginRequest]) (*connect.Response[v1.DeletePrivatePluginResponse], error) {
|
||||
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("orchestrator.v1.PluginRegistryService.DeletePrivatePlugin is not implemented"))
|
||||
}
|
||||
|
||||
func (UnimplementedPluginRegistryServiceHandler) DeletePrivatePluginVersion(context.Context, *connect.Request[v1.DeletePrivatePluginVersionRequest]) (*connect.Response[v1.DeletePrivatePluginVersionResponse], error) {
|
||||
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("orchestrator.v1.PluginRegistryService.DeletePrivatePluginVersion is not implemented"))
|
||||
}
|
||||
|
||||
func (UnimplementedPluginRegistryServiceHandler) ListPrivatePluginInstallSites(context.Context, *connect.Request[v1.ListPrivatePluginInstallSitesRequest]) (*connect.Response[v1.ListPrivatePluginInstallSitesResponse], error) {
|
||||
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("orchestrator.v1.PluginRegistryService.ListPrivatePluginInstallSites is not implemented"))
|
||||
}
|
||||
|
||||
// PluginPublishServiceClient is a client for the orchestrator.v1.PluginPublishService service.
|
||||
type PluginPublishServiceClient interface {
|
||||
PublishVersion(context.Context, *connect.Request[v1.PublishVersionRequest]) (*connect.Response[v1.PublishVersionResponse], error)
|
||||
@ -491,6 +616,10 @@ type PluginAuthServiceClient interface {
|
||||
DenyDevice(context.Context, *connect.Request[v1.DenyDeviceRequest]) (*connect.Response[v1.DenyDeviceResponse], error)
|
||||
GetDeviceStatus(context.Context, *connect.Request[v1.GetDeviceStatusRequest]) (*connect.Response[v1.GetDeviceStatusResponse], error)
|
||||
Whoami(context.Context, *connect.Request[v1.WhoamiRequest]) (*connect.Response[v1.WhoamiResponse], error)
|
||||
// ListMyAccounts returns the accounts the authenticated user belongs to.
|
||||
// Used by `ninja login` (forced selection when multiple) and
|
||||
// `ninja account list`.
|
||||
ListMyAccounts(context.Context, *connect.Request[v1.ListMyAccountsRequest]) (*connect.Response[v1.ListMyAccountsResponse], error)
|
||||
}
|
||||
|
||||
// NewPluginAuthServiceClient constructs a client for the orchestrator.v1.PluginAuthService service.
|
||||
@ -540,6 +669,12 @@ func NewPluginAuthServiceClient(httpClient connect.HTTPClient, baseURL string, o
|
||||
connect.WithSchema(pluginAuthServiceMethods.ByName("Whoami")),
|
||||
connect.WithClientOptions(opts...),
|
||||
),
|
||||
listMyAccounts: connect.NewClient[v1.ListMyAccountsRequest, v1.ListMyAccountsResponse](
|
||||
httpClient,
|
||||
baseURL+PluginAuthServiceListMyAccountsProcedure,
|
||||
connect.WithSchema(pluginAuthServiceMethods.ByName("ListMyAccounts")),
|
||||
connect.WithClientOptions(opts...),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
@ -551,6 +686,7 @@ type pluginAuthServiceClient struct {
|
||||
denyDevice *connect.Client[v1.DenyDeviceRequest, v1.DenyDeviceResponse]
|
||||
getDeviceStatus *connect.Client[v1.GetDeviceStatusRequest, v1.GetDeviceStatusResponse]
|
||||
whoami *connect.Client[v1.WhoamiRequest, v1.WhoamiResponse]
|
||||
listMyAccounts *connect.Client[v1.ListMyAccountsRequest, v1.ListMyAccountsResponse]
|
||||
}
|
||||
|
||||
// StartDevice calls orchestrator.v1.PluginAuthService.StartDevice.
|
||||
@ -583,6 +719,11 @@ func (c *pluginAuthServiceClient) Whoami(ctx context.Context, req *connect.Reque
|
||||
return c.whoami.CallUnary(ctx, req)
|
||||
}
|
||||
|
||||
// ListMyAccounts calls orchestrator.v1.PluginAuthService.ListMyAccounts.
|
||||
func (c *pluginAuthServiceClient) ListMyAccounts(ctx context.Context, req *connect.Request[v1.ListMyAccountsRequest]) (*connect.Response[v1.ListMyAccountsResponse], error) {
|
||||
return c.listMyAccounts.CallUnary(ctx, req)
|
||||
}
|
||||
|
||||
// PluginAuthServiceHandler is an implementation of the orchestrator.v1.PluginAuthService service.
|
||||
type PluginAuthServiceHandler interface {
|
||||
StartDevice(context.Context, *connect.Request[v1.StartDeviceRequest]) (*connect.Response[v1.StartDeviceResponse], error)
|
||||
@ -591,6 +732,10 @@ type PluginAuthServiceHandler interface {
|
||||
DenyDevice(context.Context, *connect.Request[v1.DenyDeviceRequest]) (*connect.Response[v1.DenyDeviceResponse], error)
|
||||
GetDeviceStatus(context.Context, *connect.Request[v1.GetDeviceStatusRequest]) (*connect.Response[v1.GetDeviceStatusResponse], error)
|
||||
Whoami(context.Context, *connect.Request[v1.WhoamiRequest]) (*connect.Response[v1.WhoamiResponse], error)
|
||||
// ListMyAccounts returns the accounts the authenticated user belongs to.
|
||||
// Used by `ninja login` (forced selection when multiple) and
|
||||
// `ninja account list`.
|
||||
ListMyAccounts(context.Context, *connect.Request[v1.ListMyAccountsRequest]) (*connect.Response[v1.ListMyAccountsResponse], error)
|
||||
}
|
||||
|
||||
// NewPluginAuthServiceHandler builds an HTTP handler from the service implementation. It returns
|
||||
@ -636,6 +781,12 @@ func NewPluginAuthServiceHandler(svc PluginAuthServiceHandler, opts ...connect.H
|
||||
connect.WithSchema(pluginAuthServiceMethods.ByName("Whoami")),
|
||||
connect.WithHandlerOptions(opts...),
|
||||
)
|
||||
pluginAuthServiceListMyAccountsHandler := connect.NewUnaryHandler(
|
||||
PluginAuthServiceListMyAccountsProcedure,
|
||||
svc.ListMyAccounts,
|
||||
connect.WithSchema(pluginAuthServiceMethods.ByName("ListMyAccounts")),
|
||||
connect.WithHandlerOptions(opts...),
|
||||
)
|
||||
return "/orchestrator.v1.PluginAuthService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case PluginAuthServiceStartDeviceProcedure:
|
||||
@ -650,6 +801,8 @@ func NewPluginAuthServiceHandler(svc PluginAuthServiceHandler, opts ...connect.H
|
||||
pluginAuthServiceGetDeviceStatusHandler.ServeHTTP(w, r)
|
||||
case PluginAuthServiceWhoamiProcedure:
|
||||
pluginAuthServiceWhoamiHandler.ServeHTTP(w, r)
|
||||
case PluginAuthServiceListMyAccountsProcedure:
|
||||
pluginAuthServiceListMyAccountsHandler.ServeHTTP(w, r)
|
||||
default:
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
@ -682,3 +835,7 @@ func (UnimplementedPluginAuthServiceHandler) GetDeviceStatus(context.Context, *c
|
||||
func (UnimplementedPluginAuthServiceHandler) Whoami(context.Context, *connect.Request[v1.WhoamiRequest]) (*connect.Response[v1.WhoamiResponse], error) {
|
||||
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("orchestrator.v1.PluginAuthService.Whoami is not implemented"))
|
||||
}
|
||||
|
||||
func (UnimplementedPluginAuthServiceHandler) ListMyAccounts(context.Context, *connect.Request[v1.ListMyAccountsRequest]) (*connect.Response[v1.ListMyAccountsResponse], error) {
|
||||
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("orchestrator.v1.PluginAuthService.ListMyAccounts is not implemented"))
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -4,8 +4,31 @@ import (
|
||||
"strings"
|
||||
|
||||
tomlpkg "github.com/BurntSushi/toml"
|
||||
|
||||
v1 "git.dev.alexdunmow.com/block/core/internal/api/orchestrator/v1"
|
||||
)
|
||||
|
||||
// VisibilityLabel returns the lowercase, user-facing form of a PluginVisibility
|
||||
// value (e.g. "private", "public", "under_review"). The default for the
|
||||
// UNSPECIFIED zero value is "public", matching pre-enum behaviour for plugins
|
||||
// that don't carry an explicit visibility.
|
||||
func VisibilityLabel(v v1.PluginVisibility) string {
|
||||
switch v {
|
||||
case v1.PluginVisibility_PLUGIN_VISIBILITY_PRIVATE:
|
||||
return "private"
|
||||
case v1.PluginVisibility_PLUGIN_VISIBILITY_UNDER_REVIEW:
|
||||
return "under_review"
|
||||
case v1.PluginVisibility_PLUGIN_VISIBILITY_PUBLIC,
|
||||
v1.PluginVisibility_PLUGIN_VISIBILITY_UNSPECIFIED:
|
||||
return "public"
|
||||
case v1.PluginVisibility_PLUGIN_VISIBILITY_REJECTED:
|
||||
return "rejected"
|
||||
case v1.PluginVisibility_PLUGIN_VISIBILITY_TAKEN_DOWN:
|
||||
return "taken_down"
|
||||
}
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
type ModFile struct {
|
||||
Plugin ModPlugin `toml:"plugin"`
|
||||
Compatibility *ModCompat `toml:"compatibility"`
|
||||
@ -30,6 +53,11 @@ type ModPlugin struct {
|
||||
Version string `toml:"version"`
|
||||
Kind string `toml:"kind,omitempty"`
|
||||
Categories []string `toml:"categories,omitempty"`
|
||||
// Private marks the plugin as account-scoped. When true, Coords() returns
|
||||
// the canonical "@private/<name>@<version>" form regardless of the Scope
|
||||
// field, and the publish flow attributes the plugin to the publisher's
|
||||
// active account rather than to a public scope.
|
||||
Private bool `toml:"private,omitempty"`
|
||||
}
|
||||
|
||||
type ModCompat struct {
|
||||
@ -60,9 +88,17 @@ func (m *ModFile) Coords() string {
|
||||
if m == nil {
|
||||
return ""
|
||||
}
|
||||
if m.Plugin.Private {
|
||||
return "@" + PrivateScopeSlug + "/" + m.Plugin.Name + "@" + m.Plugin.Version
|
||||
}
|
||||
scope := strings.TrimPrefix(m.Plugin.Scope, "@")
|
||||
if scope == "" {
|
||||
return m.Plugin.Name + "@" + m.Plugin.Version
|
||||
}
|
||||
return "@" + scope + "/" + m.Plugin.Name + "@" + m.Plugin.Version
|
||||
}
|
||||
|
||||
// PrivateScopeSlug is the registry namespace under which all private plugins
|
||||
// live. Coords for private plugins resolve to "@private/<name>@<version>";
|
||||
// uniqueness is enforced by (owner_account_id, name), not by the slug.
|
||||
const PrivateScopeSlug = "private"
|
||||
|
||||
@ -114,6 +114,61 @@ func TestCoords_AcceptsScopeWithOrWithoutAt(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseModFull_PrivateField(t *testing.T) {
|
||||
src := []byte(`
|
||||
[plugin]
|
||||
name = "internal-tool"
|
||||
version = "0.1.0"
|
||||
private = true
|
||||
`)
|
||||
m, err := ParseModFull(src)
|
||||
if err != nil {
|
||||
t.Fatalf("ParseModFull err: %v", err)
|
||||
}
|
||||
if !m.Plugin.Private {
|
||||
t.Errorf("Private = false, want true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseModFull_PrivateDefaultsFalse(t *testing.T) {
|
||||
src := []byte(`
|
||||
[plugin]
|
||||
name = "public-thing"
|
||||
scope = "themes"
|
||||
version = "0.1.0"
|
||||
`)
|
||||
m, err := ParseModFull(src)
|
||||
if err != nil {
|
||||
t.Fatalf("ParseModFull err: %v", err)
|
||||
}
|
||||
if m.Plugin.Private {
|
||||
t.Errorf("Private = true, want false (default)")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCoords_PrivateOverridesScope(t *testing.T) {
|
||||
m := &ModFile{Plugin: ModPlugin{
|
||||
Name: "myplugin",
|
||||
Scope: "@themes",
|
||||
Version: "0.1.0",
|
||||
Private: true,
|
||||
}}
|
||||
if got := m.Coords(); got != "@private/myplugin@0.1.0" {
|
||||
t.Errorf("Coords() = %q, want @private/myplugin@0.1.0", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCoords_PrivateNoScope(t *testing.T) {
|
||||
m := &ModFile{Plugin: ModPlugin{
|
||||
Name: "myplugin",
|
||||
Version: "0.1.0",
|
||||
Private: true,
|
||||
}}
|
||||
if got := m.Coords(); got != "@private/myplugin@0.1.0" {
|
||||
t.Errorf("Coords() = %q, want @private/myplugin@0.1.0", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseModFull_RequiresAndCompat(t *testing.T) {
|
||||
src := []byte(`
|
||||
[plugin]
|
||||
|
||||
@ -21,6 +21,13 @@ service PluginRegistryService {
|
||||
rpc GetVersion(GetVersionRequest) returns (GetVersionResponse);
|
||||
rpc ResolveInstall(ResolveInstallRequest) returns (ResolveInstallResponse);
|
||||
rpc ListCategories(ListCategoriesRequest) returns (ListCategoriesResponse);
|
||||
|
||||
// Private-plugin RPCs. All require the caller to be a member of the target
|
||||
// account; the server resolves account membership from the bearer token.
|
||||
rpc ListPrivatePlugins(ListPrivatePluginsRequest) returns (ListPrivatePluginsResponse);
|
||||
rpc DeletePrivatePlugin(DeletePrivatePluginRequest) returns (DeletePrivatePluginResponse);
|
||||
rpc DeletePrivatePluginVersion(DeletePrivatePluginVersionRequest) returns (DeletePrivatePluginVersionResponse);
|
||||
rpc ListPrivatePluginInstallSites(ListPrivatePluginInstallSitesRequest) returns (ListPrivatePluginInstallSitesResponse);
|
||||
}
|
||||
|
||||
// PluginPublishService is called by the ninja CLI to publish a version.
|
||||
@ -36,6 +43,10 @@ service PluginAuthService {
|
||||
rpc DenyDevice(DenyDeviceRequest) returns (DenyDeviceResponse);
|
||||
rpc GetDeviceStatus(GetDeviceStatusRequest) returns (GetDeviceStatusResponse);
|
||||
rpc Whoami(WhoamiRequest) returns (WhoamiResponse);
|
||||
// ListMyAccounts returns the accounts the authenticated user belongs to.
|
||||
// Used by `ninja login` (forced selection when multiple) and
|
||||
// `ninja account list`.
|
||||
rpc ListMyAccounts(ListMyAccountsRequest) returns (ListMyAccountsResponse);
|
||||
}
|
||||
|
||||
// --- Shared messages ---
|
||||
@ -47,11 +58,24 @@ message Scope {
|
||||
google.protobuf.Timestamp created_at = 4;
|
||||
}
|
||||
|
||||
// PluginVisibility is the lifecycle/access state of a plugin in the registry.
|
||||
// PRIVATE plugins are scoped to a single account; PUBLIC plugins are visible
|
||||
// to all. UNDER_REVIEW / REJECTED / TAKEN_DOWN are public-registry moderation
|
||||
// states and do not apply to private plugins.
|
||||
enum PluginVisibility {
|
||||
PLUGIN_VISIBILITY_UNSPECIFIED = 0;
|
||||
PLUGIN_VISIBILITY_PRIVATE = 1;
|
||||
PLUGIN_VISIBILITY_UNDER_REVIEW = 2;
|
||||
PLUGIN_VISIBILITY_PUBLIC = 3;
|
||||
PLUGIN_VISIBILITY_REJECTED = 4;
|
||||
PLUGIN_VISIBILITY_TAKEN_DOWN = 5;
|
||||
}
|
||||
|
||||
message Plugin {
|
||||
string id = 1;
|
||||
string scope_slug = 2;
|
||||
string name = 3;
|
||||
string visibility = 4;
|
||||
PluginVisibility visibility = 4;
|
||||
bool premium = 5;
|
||||
string description = 6;
|
||||
string homepage_url = 7;
|
||||
@ -59,6 +83,19 @@ message Plugin {
|
||||
google.protobuf.Timestamp updated_at = 9;
|
||||
string kind = 10;
|
||||
string display_name = 11;
|
||||
// owner_account_id is set for private plugins and identifies the account
|
||||
// that owns the plugin. Empty for public plugins.
|
||||
string owner_account_id = 12;
|
||||
}
|
||||
|
||||
// Account is a multi-user organisational unit in the orchestrator. The
|
||||
// authenticated user may belong to one or more accounts via account_users.
|
||||
message Account {
|
||||
string id = 1;
|
||||
string slug = 2;
|
||||
string display_name = 3;
|
||||
// role of the authenticated caller in this account: owner | manager | member.
|
||||
string role = 4;
|
||||
}
|
||||
|
||||
message Category {
|
||||
@ -103,6 +140,14 @@ message CreatePluginRequest {
|
||||
string kind = 4;
|
||||
repeated string categories = 5;
|
||||
string display_name = 6;
|
||||
// visibility is the requested visibility of the plugin. UNSPECIFIED defers
|
||||
// to the registry's default (public for explicit scopes, private for the
|
||||
// "@private" sentinel).
|
||||
PluginVisibility visibility = 7;
|
||||
// active_account_id is required when visibility = PRIVATE; the server
|
||||
// verifies the caller is a member of this account before creating the
|
||||
// private plugin under it.
|
||||
string active_account_id = 8;
|
||||
}
|
||||
message CreatePluginResponse {
|
||||
Plugin plugin = 1;
|
||||
@ -127,6 +172,58 @@ message ListPluginsResponse { repeated Plugin plugins = 1; }
|
||||
message ListCategoriesRequest {}
|
||||
message ListCategoriesResponse { repeated Category categories = 1; }
|
||||
|
||||
// --- Private plugin RPC messages ---
|
||||
|
||||
message ListPrivatePluginsRequest {
|
||||
// account_id selects which account's private plugins to list. The caller
|
||||
// must be a member of this account.
|
||||
string account_id = 1;
|
||||
}
|
||||
message ListPrivatePluginsResponse {
|
||||
repeated PrivatePluginSummary plugins = 1;
|
||||
}
|
||||
|
||||
// PrivatePluginSummary is the row shape used by the publisher dashboard and
|
||||
// by the CMS "Private" installer tab. It bundles a Plugin with the latest
|
||||
// version per channel and an installation count.
|
||||
message PrivatePluginSummary {
|
||||
Plugin plugin = 1;
|
||||
// channel_versions maps channel name -> latest version string published on
|
||||
// that channel (e.g. "latest" -> "0.3.0", "beta" -> "0.4.0-beta.1").
|
||||
map<string,string> channel_versions = 2;
|
||||
// installed_site_count is the number of CMS sites in the owning account
|
||||
// that currently have any version of this plugin installed. Used to gate
|
||||
// delete-plugin in the dashboard.
|
||||
int32 installed_site_count = 3;
|
||||
}
|
||||
|
||||
message DeletePrivatePluginRequest {
|
||||
string account_id = 1;
|
||||
string plugin_name = 2;
|
||||
}
|
||||
message DeletePrivatePluginResponse {}
|
||||
|
||||
message DeletePrivatePluginVersionRequest {
|
||||
string account_id = 1;
|
||||
string plugin_name = 2;
|
||||
string version = 3;
|
||||
}
|
||||
message DeletePrivatePluginVersionResponse {}
|
||||
|
||||
message ListPrivatePluginInstallSitesRequest {
|
||||
string account_id = 1;
|
||||
string plugin_name = 2;
|
||||
}
|
||||
message ListPrivatePluginInstallSitesResponse {
|
||||
repeated PrivatePluginInstallSite sites = 1;
|
||||
}
|
||||
message PrivatePluginInstallSite {
|
||||
string instance_id = 1;
|
||||
string site_display_name = 2;
|
||||
string installed_version = 3;
|
||||
string pinned_channel = 4;
|
||||
}
|
||||
|
||||
message GetVersionRequest {
|
||||
string scope_slug = 1;
|
||||
string plugin_name = 2;
|
||||
@ -211,3 +308,8 @@ message WhoamiResponse {
|
||||
string email = 2;
|
||||
string display_name = 3;
|
||||
}
|
||||
|
||||
message ListMyAccountsRequest {}
|
||||
message ListMyAccountsResponse {
|
||||
repeated Account accounts = 1;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user