Merge pull request #8453 from naderman/funding
Add funding field to composer.json and composer fund commandpull/8611/head
commit
055a179cc5
|
@ -420,6 +420,11 @@ This implies `--by-package --by-suggestion`, showing both lists.
|
||||||
* **--by-suggestion:** Groups output by suggested package.
|
* **--by-suggestion:** Groups output by suggested package.
|
||||||
* **--no-dev:** Excludes suggestions from `require-dev` packages.
|
* **--no-dev:** Excludes suggestions from `require-dev` packages.
|
||||||
|
|
||||||
|
## fund
|
||||||
|
|
||||||
|
Discover how to help fund the maintenance of your dependencies. This lists
|
||||||
|
all funding links from the installed dependencies.
|
||||||
|
|
||||||
## depends (why)
|
## depends (why)
|
||||||
|
|
||||||
The `depends` command tells you which other packages depend on a certain
|
The `depends` command tells you which other packages depend on a certain
|
||||||
|
|
|
@ -258,6 +258,39 @@ An example:
|
||||||
|
|
||||||
Optional.
|
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
|
### Package links
|
||||||
|
|
||||||
All of the following take an object which maps package names to
|
All of the following take an object which maps package names to
|
||||||
|
|
|
@ -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": {
|
"non-feature-branches": {
|
||||||
"type": ["array"],
|
"type": ["array"],
|
||||||
"description": "A set of string or regex patterns for non-numeric branch names that will not be handled as feature branches.",
|
"description": "A set of string or regex patterns for non-numeric branch names that will not be handled as feature branches.",
|
||||||
|
|
|
@ -0,0 +1,89 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Composer.
|
||||||
|
*
|
||||||
|
* (c) Nils Adermann <naderman@naderman.de>
|
||||||
|
* Jordi Boggiano <j.boggiano@seld.be>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Composer\Command;
|
||||||
|
|
||||||
|
use Composer\Package\CompletePackageInterface;
|
||||||
|
use Composer\Package\AliasPackage;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Nicolas Grekas <p@tchwork.com>
|
||||||
|
* @author Jordi Boggiano <j.boggiano@seld.be>
|
||||||
|
*/
|
||||||
|
class FundCommand extends BaseCommand
|
||||||
|
{
|
||||||
|
protected function configure()
|
||||||
|
{
|
||||||
|
$this->setName('fund')
|
||||||
|
->setDescription('Discover how to help fund the maintenance of your dependencies.')
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function execute(InputInterface $input, OutputInterface $output)
|
||||||
|
{
|
||||||
|
$composer = $this->getComposer();
|
||||||
|
|
||||||
|
$repo = $composer->getRepositoryManager()->getLocalRepository();
|
||||||
|
$fundings = array();
|
||||||
|
foreach ($repo->getPackages() as $package) {
|
||||||
|
if ($package instanceof AliasPackage) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($package instanceof CompletePackageInterface && $funding = $package->getFunding()) {
|
||||||
|
foreach ($funding as $fundingOption) {
|
||||||
|
list($vendor, $packageName) = explode('/', $package->getPrettyName());
|
||||||
|
$url = $fundingOption['url'];
|
||||||
|
if (!empty($fundingOption['type']) && $fundingOption['type'] === 'github' && preg_match('{^https://github.com/([^/]+)$}', $url, $match)) {
|
||||||
|
$url = 'https://github.com/sponsors/'.$match[1];
|
||||||
|
}
|
||||||
|
$fundings[$vendor][$url][] = $packageName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ksort($fundings);
|
||||||
|
|
||||||
|
$io = $this->getIO();
|
||||||
|
|
||||||
|
if ($fundings) {
|
||||||
|
$prev = null;
|
||||||
|
|
||||||
|
$io->write('The following packages were found in your dependencies which publish funding information:');
|
||||||
|
|
||||||
|
foreach ($fundings as $vendor => $links) {
|
||||||
|
$io->write('');
|
||||||
|
$io->write(sprintf("<comment>%s</comment>", $vendor));
|
||||||
|
foreach ($links as $url => $packages) {
|
||||||
|
$line = sprintf(' <info>%s</info>', implode(', ', $packages));
|
||||||
|
|
||||||
|
if ($prev !== $line) {
|
||||||
|
$io->write($line);
|
||||||
|
$prev = $line;
|
||||||
|
}
|
||||||
|
|
||||||
|
$io->write(sprintf(' %s', $url));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$io->write("");
|
||||||
|
$io->write("Please consider following these links and sponsoring the work of package authors!");
|
||||||
|
$io->write("Thank you!");
|
||||||
|
} else {
|
||||||
|
$io->write("No funding links were found in your package dependencies. This doesn't mean they don't need your support!");
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
|
@ -436,6 +436,7 @@ class Application extends BaseApplication
|
||||||
new Command\ExecCommand(),
|
new Command\ExecCommand(),
|
||||||
new Command\OutdatedCommand(),
|
new Command\OutdatedCommand(),
|
||||||
new Command\CheckPlatformReqsCommand(),
|
new Command\CheckPlatformReqsCommand(),
|
||||||
|
new Command\FundCommand(),
|
||||||
));
|
));
|
||||||
|
|
||||||
if ('phar:' === substr(__FILE__, 0, 5)) {
|
if ('phar:' === substr(__FILE__, 0, 5)) {
|
||||||
|
|
|
@ -35,6 +35,7 @@ use Composer\IO\IOInterface;
|
||||||
use Composer\Package\AliasPackage;
|
use Composer\Package\AliasPackage;
|
||||||
use Composer\Package\BasePackage;
|
use Composer\Package\BasePackage;
|
||||||
use Composer\Package\CompletePackage;
|
use Composer\Package\CompletePackage;
|
||||||
|
use Composer\Package\CompletePackageInterface;
|
||||||
use Composer\Package\Link;
|
use Composer\Package\Link;
|
||||||
use Composer\Package\Loader\ArrayLoader;
|
use Composer\Package\Loader\ArrayLoader;
|
||||||
use Composer\Package\Dumper\ArrayDumper;
|
use Composer\Package\Dumper\ArrayDumper;
|
||||||
|
@ -313,6 +314,24 @@ class Installer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$fundingCount = 0;
|
||||||
|
foreach ($localRepo->getPackages() as $package) {
|
||||||
|
if ($package instanceof CompletePackageInterface && !$package instanceof AliasPackage && $package->getFunding()) {
|
||||||
|
$fundingCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($fundingCount) {
|
||||||
|
$this->io->writeError(array(
|
||||||
|
sprintf(
|
||||||
|
"<info>%d package%s you are using %s looking for funding.</info>",
|
||||||
|
$fundingCount,
|
||||||
|
1 === $fundingCount ? '' : 's',
|
||||||
|
1 === $fundingCount ? 'is' : 'are'
|
||||||
|
),
|
||||||
|
'<info>Use the composer fund command to find out more!</info>',
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
if ($this->runScripts) {
|
if ($this->runScripts) {
|
||||||
// dispatch post event
|
// dispatch post event
|
||||||
$eventName = $this->update ? ScriptEvents::POST_UPDATE_CMD : ScriptEvents::POST_INSTALL_CMD;
|
$eventName = $this->update ? ScriptEvents::POST_UPDATE_CMD : ScriptEvents::POST_INSTALL_CMD;
|
||||||
|
|
|
@ -377,6 +377,11 @@ class AliasPackage extends BasePackage implements CompletePackageInterface
|
||||||
return $this->aliasOf->getSupport();
|
return $this->aliasOf->getSupport();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getFunding()
|
||||||
|
{
|
||||||
|
return $this->aliasOf->getFunding();
|
||||||
|
}
|
||||||
|
|
||||||
public function getNotificationUrl()
|
public function getNotificationUrl()
|
||||||
{
|
{
|
||||||
return $this->aliasOf->getNotificationUrl();
|
return $this->aliasOf->getNotificationUrl();
|
||||||
|
|
|
@ -27,6 +27,7 @@ class CompletePackage extends Package implements CompletePackageInterface
|
||||||
protected $homepage;
|
protected $homepage;
|
||||||
protected $scripts = array();
|
protected $scripts = array();
|
||||||
protected $support = array();
|
protected $support = array();
|
||||||
|
protected $funding = array();
|
||||||
protected $abandoned = false;
|
protected $abandoned = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -171,6 +172,24 @@ class CompletePackage extends Package implements CompletePackageInterface
|
||||||
return $this->support;
|
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
|
* @return bool
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -79,6 +79,15 @@ interface CompletePackageInterface extends PackageInterface
|
||||||
*/
|
*/
|
||||||
public function getSupport();
|
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
|
* Returns if the package is abandoned or not
|
||||||
*
|
*
|
||||||
|
|
|
@ -104,6 +104,7 @@ class ArrayDumper
|
||||||
'keywords',
|
'keywords',
|
||||||
'repositories',
|
'repositories',
|
||||||
'support',
|
'support',
|
||||||
|
'funding',
|
||||||
);
|
);
|
||||||
|
|
||||||
$data = $this->dumpValues($package, $keys, $data);
|
$data = $this->dumpValues($package, $keys, $data);
|
||||||
|
|
|
@ -198,6 +198,10 @@ class ArrayLoader implements LoaderInterface
|
||||||
$package->setSupport($config['support']);
|
$package->setSupport($config['support']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!empty($config['funding']) && is_array($config['funding'])) {
|
||||||
|
$package->setFunding($config['funding']);
|
||||||
|
}
|
||||||
|
|
||||||
if (isset($config['abandoned'])) {
|
if (isset($config['abandoned'])) {
|
||||||
$package->setAbandoned($config['abandoned']);
|
$package->setAbandoned($config['abandoned']);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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'));
|
$unboundConstraint = new Constraint('=', $this->versionParser->normalize('dev-master'));
|
||||||
$stableConstraint = new Constraint('=', '1.0.0');
|
$stableConstraint = new Constraint('=', '1.0.0');
|
||||||
|
|
||||||
|
|
|
@ -35,6 +35,7 @@ class GitHubDriver extends VcsDriver
|
||||||
protected $infoCache = array();
|
protected $infoCache = array();
|
||||||
protected $isPrivate = false;
|
protected $isPrivate = false;
|
||||||
private $isArchived = false;
|
private $isArchived = false;
|
||||||
|
private $fundingInfo;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Git Driver
|
* Git Driver
|
||||||
|
@ -166,6 +167,10 @@ class GitHubDriver extends VcsDriver
|
||||||
if (!isset($composer['abandoned']) && $this->isArchived) {
|
if (!isset($composer['abandoned']) && $this->isArchived) {
|
||||||
$composer['abandoned'] = true;
|
$composer['abandoned'] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!isset($composer['funding']) && $funding = $this->getFundingInfo()) {
|
||||||
|
$composer['funding'] = $funding;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->shouldCache($identifier)) {
|
if ($this->shouldCache($identifier)) {
|
||||||
|
@ -178,6 +183,40 @@ class GitHubDriver extends VcsDriver
|
||||||
return $this->infoCache[$identifier];
|
return $this->infoCache[$identifier];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function getFundingInfo()
|
||||||
|
{
|
||||||
|
if (null !== $this->fundingInfo) {
|
||||||
|
return $this->fundingInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->originUrl !== 'github.com') {
|
||||||
|
return $this->fundingInfo = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$graphql = 'query{repository(owner:"'.$this->owner.'",name:"'.$this->repository.'"){fundingLinks{platform,url}}}';
|
||||||
|
try {
|
||||||
|
$result = $this->remoteFilesystem->getContents($this->originUrl, 'https://api.github.com/graphql', false, array(
|
||||||
|
'http' => array(
|
||||||
|
'method' => 'POST',
|
||||||
|
'content' => json_encode(array('query' => $graphql)),
|
||||||
|
'header' => array('Content-Type: application/json'),
|
||||||
|
),
|
||||||
|
'retry-auth-failure' => false,
|
||||||
|
));
|
||||||
|
} catch (\TransportException $e) {
|
||||||
|
return $this->fundingInfo = false;
|
||||||
|
}
|
||||||
|
$result = json_decode($result, true);
|
||||||
|
|
||||||
|
if (empty($result['data']['repository']['fundingLinks'])) {
|
||||||
|
return $this->fundingInfo = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->fundingInfo = array_map(function ($link) {
|
||||||
|
return array('type' => strtolower($link['platform']), 'url' => $link['url']);
|
||||||
|
}, $result['data']['repository']['fundingLinks']);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
--TEST--
|
||||||
|
Installs a simple package with exact match requirement
|
||||||
|
--COMPOSER--
|
||||||
|
{
|
||||||
|
"repositories": [
|
||||||
|
{
|
||||||
|
"type": "package",
|
||||||
|
"package": [
|
||||||
|
{
|
||||||
|
"name": "a/a",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"funding": [{ "type": "example", "url": "http://example.org/fund" }],
|
||||||
|
"require": {
|
||||||
|
"d/d": "^1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "b/b",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"funding": [{ "type": "example", "url": "http://example.org/fund" }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "c/c",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"funding": [{ "type": "example", "url": "http://example.org/fund" }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "d/d",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"require": {
|
||||||
|
"b/b": "^1.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"require": {
|
||||||
|
"a/a": "1.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
--RUN--
|
||||||
|
install
|
||||||
|
--EXPECT-OUTPUT--
|
||||||
|
Loading composer repositories with package information
|
||||||
|
Updating dependencies (including require-dev)
|
||||||
|
Package operations: 3 installs, 0 updates, 0 removals
|
||||||
|
Writing lock file
|
||||||
|
Generating autoload files
|
||||||
|
2 packages you are using are looking for funding.
|
||||||
|
Use the composer fund command to find out more!
|
||||||
|
--EXPECT--
|
||||||
|
Installing b/b (1.0.0)
|
||||||
|
Installing d/d (1.0.0)
|
||||||
|
Installing a/a (1.0.0)
|
|
@ -21,6 +21,12 @@
|
||||||
"irc": "irc://irc.freenode.org/composer",
|
"irc": "irc://irc.freenode.org/composer",
|
||||||
"issues": "https://github.com/composer/composer/issues"
|
"issues": "https://github.com/composer/composer/issues"
|
||||||
},
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "service-subscription",
|
||||||
|
"url": "https://packagist.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
"require": {
|
"require": {
|
||||||
"php": ">=5.3.2",
|
"php": ">=5.3.2",
|
||||||
"justinrainbow/json-schema": "~1.4",
|
"justinrainbow/json-schema": "~1.4",
|
||||||
|
|
|
@ -191,6 +191,10 @@ class ArrayDumperTest extends TestCase
|
||||||
'support',
|
'support',
|
||||||
array('foo' => 'bar'),
|
array('foo' => 'bar'),
|
||||||
),
|
),
|
||||||
|
array(
|
||||||
|
'funding',
|
||||||
|
array('type' => 'foo', 'url' => 'https://example.com'),
|
||||||
|
),
|
||||||
array(
|
array(
|
||||||
'require',
|
'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')),
|
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')),
|
||||||
|
|
|
@ -97,6 +97,9 @@ class ArrayLoaderTest extends TestCase
|
||||||
'authors' => array(
|
'authors' => array(
|
||||||
array('name' => 'Bob', 'email' => 'bob@example.org', 'homepage' => 'example.org', 'role' => 'Developer'),
|
array('name' => 'Bob', 'email' => 'bob@example.org', 'homepage' => 'example.org', 'role' => 'Developer'),
|
||||||
),
|
),
|
||||||
|
'funding' => array(
|
||||||
|
array('type' => 'example', 'url' => 'https://example.org/fund'),
|
||||||
|
),
|
||||||
'require' => array(
|
'require' => array(
|
||||||
'foo/bar' => '1.0',
|
'foo/bar' => '1.0',
|
||||||
),
|
),
|
||||||
|
|
|
@ -73,6 +73,15 @@ class ValidatingArrayLoaderTest extends TestCase
|
||||||
'rss' => 'http://example.org/rss',
|
'rss' => 'http://example.org/rss',
|
||||||
'chat' => 'http://example.org/chat',
|
'chat' => 'http://example.org/chat',
|
||||||
),
|
),
|
||||||
|
'funding' => array(
|
||||||
|
array(
|
||||||
|
'type' => 'example',
|
||||||
|
'url' => 'https://example.org/fund'
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'url' => 'https://example.org/fund'
|
||||||
|
),
|
||||||
|
),
|
||||||
'require' => array(
|
'require' => array(
|
||||||
'a/b' => '1.*',
|
'a/b' => '1.*',
|
||||||
'b/c' => '~2',
|
'b/c' => '~2',
|
||||||
|
|
Loading…
Reference in New Issue