civicrm / cv

CiviCRM CLI Utility
28 stars 30 forks source link

Add support for cv plugins #191

Closed totten closed 2 months ago

totten commented 8 months ago

Overview

Adds support for loading plugins (*.php files) to modify the behavior of cv. In particular, some useful things you might do are (a) defining new subcommands or (b) defining alias services (cv @mysite ext:list).

The design is based on heavily on loco CLI plugins.

Plugin Loading

Plugins are loaded from the CV_PLUGIN_PATH. If the path is not set, then it loads PHP files from these folders:

Each plugin is a *.php file.

Example: "Hello" Command

For example, to implement cv hello, you could create:

// FILE: /etc/cv/plugin/hello-command.php
use Civi\Cv\Cv;
use CvDeps\Symfony\Component\Console\Input\InputInterface;
use CvDeps\Symfony\Component\Console\Output\OutputInterface;
use CvDeps\Symfony\Component\Console\Command\Command;

Cv::dispatcher()->addListener('cv.app.commands', function($e) {
  $e['commands'][] = new class extends Command {
    protected function configure() {
      $this->setName('hello')
        ->setDescription('Say a greeting');
    }
    protected function execute(InputInterface $input, OutputInterface $output): int {
      $output->writeln('Hello there!');
      return 0;
    }
  };
});

The file doc/plugins.md gives more details.

Example: Site Aliases

Additionally, I've drafted a bigger example with support for site-aliases, eg

cv @mysite ext:list

The draft is at https://gist.github.com/totten/8241b80440221555c0051d4f3447fa40

Why do this as a plugin? Well, there are several little questions to sort out. Like:

Whatever the eventual answers, we'd probably want to include something like this in cv by default. But TBH, I'm kinda ambivalent about the details of those questions. Seems appealing to incubate as a plugin...

Documentation

The PR also adds a file doc/plugins.md.

totten commented 8 months ago

civibot, test this please

totten commented 5 months ago

This might be problematic. cv uses namespace prefixing for cross-CMS support -- but only for PHAR. So:

Brainstorming:

  1. Maybe folks using cv-plugins would be focused on PHAR-deployments. So just recommend Cvphar variant.

  2. Maybe we define some different contracts for Commands -- so it's not necessary to reference \Symfony classes directly.

  3. Maybe we standardize on the PHAR-style naming -- and make a compatibility shim for basic-source-deployments.

    spl_autoload_register(function($class){
      if ($class looks like 'Cvphar\Symfony\...') {
        class_alias('Symfony\...', 'Cvphar\Symfony\...', )
      }
    });
  4. Similarly, it might work to have a whitelist of classes in a CvApi namespace, eg

    class_alias('CvApi\Symfony\...\InputInterface', 'Symfony\...\InputInterface');
    class_alias('CvApi\Symfony\...\OutputInterface', 'Symfony\...\OutputInterface');
    // During PHAR compilation, first symbol is preserved -- second symbol goes through namespace-prefixing.
    // The effect is that (on any deployment) `CvApi\...` is an alias pointing to the real class.
totten commented 5 months ago

Rebased. Added a unit-test for a plugin which defines a new Command. Added class-aliases that should be portable between PHAR and non-PHAR environments.

totten commented 5 months ago

I suspect most of the test-failures are because the test-matrix is written in the older style. This leads to testing some invalid combinations like drupal9,max==php82. Need to switch it over to Duderino to look more like the civix test matrix.

totten commented 5 months ago

OK, after some general house-keeping on the repo (switch to phpunit9+duderino) and a rebase, the tests are passing. Also added some coverage for the internal hook that enables plugins to define @aliases.

vurt2 commented 4 months ago

Hi, Martin from the civicamp Hamburg here: I did test the hello example and rewrote our extension upgrade script as a cv plugin. I all worked like a charm. It would be nice to have an option to deploy commands in civi-extensions (like drush does). But that would require to search all the extension dirs for a cv-plugin folder...

best, Martin

totten commented 2 months ago

civibot, test this please

totten commented 2 months ago

Cheers @vurt2 - very glad it worked.

Agree it would be handy for extensions to bundle cv-plugins, though doing so requires sorting some bootstrap issues. (I think... it requires a 2-phase command-dispatcher; an earlier phase to dispatch early/built-in commands like cv core:install; and then if that doesn't find anything, then another phase to boot+scan the Civi extensions. In any event, that'll have to be some kind of follow-up. Don't need to hold this one back any more.)

I had to do a few minor updates to accommodate recent Symfony 5/PHP8.4 updates. (In the example plugin-commands, the execute() methods needs to explicitly return an exit-code.)

But it's passing again. :green_circle:

vurt2 commented 2 months ago

@totten Very cool. Thanks for the work!

totten commented 2 months ago

JFYI @vurt2 - For cv v0.3.56, I've done some rearranging of the command classes.

Strictly speaking, the examples from before should still work the same way.

However, the examples from before were missing important things... like, they didn't actually bootstrap CiviCRM. (They just said "Hello"...) And this undocumented part changed (#218) -- most notably, with the introduction of a new base-class CvCommand.

I've updated the doc file: https://github.com/civicrm/cv/blob/master/doc/plugins.md

vurt2 commented 1 month ago

Thanks for the update @totten - yes, I had to boot cv with "$this->boot()". Cool, that you added this.

I encountered a small typo in the plugins.md: the use of functione instead of function in the structured-class style example.