Hello community,

here is the log from the commit of package platformsh-cli for openSUSE:Factory 
checked in at 2018-02-20 17:55:41
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/platformsh-cli (Old)
 and      /work/SRC/openSUSE:Factory/.platformsh-cli.new (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "platformsh-cli"

Tue Feb 20 17:55:41 2018 rev:36 rq:578163 version:3.29.1

Changes:
--------
--- /work/SRC/openSUSE:Factory/platformsh-cli/platformsh-cli.changes    
2018-02-13 10:32:20.082756333 +0100
+++ /work/SRC/openSUSE:Factory/.platformsh-cli.new/platformsh-cli.changes       
2018-02-20 17:55:46.816941304 +0100
@@ -1,0 +2,15 @@
+Mon Feb 19 17:19:37 UTC 2018 - [email protected]
+
+- Update to version 3.29.1:
+  * Release v3.29.1
+  * Add self:release command
+  * Fix array to string conversion in project identifier
+  * [auth:browser-login] Always show help message
+  * [activity:get] [activity:log] Match partial activity IDs
+  * New line tweak [skip changelog]
+  * Add self:stats command
+  * [project:create] Get the list of regions dynamically
+  * [activity:log] Do not require ANSI check for polling/logging
+  * Merge metadata back from master [skip changelog]
+
+-------------------------------------------------------------------

Old:
----
  platformsh-cli-3.29.0.tar.xz

New:
----
  platformsh-cli-3.29.1.tar.xz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ platformsh-cli.spec ++++++
--- /var/tmp/diff_new_pack.5mtXT3/_old  2018-02-20 17:55:48.168892624 +0100
+++ /var/tmp/diff_new_pack.5mtXT3/_new  2018-02-20 17:55:48.172892480 +0100
@@ -17,7 +17,7 @@
 
 
 Name:           platformsh-cli
-Version:        3.29.0
+Version:        3.29.1
 Release:        0
 Summary:        Tool for managing Platform.sh services from the command line
 # See licenses.txt for dependency licenses.

++++++ _service ++++++
--- /var/tmp/diff_new_pack.5mtXT3/_old  2018-02-20 17:55:48.208891184 +0100
+++ /var/tmp/diff_new_pack.5mtXT3/_new  2018-02-20 17:55:48.216890896 +0100
@@ -2,7 +2,7 @@
   <service name="tar_scm" mode="disabled">
     <param name="versionformat">@PARENT_TAG@</param>
     <param name="versionrewrite-pattern">v(.*)</param>
-    <param name="revision">refs/tags/v3.29.0</param>
+    <param name="revision">refs/tags/v3.29.1</param>
     <param name="url">git://github.com/platformsh/platformsh-cli.git</param>
     <param name="scm">git</param>
     <param name="changesgenerate">enable</param>

++++++ _servicedata ++++++
--- /var/tmp/diff_new_pack.5mtXT3/_old  2018-02-20 17:55:48.240890032 +0100
+++ /var/tmp/diff_new_pack.5mtXT3/_new  2018-02-20 17:55:48.240890032 +0100
@@ -1,6 +1,6 @@
 <servicedata>
   <service name="tar_scm">
     <param name="url">git://github.com/platformsh/platformsh-cli.git</param>
-    <param 
name="changesrevision">55eff65515f99bd641aff9905c3b664f1119fc79</param>
+    <param 
name="changesrevision">eca9da9648826c4932b4b74f5fc6d4d3717dc6e3</param>
   </service>
 </servicedata>

++++++ licenses.txt ++++++
--- /var/tmp/diff_new_pack.5mtXT3/_old  2018-02-20 17:55:48.300887871 +0100
+++ /var/tmp/diff_new_pack.5mtXT3/_new  2018-02-20 17:55:48.300887871 +0100
@@ -16,8 +16,8 @@
 padraic/humbug_get_contents         1.0.4    BSD-3-Clause  
 padraic/phar-updater                1.0.4    BSD-3-Clause  
 paragonie/random_compat             v2.0.11  MIT           
-platformsh/client                   v0.14.1  MIT           
-platformsh/console-form             v0.0.15  MIT           
+platformsh/client                   v0.15.0  MIT           
+platformsh/console-form             v0.0.16  MIT           
 psr/container                       1.0.0    MIT           
 psr/log                             1.0.2    MIT           
 react/promise                       v2.5.1   MIT           

++++++ platformsh-cli-3.29.0.tar.xz -> platformsh-cli-3.29.1.tar.xz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/platformsh-cli-3.29.0/composer.json 
new/platformsh-cli-3.29.1/composer.json
--- old/platformsh-cli-3.29.0/composer.json     2018-02-12 10:10:33.000000000 
+0100
+++ new/platformsh-cli-3.29.1/composer.json     2018-02-19 13:10:22.000000000 
+0100
@@ -7,8 +7,8 @@
         "doctrine/cache": "~1.5",
         "guzzlehttp/guzzle": "^5.3",
         "guzzlehttp/ringphp": "^1.1",
-        "platformsh/console-form": ">=0.0.14 <2.0",
-        "platformsh/client": ">=0.14.0 <2.0",
+        "platformsh/console-form": ">=0.0.16 <2.0",
+        "platformsh/client": ">=0.15.0 <2.0",
         "symfony/console": "^3.0 !=3.2.5 !=3.2.6",
         "symfony/yaml": "^3.0 || ^2.6",
         "symfony/finder": "^3.0",
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/platformsh-cli-3.29.0/composer.lock 
new/platformsh-cli-3.29.1/composer.lock
--- old/platformsh-cli-3.29.0/composer.lock     2018-02-12 10:10:33.000000000 
+0100
+++ new/platformsh-cli-3.29.1/composer.lock     2018-02-19 13:10:22.000000000 
+0100
@@ -4,7 +4,7 @@
         "Read more about it at 
https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file";,
         "This file is @generated automatically"
     ],
