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>
105 lines
3.8 KiB
Go
105 lines
3.8 KiB
Go
package plugin
|
|
|
|
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"`
|
|
Requires []ModRequirement `toml:"requires"`
|
|
}
|
|
|
|
type ModPlugin struct {
|
|
// Name is the lowercase identifier used for the plugin slug in URLs and DB
|
|
// lookups. The CLI normalises this on write; the registry normalises on
|
|
// create. Use DisplayName for human-readable presentation.
|
|
Name string `toml:"name"`
|
|
// DisplayName is the human-readable form (any case). Optional; if empty
|
|
// the registry falls back to the input name with its original case.
|
|
DisplayName string `toml:"display_name,omitempty"`
|
|
// Description is the short summary surfaced in the registry. Optional.
|
|
Description string `toml:"description,omitempty"`
|
|
// Scope is the plugin owner namespace as it appears in plugin.mod. It may
|
|
// include the leading "@" (e.g. "@themes") or omit it (e.g. "themes") —
|
|
// both forms are accepted. Consumers comparing scopes should trim the "@"
|
|
// before comparing; use ModFile.Coords() for a normalised display string.
|
|
Scope string `toml:"scope"`
|
|
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 {
|
|
BlockCore string `toml:"block_core"`
|
|
}
|
|
|
|
type ModRequirement struct {
|
|
Name string `toml:"name"`
|
|
Version string `toml:"version"`
|
|
}
|
|
|
|
func ParseModFull(b []byte) (*ModFile, error) {
|
|
var m ModFile
|
|
if err := tomlpkg.Unmarshal(b, &m); err != nil {
|
|
return nil, err
|
|
}
|
|
return &m, nil
|
|
}
|
|
|
|
// Coords returns the canonical display coordinate for the plugin in the form
|
|
// "@scope/name@version" (or "name@version" when no scope is set).
|
|
//
|
|
// The leading "@" on m.Plugin.Scope is intentionally trimmed before
|
|
// re-prefixing so that authors may write either "@themes" or "themes" in
|
|
// plugin.mod and get the same output. Callers that need the raw scope as
|
|
// written should read m.Plugin.Scope directly.
|
|
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"
|