Browse Source

Add funding field to composer.json

You can specify a list of funding options each with a type and URL. The
type is used to specify the kind of funding or the platform through
which funding is possible.
Nils Adermann 5 years ago
parent
commit
5c4f524d6a

+ 32 - 0
doc/04-schema.md

@@ -258,6 +258,38 @@ An example:
 
 Optional.
 
+### funding
+
+A list of URLs to provide funding to the package authors for maintenance and
+development of new functionality.
+
+Each entry consists of the following
+
+* **type:** The type of funding or the platform through which funding can be provided, e.g. patreon, opencollective, tidelift or github.
+* **url:** URL to a website with details and a way to fund the package.
+
+An example:
+
+```json
+{
+    "funding": [
+        {
+            "type": "patreon",
+            "url": "https://www.patreon.com/phpdoctrine"
+        },
+        {
+            "type": "tidelift",
+            "url": "https://tidelift.com/subscription/pkg/packagist-doctrine_doctrine-bundle"
+        },
+        {
+            "type": "other",
+            "url": "https://www.doctrine-project.org/sponsorship.html"
+        }
+}
+```
+
+Optional.
+
 ### Package links
 
 All of the following take an object which maps package names to

+ 18 - 0
res/composer-schema.json

@@ -522,6 +522,24 @@
                 }
             }
         },
+        "funding": {
+            "type": "array",
+            "description": "A list of options to fund the development and maintenance of the package.",
+            "items": {
+                "type": "object",
+                "properties": {
+                    "type": {
+                        "type": "string",
+                        "description": "Type of funding or platform through which funding is possible."
+                    },
+                    "url": {
+                        "type": "string",
+                        "description": "URL to a website with details on funding and a way to fund the package.",
+                        "format": "uri"
+                    }
+                }
+            }
+        },
         "non-feature-branches": {
             "type": ["array"],
             "description": "A set of string or regex patterns for non-numeric branch names that will not be handled as feature branches.",

+ 5 - 0
src/Composer/Package/AliasPackage.php

@@ -377,6 +377,11 @@ class AliasPackage extends BasePackage implements CompletePackageInterface
         return $this->aliasOf->getSupport();
     }
 