-    "content-hash": "99aec30edb51fe7608bde25ca891ca04",
+    "content-hash": "61139468c3903d4897f8fcfece451e34",
     "packages": [
         {
             "name": "cocur/slugify",
@@ -643,16 +643,16 @@
         },
         {
             "name": "platformsh/client",
-            "version": "v0.14.1",
+            "version": "v0.15.0",
             "source": {
                 "type": "git",
                 "url": 
"https://github.com/platformsh/platformsh-client-php.git";,
-                "reference": "b1c5ff8a0cd896a80aa7330a42d7be9e9ef0dd98"
+                "reference": "1ab0bd8d229dda78b4a545379fd107de4ead872c"
             },
             "dist": {
                 "type": "zip",
-                "url": 
"https://api.github.com/repos/platformsh/platformsh-client-php/zipball/b1c5ff8a0cd896a80aa7330a42d7be9e9ef0dd98";,
-                "reference": "b1c5ff8a0cd896a80aa7330a42d7be9e9ef0dd98",
+                "url": 
"https://api.github.com/repos/platformsh/platformsh-client-php/zipball/1ab0bd8d229dda78b4a545379fd107de4ead872c";,
+                "reference": "1ab0bd8d229dda78b4a545379fd107de4ead872c",
                 "shasum": ""
             },
             "require": {
@@ -688,20 +688,20 @@
                 }
             ],
             "description": "Platform.sh API client",
-            "time": "2018-01-19T10:05:52+00:00"
+            "time": "2018-02-12T10:31:29+00:00"
         },
         {
             "name": "platformsh/console-form",
-            "version": "v0.0.15",
+            "version": "v0.0.16",
             "source": {
                 "type": "git",
                 "url": "https://github.com/platformsh/console-form.git";,
-                "reference": "f8d6ec0d810e24e3a028bfffae905325289b8559"
+                "reference": "57931bde54bcc13ae0c784a71b41bcf32f1adeba"
             },
             "dist": {
                 "type": "zip",
-                "url": 
"https://api.github.com/repos/platformsh/console-form/zipball/f8d6ec0d810e24e3a028bfffae905325289b8559";,
-                "reference": "f8d6ec0d810e24e3a028bfffae905325289b8559",
+                "url": 
"https://api.github.com/repos/platformsh/console-form/zipball/57931bde54bcc13ae0c784a71b41bcf32f1adeba";,
+                "reference": "57931bde54bcc13ae0c784a71b41bcf32f1adeba",
                 "shasum": ""
             },
             "require": {
@@ -727,7 +727,7 @@
                 }
             ],
             "description": "A lightweight Symfony Console form system.",
