From efd3bbd12635c297c85e4aa9da5c3ec05c5e7190 Mon Sep 17 00:00:00 2001 From: Thomas Bruyelle Date: Mon, 9 Jan 2023 14:23:53 +0100 Subject: [PATCH] docs: various plugins stuff (#3372) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * docs: various plugins stuff - replace `config.yml` with `plugins.yml` (fix #3204) - use `plugin add` instead of edit config manually - revamp `plugin describe` for better and simpler output - change plugin command icon from πŸ’» to ❯⎯ - use hash from main branch for plugin go.mod.plush * Update docs/docs/contributing/01-plugins.md Co-authored-by: JerΓ³nimo Albi * docs: rename title * docs: move plugins to its own category (#3378) related discussion: https://github.com/ignite/cli/pull/3372#issuecomment-1372687974 Co-authored-by: JerΓ³nimo Albi Co-authored-by: Alex Johnson --- .../contributing/{02-docs.md => 01-docs.md} | 0 docs/docs/plugins/01-using-plugins.md | 43 +++++++++ .../02-dev-plugins.md} | 68 +++----------- docs/docs/plugins/_category_.json | 5 ++ ignite/cmd/plugin.go | 89 ++++--------------- ignite/cmd/plugin_default_test.go | 7 +- ignite/pkg/cliui/icons/icon.go | 8 +- ignite/services/plugin/interface.go | 20 +++++ 8 files changed, 108 insertions(+), 132 deletions(-) rename docs/docs/contributing/{02-docs.md => 01-docs.md} (100%) create mode 100644 docs/docs/plugins/01-using-plugins.md rename docs/docs/{contributing/01-plugins.md => plugins/02-dev-plugins.md} (79%) create mode 100644 docs/docs/plugins/_category_.json diff --git a/docs/docs/contributing/02-docs.md b/docs/docs/contributing/01-docs.md similarity index 100% rename from docs/docs/contributing/02-docs.md rename to docs/docs/contributing/01-docs.md diff --git a/docs/docs/plugins/01-using-plugins.md b/docs/docs/plugins/01-using-plugins.md new file mode 100644 index 0000000000..9b830a2ef6 --- /dev/null +++ b/docs/docs/plugins/01-using-plugins.md @@ -0,0 +1,43 @@ +--- +description: Using and Developing plugins +--- + +# Using Plugins + +Ignite plugins offer a way to extend the functionality of the Ignite CLI. There +are two core concepts within plugins : `Commands` and `Hooks`. Where `Commands` +extend the cli's functionality, and `Hooks` extend existing command +functionality. + +Plugins are registered in an Ignite scaffolded Blockchain project through the +`plugins.yml`, or globally through `$HOME/.ignite/plugins/plugins.yml`. + +To use a plugin within your project, execute the following command inside the +project directory: + +```sh +ignite plugin add github.com/project/cli-plugin +``` + +The plugin will be available only when running `ignite` inside the project +directory. + +To use a plugin globally on the other hand, execute the following command: + +```sh +ignite plugin add -g github.com/project/cli-plugin +``` + +The command will compile the plugin and make it immediately available to the +`ignite` command lists. + +## Listing installed plugins + +When in an ignite scaffolded blockchain you can use the command `ignite plugin +list` to list all plugins and there statuses. + +## Updating plugins + +When a plugin in a remote repository releases updates, running `ignite plugin +update ` will update a specific plugin declared in your +project's `config.yml`. diff --git a/docs/docs/contributing/01-plugins.md b/docs/docs/plugins/02-dev-plugins.md similarity index 79% rename from docs/docs/contributing/01-plugins.md rename to docs/docs/plugins/02-dev-plugins.md index 39184d727d..6cbc1b6426 100644 --- a/docs/docs/contributing/01-plugins.md +++ b/docs/docs/plugins/02-dev-plugins.md @@ -2,44 +2,7 @@ description: Using and Developing plugins --- -# Developing plugins - -## Using Plugins - -Ignite plugins offer a way to extend the functionality of the Ignite CLI. There -are two core concepts within plugins : `Commands` and `Hooks`. Where `Commands` -extend the cli's functionality, and `Hooks` extend existing command -functionality. - -Plugins are registered in an Ignite scaffolded Blockchain project through the -`config.yml`. - -### Adding plugins to a project - -Plugins are registered per project, using the `config.yml` file. To use a plugin -within your project, add a `plugins` section with the following: - -```yaml title=config.yml -plugins: -- path: github.com/project/cli-plugin -``` - -Now the next time the `ignite` command is run under your project, the declared -plugin will be fetched, compiled and ran. This will result in more available -commands, and/or hooks attached to existing commands. - -### Listing installed plugins - -When in an ignite scaffolded blockchain you can use the command `ignite plugin -list` to list all plugins and there statuses. - -### Updating plugins - -When a plugin in a remote repository releases updates, running `ignite plugin -update ` will update a specific plugin declared in your -project's `config.yml`. - -## Developing Plugins +# Developing Plugins It's easy to create a plugin and use it immediately in your project. First choose a directory outside your project and run : @@ -49,9 +12,9 @@ $ ignite plugin scaffold my-plugin ``` This will create a new directory `my-plugin` that contains the plugin's code, -and will output some instructions about how to declare your plugin in your -project. Indeed it's possible to declare a local directory in your project's -`config.yml`, which has several benefits: +and will output some instructions about how to use your plugin with the +`ignite` command. Indeed, a plugin path can be a local directory, which has +several benefits: - you don't need to use a git repository during the development of your plugin. - the plugin is recompiled each time you run the `ignite` binary in your @@ -59,22 +22,18 @@ project, if the source files are older than the plugin binary. Thus, the plugin development workflow is as simple as : -1. scaffold a plugin -2. declare it in the `config.yml` of a chain (which can be a fresh new chain -created via `ignite scaffold chain my-chain`) +1. scaffold a plugin with `ignite plugin scaffold my-plugin` +2. add it to your config via `ignite plugin add -g /path/to/my-plugin` 3. update plugin code -4. run `ignite my-command` binary in your chain to compile and run the plugin, -where `my-command` is a command added by your plugin, or an existing `ignite` -command with hooks added by your plugin. +4. run `ignite my-plugin` binary to compile and run the plugin. 5. go back to 3. Once your plugin is ready, you can publish it to a git repository, and the -community can use it by declaring this git repository path in their chain's -`config.yml`. +community can use it by calling `ignite plugin add github.com/foo/my-plugin`. Now let's detail how to update your plugin's code. -### The plugin interface +## The plugin interface The `ignite` plugin system uses `github.com/hashicorp/go-plugin` under the hood, which implies to implement a predefined interface: @@ -115,7 +74,7 @@ The code scaffolded already implements this interface, you just need to update the methods' body. -### Defining plugin's manifest +## Defining plugin's manifest Here is the `Manifest` struct : @@ -160,7 +119,7 @@ A plugin may also share a host process by setting `SharedHost` to `true`. Commands executed from the same plugin context interact with the same plugin server. Allowing all executing commands to share the same server instance, giving shared execution context. -### Adding new command +## Adding new command Plugin commands are custom commands added to the ignite cli by a registered plugin. Commands can be of any path not defined already by ignite. All plugin @@ -212,10 +171,9 @@ func (p) Execute(cmd plugin.ExecutedCommand) error { } ``` -Then, run `ignite scaffold oracle` in a chain where the plugin is registered to -compile and execute the plugin. +Then, run `ignite scaffold oracle` to execute the plugin. -### Adding hooks +## Adding hooks Plugin `Hooks` allow existing ignite commands to be extended with new functionality. Hooks are useful when you want to streamline functionality diff --git a/docs/docs/plugins/_category_.json b/docs/docs/plugins/_category_.json new file mode 100644 index 0000000000..27114924ce --- /dev/null +++ b/docs/docs/plugins/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Plugins", + "position": 7, + "link": null +} diff --git a/ignite/cmd/plugin.go b/ignite/cmd/plugin.go index a6adca70a3..3710bfb311 100644 --- a/ignite/cmd/plugin.go +++ b/ignite/cmd/plugin.go @@ -21,7 +21,6 @@ import ( ) const ( - igniteCmdPrefix = "ignite " flagPluginsGlobal = "global" ) @@ -163,7 +162,7 @@ func linkPluginHooks(rootCmd *cobra.Command, p *plugin.Plugin, hooks []plugin.Ho } func linkPluginHook(rootCmd *cobra.Command, p *plugin.Plugin, hook plugin.Hook) { - cmdPath := hook.PlaceHookOn + cmdPath := hook.PlaceHookOnFull() cmd := findCommandByPath(rootCmd, cmdPath) if cmd == nil { p.Error = errors.Errorf("unable to find commandPath %q for plugin hook %q", cmdPath, hook.Name) @@ -265,7 +264,7 @@ func linkPluginCmds(rootCmd *cobra.Command, p *plugin.Plugin, pluginCmds []plugi } func linkPluginCmd(rootCmd *cobra.Command, p *plugin.Plugin, pluginCmd plugin.Command) { - cmdPath := pluginCmd.PlaceCommandUnder + cmdPath := pluginCmd.PlaceCommandUnderFull() cmd := findCommandByPath(rootCmd, cmdPath) if cmd == nil { p.Error = errors.Errorf("unable to find commandPath %q for plugin %q", cmdPath, p.Path) @@ -333,13 +332,6 @@ func linkPluginCmd(rootCmd *cobra.Command, p *plugin.Plugin, pluginCmd plugin.Co } func findCommandByPath(cmd *cobra.Command, cmdPath string) *cobra.Command { - if !strings.HasPrefix(cmdPath, "ignite") { - // cmdPath must start with `ignite ` before comparison with - // cmd.CommandPath() - cmdPath = igniteCmdPrefix + cmdPath - } - cmdPath = strings.TrimSpace(cmdPath) - if cmd.CommandPath() == cmdPath { return cmd } @@ -579,9 +571,13 @@ func NewPluginScaffold() *cobra.Command { ⭐️ Successfully created a new plugin '%[1]s'. πŸ‘‰ update plugin code at '%[2]s/main.go' -πŸ‘‰ test plugin integration by adding the following lines in a chain config.yaml: -plugins: -- path: %[2]s +πŸ‘‰ test plugin integration by adding the plugin to a chain's config: + + ignite plugin add %[2]s + +Or to the global config: + + ignite plugin add -g %[2]s πŸ‘‰ once the plugin is pushed to a repository, replace the local path by the repository path. ` @@ -606,12 +602,15 @@ func NewPluginDescribe() *cobra.Command { if err != nil { return fmt.Errorf("error while loading plugin manifest: %w", err) } - - if err := printPluginCommands(manifest.Commands, s); err != nil { - return err + s.Printf("Plugin '%s':\n", args[0]) + s.Printf("%s %d Command(s):\n", icons.Command, len(manifest.Commands)) + for i, c := range manifest.Commands { + cmdPath := fmt.Sprintf("%s %s", c.PlaceCommandUnderFull(), c.Use) + s.Printf("\t%d) '%s'\n", i+1, cmdPath) } - if err := printPluginHooks(manifest.Hooks, s); err != nil { - return err + s.Printf("%s %d Hook(s):\n", icons.Hook, len(manifest.Hooks)) + for i, h := range manifest.Hooks { + s.Printf("\t%d) '%s' on command '%s'\n", i+1, h.Name, h.PlaceHookOnFull()) } break } @@ -637,8 +636,7 @@ func printPlugins(session *cliui.Session) error { hookCount = len(manifest.Hooks) cmdCount = len(manifest.Commands) ) - - return fmt.Sprintf("%s Loaded: πŸͺ%d πŸ’»%d", icons.OK, hookCount, cmdCount) + return fmt.Sprintf("%s Loaded: %s %d %s%d ", icons.OK, icons.Command, cmdCount, icons.Hook, hookCount) } installedStatus = func(p *plugin.Plugin) string { if p.IsGlobal() { @@ -656,57 +654,6 @@ func printPlugins(session *cliui.Session) error { return nil } -func printPluginCommands(cmds []plugin.Command, session *cliui.Session) error { - var entries [][]string - // Processes command graph - traverse := func(cmd plugin.Command) { - // cmdPair is a Wrapper struct to create parent child relationship for sub commands without a `place command under` - type cmdPair struct { - cmd plugin.Command - parent plugin.Command - } - - queue := make([]cmdPair, 0) - queue = append(queue, cmdPair{cmd: cmd, parent: plugin.Command{}}) - - for len(queue) > 0 { - c := queue[0] - queue = queue[1:] - if c.cmd.PlaceCommandUnder != "" { - entries = append(entries, []string{c.cmd.Use, c.cmd.PlaceCommandUnder}) - } else { - entries = append(entries, []string{c.cmd.Use, c.parent.Use}) - } - - for _, sc := range c.cmd.Commands { - queue = append(queue, cmdPair{cmd: sc, parent: c.cmd}) - } - } - } - - for _, c := range cmds { - traverse(c) - } - - if err := session.PrintTable([]string{"command use", "under"}, entries...); err != nil { - return fmt.Errorf("error while printing plugin commands: %w", err) - } - return nil -} - -func printPluginHooks(hooks []plugin.Hook, session *cliui.Session) error { - var entries [][]string - - for _, h := range hooks { - entries = append(entries, []string{h.Name, h.PlaceHookOn}) - } - - if err := session.PrintTable([]string{"hook name", "on"}, entries...); err != nil { - return fmt.Errorf("error while printing plugin hooks: %w", err) - } - return nil -} - func flagSetPluginsGlobal() *flag.FlagSet { fs := flag.NewFlagSet("", flag.ContinueOnError) fs.BoolP(flagPluginsGlobal, "g", false, "use global plugins configuration"+ diff --git a/ignite/cmd/plugin_default_test.go b/ignite/cmd/plugin_default_test.go index cf498eb0a2..04528f14ec 100644 --- a/ignite/cmd/plugin_default_test.go +++ b/ignite/cmd/plugin_default_test.go @@ -16,17 +16,18 @@ func TestEnsureDefaultPlugins(t *testing.T) { expectAddedInCommand bool }{ { - name: "empty config", + name: "should add because absent from config", cfg: &pluginsconfig.Config{}, expectAddedInCommand: true, }, { - name: "config with default plugin", + name: "should not add because already present in config", cfg: &pluginsconfig.Config{ Plugins: []pluginsconfig.Plugin{{ Path: "github.com/ignite/cli-plugin-network@v42", }}, }, + expectAddedInCommand: false, }, } for _, tt := range tests { @@ -35,7 +36,7 @@ func TestEnsureDefaultPlugins(t *testing.T) { ensureDefaultPlugins(cmd, tt.cfg) - expectedCmd := findCommandByPath(cmd, "network") + expectedCmd := findCommandByPath(cmd, "ignite network") if tt.expectAddedInCommand { assert.NotNil(t, expectedCmd) } else { diff --git a/ignite/pkg/cliui/icons/icon.go b/ignite/pkg/cliui/icons/icon.go index d5c75a441c..7a9d5373a9 100644 --- a/ignite/pkg/cliui/icons/icon.go +++ b/ignite/pkg/cliui/icons/icon.go @@ -5,9 +5,11 @@ import ( ) var ( - Earth = "🌍" - CD = "πŸ’Ώ" - User = "πŸ‘€" + Earth = "🌍" + CD = "πŸ’Ώ" + User = "πŸ‘€" + Command = "❯⎯" + Hook = "πŸͺ" // OK is an OK mark. OK = colors.SprintFunc(colors.Green)("βœ”") diff --git a/ignite/services/plugin/interface.go b/ignite/services/plugin/interface.go index c4ce6421ab..146b588fcc 100644 --- a/ignite/services/plugin/interface.go +++ b/ignite/services/plugin/interface.go @@ -119,6 +119,20 @@ type Command struct { Commands []Command } +// PlaceCommandUnderFull returns a normalized p.PlaceCommandUnder, by adding +// the `ignite ` prefix if not present. +func (c Command) PlaceCommandUnderFull() string { + return commandFull(c.PlaceCommandUnder) +} + +func commandFull(cmdPath string) string { + const rootCmdName = "ignite" + if !strings.HasPrefix(cmdPath, rootCmdName) { + cmdPath = rootCmdName + " " + cmdPath + } + return strings.TrimSpace(cmdPath) +} + // ToCobraCommand turns Command into a cobra.Command so it can be added to a // parent command. func (c Command) ToCobraCommand() (*cobra.Command, error) { @@ -147,6 +161,12 @@ type Hook struct { PlaceHookOn string } +// PlaceHookOnFull returns a normalized p.PlaceCommandUnder, by adding the +// `ignite ` prefix if not present. +func (h Hook) PlaceHookOnFull() string { + return commandFull(h.PlaceHookOn) +} + // ExecutedCommand represents a plugin command under execution. type ExecutedCommand struct { // Use is copied from Command.Use