+    public function getFunding()
+    {
+        return $this->aliasOf->getFunding();
+    }
+
     public function getNotificationUrl()
     {
         return $this->aliasOf->getNotificationUrl();

+ 19 - 0
src/Composer/Package/CompletePackage.php

@@ -27,6 +27,7 @@ class CompletePackage extends Package implements CompletePackageInterface
     protected $homepage;
     protected $scripts = array();
     protected $support = array();
+    protected $funding = array();
     protected $abandoned = false;
 
     /**
@@ -171,6 +172,24 @@ class CompletePackage extends Package implements CompletePackageInterface
         return $this->support;
     }
 
+    /**
+     * Set the funding
+     *
+     * @param array $funding
+     */
+    public function setFunding(array $funding)
+    {
+        $this->funding = $funding;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getFunding()
+    {
+        return $this->funding;
+    }
+
     /**
      * @return bool
      */

+ 9 - 0
src/Composer/Package/CompletePackageInterface.php

@@ -79,6 +79,15 @@ interface CompletePackageInterface extends PackageInterface
      */
     public function getSupport();
 
+    /**
+     * Returns an array of funding options for the package
+     *
+     * Each item will contain type and url keys
+     *
+     * @return array
+     */
+    public function getFunding();
+
     /**
      * Returns if the package is abandoned or not
      *

+ 1 - 0
src/Composer/Package/Dumper/ArrayDumper.php

@@ -104,6 +104,7 @@ class ArrayDumper
                 'keywords',
                 'repositories',
                 'support',
+                'funding',
             );
 
             $data = $this->dumpValues($package, $keys, $data);

+ 4 - 0
src/Composer/Package/Loader/ArrayLoader.php

@@ -198,6 +198,10 @@ class ArrayLoader implements LoaderInterface
                 $package->setSupport($config['support']);
             }
 
+            if (!empty($config['funding']) && is_array($config['funding'])) {
+                $package->setFunding($config['funding']);
+            }
+
             if (isset($config['abandoned'])) {
                 $package->setAbandoned($config['abandoned']);
             }

+ 26 - 0
src/Composer/Package/Loader/ValidatingArrayLoader.php

@@ -193,6 +193,32 @@ class ValidatingArrayLoader implements LoaderInterface
             }
         }
 
+        if ($this->validateArray('funding') && !empty($this->config['funding'])) {
+            foreach ($this->config['funding'] as $key => $fundingOption) {
+                if (!is_array($fundingOption)) {
+                    $this->errors[] = 'funding.'.$key.' : should be an array, '.gettype($fundingOption).' given';
+                    unset($this->config['funding'][$key]);
+                    continue;
+                }
+                foreach (array('type', 'url') as $fundingData) {
+                    if (isset($fundingOption[$fundingData]) && !is_string($fundingOption[$fundingData])) {
+                        $this->errors[] = 'funding.'.$key.'.'.$fundingData.' : invalid value, must be a string';
+                        unset($this->config['funding'][$key][$fundingData]);
+                    }
+                }
+                if (isset($fundingOption['url']) && !$this->filterUrl($fundingOption['url'])) {
+                    $this->warnings[] = 'funding.'.$key.'.url : invalid value ('.$fundingOption['url'].'), must be an http/https URL';
+                    unset($this->config['funding'][$key]['url']);
+                }
+                if (empty($this->config['funding'][$key])) {
+                    unset($this->config['funding'][$key]);
+                }
+            }
+            if (empty($this->config['funding'])) {
+                unset($this->config['funding']);
+            }
+        }
+
         $unboundConstraint = new Constraint('=', $this->versionParser->normalize('dev-master'));
         $stableConstraint = new Constraint('=', '1.0.0');
 

+ 6 - 0
tests/Composer/Test/Json/Fixtures/composer.json

@@ -21,6 +21,12 @@
         "irc": "irc://irc.freenode.org/composer",
         "issues": "https://github.com/composer/composer/issues"
     },
+    "funding": [
+        {
+            "type": "service-subscription",
+            "url": "https://packagist.com"
+        }
+    ],
     "require": {
         "php": ">=5.3.2",
         "justinrainbow/json-schema": "~1.4",

+ 4 - 0
tests/Composer/Test/Package/Dumper/ArrayDumperTest.php

@@ -191,6 +191,10 @@ class ArrayDumperTest extends TestCase
                 'support',
                 array('foo' => 'bar'),
             ),
+            array(
+                'funding',
+                array('type' => 'foo', 'url' => 'https://example.com'),
+            ),
             array(
                 'require',
                 array(new Link('foo', 'foo/bar', new Constraint('=', '1.0.0.0'), 'requires', '1.0.0'), new Link('bar', 'bar/baz', new Constraint('=', '1.0.0.0'), 'requires', '1.0.0')),

+ 3 - 0
tests/Composer/Test/Package/Loader/ArrayLoaderTest.php

@@ -97,6 +97,9 @@ class ArrayLoaderTest extends TestCase
             'authors' => array(
                 array('name' => 'Bob', 'email' => 'bob@example.org', 'homepage' => 'example.org', 'role' => 'Developer'),
             ),
+            'funding' => array(
+                array('type' => 'example', 'url' => 'https://example.org/fund'),
+            ),
             'require' => array(
                 'foo/bar' => '1.0',
             ),

+ 9 - 0
tests/Composer/Test/Package/Loader/ValidatingArrayLoaderTest.php

@@ -73,6 +73,15 @@ class ValidatingArrayLoaderTest extends TestCase
                         'rss' => 'http://example.org/rss',
                         'chat' => 'http://example.org/chat',
                     ),
+                    'funding' => array(
+                        array(
+                            'type' => 'example',
+                            'url' => 'https://example.org/fund'
+                        ),
+                        array(
+                            'url' => 'https://example.org/fund'
+                        ),
+                    ),
                     'require' => array(
                         'a/b' => '1.*',
                         'b/c' => '~2',