feat(build): summer plugin:install for registry plugins as Go modules
- kiosk.Client reads the registry API v1 over fetchguard (trusted mode, 4 MiB, 10 s, no redirects); kiosk.RegistryURL reads plugins.registry_url (default https://summercms.io, empty disables) - build.PlanInstall/ApplyInstall pick the newest release the effective framework satisfies (or a pin), validate registry strings, go get it and append it to summer.yaml - summer.yaml edits (plugin:add and plugin:install) keep comments and blank lines - TestInstallSmoke/module installs from a file:// GOPROXY and builds
This commit is contained in:
@@ -14,6 +14,7 @@ import (
|
||||
"git.golem15.com/golem15/summercms/internal/build"
|
||||
"git.golem15.com/golem15/summercms/internal/dev"
|
||||
"git.golem15.com/golem15/summercms/modules/bonfire"
|
||||
"git.golem15.com/golem15/summercms/modules/compass"
|
||||
"git.golem15.com/golem15/summercms/modules/diary"
|
||||
"git.golem15.com/golem15/summercms/modules/kiosk"
|
||||
"git.golem15.com/golem15/summercms/modules/lagoon"
|
||||
@@ -42,6 +43,7 @@ func toolCommands() []bonfire.Command {
|
||||
makeAdminControllerCommand(),
|
||||
addPluginCommand(),
|
||||
pluginManifestCommand(),
|
||||
pluginInstallCommand(),
|
||||
newCommand(),
|
||||
devCommand(),
|
||||
parityProxyCommand(),
|
||||
@@ -508,6 +510,106 @@ plugin.yaml is missing or differs from what the command would write.`,
|
||||
}
|
||||
}
|
||||
|
||||
// registryFor opens the application's config and returns the registry
|
||||
// client plugins.registry_url names, or nil when the setting is empty or
|
||||
// the run is --offline.
|
||||
func registryFor(appDir string, offline bool) (build.Registry, error) {
|
||||
if offline {
|
||||
return nil, nil
|
||||
}
|
||||
cfg, err := compass.Open(compass.Options{Dir: filepath.Join(appDir, "config")})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
base := kiosk.RegistryURL(cfg)
|
||||
if base == "" {
|
||||
return nil, nil
|
||||
}
|
||||
client, err := kiosk.NewClient(base, cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func pluginInstallCommand() bonfire.Command {
|
||||
return bonfire.Command{
|
||||
Name: "plugin:install",
|
||||
Description: "Install a plugin from the registry or a git repository",
|
||||
Long: `Adds a plugin the application does not have yet. The argument is a plugin ID
|
||||
(vendor.name), an ID pinned to a release (vendor.name@vX.Y.Z), or a git URL
|
||||
(https://, ssh:// or user@host:path).
|
||||
|
||||
For an ID, the registry (plugins.registry_url in the application's config,
|
||||
https://summercms.io by default, SUMMER_PLUGINS__REGISTRY_URL overrides it)
|
||||
lists the plugin's releases. The newest release whose framework minimum the
|
||||
application's framework version meets is chosen; a pinned release must meet it
|
||||
too. When only releases needing a newer framework exist, the command fails and
|
||||
names the framework version to move to; it never changes the framework. The
|
||||
plugin is installed as a Go module with go get and listed in summer.yaml.
|
||||
--source installs it as a git submodule under plugins/<vendor>/<name> instead.
|
||||
|
||||
The plugins to add are printed as id@version (mode) and confirmed with a
|
||||
prompt. --yes skips the prompt; a run without a terminal needs --yes. Run
|
||||
summer build afterwards. --offline makes no registry call, so it works only
|
||||
with a git URL. An empty plugins.registry_url disables registry calls.`,
|
||||
Examples: []bonfire.Example{
|
||||
{Command: "plugin:install acme.blog", Description: "Install the newest compatible release of acme.blog as a Go module"},
|
||||
{Command: "plugin:install acme.blog@v0.3.0 --yes", Description: "Install release v0.3.0 without asking"},
|
||||
{Command: "plugin:install acme.blog --source", Description: "Install it as a git submodule under plugins/acme/blog"},
|
||||
{Command: "plugin:install git@git.example.com:acme/sm-blog-plugin.git", Description: "Install a private plugin from its repository, as a submodule"},
|
||||
},
|
||||
Args: []bonfire.Arg{{
|
||||
Name: "plugin",
|
||||
Description: "vendor.name, vendor.name@vX.Y.Z or a git URL",
|
||||
Required: true,
|
||||
}},
|
||||
Flags: []bonfire.Flag{
|
||||
{Name: "source", Description: "Install registry plugins as git submodules under plugins/<vendor>/<name>", Bare: true},
|
||||
{Name: "yes", Description: "Install without asking for confirmation", Bare: true},
|
||||
{Name: "offline", Description: "Make no registry call; dependencies must be installed already", Bare: true},
|
||||
},
|
||||
Run: func(ctx context.Context, in bonfire.Input, out bonfire.Output) error {
|
||||
arg, ok := in.Argument("plugin")
|
||||
if !ok || arg == "" {
|
||||
return fmt.Errorf("plugin:install requires a plugin ID or a git URL")
|
||||
}
|
||||
dir, err := os.Getwd()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
appDir, err := build.FindAppDir(dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
offline := bonfire.FlagBool(in, "offline")
|
||||
reg, err := registryFor(appDir, offline)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
plan, err := build.PlanInstall(ctx, build.InstallOptions{
|
||||
StartDir: dir,
|
||||
Arg: arg,
|
||||
Source: bonfire.FlagBool(in, "source"),
|
||||
Yes: bonfire.FlagBool(in, "yes"),
|
||||
Offline: offline,
|
||||
Registry: reg,
|
||||
Interactive: bonfire.Interactive(out),
|
||||
Confirm: func(q string) (bool, error) { return out.Confirm(q, false) },
|
||||
Out: out,
|
||||
})
|
||||
if errors.Is(err, build.ErrCancelled) {
|
||||
out.Println("Nothing installed.")
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return build.ApplyInstall(ctx, plan, out)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func newCommand() bonfire.Command {
|
||||
return bonfire.Command{
|
||||
Name: "new",
|
||||
|
||||
Reference in New Issue
Block a user