-            "time": "2017-12-11T15:23:12+00:00"
+            "time": "2018-02-12T11:15:24+00:00"
         },
         {
             "name": "psr/container",
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/platformsh-cli-3.29.0/config.yaml 
new/platformsh-cli-3.29.1/config.yaml
--- old/platformsh-cli-3.29.0/config.yaml       2018-02-12 10:10:33.000000000 
+0100
+++ new/platformsh-cli-3.29.1/config.yaml       2018-02-19 13:10:22.000000000 
+0100
@@ -1,7 +1,7 @@
 # Metadata about the CLI application itself.
 application:
   name: 'Platform.sh CLI'
-  version: '3.29.0'
+  version: '3.29.1'
   executable: 'platform'
   package_name: 'platformsh/cli'
   installer_url: 'https://platform.sh/cli/installer'
@@ -10,6 +10,7 @@
   user_state_file: '.platformsh/state.json'
   env_prefix: 'PLATFORMSH_CLI_'
   tmp_sub_dir: 'platformsh-cli-tmp'
+  github_repo: 'platformsh/platformsh-cli'
 
   # The default interactive login method: either 'browser' or 'password'.
   # This can be overridden in the user config file.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/platformsh-cli-3.29.0/dist/manifest.json 
new/platformsh-cli-3.29.1/dist/manifest.json
--- old/platformsh-cli-3.29.0/dist/manifest.json        2018-02-12 
10:10:33.000000000 +0100
+++ new/platformsh-cli-3.29.1/dist/manifest.json        2018-02-19 
13:10:22.000000000 +0100
@@ -1,10 +1,10 @@
 [
     {
         "name": "platform.phar",
-        "sha1": "2f269ba903905599f2b2f6f94cde8174c551a25a",
-        "sha256": 
"7ca20c402d2514abb374fc4ec0deac04d2d1a0979e66336b3a4305806c414a38",
-        "url": 
"https://github.com/platformsh/platformsh-cli/releases/download/v3.29.0/platform.phar";,
-        "version": "3.29.0",
+        "sha1": "11d95c5f2e68a534cb19b7be34c146ce5d0ec142",
+        "sha256": 
"e3c41764818063bbdb9f59145d187cf8abd8dbc03a6aeadb7f4984b3fde00a59",
+        "url": 
"https://github.com/platformsh/platformsh-cli/releases/download/v3.29.1/platform.phar";,
+        "version": "3.29.1",
         "php": {
             "min": "5.5.9"
         },
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/platformsh-cli-3.29.0/src/Application.php 
new/platformsh-cli-3.29.1/src/Application.php
--- old/platformsh-cli-3.29.0/src/Application.php       2018-02-12 
10:10:33.000000000 +0100
+++ new/platformsh-cli-3.29.1/src/Application.php       2018-02-19 
13:10:22.000000000 +0100
@@ -157,6 +157,8 @@
         $commands[] = new Command\Self\SelfBuildCommand();
         $commands[] = new Command\Self\SelfInstallCommand();
         $commands[] = new Command\Self\SelfUpdateCommand();
+        $commands[] = new Command\Self\SelfReleaseCommand();
+        $commands[] = new Command\Self\SelfStatsCommand();
         $commands[] = new Command\Server\ServerRunCommand();
         $commands[] = new Command\Server\ServerStartCommand();
         $commands[] = new Command\Server\ServerListCommand();
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/platformsh-cli-3.29.0/src/Command/Activity/ActivityGetCommand.php 
new/platformsh-cli-3.29.1/src/Command/Activity/ActivityGetCommand.php
--- old/platformsh-cli-3.29.0/src/Command/Activity/ActivityGetCommand.php       
2018-02-12 10:10:33.000000000 +0100
+++ new/platformsh-cli-3.29.1/src/Command/Activity/ActivityGetCommand.php       
2018-02-19 13:10:22.000000000 +0100
@@ -43,9 +43,14 @@
             $activity = $this->getSelectedProject()
                 ->getActivity($id);
             if (!$activity) {
-                $this->stdErr->writeln("Activity not found: 
<error>$id</error>");
+                $activities = $this->getSelectedEnvironment()
+                    ->getActivities(0, $input->getOption('type'));
+                $activity = $this->api()->matchPartialId($id, $activities, 
'Activity');
+                if (!$activity) {
+                    $this->stdErr->writeln("Activity not found: 
<error>$id</error>");
 
-                return 1;
+                    return 1;
+                }
             }
         } else {
             if ($this->hasSelectedEnvironment() && !$input->getOption('all')) {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/platformsh-cli-3.29.0/src/Command/Activity/ActivityLogCommand.php 
new/platformsh-cli-3.29.1/src/Command/Activity/ActivityLogCommand.php
--- old/platformsh-cli-3.29.0/src/Command/Activity/ActivityLogCommand.php       
2018-02-12 10:10:33.000000000 +0100
+++ new/platformsh-cli-3.29.1/src/Command/Activity/ActivityLogCommand.php       
2018-02-19 13:10:22.000000000 +0100
@@ -46,9 +46,14 @@
             $activity = $this->getSelectedProject()
                              ->getActivity($id);
             if (!$activity) {
-                $this->stdErr->writeln("Activity not found: 
<error>$id</error>");
+                $activities = $this->getSelectedEnvironment()
+                    ->getActivities(0, $input->getOption('type'));
+                $activity = $this->api()->matchPartialId($id, $activities, 
'Activity');
+                if (!$activity) {
+                    $this->stdErr->writeln("Activity not found: 
<error>$id</error>");
 
-                return 1;
+                    return 1;
+                }
             }
         } else {
             if ($this->hasSelectedEnvironment() && !$input->getOption('all')) {
@@ -79,7 +84,7 @@
         ]);
 
         $refresh = $input->getOption('refresh');
-        if ($refresh > 0 && !$this->runningViaMulti && $output->isDecorated() 
&& !$activity->isComplete()) {
+        if ($refresh > 0 && !$this->runningViaMulti && 
!$activity->isComplete()) {
             $activity->wait(
                 null,
                 function ($log) use ($output) {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/platformsh-cli-3.29.0/src/Command/Auth/BrowserLoginCommand.php 
new/platformsh-cli-3.29.1/src/Command/Auth/BrowserLoginCommand.php
--- old/platformsh-cli-3.29.0/src/Command/Auth/BrowserLoginCommand.php  
2018-02-12 10:10:33.000000000 +0100
+++ new/platformsh-cli-3.29.1/src/Command/Auth/BrowserLoginCommand.php  
2018-02-19 13:10:22.000000000 +0100
@@ -128,24 +128,30 @@
         } else {
             $this->stdErr->writeln('Please open the following URL in a browser 
and log in:');
             $this->stdErr->writeln('<info>' . $localUrl . '</info>');
-            $this->stdErr->writeln('');
-            $this->stdErr->writeln(sprintf(
-                'For help, quit this process (e.g. with Ctrl+C), and run: 
<info>%s help login</info>',
-                $this->config()->get('application.executable')
-            ));
         }
 
+        // Show some help.
+        $this->stdErr->writeln('');
+        $this->stdErr->writeln('<options=bold>Help:</>');
+        $this->stdErr->writeln('  Use Ctrl+C to quit this process.');
+        $executable = $this->config()->get('application.executable');
+        $this->stdErr->writeln(sprintf('  To log in within the terminal 
instead, quit and run: <info>%s auth:password-login</info>', $executable));
+        $this->stdErr->writeln(sprintf('  For more info, quit and run: 
<info>%s help login</info>', $executable));
+        $this->stdErr->writeln('');
+
         // Wait for the file to be filled with an OAuth2 authorization code.
         $code = null;
         while ($process->isRunning() && empty($code)) {
             usleep(300000);
             if (!file_exists($codeFile)) {
                 $this->stdErr->writeln('File not found: <error>' . $codeFile . 
'</error>');
+                $this->stdErr->writeln('');
                 break;
             }
             $code = file_get_contents($codeFile);
             if ($code === false) {
                 $this->stdErr->writeln('Failed to read file: <error>' . 
$codeFile . '</error>');
+                $this->stdErr->writeln('');
                 break;
             }
         }
@@ -154,8 +160,6 @@
         $process->stop();
         (new Filesystem())->remove([$listenerDir]);
 
-        $this->stdErr->writeln('');
-
         if (empty($code)) {
             $this->stdErr->writeln('Failed to get an authorization code. 
Please try again.');
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/platformsh-cli-3.29.0/src/Command/Project/ProjectCreateCommand.php 
new/platformsh-cli-3.29.1/src/Command/Project/ProjectCreateCommand.php
--- old/platformsh-cli-3.29.0/src/Command/Project/ProjectCreateCommand.php      
2018-02-12 10:10:33.000000000 +0100
+++ new/platformsh-cli-3.29.1/src/Command/Project/ProjectCreateCommand.php      
2018-02-19 13:10:22.000000000 +0100
@@ -189,19 +189,27 @@
     /**
      * Return a list of regions.
      *
-     * The default list is in the config `service.available_regions`. This can
-     * be overridden by the user config `experimental.available_regions`.
+     * The default list is in the config `service.available_regions`. This is
+     * replaced at runtime by an API call.
      *
-     * @return string[]
+     * @param bool $runtime
+     *
+     * @return array
      */
-    protected function getAvailableRegions()
+    protected function getAvailableRegions($runtime = false)
     {
-        $config = $this->config();
-        if ($config->has('experimental.available_regions')) {
-            return $config->get('experimental.available_regions');
+        if ($runtime) {
+            $regions = [];
+            foreach ($this->api()->getClient()->getRegions() as $region) {
+                if ($region->available) {
+                    $regions[$region->id] = $region->label;
+                }
+            }
+        } else {
+            $regions = (array) 
$this->config()->get('service.available_regions');
         }
 
-        return $config->get('service.available_regions');
+        return $regions;
     }
 
     /**
@@ -222,7 +230,9 @@
             'optionName' => 'region',
             'description' => 'The region where the project will be hosted',
             'options' => $this->getAvailableRegions(),
-            'allowOther' => true,
+            'optionsCallback' => function () {
+                return $this->getAvailableRegions(true);
+            },
           ]),
           'plan' => new OptionsField('Plan', [
             'optionName' => 'plan',
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/platformsh-cli-3.29.0/src/Command/Self/SelfReleaseCommand.php 
new/platformsh-cli-3.29.1/src/Command/Self/SelfReleaseCommand.php
--- old/platformsh-cli-3.29.0/src/Command/Self/SelfReleaseCommand.php   
1970-01-01 01:00:00.000000000 +0100
+++ new/platformsh-cli-3.29.1/src/Command/Self/SelfReleaseCommand.php   
2018-02-19 13:10:22.000000000 +0100
@@ -0,0 +1,252 @@
+<?php
+namespace Platformsh\Cli\Command\Self;
+
+use GuzzleHttp\Client;
+use Platformsh\Cli\Command\CommandBase;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class SelfReleaseCommand extends CommandBase
+{
+    protected $hiddenInList = true;
+
+    protected function configure()
+    {
+        $this
+            ->setName('self:release')
+            ->setDescription('Build and release a new version')
+            ->addOption('phar', null, InputOption::VALUE_REQUIRED, 'The path 
to a newly built Phar file')
+            ->addOption('repo', null, InputOption::VALUE_REQUIRED, 'The GitHub 
repository', $this->config()->has('application.github_repo') ? 
$this->config()->get('application.github_repo') : null);
+    }
+
+    public function isEnabled()
+    {
+        return $this->config()->has('application.github_repo')
+            && (!extension_loaded('Phar') || !\Phar::running(false));
+    }
+
+    /**
+     * @throws \Exception
+     */
+    protected function execute(InputInterface $input, OutputInterface $output)
+    {
+        /** @var \Platformsh\Cli\Service\QuestionHelper $questionHelper */
+        $questionHelper = $this->getService('question_helper');
+
+        /** @var \Platformsh\Cli\Service\Shell $shell */
+        $shell = $this->getService('shell');
+
+        /** @var \Platformsh\Cli\Service\Git $git */
+        $git = $this->getService('git');
+        $git->setDefaultRepositoryDir(CLI_ROOT);
+
+        if ($git->getCurrentBranch(CLI_ROOT, true) !== 'master') {
+            $this->stdErr->writeln('You must be on the master branch to make a 
release.');
+
+            return 1;
+        }
+
+        if ($git->execute(['diff', 'master...development'], CLI_ROOT, true) && 
$questionHelper->confirm('Merge changes from development?')) {
+            $git->execute(['merge', 'development'], CLI_ROOT, true);
+        }
+
+        $gitStatus = $git->execute(['status', '--porcelain'], CLI_ROOT, true);
+        if (is_string($gitStatus) && !empty($gitStatus)) {
+            foreach (explode("\n", $gitStatus) as $statusLine) {
+                if (strpos($statusLine, ' config.yaml') === false) {
+                    $this->stdErr->writeln('There are uncommitted changes in 
Git. Cannot proceed.');
+
+                    return 1;
+                }
+            }
+        }
+
+        if (getenv('GITHUB_TOKEN')) {
+            $gitHubToken = getenv('GITHUB_TOKEN');
+        } else {
+            $this->stdErr->writeln('The GITHUB_TOKEN environment variable must 
be set');
+
+            return 1;
+        }
+
+        $newVersion = $this->config()->get('application.version');
+        $this->stdErr->writeln('The version number defined in the config.yaml 
file is: <comment>' . $newVersion . '</comment>');
+
+        if (substr($newVersion, 0, 1) === 'v') {
+            $this->stdErr->writeln('The version number should not be prefixed 
by `v`.');
+
+            return 1;
+        }
+        if (!$questionHelper->confirm('Is <comment>' . $newVersion . 
'</comment> the correct new version number?')) {
+            $this->stdErr->writeln('Update the version number in config.yaml 
and re-run this command.');
+
+            return 1;
+        }
+
+        $tagName = 'v' . $newVersion;
+        $http = new Client();
+        $repo = $input->getOption('repo') ?: 
$this->config()->get('application.github_repo');
+        $repoUrl = implode('/', array_map('rawurlencode', explode('/', 
$repo)));
+        $repoApiUrl = 'https://api.github.com/repos/' . $repoUrl;
+        $repoGitUrl = '[email protected]:' . $repo . '.git';
+
+        $existsResponse = $http->get($repoApiUrl . '/releases/tags/' . 
$tagName, [
+            'headers' => [
+                'Authorization' => 'token ' . $gitHubToken,
+                'Accept' => 'application/vnd.github.v3+json',
+                'Content-Type' => 'application/json',
+            ],
+            'exceptions' => false,
+            'debug' => $output->isDebug(),
+        ]);
+        if ($existsResponse->getStatusCode() !== 404) {
+            if ($existsResponse->getStatusCode() >= 300) {
+                $this->stdErr->writeln('Failed to check for an existing 
release on GitHub.');
+
+                return 1;
+            }
+            $this->stdErr->writeln('A release tagged ' . $tagName . ' already 
exists on GitHub.');
+
+            return 1;
+        }
+
+        $pharFilename = $input->getOption('phar');
+        if ($pharFilename && !file_exists($pharFilename)) {
+            $this->stdErr->writeln('File not found: <error>' . $pharFilename . 
'</error>');
+
+            return 1;
+        }
+        if (!$pharFilename) {
+            $pharFilename = CLI_ROOT . '/' . 
$this->config()->get('application.executable') . '.phar';
+            $result = $this->runOtherCommand('self:build', [
+                '--output' => $pharFilename,
+            ]);
+            if ($result !== 0) {
+                $this->stdErr->writeln('The build failed');
+
+                return $result;
+            }
+        } else {
+            $versionInPhar = $shell->execute(['php', $pharFilename, 
'--version'], null, true);
+            if (strpos($versionInPhar, $newVersion) === false) {
+                $this->stdErr->writeln('The file ' . $pharFilename . ' reports 
a different version: "' . $versionInPhar . '"');
+
+                return 1;
+            }
+        }
+
+        $gitStatus = $git->execute(['status', '--porcelain'], CLI_ROOT, true);
+        if (is_string($gitStatus) && !empty($gitStatus)) {
+            $this->stdErr->writeln('Committing changes to Git');
+
+            $result = $shell->executeSimple('git commit --patch config.yaml 
dist/manifest.json --message ' . escapeshellarg('Release v' . $newVersion) . ' 
--edit', CLI_ROOT);
+            if ($result !== 0) {
+                return $result;
+            }
+        }
+
+        $latest = $http->get($repoApiUrl . '/releases/latest', [
+            'headers' => [
+                'Authorization' => 'token ' . $gitHubToken,
+                'Accept' => 'application/vnd.github.v3+json',
+                'Content-Type' => 'application/json',
+            ],
+            'debug' => $output->isDebug(),
+        ])->json();
+        $lastVersion = $latest['tag_name'];
+
+        $changelog = $git->execute([
+            'log',
+            '--pretty=format:* %s',
+            '--no-merges',
+            '--invert-grep',
+            '--grep=(Release v|\[skip changelog\])',
+            '--perl-regexp',
+            '--regexp-ignore-case',
+            $lastVersion . '...' . $tagName
+        ], CLI_ROOT, true);
+        if (empty($changelog)) {
+            $this->stdErr->writeln('Failed to find changelog for ' . 
$lastVersion . '...' . $tagName);
+
+            return 1;
+        }
+
+        $this->stdErr->writeln('Creating tag <info>' . $tagName . '</info>');
+        $git->execute(['tag', '--force', $tagName], CLI_ROOT, true);
+
+        if (!$questionHelper->confirm('Push changes and tag to 
<comment>master</comment> branch on ' . $repoGitUrl . '?')) {
+            return 1;
+        }
+        $shell->execute(['git', 'push', $repoGitUrl, 'HEAD:master'], CLI_ROOT, 
true);
+        $shell->execute(['git', 'push', '--force', $repoGitUrl, $tagName], 
CLI_ROOT, true);
+
+        $lastReleasePublicUrl = 'https://github.com/' . $repoUrl . 
'/releases/' . $lastVersion;
+        $pharPublicFilename = $this->config()->get('application.executable') . 
'.phar';
+        $releaseDescription = sprintf('Changes since [%s](%s):', $lastVersion, 
$lastReleasePublicUrl)
+            . "\n\n" . $changelog
+            . "\n\n" . sprintf('SHA-256 checksum for `%s`:', 
$pharPublicFilename)
+            . "\n" . sprintf('`%s`', hash_file('sha256', $pharFilename));
+
+        $this->stdErr->writeln('');
+        $this->stdErr->writeln('Creating new release ' . $tagName . ' on 
GitHub');
+        $this->stdErr->writeln('Release description:');
+        $this->stdErr->writeln(preg_replace('/^/m', '  ', 
$releaseDescription));
+        $this->stdErr->writeln('');
+
+        if (!$questionHelper->confirm('Is this OK?')) {
+            return 1;
+        }
+
+        $http = new Client();
+        $response = $http->post($repoApiUrl . '/releases', [
+            'headers' => [
+                'Authorization' => 'token ' . $gitHubToken,
+                'Accept' => 'application/vnd.github.v3+json',
+                'Content-Type' => 'application/json',
+            ],
+            'json' => [
+                'tag_name' => $tagName,
+                'name' => $tagName,
+                'body' => $releaseDescription,
+                'draft' => true,
+            ],
+            'debug' => $output->isDebug(),
+        ]);
+        $release = $response->json();
+        $releaseUrl = $repoApiUrl . '/releases/' . $release['id'];
+        $uploadUrl = preg_replace('/\{.+?\}/', '', $release['upload_url']);
+
+        $this->stdErr->writeln('Uploading the Phar file to the release');
+        $fileResource = fopen($pharFilename, 'r');
+        if (!$fileResource) {
+            throw new \RuntimeException('Failed to open file for reading: ' . 
$fileResource);
+        }
+        $http->post($uploadUrl . '?name=' . rawurldecode($pharPublicFilename), 
[
+            'headers' => [
+                'Authorization' => 'token ' . $gitHubToken,
+                'Accept' => 'application/vnd.github.v3+json',
+                'Content-Type' => 'application/octet-stream',
+            ],
+            'body' => $fileResource,
+            'debug' => $output->isDebug(),
+        ]);
+
+        $this->stdErr->writeln('Publishing the release');
+        $http->patch($releaseUrl, [
+            'headers' => [
+                'Authorization' => 'token ' . $gitHubToken,
+                'Accept' => 'application/vnd.github.v3+json',
+                'Content-Type' => 'application/json',
+            ],
+            'json' => ['draft' => false],
+            'debug' => $output->isDebug(),
+        ]);
+
+        $this->stdErr->writeln('');
+        $this->stdErr->writeln('Release successfully published');
+        $this->stdErr->writeln('https://github.com/' . $repoUrl . 
'/releases/latest');
+
+        return 0;
+    }
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/platformsh-cli-3.29.0/src/Command/Self/SelfStatsCommand.php 
new/platformsh-cli-3.29.1/src/Command/Self/SelfStatsCommand.php
--- old/platformsh-cli-3.29.0/src/Command/Self/SelfStatsCommand.php     
1970-01-01 01:00:00.000000000 +0100
+++ new/platformsh-cli-3.29.1/src/Command/Self/SelfStatsCommand.php     
2018-02-19 13:10:22.000000000 +0100
@@ -0,0 +1,65 @@
+<?php
+namespace Platformsh\Cli\Command\Self;
+
+use Platformsh\Cli\Command\CommandBase;
+use Platformsh\Cli\Service\PropertyFormatter;
+use Platformsh\Cli\Service\Table;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class SelfStatsCommand extends CommandBase
+{
+    protected $hiddenInList = true;
+
+    protected function configure()
+    {
+        $this
+            ->setName('self:stats')
+            ->setDescription('View stats on GitHub package downloads');
+        Table::configureInput($this->getDefinition());
+        PropertyFormatter::configureInput($this->getDefinition());
+    }
+
+    public function isEnabled()
+    {
+        return $this->config()->has('application.github_repo');
+    }
+
+    protected function execute(InputInterface $input, OutputInterface $output)
+    {
+        $repo = $this->config()->get('application.github_repo');
+        $repoUrl = implode('/', array_map('rawurlencode', explode('/', 
$repo)));
+        $releases = $this->api()
+            ->getHttpClient()
+            ->get('https://api.github.com/repos/' . $repoUrl . '/releases', [
+                'headers' => [
+                    'Accept' => 'application/vnd.github.v3+json',
+                ],
+                'auth' => false,
+            ])->json();
+
+        /** @var \Platformsh\Cli\Service\Table $table */
+        $table = $this->getService('table');
+        /** @var \Platformsh\Cli\Service\PropertyFormatter $formatter */
+        $formatter = $this->getService('property_formatter');
+        $headers = ['Release', 'Date', 'Asset', 'Downloads'];
+        $rows = [];
+        foreach ($releases as $release) {
+            if (!empty($release['assets'])) {
+                foreach ($release['assets'] as $asset) {
+                    $row = [];
+                    $row[] = $release['name'];
+                    $time = !empty($release['published_at']) ? 
$release['published_at'] : $release['created_at'];
+                    $row[] = $formatter->format($time, 'created_at');
+                    $row[] = $asset['name'];
+                    $row[] = $formatter->format($asset['download_count']);
+                    $rows[] = $row;
+                }
+            }
+        }
+
+        $table->render($rows, $headers);
+
+        return 0;
+    }
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/platformsh-cli-3.29.0/src/Command/User/UserAddCommand.php 
new/platformsh-cli-3.29.1/src/Command/User/UserAddCommand.php
--- old/platformsh-cli-3.29.0/src/Command/User/UserAddCommand.php       
2018-02-12 10:10:33.000000000 +0100
+++ new/platformsh-cli-3.29.1/src/Command/User/UserAddCommand.php       
2018-02-19 13:10:22.000000000 +0100
@@ -59,6 +59,9 @@
                     $choices[$account['email']] = $this->getUserLabel($access);
                 }
                 $email = $questionHelper->choose($choices, 'Enter a number to 
choose a user to update:');
+                if (count($choices) > 1) {
+                    $this->stdErr->writeln('');
+                }
             } else {
                 $question = new Question("Enter the user's email address: ");
                 $question->setValidator(function ($answer) {
@@ -66,8 +69,8 @@
                 });
                 $question->setMaxAttempts(5);
                 $email = $questionHelper->ask($input, $this->stdErr, 
$question);
+                $this->stdErr->writeln('');
             }
-            $this->stdErr->writeln('');
         }
         $this->validateEmail($email);
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/platformsh-cli-3.29.0/src/Command/User/UserRoleCommand.php 
new/platformsh-cli-3.29.1/src/Command/User/UserRoleCommand.php
--- old/platformsh-cli-3.29.0/src/Command/User/UserRoleCommand.php      
2018-02-12 10:10:33.000000000 +0100
+++ new/platformsh-cli-3.29.1/src/Command/User/UserRoleCommand.php      
2018-02-19 13:10:22.000000000 +0100
@@ -59,7 +59,9 @@
                 $choices[$account['email']] = sprintf('%s (%s)', 
$account['display_name'], $account['email']);
             }
             $email = $questionHelper->choose($choices, 'Enter a number to 
choose a user:');
-            $this->stdErr->writeln('');
+            if (count($choices) > 1) {
+                $this->stdErr->writeln('');
+            }
         }
         $projectAccess = $this->api()->loadProjectAccessByEmail($project, 
$email);
         if (!$projectAccess) {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/platformsh-cli-3.29.0/src/Service/Identifier.php 
new/platformsh-cli-3.29.1/src/Service/Identifier.php
--- old/platformsh-cli-3.29.0/src/Service/Identifier.php        2018-02-12 
10:10:33.000000000 +0100
+++ new/platformsh-cli-3.29.1/src/Service/Identifier.php        2018-02-19 
13:10:22.000000000 +0100
@@ -147,7 +147,7 @@
      *
      * @param string $url
      *
-     * @return string
+     * @return string|false
      */
     private function getClusterHeader($url)
     {
@@ -162,14 +162,16 @@
                     'connect_timeout' => 5,
                     'allow_redirects' => false,
                 ]);
-                $cluster = 
$response->getHeader($this->config->get('service.header_prefix') . '-cluster');
+                $cluster = 
$response->getHeaderAsArray($this->config->get('service.header_prefix') . 
'-cluster');
                 $this->cache->save($cacheKey, $cluster, 86400);
             } catch (RequestException $e) {
                 $this->debug($e->getMessage());
+
+                return false;
             }
         }
 
-        return $cluster;
+        return is_array($cluster) ? reset($cluster) : false;
     }
 
     /**
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/platformsh-cli-3.29.0/src/Service/Url.php 
new/platformsh-cli-3.29.1/src/Service/Url.php
--- old/platformsh-cli-3.29.0/src/Service/Url.php       2018-02-12 
10:10:33.000000000 +0100
+++ new/platformsh-cli-3.29.1/src/Service/Url.php       2018-02-19 
13:10:22.000000000 +0100
@@ -73,6 +73,7 @@
         // If the user wants to pipe the output to another command, stop here.
         if ($this->input->hasOption('pipe') && 
$this->input->getOption('pipe')) {
             $open = false;
+            $print = true;
         }
         // Check if the user has requested not to use a browser.
         elseif ($browserOption === '0') {

++++++ platformsh-cli-vendor.tar.xz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/vendor/autoload.php new/vendor/autoload.php
--- old/vendor/autoload.php     2018-02-12 22:57:57.119722174 +0100
+++ new/vendor/autoload.php     2018-02-19 18:19:43.443949293 +0100
@@ -4,4 +4,4 @@
 
 require_once __DIR__ . '/composer/autoload_real.php';
 
-return ComposerAutoloaderInit1000482ab31b6ea30c0fe42bed510766::getLoader();
+return ComposerAutoloaderInit76f933238b0cbdfc8a10ca06f30d0bbc::getLoader();
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/vendor/composer/autoload_real.php 
new/vendor/composer/autoload_real.php
--- old/vendor/composer/autoload_real.php       2018-02-12 22:57:57.119722174 
+0100
+++ new/vendor/composer/autoload_real.php       2018-02-19 18:19:43.443949293 
+0100
@@ -2,7 +2,7 @@
 
 // autoload_real.php @generated by Composer
 
-class ComposerAutoloaderInit1000482ab31b6ea30c0fe42bed510766
+class ComposerAutoloaderInit76f933238b0cbdfc8a10ca06f30d0bbc
 {
     private static $loader;
 
@@ -19,15 +19,15 @@
             return self::$loader;
         }
 
-        
spl_autoload_register(array('ComposerAutoloaderInit1000482ab31b6ea30c0fe42bed510766',
 'loadClassLoader'), true, true);
+        
spl_autoload_register(array('ComposerAutoloaderInit76f933238b0cbdfc8a10ca06f30d0bbc',
 'loadClassLoader'), true, true);
         self::$loader = $loader = new \Composer\Autoload\ClassLoader();
-        
spl_autoload_unregister(array('ComposerAutoloaderInit1000482ab31b6ea30c0fe42bed510766',
 'loadClassLoader'));
+        
spl_autoload_unregister(array('ComposerAutoloaderInit76f933238b0cbdfc8a10ca06f30d0bbc',
 'loadClassLoader'));
 
         $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') 
&& (!function_exists('zend_loader_file_encoded') || 
!zend_loader_file_encoded());
         if ($useStaticLoader) {
             require_once __DIR__ . '/autoload_static.php';
 
-            
call_user_func(\Composer\Autoload\ComposerStaticInit1000482ab31b6ea30c0fe42bed510766::getInitializer($loader));
+            
call_user_func(\Composer\Autoload\ComposerStaticInit76f933238b0cbdfc8a10ca06f30d0bbc::getInitializer($loader));
         } else {
             $map = require __DIR__ . '/autoload_namespaces.php';
             foreach ($map as $namespace => $path) {
@@ -48,19 +48,19 @@
         $loader->register(true);
 
         if ($useStaticLoader) {
-            $includeFiles = 
Composer\Autoload\ComposerStaticInit1000482ab31b6ea30c0fe42bed510766::$files;
+            $includeFiles = 
Composer\Autoload\ComposerStaticInit76f933238b0cbdfc8a10ca06f30d0bbc::$files;
         } else {
             $includeFiles = require __DIR__ . '/autoload_files.php';
         }
         foreach ($includeFiles as $fileIdentifier => $file) {
-            composerRequire1000482ab31b6ea30c0fe42bed510766($fileIdentifier, 
$file);
+            composerRequire76f933238b0cbdfc8a10ca06f30d0bbc($fileIdentifier, 
$file);
         }
 
         return $loader;
     }
 }
 
-function composerRequire1000482ab31b6ea30c0fe42bed510766($fileIdentifier, 
$file)
+function composerRequire76f933238b0cbdfc8a10ca06f30d0bbc($fileIdentifier, 
$file)
 {
     if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
         require $file;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/vendor/composer/autoload_static.php 
new/vendor/composer/autoload_static.php
--- old/vendor/composer/autoload_static.php     2018-02-12 22:57:57.119722174 
+0100
+++ new/vendor/composer/autoload_static.php     2018-02-19 18:19:43.443949293 
+0100
@@ -4,7 +4,7 @@
 
 namespace Composer\Autoload;
 
-class ComposerStaticInit1000482ab31b6ea30c0fe42bed510766
+class ComposerStaticInit76f933238b0cbdfc8a10ca06f30d0bbc
 {
     public static $files = array (
         '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . 
'/symfony/polyfill-mbstring/bootstrap.php',
@@ -189,9 +189,9 @@
     public static function getInitializer(ClassLoader $loader)
     {
         return \Closure::bind(function () use ($loader) {
-            $loader->prefixLengthsPsr4 = 
ComposerStaticInit1000482ab31b6ea30c0fe42bed510766::$prefixLengthsPsr4;
-            $loader->prefixDirsPsr4 = 
ComposerStaticInit1000482ab31b6ea30c0fe42bed510766::$prefixDirsPsr4;
-            $loader->classMap = 
ComposerStaticInit1000482ab31b6ea30c0fe42bed510766::$classMap;
+            $loader->prefixLengthsPsr4 = 
ComposerStaticInit76f933238b0cbdfc8a10ca06f30d0bbc::$prefixLengthsPsr4;
+            $loader->prefixDirsPsr4 = 
ComposerStaticInit76f933238b0cbdfc8a10ca06f30d0bbc::$prefixDirsPsr4;
+            $loader->classMap = 
ComposerStaticInit76f933238b0cbdfc8a10ca06f30d0bbc::$classMap;
 
         }, null, ClassLoader::class);
     }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/vendor/composer/installed.json 
new/vendor/composer/installed.json
--- old/vendor/composer/installed.json  2018-02-12 22:57:56.495716106 +0100
+++ new/vendor/composer/installed.json  2018-02-19 18:19:42.435939770 +0100
@@ -708,17 +708,17 @@
     },
     {
         "name": "platformsh/client",
-        "version": "v0.14.1",
-        "version_normalized": "0.14.1.0",
+        "version": "v0.15.0",
+        "version_normalized": "0.15.0.0",
         "source": {
             "type": "git",
             "url": "https://github.com/platformsh/platformsh-client-php.git";,
-            "reference": "b1c5ff8a0cd896a80aa7330a42d7be9e9ef0dd98"
+            "reference": "1ab0bd8d229dda78b4a545379fd107de4ead872c"
         },
         "dist": {
             "type": "zip",
-            "url": 
"https://api.github.com/repos/platformsh/platformsh-client-php/zipball/b1c5ff8a0cd896a80aa7330a42d7be9e9ef0dd98";,
-            "reference": "b1c5ff8a0cd896a80aa7330a42d7be9e9ef0dd98",
+            "url": 
"https://api.github.com/repos/platformsh/platformsh-client-php/zipball/1ab0bd8d229dda78b4a545379fd107de4ead872c";,
+            "reference": "1ab0bd8d229dda78b4a545379fd107de4ead872c",
             "shasum": ""
         },
         "require": {
@@ -731,7 +731,7 @@
         "require-dev": {
             "phpunit/phpunit": "~4.5"
         },
-        "time": "2018-01-19T10:05:52+00:00",
+        "time": "2018-02-12T10:31:29+00:00",
         "type": "library",
         "extra": {
             "patches": {
@@ -998,17 +998,17 @@
     },
     {
         "name": "platformsh/console-form",
-        "version": "v0.0.15",
-        "version_normalized": "0.0.15.0",
+        "version": "v0.0.16",
+        "version_normalized": "0.0.16.0",
         "source": {
             "type": "git",
             "url": "https://github.com/platformsh/console-form.git";,
-            "reference": "f8d6ec0d810e24e3a028bfffae905325289b8559"
+            "reference": "57931bde54bcc13ae0c784a71b41bcf32f1adeba"
         },
         "dist": {
             "type": "zip",
-            "url": 
"https://api.github.com/repos/platformsh/console-form/zipball/f8d6ec0d810e24e3a028bfffae905325289b8559";,
-            "reference": "f8d6ec0d810e24e3a028bfffae905325289b8559",
+            "url": 
"https://api.github.com/repos/platformsh/console-form/zipball/57931bde54bcc13ae0c784a71b41bcf32f1adeba";,
+            "reference": "57931bde54bcc13ae0c784a71b41bcf32f1adeba",
             "shasum": ""
         },
         "require": {
@@ -1018,7 +1018,7 @@
         "require-dev": {
             "phpunit/phpunit": "^5.0"
         },
-        "time": "2017-12-11T15:23:12+00:00",
+        "time": "2018-02-12T11:15:24+00:00",
         "type": "library",
         "installation-source": "dist",
         "autoload": {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/vendor/platformsh/client/src/Model/Region.php 
new/vendor/platformsh/client/src/Model/Region.php
--- old/vendor/platformsh/client/src/Model/Region.php   1970-01-01 
01:00:00.000000000 +0100
+++ new/vendor/platformsh/client/src/Model/Region.php   2018-02-12 
11:31:29.000000000 +0100
@@ -0,0 +1,63 @@
+<?php
+
+namespace Platformsh\Client\Model;
+
+use GuzzleHttp\ClientInterface;
+
+/**
+ * Represents a Platform.sh region.
+ *
+ * @property-read int    $id
+ * @property-read string $label
+ * @property-read bool   $available
+ * @property-read bool   $private
+ * @property-read string $zone
+ * @property-read string $endpoint
+ */
+class Region extends Resource
+{
+    /**
+     * @inheritdoc
+     */
+    protected function setData(array $data)
+    {
+        $data = isset($data['regions'][0]) ? $data['regions'][0] : $data;
+        $data['available'] = !empty($data['available']);
+        $data['private'] = !empty($data['private']);
+        $this->data = $data;
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public static function wrapCollection(array $data, $baseUrl, 
ClientInterface $client)
+    {
+        $data = isset($data['regions']) ? $data['regions'] : [];
+
+        return parent::wrapCollection($data, $baseUrl, $client);
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function operationAvailable($op)
+    {
+        if ($op === 'edit') {
+            return true;
+        }
+
+        return parent::operationAvailable($op);
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function getLink($rel, $absolute = false)
+    {
+        if ($rel === '#edit') {
+            return $this->getUri($absolute);
+        }
+
+        return parent::getLink($rel, $absolute);
+    }
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/vendor/platformsh/client/src/PlatformClient.php 
new/vendor/platformsh/client/src/PlatformClient.php
--- old/vendor/platformsh/client/src/PlatformClient.php 2018-01-19 
11:05:52.000000000 +0100
+++ new/vendor/platformsh/client/src/PlatformClient.php 2018-02-12 
11:31:29.000000000 +0100
@@ -7,6 +7,7 @@
 use Platformsh\Client\Connection\ConnectorInterface;
 use Platformsh\Client\Exception\ApiResponseException;
 use Platformsh\Client\Model\Project;
+use Platformsh\Client\Model\Region;
 use Platformsh\Client\Model\Result;
 use Platformsh\Client\Model\SshKey;
 use Platformsh\Client\Model\Subscription;
@@ -224,13 +225,15 @@
     /**
      * Create a new Platform.sh subscription.
      *
-     * @param string $region  The region. See Subscription::$availableRegions.
-     * @param string $plan    The plan. See Subscription::$availablePlans.
-     * @param string $title   The project title.
-     * @param int    $storage The storage of each environment, in MiB.
-     * @param int    $environments The number of available environments.
+     * @param string $region             The region ID. See getRegions().
+     * @param string $plan               The plan. See 
Subscription::$availablePlans.
+     * @param string $title              The project title.
+     * @param int    $storage            The storage of each environment, in 
MiB.
+     * @param int    $environments       The number of available environments.
      * @param array  $activationCallback An activation callback for the 
subscription.
      *
+     * @see PlatformClient::getRegions().
+     *
      * @return Subscription
      */
     public function createSubscription($region, $plan = 'development', $title 
= null, $storage = null, $environments = null, array $activationCallback = null)
@@ -301,4 +304,14 @@
 
         return $response->json();
     }
+
+    /**
+     * Get a list of available regions.
+     *
+     * @return Region[]
+     */
+    public function getRegions()
+    {
+        return Region::getCollection($this->accountsEndpoint . 'regions', 0, 
[], $this->getConnector()->getClient());
+    }
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/vendor/platformsh/console-form/src/Field/ArrayField.php 
new/vendor/platformsh/console-form/src/Field/ArrayField.php
--- old/vendor/platformsh/console-form/src/Field/ArrayField.php 2017-12-11 
16:23:12.000000000 +0100
+++ new/vendor/platformsh/console-form/src/Field/ArrayField.php 2018-02-12 
12:15:24.000000000 +0100
@@ -11,14 +11,32 @@
     /**
      * {@inheritdoc}
      */
-    public function getAsQuestion()
+    public function normalize($value)
     {
-        $question = parent::getAsQuestion();
-        $question->setNormalizer(function ($value) {
-            return is_array($value) ? $value : 
array_filter(preg_split('/[,;\n] */', $value), 'strlen');
-        });
+        // If the value is an array of only one element, it might be a
+        // comma-separated string provided to the command-line option. Extract
+        // the first element.
+        if (is_array($value) && count($value) === 1) {
+            $value = reset($value);
+        }
 
-        return $question;
+        // If the value is a string, split it into an array.
+        if (is_string($value)) {
+            $value = $this->split($value);
+        }
+
+        return parent::normalize($value);
+    }
+
+    /**
+     * Split a comma or whitespace-separated string into an array.
+     *
+     * @param string $str
+     *
+     * @return array
+     */
+    private function split($str) {
+        return array_filter(preg_split('/[,\s]+/', $str), 'strlen');
     }
 
     /**
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/vendor/platformsh/console-form/src/Field/OptionsField.php 
new/vendor/platformsh/console-form/src/Field/OptionsField.php
--- old/vendor/platformsh/console-form/src/Field/OptionsField.php       
2017-12-11 16:23:12.000000000 +0100
+++ new/vendor/platformsh/console-form/src/Field/OptionsField.php       
2018-02-12 12:15:24.000000000 +0100
@@ -27,8 +27,13 @@
     {
         parent::__construct($name, $config);
         $this->validators[] = function ($value) {
-            return $this->allowOther || in_array($value, $this->options, true)
-                ? true : "$value is not one of: " . implode(', ', 
$this->options);
+            if ($this->allowOther) {
+                return true;
+            }
+            $options = $this->isNumeric() ? $this->options : 
array_keys($this->options);
+
+            return array_search($value, $options, true) !== false
+                ? true : "$value is not one of: " . implode(', ', $options);
         };
     }
 
@@ -63,17 +68,22 @@
      */
     protected function getChoiceQuestion()
     {
-        // Translate the default into an array key.
-        $defaultKey = $this->default !== null
-            ? array_search($this->default, $this->options, true) : 
$this->default;
+        $numeric = $this->isNumeric();
+        $text = $this->getQuestionHeader();
+        if ($numeric) {
+            $text .= "\nEnter a number to choose: ";
+        }
 
         $question = new ChoiceQuestion(
-            $this->getQuestionHeader() . "\nEnter a number to choose: ",
+            $text,
             $this->options,
-            $defaultKey !== false ? $defaultKey : null
+            $this->default
         );
         $question->setPrompt($this->prompt);
         $question->setMaxAttempts($this->maxAttempts);
+        if (!$numeric) {
+            $question->setAutocompleterValues(array_keys($this->options));
+        }
 
         return $question;
     }
@@ -84,9 +94,11 @@
     protected function getDescription()
     {
         $description = parent::getDescription();
-        $optionsString = "'" . implode("', '", $this->options) . "'";
-        if (strlen($optionsString) < 255) {
-            $description .= ' (' . $optionsString . ')';
+        if (!empty($this->options)) {
+            $optionsString = "'" . implode("', '", $this->options) . "'";
+            if (strlen($optionsString) < 255) {
+                $description .= ' (' . $optionsString . ')';
+            }
         }
 
         return $description;
@@ -102,4 +114,20 @@
             $this->options = $callback($previousValues);
         }
     }
+
+    /**
+     * Check if this is numeric, rather than associative.
+     *
+     * @return bool
+     */
+    private function isNumeric()
+    {
+        foreach (array_keys($this->options) as $key) {
+            if (!is_int($key)) {
+                return false;
+            }
+        }
+
+        return true;
+    }
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/vendor/platformsh/console-form/tests/FormTest.php 
new/vendor/platformsh/console-form/tests/FormTest.php
--- old/vendor/platformsh/console-form/tests/FormTest.php       2017-12-11 
16:23:12.000000000 +0100
+++ new/vendor/platformsh/console-form/tests/FormTest.php       2018-02-12 
12:15:24.000000000 +0100
@@ -14,6 +14,7 @@
 use Symfony\Component\Console\Helper\FormatterHelper;
 use Symfony\Component\Console\Helper\HelperSet;
 use Symfony\Component\Console\Helper\QuestionHelper;
+use Symfony\Component\Console\Input\ArgvInput;
 use Symfony\Component\Console\Input\ArrayInput;
 use Symfony\Component\Console\Input\InputDefinition;
 use Symfony\Component\Console\Output\NullOutput;
@@ -288,6 +289,10 @@
             'allowOther' => true,
             'optionName' => 'options-allow-other',
         ]), 'options_non_strict');
+        $this->form->addField(new OptionsField('Associative options', [
+            'options' => ['option1' => 'Option 1', 'option2' => 'Option 2', 
'option3' => 'Option 3'],
+            'optionName' => 'options-assoc',
+        ]), 'options-assoc');
         $this->form->configureInputDefinition($definition);
         $output = new NullOutput();
 
@@ -297,10 +302,15 @@
             '--mail' => $this->validMail,
             '--options' => 'option1',
             '--options-allow-other' => 'optionO',
+            '--options-assoc' => 'option2',
         ], $definition);
         $input->setInteractive(false);
         $result = $this->form->resolveOptions($input, $output, $helper);
-        $validResult = $this->validResult + ['options' => 'option1', 
'options_non_strict' => 'optionO'];
+        $validResult = $this->validResult + [
+            'options' => 'option1',
+            'options_non_strict' => 'optionO',
+            'options-assoc' => 'option2',
+        ];
         $this->assertEquals($validResult, $result, 'Valid non-interactive 
option input');
 
         // Test interactive input.
@@ -309,9 +319,13 @@
             '--mail' => $this->validMail,
             '--options-allow-other' => 'optionO',
         ], $definition);
-        $input->setStream($this->getInputStream(str_repeat("\n", 
$countFieldsBefore) . '1'));
+        $input->setStream($this->getInputStream(str_repeat("\n", 
$countFieldsBefore) . "1\noption2"));
         $result = $this->form->resolveOptions($input, $output, $helper);
-        $validResult = $this->validResult + ['options' => 'option2', 
'options_non_strict' => 'optionO'];
+        $validResult = $this->validResult + [
+            'options' => 'option2',
+            'options_non_strict' => 'optionO',
+            'options-assoc' => 'option2',
+        ];
         $this->assertEquals($validResult, $result, 'Valid interactive option 
input');
     }
 
@@ -375,6 +389,39 @@
       $this->assertEquals($validResult, $result, 'Empty input passes');
     }
 
+    public function testCommandLineCommaSeparatedArrayOptions()
+    {
+        $definition = new InputDefinition();
+        $this->form->configureInputDefinition($definition);
+
+        $validResult = $this->validResult;
+        $validResult['array'] = ['foo', 'bar', 'baz'];
+
+        $input = new ArgvInput([
+            'commandName',
+            '--test', $this->validString,
+            '--mail', $this->validMail,
+            '--array', 'foo, bar,baz',
+        ], $definition);
+        $input->setInteractive(false);
+        $result = $this->form->resolveOptions($input, new NullOutput(), 
$this->getQuestionHelper());
+        $this->assertEquals($validResult, $result, 'Array input with 
comma-separated values passes');
+
+        $validResult = $this->validResult;
+        $validResult['array'] = ['foo, bar', 'baz'];
+
+        $input = new ArgvInput([
+            'commandName',
+            '--test', $this->validString,
+            '--mail', $this->validMail,
+            '--array', 'foo, bar',
+            '--array', 'baz',
+        ], $definition);
+        $input->setInteractive(false);
+        $result = $this->form->resolveOptions($input, new NullOutput(), 
$this->getQuestionHelper());
+        $this->assertEquals($validResult, $result, 'Array input with array 
values passes');
+    }
+
     /**
      * @return QuestionHelper
      */


Reply via email to