Commit a1b5b5be authored by alexpott's avatar alexpott

Issue #3087482 by Mile23, greg.1.anderson, alexpott, xjm, AaronMcHale,...

Issue #3087482 by Mile23, greg.1.anderson, alexpott, xjm, AaronMcHale, johndevman, mbaynton, Mixologic, drumm: Add 'next steps' info to Composer installation output
parent 3946cf35
...@@ -3,9 +3,15 @@ ...@@ -3,9 +3,15 @@
"description": "Drupal is an open source content management platform powering millions of websites and applications.", "description": "Drupal is an open source content management platform powering millions of websites and applications.",
"type": "project", "type": "project",
"license": "GPL-2.0-or-later", "license": "GPL-2.0-or-later",
"homepage": "https://www.drupal.org/project/drupal",
"support": {
"docs": "https://www.drupal.org/docs/user_guide/en/index.html",
"chat": "https://www.drupal.org/node/314178"
},
"require": { "require": {
"composer/installers": "^1.0.24", "composer/installers": "^1.0.24",
"drupal/core": "self.version", "drupal/core": "self.version",
"drupal/core-project-message": "self.version",
"drupal/core-vendor-hardening": "self.version", "drupal/core-vendor-hardening": "self.version",
"wikimedia/composer-merge-plugin": "^1.4" "wikimedia/composer-merge-plugin": "^1.4"
}, },
...@@ -57,6 +63,18 @@ ...@@ -57,6 +63,18 @@
"drush/Commands/contrib/{$name}": ["type:drupal-drush"], "drush/Commands/contrib/{$name}": ["type:drupal-drush"],
"modules/custom/{$name}": ["type:drupal-custom-module"], "modules/custom/{$name}": ["type:drupal-custom-module"],
"themes/custom/{$name}": ["type:drupal-custom-theme"] "themes/custom/{$name}": ["type:drupal-custom-theme"]
},
"drupal-core-project-message": {
"post-install-cmd-message": [
"<bg=blue;fg=white>drupal/drupal</>: This package is meant for core development,",
" and not intended to be used for production sites.",
" See: https://www.drupal.org/node/3082474"
],
"post-create-project-cmd-message": [
"<bg=red;fg=white>drupal/drupal</>: This package is meant for core development,",
" and not intended to be used for production sites.",
" See: https://www.drupal.org/node/3082474"
]
} }
}, },
"autoload": { "autoload": {
...@@ -91,6 +109,10 @@ ...@@ -91,6 +109,10 @@
"type": "path", "type": "path",
"url": "core" "url": "core"
}, },
{
"type": "path",
"url": "composer/Plugin/ProjectMessage"
},
{ {
"type": "path", "type": "path",
"url": "composer/Plugin/VendorHardening" "url": "composer/Plugin/VendorHardening"
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "8ba406bd7f3522d51f0e55fe33e51aff", "content-hash": "0f1805164a2b3a10914c98065de051af",
"packages": [ "packages": [
{ {
"name": "asm89/stack-cors", "name": "asm89/stack-cors",
...@@ -876,6 +876,36 @@ ...@@ -876,6 +876,36 @@
], ],
"description": "Drupal is an open source content management platform powering millions of websites and applications." "description": "Drupal is an open source content management platform powering millions of websites and applications."
}, },
{
"name": "drupal/core-project-message",
"version": "8.8.x-dev",
"dist": {
"type": "path",
"url": "composer/Plugin/ProjectMessage",
"reference": "3b795f469441eb27854798f70cb38e717d80bbfc"
},
"require": {
"composer-plugin-api": "^1.1",
"php": ">=7.0.8"
},
"type": "composer-plugin",
"extra": {
"class": "Drupal\\Composer\\Plugin\\ProjectMessage\\MessagePlugin"
},
"autoload": {
"psr-4": {
"Drupal\\Composer\\Plugin\\ProjectMessage\\": "."
}
},
"license": [
"GPL-2.0-or-later"
],
"description": "Adds a message after Composer installation.",
"homepage": "https://www.drupal.org/project/drupal",
"keywords": [
"drupal"
]
},
{ {
"name": "drupal/core-vendor-hardening", "name": "drupal/core-vendor-hardening",
"version": "8.8.x-dev", "version": "8.8.x-dev",
...@@ -6234,6 +6264,7 @@ ...@@ -6234,6 +6264,7 @@
"minimum-stability": "dev", "minimum-stability": "dev",
"stability-flags": { "stability-flags": {
"drupal/core": 20, "drupal/core": 20,
"drupal/core-project-message": 20,
"drupal/core-vendor-hardening": 20, "drupal/core-vendor-hardening": 20,
"behat/mink": 20, "behat/mink": 20,
"behat/mink-selenium2-driver": 20 "behat/mink-selenium2-driver": 20
......
This diff is collapsed.
<?php
namespace Drupal\Composer\Plugin\ProjectMessage;
use Composer\Package\RootPackageInterface;
/**
* Determine configuration.
*/
class Message {
/**
* The root package.
*
* @var \Composer\Package\RootPackageInterface
*/
protected $rootPackage;
/**
* The name of the event.
*
* @var string
*/
protected $eventName;
/**
* The message to display.
*
* @var string[]
*/
protected $messageText = [];
/**
* Construct a Config object.
*
* @param \Composer\Package\RootPackageInterface $root_package
* Composer package object for the root package.
* @param string $event_name
* The event name.
*/
public function __construct(RootPackageInterface $root_package, $event_name) {
$this->rootPackage = $root_package;
$this->eventName = $event_name;
}
public function getText() {
if ($this->messageText) {
return $this->messageText;
}
$package_config = $this->rootPackage->getExtra();
$file = $this->eventName . '-message.txt';
if ($config_file = $package_config['drupal-core-project-message'][$this->eventName . '-file'] ?? FALSE) {
$file = $config_file;
}
$message = $package_config['drupal-core-project-message'][$this->eventName . '-message'] ?? [];
if ($message) {
$this->messageText = $message;
}
else {
$this->messageText = $this->getMessageFromFile($file);
}
// Include structured support info from composer.json.
if ($config_keys = $package_config['drupal-core-project-message']['include-keys'] ?? FALSE) {
foreach ($config_keys as $config_key) {
switch ($config_key) {
case 'name':
if ($homepage = $this->rootPackage->getName()) {
$this->messageText[] = ' * Name: ' . $homepage;
}
break;
case 'description':
if ($homepage = $this->rootPackage->getDescription()) {
$this->messageText[] = ' * Description: ' . $homepage;
}
break;
case 'homepage':
if ($homepage = $this->rootPackage->getHomepage()) {
$this->messageText[] = ' * Homepage: ' . $homepage;
}
break;
case 'support':
if ($support = $this->rootPackage->getSupport()) {
$this->messageText[] = ' * Support:';
foreach ($support as $support_key => $support_value) {
$this->messageText[] = ' * ' . $support_key . ': ' . $support_value;
}
}
break;
}
}
}
return $this->messageText;
}
/**
* Reads the message file as an array.
*
* @param string $file
* The file to read. Relative paths are relative to the project directory.
*
* @return string[]
*/
protected function getMessageFromFile($file) {
return file_exists($file) ? file($file, FILE_IGNORE_NEW_LINES) : [];
}
}
<?php
namespace Drupal\Composer\Plugin\ProjectMessage;
use Composer\Composer;
use Composer\EventDispatcher\EventSubscriberInterface;
use Composer\IO\IOInterface;
use Composer\Plugin\PluginInterface;
use Composer\Script\Event;
use Composer\Script\ScriptEvents;
/**
* A Composer plugin to display a message after creating a project.
*/
class MessagePlugin implements PluginInterface, EventSubscriberInterface {
/**
* Composer object.
*
* @var \Composer\Composer
*/
protected $composer;
/**
* IO object.
*
* @var \Composer\IO\IOInterface
*/
protected $io;
/**
* Configuration.
*
* @var \Drupal\Composer\VendorHardening\Config
*/
protected $config;
/**
* {@inheritdoc}
*/
public function activate(Composer $composer, IOInterface $io) {
$this->composer = $composer;
$this->io = $io;
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
return [
ScriptEvents::POST_CREATE_PROJECT_CMD => 'displayPostCreateMessage',
ScriptEvents::POST_INSTALL_CMD => 'displayPostCreateMessage',
];
}
public function displayPostCreateMessage(Event $event) {
$message = new Message($this->composer->getPackage(), $event->getName());
if ($message = $message->getText()) {
$this->io->write($message);
}
}
}
The Drupal Project Message Plugin
=================================
Thanks for using this Drupal component.
You can participate in its development on Drupal.org, through our issue system:
https://www.drupal.org/project/issues/drupal
You can get the full Drupal repo here:
https://www.drupal.org/project/drupal/git-instructions
You can browse the full Drupal repo here:
http://cgit.drupalcode.org/drupal
What does it do?
----------------
This Composer plugin displays a configurable message after Composer installation
processes have finished.
This is ideal for a 'next steps' type prompt to help get the user oriented.
Currently only two Composer events are supported:
- post-create-project-cmd, when a `composer create-project` command has
finished.
- post-install-cmd, when a `composer install` command has finished.
How do I set it up?
-------------------
Require this Composer plugin in your project template composer.json file:
"require": {
"drupal/core-project-message": "^8.8"
}
### Configuration
There are three ways to configure this plugin to output information:
- Using a text file.
- Using composer.json schema keys.
- Embedding the information in the extra section of the composer.json file.
### Using a text file
By default, the plugin will respond to `post-install-cmd` or
`post-create-project-cmd` Composer events by looking for a similarly-named file
in the root of the project. For instance, if the user issues a `composer
create-project` command, when that command is finished, the plugin will look for
a file named `post-create-project-cmd-message.txt` and then display it on the
command line.
The file should be plain text, with markup suitable for Symfony's
`OutputInterface::writeln()` method. See documentation here:
https://symfony.com/doc/3.4/console/coloring.html
You can also configure your own file(s), using the `extra` section of your
composer.json file:
"extra": {
"drupal-core-project-message": {
"post-create-project-cmd-file": "bespoke/special_file.txt"
}
}
### Using composer.json schema keys
You can tell the plugin to output the structured support information from the
composer.json file by telling it the keys you wish to display.
Currently, only `name`, `description`, `homepage` and `support` are supported.
"extra": {
"drupal-core-project-message": {
"include-keys": ["homepage", "support"],
}
}
Then you can include this information in your composer.json file, which you
should probably be doing anyway.
### Embedding the information in the extra section
You can specify text directly within the `extra` section by using the
`[event-name]-message` key. This message should be an array, with one string for
each line:
"extra": {
"drupal-core-project-message": {
"post-create-project-cmd-message": [
"Thanks for installing this project.",
"Please visit our documentation here: http://example.com/docs"
]
}
}
These strings should be plain text, with markup suitable for Symfony's
`OutputInterface::writeln()` method. See documentation here:
https://symfony.com/doc/3.4/console/coloring.html
The `-message` section will always override `-file` for a given event.
HOW-TO: Test this Drupal component
In order to test this component, you'll need to get the entire Drupal repo and
run the tests there.
You'll find the tests under core/tests/Drupal/Tests/Plugin.
You can get the full Drupal repo here:
https://www.drupal.org/project/drupal/git-instructions
You can find more information about running PHPUnit tests with Drupal here:
https://www.drupal.org/node/2116263
Each component in the Drupal\Composer namespace has its own annotated test
group. You can use this group to run only the tests for this component. Like
this:
$ ./vendor/bin/phpunit -c core --group ProjectMessage
{
"name": "drupal/core-project-message",
"description": "Adds a message after Composer installation.",
"keywords": ["drupal"],
"homepage": "https://www.drupal.org/project/drupal",
"license": "GPL-2.0-or-later",
"type": "composer-plugin",
"autoload": {
"psr-4": {
"Drupal\\Composer\\Plugin\\ProjectMessage\\": "."
}
},
"extra": {
"class": "Drupal\\Composer\\Plugin\\ProjectMessage\\MessagePlugin"
},
"require": {
"php": ">=7.0.8",
"composer-plugin-api": "^1.1"
}
}
...@@ -3,6 +3,11 @@ ...@@ -3,6 +3,11 @@
"description": "Project template for Drupal 8 projects with composer following drupal/drupal layout", "description": "Project template for Drupal 8 projects with composer following drupal/drupal layout",
"type": "project", "type": "project",
"license": "GPL-2.0-or-later", "license": "GPL-2.0-or-later",
"homepage": "https://www.drupal.org/project/drupal",
"support": {
"docs": "https://www.drupal.org/docs/user_guide/en/index.html",
"chat": "https://www.drupal.org/node/314178"
},
"repositories": [ "repositories": [
{ {
"type": "composer", "type": "composer",
...@@ -11,8 +16,9 @@ ...@@ -11,8 +16,9 @@
], ],
"require": { "require": {
"composer/installers": "^1.2", "composer/installers": "^1.2",
"drupal/core-recommended": "^8.8",
"drupal/core-composer-scaffold": "^8.8", "drupal/core-composer-scaffold": "^8.8",
"drupal/core-project-message": "^8.8",
"drupal/core-recommended": "^8.8",
"drupal/core-vendor-hardening": "^8.8" "drupal/core-vendor-hardening": "^8.8"
}, },
"require-dev": { "require-dev": {
...@@ -41,6 +47,25 @@ ...@@ -41,6 +47,25 @@
"drush/Commands/contrib/{$name}": ["type:drupal-drush"], "drush/Commands/contrib/{$name}": ["type:drupal-drush"],
"modules/custom/{$name}": ["type:drupal-custom-module"], "modules/custom/{$name}": ["type:drupal-custom-module"],
"themes/custom/{$name}": ["type:drupal-custom-theme"] "themes/custom/{$name}": ["type:drupal-custom-theme"]
},
"drupal-core-project-message": {
"include-keys": ["homepage", "support"],
"post-create-project-cmd-message": [
"<bg=blue;fg=white> </>",
"<bg=blue;fg=white> Congratulations, you’ve installed the Drupal codebase </>",
"<bg=blue;fg=white> from the drupal/legacy-project template! </>",
"<bg=blue;fg=white> </>",
"",
"<bg=yellow;fg=black>Next steps</>:",
" * Install the site: https://www.drupal.org/docs/8/install",
" * Read the user guide: https://www.drupal.org/docs/user_guide/en/index.html",
" * Get support: https://www.drupal.org/support",
" * Get involved with the Drupal community:",
" https://www.drupal.org/getting-involved",
" * Remove the plugin that prints this message:",
" composer remove drupal/core-project-message"
]
} }
} }
} }
...@@ -3,6 +3,11 @@ ...@@ -3,6 +3,11 @@
"description": "Project template for Drupal 8 projects with a relocated document root", "description": "Project template for Drupal 8 projects with a relocated document root",
"type": "project", "type": "project",
"license": "GPL-2.0-or-later", "license": "GPL-2.0-or-later",
"homepage": "https://www.drupal.org/project/drupal",
"support": {
"docs": "https://www.drupal.org/docs/user_guide/en/index.html",
"chat": "https://www.drupal.org/node/314178"
},
"repositories": [ "repositories": [
{ {
"type": "composer", "type": "composer",
...@@ -11,8 +16,9 @@ ...@@ -11,8 +16,9 @@
], ],
"require": { "require": {
"composer/installers": "^1.2", "composer/installers": "^1.2",
"drupal/core-recommended": "^8.8", "drupal/core-composer-scaffold": "^8.8",
"drupal/core-composer-scaffold": "^8.8" "drupal/core-project-message": "^8.8",
"drupal/core-recommended": "^8.8"
}, },
"require-dev": { "require-dev": {
"drupal/core-dev": "^8.8" "drupal/core-dev": "^8.8"
...@@ -40,6 +46,25 @@ ...@@ -40,6 +46,25 @@
"drush/Commands/contrib/{$name}": ["type:drupal-drush"], "drush/Commands/contrib/{$name}": ["type:drupal-drush"],
"web/modules/custom/{$name}": ["type:drupal-custom-module"], "web/modules/custom/{$name}": ["type:drupal-custom-module"],
"web/themes/custom/{$name}": ["type:drupal-custom-theme"] "web/themes/custom/{$name}": ["type:drupal-custom-theme"]
},
"drupal-core-project-message": {
"include-keys": ["homepage", "support"],
"post-create-project-cmd-message": [
"<bg=blue;fg=white> </>",
"<bg=blue;fg=white> Congratulations, you’ve installed the Drupal codebase </>",
"<bg=blue;fg=white> from the drupal/recommended-project template! </>",
"<bg=blue;fg=white> </>",
"",
"<bg=yellow;fg=black>Next steps</>:",
" * Install the site: https://www.drupal.org/docs/8/install",
" * Read the user guide: https://www.drupal.org/docs/user_guide/en/index.html",
" * Get support: https://www.drupal.org/support",
" * Get involved with the Drupal community:",
" https://www.drupal.org/getting-involved",
" * Remove the plugin that prints this message:",
" composer remove drupal/core-project-message"
]
} }
} }
} }
<?php
namespace Drupal\Tests\Composer\Plugin\ProjectMessage;
use Composer\Package\RootPackageInterface;
use Drupal\Composer\Plugin\ProjectMessage\Message;
use PHPUnit\Framework\TestCase;
use org\bovigo\vfs\vfsStream;
/**
* @coversDefaultClass Drupal\Composer\Plugin\ProjectMessage\Message
* @group ProjectMessage
*/
class ConfigTest extends TestCase {
public static function setUpBeforeClass() {
parent::setUpBeforeClass();
vfsStream::setup('config_test', NULL, [
'bespoke' => [
'special_file.txt' => "Special\nFile",
],
]);
}
public function provideGetMessageText() {
return [
[[], []],
[
['Special', 'File'],
[
'drupal-core-project-message' => [
'event-name-file' => vfsStream::url('config_test/bespoke/special_file.txt'),
],
],
],
[
['I am the message.'],
[
'drupal-core-project-message' => [
'event-name-message' => ['I am the message.'],
],
],
],
[
['This message overrides file.'],
[
'drupal-core-project-message' => [
'event-name-message' => ['This message overrides file.'],
'event-name-file' => vfsStream::url('config_test/bespoke/special_file.txt'),
],
],
],
];
}
/**
* @dataProvider provideGetMessageText
* @covers ::getText
*/
public function testGetMessageText($expected, $config) {
// Root package has our config.
$root = $this->getMockBuilder(RootPackageInterface::class)
->setMethods(['getExtra'])
->getMockForAbstractClass();
$root->expects($this->once())
->method('getExtra')
->willReturn($config);
$message = new Message($root, 'event-name');
$this->assertSame($expected, $message->getText());
}
/**
* @covers ::getText
*/
public function testDefaultFile() {
// Root package has no extra field.
$root = $this->getMockBuilder(RootPackageInterface::class)
->setMethods(['getExtra'])
->getMockForAbstractClass();
$root->expects($this->once())
->method('getExtra')
->willReturn([]);
// The default is to try to read from event-name-message.txt, so we expect
// config to try that.
$message = $this->getMockBuilder(Message::class)
->setConstructorArgs([$root, 'event-name'])
->setMethods(['getMessageFromFile'])
->getMock();
$message->expects($this->once())
->method('getMessageFromFile')
->with('event-name-message.txt')
->willReturn([]);
$this->assertSame([], $message->getText());
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment