public function testGetAppConfigNested()
 {
     $fakeAppRoot = 'tests/data/repositories/multiple/nest/nested';
     $app = new LocalApplication($fakeAppRoot);
     $config = $app->getConfig();
     $this->assertEquals(['name' => 'nested1'], $config);
     $this->assertEquals('nested1', $app->getName());
     $this->assertEquals('nested1', $app->getId());
 }
 /**
  * Process shared file mounts in the application.
  *
  * For each "mount", this creates a corresponding directory in the project's
  * shared files directory, and symlinks it into the appropriate path in the
  * build.
  */
 protected function processSharedFileMounts()
 {
     $sharedDir = $this->getSharedDir();
     if ($sharedDir === false) {
         return;
     }
     // If the build directory is a symlink, then skip, so that we don't risk
     // modifying the user's repository.
     if (is_link($this->buildDir)) {
         return;
     }
     $sharedFileMounts = $this->app->getSharedFileMounts();
     if (empty($sharedFileMounts)) {
         return;
     }
     $sharedDirRelative = $this->config->get('local.shared_dir');
     $this->output->writeln('Creating symbolic links to mimic shared file mounts');
     foreach ($sharedFileMounts as $appPath => $sharedPath) {
         $target = $sharedDir . '/' . $sharedPath;
         $targetRelative = $sharedDirRelative . '/' . $sharedPath;
         $link = $this->buildDir . '/' . $appPath;
         if (file_exists($link) && !is_link($link)) {
             $this->output->writeln('  Overwriting existing file <comment>' . $appPath . '</comment>');
         }
         if (!file_exists($target)) {
             $this->fsHelper->mkdir($target, 0775);
         }
         $this->output->writeln('  Symlinking <info>' . $appPath . '</info> to <info>' . $targetRelative . '</info>');
         $this->fsHelper->symlink($target, $link);
     }
 }
 protected function execute(InputInterface $input, OutputInterface $output)
 {
     $this->validateInput($input);
     $drushCommand = $input->getArgument('cmd');
     $sshOptions = '';
     // Pass through options that the CLI shares with Drush and SSH.
     foreach (['yes', 'no', 'quiet'] as $option) {
         if ($input->getOption($option)) {
             $drushCommand .= " --{$option}";
         }
     }
     if ($output->getVerbosity() >= OutputInterface::VERBOSITY_DEBUG) {
         $drushCommand .= " --debug";
         $sshOptions .= ' -vv';
     } elseif ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERY_VERBOSE) {
         $drushCommand .= " --verbose";
         $sshOptions .= ' -v';
     } elseif ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) {
         $drushCommand .= " --verbose";
     } elseif ($output->getVerbosity() == OutputInterface::VERBOSITY_QUIET) {
         $drushCommand .= " --quiet";
         $sshOptions .= ' -q';
     }
     $appName = $this->selectApp($input, function (LocalApplication $app) {
         return Drupal::isDrupal($app->getRoot());
     });
     $selectedEnvironment = $this->getSelectedEnvironment();
     $sshUrl = $selectedEnvironment->getSshUrl($appName);
     // Get the LocalApplication object for the specified application, if
     // available.
     $projectRoot = $this->getProjectRoot();
     if ($projectRoot && $this->selectedProjectIsCurrent()) {
         $app = LocalApplication::getApplication($appName, $projectRoot, self::$config);
     }
     // Use the local application configuration (if available) to determine
     // the correct Drupal root.
     if (isset($app)) {
         $drupalRoot = '/app/' . $app->getDocumentRoot();
     } else {
         // Fall back to the PLATFORM_DOCUMENT_ROOT environment variable,
         // which is usually correct, except where the document_root was
         // specified as '/'.
         $documentRootEnvVar = self::$config->get('service.env_prefix') . 'DOCUMENT_ROOT';
         $drupalRoot = '${' . $documentRootEnvVar . ':-/app/public}';
         $this->debug('<comment>Warning:</comment> using $' . $documentRootEnvVar . ' for the Drupal root. This fails in cases where the document_root is /.');
     }
     $dimensions = $this->getApplication()->getTerminalDimensions();
     $columns = $dimensions[0] ?: 80;
     $sshDrushCommand = "COLUMNS={$columns} drush --root=\"{$drupalRoot}\"";
     if ($environmentUrl = $selectedEnvironment->getLink('public-url')) {
         $sshDrushCommand .= " --uri=" . escapeshellarg($environmentUrl);
     }
     $sshDrushCommand .= ' ' . $drushCommand . ' 2>&1';
     $command = 'ssh' . $sshOptions . ' ' . escapeshellarg($sshUrl) . ' ' . escapeshellarg($sshDrushCommand);
     return $this->getHelper('shell')->executeSimple($command);
 }
 /**
  * {@inheritdoc}
  */
 protected function execute(InputInterface $input, OutputInterface $output)
 {
     if (!($projectRoot = $this->getProjectRoot())) {
         throw new RootNotFoundException();
     }
     $repository = $projectRoot . '/' . LocalProject::REPOSITORY_DIR;
     $apps = LocalApplication::getApplications($repository);
     $rows = [];
     foreach ($apps as $app) {
         $rows[] = [$app->getName(), $app->getRoot()];
     }
     $table = new Table($input, $output);
     $table->render($rows, ['Name', 'Path']);
 }
 /**
  * {@inheritdoc}
  */
 protected function execute(InputInterface $input, OutputInterface $output)
 {
     if (!($projectRoot = $this->getProjectRoot())) {
         throw new RootNotFoundException();
     }
     $apps = LocalApplication::getApplications($projectRoot, self::$config);
     $rows = [];
     foreach ($apps as $app) {
         $config = $app->getConfig();
         $type = isset($config['type']) ? $config['type'] : null;
         $rows[] = [$app->getName(), $type, $app->getRoot()];
     }
     $table = new Table($input, $output);
     $table->render($rows, ['Name', 'Type', 'Path']);
 }
 /**
  * Find the name of the app the user wants to use for an SSH command.
  *
  * @param InputInterface $input
  *   The user input object.
  * @param callable|null $filter
  *   A filter callback that takes one argument: a LocalApplication object.
  *
  * @return string|null
  *   The application name, or null if it could not be found.
  */
 protected function selectApp(InputInterface $input, callable $filter = null)
 {
     $appName = $input->getOption('app');
     if ($appName) {
         return $appName;
     }
     $projectRoot = $this->getProjectRoot();
     if (!$projectRoot || !$this->selectedProjectIsCurrent() || !is_dir($projectRoot . '/' . LocalProject::REPOSITORY_DIR)) {
         return null;
     }
     $this->debug('Searching for applications in local repository');
     /** @var LocalApplication[] $apps */
     $apps = LocalApplication::getApplications($projectRoot . '/' . LocalProject::REPOSITORY_DIR);
     if ($filter) {
         $apps = array_filter($apps, $filter);
     }
     if (count($apps) > 1 && $input->isInteractive()) {
         /** @var \Platformsh\Cli\Helper\PlatformQuestionHelper $questionHelper */
         $questionHelper = $this->getHelper('question');
         $choices = [];
         foreach ($apps as $app) {
             $choices[$app->getName()] = $app->getName();
         }
         $appName = $questionHelper->choose($choices, 'Enter a number to choose an app:', $input, $this->stdErr);
     }
     $input->setOption('app', $appName);
     return $appName;
 }
 /**
  * @param LocalApplication $app
  * @param string           $sourceDir
  * @param string           $destination
  *
  * @return bool
  */
 protected function buildApp($app, $sourceDir, $destination = null)
 {
     $verbose = $this->output->isVerbose();
     $destination = $destination ?: $sourceDir . '/' . $this->config->get('local.web_root');
     $appRoot = $app->getRoot();
     $appConfig = $app->getConfig();
     $multiApp = $appRoot != $sourceDir;
     $appId = $app->getId();
     $toolstack = $app->getToolstack();
     if (!$toolstack) {
         $this->output->writeln("Toolstack not found for application <error>{$appId}</error>");
         return false;
     }
     // Find the right build directory.
     $buildName = $multiApp ? str_replace('/', '-', $appId) : 'default';
     $tmpBuildDir = $sourceDir . '/' . $this->config->get('local.build_dir') . '/' . $buildName . '-tmp';
     if (file_exists($tmpBuildDir)) {
         if (!$this->fsHelper->remove($tmpBuildDir)) {
             $this->output->writeln(sprintf('Failed to remove directory <error>%s</error>', $tmpBuildDir));
             return false;
         }
     }
     // If the destination is inside the source directory, ensure it isn't
     // copied or symlinked into the build.
     if (strpos($destination, $sourceDir) === 0) {
         $toolstack->addIgnoredFiles([ltrim(substr($destination, strlen($sourceDir)), '/')]);
     }
     // Warn about a mismatched PHP version.
     if (isset($appConfig['type']) && strpos($appConfig['type'], ':')) {
         list($stack, $version) = explode(':', $appConfig['type'], 2);
         if ($stack === 'php' && version_compare($version, PHP_VERSION, '>')) {
             $this->output->writeln(sprintf('<comment>Warning:</comment> the application <comment>%s</comment> expects PHP %s, but the system version is %s.', $appId, $version, PHP_VERSION));
         }
     }
     $toolstack->setOutput($this->output);
     $buildSettings = $this->settings + ['multiApp' => $multiApp, 'sourceDir' => $sourceDir];
     $toolstack->prepare($tmpBuildDir, $app, $this->config, $buildSettings);
     $archive = false;
     if (empty($this->settings['no-archive']) && empty($this->settings['no-cache'])) {
         $treeId = $this->getTreeId($appRoot);
         if ($treeId) {
             if ($verbose) {
                 $this->output->writeln("Tree ID: {$treeId}");
             }
             $archive = $sourceDir . '/' . $this->config->get('local.archive_dir') . '/' . $treeId . '.tar.gz';
         }
     }
     if ($archive && file_exists($archive)) {
         $message = "Extracting archive for application <info>{$appId}</info>";
         $this->output->writeln($message);
         $this->fsHelper->extractArchive($archive, $tmpBuildDir);
     } else {
         $message = "Building application <info>{$appId}</info>";
         if (isset($appConfig['type'])) {
             $message .= ' (runtime type: ' . $appConfig['type'] . ')';
         }
         $this->output->writeln($message);
         $toolstack->build();
         if ($this->runPostBuildHooks($appConfig, $toolstack->getAppDir()) === false) {
             // The user may not care if build hooks fail, but we should
             // not archive the result.
             $archive = false;
         }
         if ($archive && $toolstack->canArchive()) {
             $this->output->writeln("Saving build archive");
             if (!is_dir(dirname($archive))) {
                 mkdir(dirname($archive));
             }
             $this->fsHelper->archiveDir($tmpBuildDir, $archive);
         }
     }
     // The build is complete. Move the directory.
     $buildDir = substr($tmpBuildDir, 0, strlen($tmpBuildDir) - 4);
     if (file_exists($buildDir)) {
         if (empty($this->settings['no-backup']) && is_dir($buildDir) && !is_link($buildDir)) {
             $previousBuildArchive = dirname($buildDir) . '/' . basename($buildDir) . '-old.tar.gz';
             $this->output->writeln("Backing up previous build to: " . $previousBuildArchive);
             $this->fsHelper->archiveDir($buildDir, $previousBuildArchive);
         }
         if (!$this->fsHelper->remove($buildDir, true)) {
             $this->output->writeln(sprintf('Failed to remove directory <error>%s</error>', $buildDir));
             return false;
         }
     }
     if (!rename($tmpBuildDir, $buildDir)) {
         $this->output->writeln(sprintf('Failed to move temporary build directory into <error>%s</error>', $buildDir));
         return false;
     }
     $toolstack->setBuildDir($buildDir);
     $toolstack->install();
     $this->runPostDeployHooks($appConfig, $buildDir);
     $webRoot = $toolstack->getWebRoot();
     // Symlink the built web root ($webRoot) into www or www/appId.
     if (!is_dir($webRoot)) {
         $this->output->writeln("\nWeb root not found: <error>{$webRoot}</error>\n");
         return false;
     }
     if ($multiApp) {
         $appDir = str_replace('/', '-', $appId);
         if (is_link($destination)) {
             $this->fsHelper->remove($destination);
         }
         $destination .= "/{$appDir}";
     }
     $this->fsHelper->symlink($webRoot, $destination);
     $message = "\nBuild complete for application <info>{$appId}</info>";
     $this->output->writeln($message);
     $this->output->writeln("Web root: <info>{$destination}</info>\n");
     return true;
 }
 protected function execute(InputInterface $input, OutputInterface $output)
 {
     $this->validateInput($input);
     if ($input instanceof ArgvInput) {
         $helper = new ArgvHelper();
         $drushCommand = $helper->getPassedCommand($this, $input);
     }
     if (empty($drushCommand)) {
         $drushCommand = $input->getArgument('cmd');
     }
     $sshOptions = '';
     // Pass through options that the CLI shares with Drush and SSH.
     foreach (['yes', 'no', 'quiet'] as $option) {
         if ($input->getOption($option)) {
             $drushCommand .= " --{$option}";
         }
     }
     if ($output->getVerbosity() >= OutputInterface::VERBOSITY_DEBUG) {
         $drushCommand .= " --debug";
         $sshOptions .= ' -vv';
     } elseif ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERY_VERBOSE) {
         $drushCommand .= " --verbose";
         $sshOptions .= ' -v';
     } elseif ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) {
         $drushCommand .= " --verbose";
     } elseif ($output->getVerbosity() == OutputInterface::VERBOSITY_QUIET) {
         $drushCommand .= " --quiet";
         $sshOptions .= ' -q';
     }
     $appName = $this->selectApp($input, function (LocalApplication $app) {
         return Drupal::isDrupal($app->getRoot());
     });
     $selectedEnvironment = $this->getSelectedEnvironment();
     $sshUrl = $selectedEnvironment->getSshUrl($appName);
     // Get the LocalApplication object for the specified application, if
     // available.
     $projectRoot = $this->getProjectRoot();
     if ($projectRoot && $this->selectedProjectIsCurrent() && is_dir($projectRoot . '/' . LocalProject::REPOSITORY_DIR)) {
         $apps = LocalApplication::getApplications($projectRoot . '/' . LocalProject::REPOSITORY_DIR);
         if (count($apps) === 1 && $appName === null) {
             $app = reset($apps);
         } else {
             foreach ($apps as $possibleApp) {
                 if ($possibleApp->getName() === $appName) {
                     $app = $possibleApp;
                     break;
                 }
             }
         }
     }
     // Use the local application configuration (if available) to determine
     // the correct Drupal root.
     if (isset($app) && isset($app->getConfig()['web']['document_root'])) {
         $documentRoot = trim($app->getConfig()['web']['document_root'], '/') ?: 'public';
         $drupalRoot = '/app/' . $documentRoot;
     } else {
         // Fall back to the PLATFORM_DOCUMENT_ROOT environment variable,
         // which is usually correct, except where the document_root was
         // specified as '/'.
         $drupalRoot = '${PLATFORM_DOCUMENT_ROOT:-/app/public}';
         $this->debug('<comment>Warning:</comment> using $PLATFORM_DOCUMENT_ROOT for the Drupal root. This fails in cases where the document_root is /.');
     }
     $dimensions = $this->getApplication()->getTerminalDimensions();
     $columns = $dimensions[0] ?: 80;
     $sshDrushCommand = "COLUMNS={$columns} drush --root=\"{$drupalRoot}\"";
     if ($environmentUrl = $selectedEnvironment->getLink('public-url')) {
         $sshDrushCommand .= " --uri=" . escapeshellarg($environmentUrl);
     }
     $sshDrushCommand .= ' ' . $drushCommand . ' 2>&1';
     $command = 'ssh' . $sshOptions . ' ' . escapeshellarg($sshUrl) . ' ' . escapeshellarg($sshDrushCommand);
     if ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) {
         $this->stdErr->writeln("Running command: <info>{$command}</info>");
     }
     passthru($command, $return_var);
     return $return_var;
 }
 public function testGetSharedFileMounts()
 {
     $appRoot = 'tests/data/apps/drupal/project';
     $app = new LocalApplication($appRoot);
     $this->assertEquals(['public/sites/default/files' => 'files', 'tmp' => 'tmp', 'private' => 'private', 'drush-backups' => 'drush-backups'], $app->getSharedFileMounts());
 }
 /**
  * @param LocalApplication $app
  * @param string           $sourceDir
  * @param string           $destination
  *
  * @return bool
  */
 protected function buildApp($app, $sourceDir, $destination)
 {
     $verbose = $this->output->isVerbose();
     $appRoot = $app->getRoot();
     $appConfig = $app->getConfig();
     $multiApp = $appRoot != $sourceDir;
     $appName = $app->getName();
     $appId = $app->getId();
     $buildName = date('Y-m-d--H-i-s');
     if (!empty($this->settings['environmentId'])) {
         $buildName .= '--' . $this->settings['environmentId'];
     }
     if ($multiApp) {
         $buildName .= '--' . str_replace('/', '-', $appId);
     }
     if (!empty($this->settings['projectRoot'])) {
         $buildDir = $this->settings['projectRoot'] . '/' . LocalProject::BUILD_DIR . '/' . $buildName;
     } else {
         $buildDir = $sourceDir . '/' . LocalProject::BUILD_DIR . '/' . $buildName;
     }
     // Get the configured document root.
     $documentRoot = $this->getDocumentRoot($appConfig);
     $toolstack = $app->getToolstack();
     if (!$toolstack) {
         $this->output->writeln("Toolstack not found for application <error>{$appId}</error>");
         return false;
     }
     // Warn about a mismatched PHP version.
     if (isset($appConfig['type']) && strpos($appConfig['type'], ':')) {
         list($stack, $version) = explode(':', $appConfig['type'], 2);
         if ($stack === 'php' && version_compare($version, PHP_VERSION, '>')) {
             $this->output->writeln(sprintf('<comment>Warning:</comment> the application <comment>%s</comment> expects PHP %s, but the system version is %s.', $appId, $version, PHP_VERSION));
         }
     }
     $toolstack->setOutput($this->output);
     $buildSettings = $this->settings + ['multiApp' => $multiApp, 'appName' => $appName];
     $toolstack->prepare($buildDir, $documentRoot, $appRoot, $buildSettings);
     $archive = false;
     if (empty($this->settings['noArchive']) && empty($this->settings['noCache']) && !empty($this->settings['projectRoot'])) {
         $treeId = $this->getTreeId($appRoot);
         if ($treeId) {
             if ($verbose) {
                 $this->output->writeln("Tree ID: {$treeId}");
             }
             $archive = $this->settings['projectRoot'] . '/' . LocalProject::ARCHIVE_DIR . '/' . $treeId . '.tar.gz';
         }
     }
     if ($archive && file_exists($archive)) {
         $message = "Extracting archive for application <info>{$appId}</info>";
         $this->output->writeln($message);
         $this->fsHelper->extractArchive($archive, $buildDir);
     } else {
         $message = "Building application <info>{$appId}</info>";
         if (isset($appConfig['type'])) {
             $message .= ' (runtime type: ' . $appConfig['type'] . ')';
         }
         $this->output->writeln($message);
         $toolstack->build();
         if ($this->runPostBuildHooks($appConfig, $toolstack->getAppRoot()) === false) {
             // The user may not care if build hooks fail, but we should
             // not archive the result.
             $archive = false;
         }
         if ($archive && $toolstack->canArchive()) {
             $this->output->writeln("Saving build archive");
             if (!is_dir(dirname($archive))) {
                 mkdir(dirname($archive));
             }
             $this->fsHelper->archiveDir($buildDir, $archive);
         }
     }
     $toolstack->install();
     $webRoot = $toolstack->getWebRoot();
     // Symlink the built web root ($webRoot) into www or www/appId.
     if (!is_dir($webRoot)) {
         $this->output->writeln("Web root not found: <error>{$webRoot}</error>");
         return false;
     }
     if ($multiApp) {
         $appDir = str_replace('/', '-', $appId);
         if (is_link($destination)) {
             $this->fsHelper->remove($destination);
         }
         $destination .= "/{$appDir}";
     }
     $this->fsHelper->symlink($webRoot, $destination);
     $this->output->writeln("Web root: {$destination}");
     $message = "Build complete for application <info>{$appId}</info>";
     $this->output->writeln($message);
     return true;
 }
 protected function execute(InputInterface $input, OutputInterface $output)
 {
     $this->validateInput($input);
     $projectRoot = $this->getProjectRoot();
     if (!$projectRoot) {
         throw new RootNotFoundException();
     }
     $appName = $this->selectApp($input);
     $sshUrl = $this->getSelectedEnvironment()->getSshUrl($appName);
     // Get and parse app config.
     $app = LocalApplication::getApplication($appName, $projectRoot);
     $appConfig = $app->getConfig();
     if (empty($appConfig['relationships'])) {
         $this->stdErr->writeln('No application relationships found.');
         return 1;
     }
     $util = new RelationshipsUtil($this->stdErr);
     $database = $util->chooseDatabase($sshUrl, $input);
     if (empty($database)) {
         $this->stdErr->writeln('No database selected.');
         return 1;
     }
     // Find the database's service name in the relationships.
     $dbServiceName = false;
     foreach ($appConfig['relationships'] as $relationshipName => $relationship) {
         if ($database['_relationship_name'] === $relationshipName) {
             list($dbServiceName, ) = explode(':', $relationship, 2);
             break;
         }
     }
     if (!$dbServiceName) {
         $this->stdErr->writeln('Service name not found for relationship: ' . $database['_relationship_name']);
         return 1;
     }
     // Load services yaml.
     $services = Yaml::parse(file_get_contents($projectRoot . '/.platform/services.yaml'));
     if (!empty($services[$dbServiceName]['disk'])) {
         $allocatedDisk = $services[$dbServiceName]['disk'];
     } else {
         $this->stdErr->writeln('The allocated disk size could not be determined for service: ' . $dbServiceName);
         return 1;
     }
     $this->stdErr->write('Querying database <comment>' . $dbServiceName . '</comment> to estimate disk usage. ');
     $this->stdErr->writeln('This might take a while.');
     /** @var ShellHelper $shellHelper */
     $shellHelper = $this->getHelper('shell');
     $command = ['ssh'];
     $command[] = $sshUrl;
     switch ($database['scheme']) {
         case 'pgsql':
             $command[] = $this->psqlQuery($database);
             $result = $shellHelper->execute($command);
             $resultArr = explode(PHP_EOL, $result);
             $estimatedUsage = array_sum($resultArr) / 1048576;
             break;
         default:
             $command[] = $this->mysqlQuery($database);
             $estimatedUsage = $shellHelper->execute($command);
             break;
     }
     $percentsUsed = $estimatedUsage * 100 / $allocatedDisk;
     $table = new Table($input, $output);
     $propertyNames = ['max', 'used', 'percent_used'];
     $machineReadable = $table->formatIsMachineReadable();
     $values = [(int) $allocatedDisk . ($machineReadable ? '' : 'MB'), (int) $estimatedUsage . ($machineReadable ? '' : 'MB'), (int) $percentsUsed . '%'];
     $table->renderSimple($values, $propertyNames);
     return 0;
 }
 /**
  * @param Environment $environment
  * @param LocalApplication $app
  * @param bool $multiApp
  *
  * @return array|false
  */
 protected function generateRemoteAlias($environment, $app, $multiApp = false)
 {
     if (!$environment->hasLink('ssh') || !$environment->hasLink('public-url')) {
         return false;
     }
     $sshUrl = parse_url($environment->getLink('ssh'));
     if (!$sshUrl) {
         return false;
     }
     $sshUser = $sshUrl['user'];
     if ($multiApp) {
         $sshUser .= '--' . $app->getName();
     }
     $uri = $environment->getLink('public-url');
     if ($multiApp) {
         $guess = str_replace('http://', 'http://' . $app->getName() . '---', $uri);
         if (in_array($guess, $environment->getRouteUrls())) {
             $uri = $guess;
         }
     }
     $appConfig = $app->getConfig();
     $documentRoot = '/public';
     if (isset($appConfig['web']['document_root']) && $appConfig['web']['document_root'] !== '/') {
         $documentRoot = $appConfig['web']['document_root'];
     }
     return ['uri' => $uri, 'remote-host' => $sshUrl['host'], 'remote-user' => $sshUser, 'root' => '/app/' . ltrim($documentRoot, '/'), self::AUTO_REMOVE_KEY => true, 'command-specific' => ['site-install' => ['sites-subdir' => 'default']]];
 }
 /**
  * @param Environment $environment
  * @param LocalApplication $app
  * @param bool $multiApp
  *
  * @return array|false
  */
 protected function generateRemoteAlias($environment, $app, $multiApp = false)
 {
     if (!$environment->hasLink('ssh') || !$environment->hasLink('public-url')) {
         return false;
     }
     $sshUrl = parse_url($environment->getLink('ssh'));
     if (!$sshUrl) {
         return false;
     }
     $sshUser = $sshUrl['user'];
     if ($multiApp) {
         $sshUser .= '--' . $app->getName();
     }
     $uri = $environment->getLink('public-url');
     if ($multiApp) {
         $guess = str_replace('http://', 'http://' . $app->getName() . '---', $uri);
         if (in_array($guess, $environment->getRouteUrls())) {
             $uri = $guess;
         }
     }
     return ['uri' => $uri, 'remote-host' => $sshUrl['host'], 'remote-user' => $sshUser, 'root' => '/app/' . $app->getDocumentRoot(), $this->getAutoRemoveKey() => true, 'command-specific' => ['site-install' => ['sites-subdir' => 'default']]];
 }
 /**
  * Get a list of application names in the local project.
  *
  * @return string[]
  */
 public function getAppNames()
 {
     $apps = [];
     if ($projectRoot = $this->welcomeCommand->getProjectRoot()) {
         foreach (LocalApplication::getApplications($projectRoot) as $app) {
             $name = $app->getName();
             if ($name !== null) {
                 $apps[] = $name;
             }
         }
     }
     return $apps;
 }