/** * Test with a custom destination. */ public function testBuildCustomDestination() { $projectRoot = $this->createDummyProject('tests/data/apps/vanilla'); $destination = $projectRoot . '/web'; $builder = new LocalBuild($this->buildSettings, null, self::$output); $builder->build($projectRoot, $destination); $this->assertFileExists($destination . '/index.html'); }
public function testBuildDrupalInProjectMode() { $sourceDir = 'tests/data/apps/drupal/project'; $projectRoot = $this->createDummyProject($sourceDir); $webRoot = $projectRoot . '/' . self::$config->get('local.web_root'); $shared = $projectRoot . '/' . self::$config->get('local.shared_dir'); $buildDir = $projectRoot . '/' . self::$config->get('local.build_dir') . '/default'; // Insert a dummy file into 'shared'. if (!file_exists($shared)) { mkdir($shared, 0755, true); } touch($shared . '/symlink_me'); self::$output->writeln("\nTesting build for directory: " . $sourceDir); $buildSettings = ['abslinks' => true]; $builder = new LocalBuild($buildSettings + $this->buildSettings, null, self::$output); $success = $builder->build($projectRoot); $this->assertTrue($success, 'Build success for dir: ' . $sourceDir); // Test build results. $this->assertFileExists($webRoot . '/index.php'); // Test installation results: firstly, the mounts. $this->assertFileExists($webRoot . '/sites/default/files'); $this->assertFileExists($buildDir . '/tmp'); $this->assertFileExists($buildDir . '/private'); $this->assertFileExists($buildDir . '/drush-backups'); $this->assertEquals($shared . '/files', readlink($webRoot . '/sites/default/files')); $this->assertEquals($shared . '/tmp', readlink($buildDir . '/tmp')); $this->assertEquals($shared . '/private', readlink($buildDir . '/private')); $this->assertEquals($shared . '/drush-backups', readlink($buildDir . '/drush-backups')); // Secondly, the special Drupal settings files. $this->assertFileExists($webRoot . '/sites/default/settings.php'); $this->assertFileExists($webRoot . '/sites/default/settings.local.php'); // Thirdly, the ability for any files in 'shared' to be symlinked into // sites/default (this is a legacy feature of the CLI's Drupal // toolstack). $this->assertFileExists($webRoot . '/sites/default/symlink_me'); // Test custom build hooks' results. // Build hooks are not Drupal-specific, but they can only run if the // build process creates a build directory outside the repository - // Drupal is the only current example of this. $this->assertFileNotExists($webRoot . '/robots.txt'); $this->assertFileExists($webRoot . '/test.txt'); // Test building the same project again. $success2 = $builder->build($projectRoot); $this->assertTrue($success2, 'Second build success for dir: ' . $sourceDir); }
/** * Test with a custom source and destination. */ public function testBuildCustomSourceDestination() { // N.B. the source directory and destination must be absolute for this // to work. $sourceDir = realpath('tests/data/apps/vanilla'); $tempDir = self::$root->getName(); $destination = tempnam($tempDir, ''); // Test with symlinking. $builder = new LocalBuild(['absoluteLinks' => true], self::$output); $builder->build($sourceDir, $destination); $this->assertFileExists($destination . '/index.html'); // Test with copying. $builder = new LocalBuild(['copy' => true, 'absoluteLinks' => true], self::$output); $builder->build($sourceDir, $destination); $this->assertFileExists($destination . '/index.html'); // Remove the builds directory. exec('rm -R ' . escapeshellarg($sourceDir . '/' . LocalProject::BUILD_DIR)); }
protected function execute(InputInterface $input, OutputInterface $output) { $projectRoot = $this->getProjectRoot(); /** @var \Platformsh\Cli\Helper\QuestionHelper $questionHelper */ $questionHelper = $this->getHelper('question'); $sourceDirOption = $input->getOption('source'); // If no project root is found, ask the user for a source directory. if (!$projectRoot && !$sourceDirOption && $input->isInteractive()) { $default = file_exists(self::$config->get('service.app_config_file')) || is_dir('.git') ? '.' : null; $sourceDirOption = $questionHelper->askInput('Source directory', $default); } if ($sourceDirOption) { $sourceDir = realpath($sourceDirOption); if (!is_dir($sourceDir)) { throw new \InvalidArgumentException('Source directory not found: ' . $sourceDirOption); } elseif (file_exists($sourceDir . self::$config->get('local.project_config'))) { $projectRoot = $sourceDir; $sourceDir = $projectRoot; } } elseif (!$projectRoot) { throw new RootNotFoundException('Project root not found. Specify --source or go to a project directory.'); } else { $sourceDir = $projectRoot; } $destination = $input->getOption('destination'); // If no project root is found, ask the user for a destination path. if (!$projectRoot && !$destination && $input->isInteractive()) { $default = is_dir($sourceDir . '/.git') && $sourceDir === getcwd() ? self::$config->get('local.web_root') : null; $destination = $questionHelper->askInput('Build destination', $default); } if ($destination) { /** @var \Platformsh\Cli\Helper\FilesystemHelper $fsHelper */ $fsHelper = $this->getHelper('fs'); $destination = $fsHelper->makePathAbsolute($destination); } elseif (!$projectRoot) { throw new RootNotFoundException('Project root not found. Specify --destination or go to a project directory.'); } else { $destination = $projectRoot . '/' . self::$config->get('local.web_root'); } // Ensure no conflicts between source and destination. if (strpos($sourceDir, $destination) === 0) { throw new \InvalidArgumentException("The destination '{$destination}' conflicts with the source '{$sourceDir}'"); } // Ask the user about overwriting the destination, if a project root was // not found. if (!$projectRoot && file_exists($destination)) { if (!is_writable($destination)) { $this->stdErr->writeln("The destination exists and is not writable: <error>{$destination}</error>"); return 1; } $default = is_link($destination); if (!$questionHelper->confirm("The destination exists: <comment>{$destination}</comment>. Overwrite?", $default)) { return 1; } } // Map input options to build settings. $settings = []; foreach ($input->getOptions() as $name => $value) { $settings[$name] = $value; } $apps = $input->getArgument('app'); $builder = new LocalBuild($settings, self::$config, $this->stdErr); $success = $builder->build($sourceDir, $destination, $apps); return $success ? 0 : 1; }
protected function execute(InputInterface $input, OutputInterface $output) { $projectId = $input->getArgument('id'); $environmentOption = $input->getOption('environment'); $hostOption = $input->getOption('host'); if (empty($projectId)) { if ($input->isInteractive() && ($projects = $this->api()->getProjects(true))) { $projectId = $this->offerProjectChoice($projects, $input); } else { $this->stdErr->writeln("<error>You must specify a project.</error>"); return 1; } } else { $result = $this->parseProjectId($projectId); $projectId = $result['projectId']; $hostOption = $hostOption ?: $result['host']; $environmentOption = $environmentOption ?: $result['environmentId']; } $project = $this->api()->getProject($projectId, $hostOption, true); if (!$project) { $this->stdErr->writeln("<error>Project not found: {$projectId}</error>"); return 1; } $environments = $this->api()->getEnvironments($project); if ($environmentOption) { if (!isset($environments[$environmentOption])) { // Reload the environments list. $environments = $this->api()->getEnvironments($project, true); if (!isset($environments[$environmentOption])) { $this->stdErr->writeln("Environment not found: <error>{$environmentOption}</error>"); } return 1; } $environmentId = $environmentOption; } elseif (count($environments) === 1) { $environmentId = key($environments); } else { $environmentId = 'master'; } $directory = $input->getArgument('directory'); if (empty($directory)) { $slugify = new Slugify(); $directory = $project->title ? $slugify->slugify($project->title) : $project->id; /** @var \Platformsh\Cli\Helper\QuestionHelper $questionHelper */ $questionHelper = $this->getHelper('question'); $directory = $questionHelper->askInput('Directory', $directory); } if ($projectRoot = $this->getProjectRoot()) { if (strpos(realpath(dirname($directory)), $projectRoot) === 0) { $this->stdErr->writeln("<error>A project cannot be cloned inside another project.</error>"); return 1; } } // Create the directory structure. if (file_exists($directory)) { $this->stdErr->writeln("The directory <error>{$directory}</error> already exists"); return 1; } if (!($parent = realpath(dirname($directory)))) { throw new \Exception("Not a directory: " . dirname($directory)); } $projectRoot = $parent . '/' . basename($directory); // Prepare to talk to the remote repository. $gitUrl = $project->getGitUrl(); $gitHelper = new GitHelper(new ShellHelper($this->stdErr)); $gitHelper->ensureInstalled(); // First check if the repo actually exists. try { $exists = $gitHelper->remoteRepoExists($gitUrl); } catch (\Exception $e) { // The ls-remote command failed. $this->stdErr->writeln('<error>Failed to connect to the ' . self::$config->get('service.name') . ' Git server</error>'); // Suggest SSH key commands. $sshKeys = []; try { $sshKeys = $this->api()->getClient(false)->getSshKeys(); } catch (\Exception $e) { // Ignore exceptions. } if (!empty($sshKeys)) { $this->stdErr->writeln(''); $this->stdErr->writeln('Please check your SSH credentials'); $this->stdErr->writeln('You can list your keys with: <comment>' . self::$config->get('application.executable') . ' ssh-keys</comment>'); } else { $this->stdErr->writeln('You probably need to add an SSH key, with: <comment>' . self::$config->get('application.executable') . ' ssh-key:add</comment>'); } return 1; } $projectConfig = ['id' => $projectId]; $host = parse_url($project->getUri(), PHP_URL_HOST); if ($host) { $projectConfig['host'] = $host; } // If the remote repository exists, then locally we need to create the // folder, run git init, and attach the remote. if (!$exists) { $this->stdErr->writeln('Creating project directory: <info>' . $directory . '</info>'); if (mkdir($projectRoot) === false) { $this->stdErr->writeln('Failed to create the project directory.'); return 1; } // Initialize the repo and attach our remotes. $this->debug('Initializing the repository'); $gitHelper->init($projectRoot, true); // As soon as there is a Git repo present, add the project config file. $this->localProject->writeCurrentProjectConfig($projectConfig, $projectRoot); $this->debug('Adding Git remote(s)'); $this->localProject->ensureGitRemote($projectRoot, $gitUrl); $this->stdErr->writeln(''); $this->stdErr->writeln('Your project has been initialized and connected to <info>' . self::$config->get('service.name') . '</info>!'); $this->stdErr->writeln(''); $this->stdErr->writeln('Commit and push to the <info>master</info> branch of the <info>' . self::$config->get('detection.git_remote_name') . '</info> Git remote, and ' . self::$config->get('service.name') . ' will build your project automatically.'); return 0; } // We have a repo! Yay. Clone it. $projectLabel = $this->api()->getProjectLabel($project); $this->stdErr->writeln('Downloading project ' . $projectLabel); $cloneArgs = ['--branch', $environmentId, '--origin', self::$config->get('detection.git_remote_name')]; if ($output->isDecorated()) { $cloneArgs[] = '--progress'; } $cloned = $gitHelper->cloneRepo($gitUrl, $projectRoot, $cloneArgs); if ($cloned === false) { // The clone wasn't successful. Clean up the folders we created // and then bow out with a message. $this->stdErr->writeln('<error>Failed to clone Git repository</error>'); $this->stdErr->writeln('Please check your SSH credentials or contact ' . self::$config->get('service.name') . ' support'); return 1; } $this->setProjectRoot($projectRoot); $this->localProject->writeCurrentProjectConfig($projectConfig, $projectRoot); $this->localProject->ensureGitRemote($projectRoot, $gitUrl); $gitHelper->updateSubmodules(true, $projectRoot); $this->stdErr->writeln("\nThe project <info>{$projectLabel}</info> was successfully downloaded to: <info>{$directory}</info>"); // Return early if there is no code in the repository. if (!glob($projectRoot . '/*', GLOB_NOSORT)) { return 0; } // Ensure that Drush aliases are created. if (Drupal::isDrupal($projectRoot)) { $this->stdErr->writeln(''); $this->runOtherCommand('local:drush-aliases', ['--group' => basename($directory)]); } // Launch the first build. $success = true; if ($input->getOption('build')) { // Launch the first build. $this->stdErr->writeln(''); $this->stdErr->writeln('Building the project locally for the first time. Run <info>' . self::$config->get('application.executable') . ' build</info> to repeat this.'); $options = ['no-clean' => true]; $builder = new LocalBuild($options, self::$config, $output); $success = $builder->build($projectRoot); } else { $this->stdErr->writeln("\nYou can build the project with: " . "\n cd {$directory}" . "\n " . self::$config->get('application.executable') . " build"); } return $success ? 0 : 1; }
protected function execute(InputInterface $input, OutputInterface $output) { $projectRoot = $this->getProjectRoot(); /** @var \Platformsh\Cli\Helper\PlatformQuestionHelper $questionHelper */ $questionHelper = $this->getHelper('question'); $sourceDirOption = $input->getOption('source'); // If no project root is found, ask the user for a source directory. if (!$projectRoot && !$sourceDirOption && $input->isInteractive()) { $sourceDirOption = $questionHelper->askInput('Source directory', $input, $this->stdErr); } if ($sourceDirOption) { $sourceDir = realpath($sourceDirOption); if (!is_dir($sourceDir)) { throw new \InvalidArgumentException('Source directory not found: ' . $sourceDirOption); } elseif (file_exists($sourceDir . '/.platform-project')) { $projectRoot = $sourceDir; $sourceDir = $projectRoot . '/' . LocalProject::REPOSITORY_DIR; } } elseif (!$projectRoot) { throw new RootNotFoundException('Project root not found. Specify --source or go to a project directory.'); } else { $sourceDir = $projectRoot . '/' . LocalProject::REPOSITORY_DIR; } $destination = $input->getOption('destination'); // If no project root is found, ask the user for a destination path. if (!$projectRoot && !$destination && $input->isInteractive()) { $destination = $questionHelper->askInput('Build destination', $input, $this->stdErr); } if ($destination) { /** @var \Platformsh\Cli\Helper\FilesystemHelper $fsHelper */ $fsHelper = $this->getHelper('fs'); $destination = $fsHelper->makePathAbsolute($destination); } elseif (!$projectRoot) { throw new RootNotFoundException('Project root not found. Specify --destination or go to a project directory.'); } else { $destination = $projectRoot . '/' . LocalProject::WEB_ROOT; } // Ensure no conflicts between source and destination. if (strpos($sourceDir, $destination) === 0) { throw new \InvalidArgumentException("The destination '{$destination}' conflicts with the source '{$sourceDir}'"); } // Ask the user about overwriting the destination, if a project root was // not found. if (!$projectRoot && file_exists($destination)) { if (!is_writable($destination)) { $this->stdErr->writeln("The destination exists and is not writable: <error>{$destination}</error>"); return 1; } $default = is_link($destination); if (!$questionHelper->confirm("The destination exists: <comment>{$destination}</comment>. Overwrite?", $input, $this->stdErr, $default)) { return 1; } } $settings = []; $settings['projectRoot'] = $projectRoot; $settings['environmentId'] = $this->determineEnvironmentId($sourceDir, $projectRoot); $settings['drushConcurrency'] = $input->hasOption('concurrency') ? $input->getOption('concurrency') : $this->defaultDrushConcurrency; // Some simple settings flags. $settingsMap = ['absoluteLinks' => 'abslinks', 'copy' => 'copy', 'drushWorkingCopy' => 'working-copy', 'drushUpdateLock' => 'lock', 'noArchive' => 'no-archive', 'noCache' => 'no-cache', 'noClean' => 'no-clean', 'noBuildHooks' => 'no-build-hooks']; foreach ($settingsMap as $setting => $option) { $settings[$setting] = $input->hasOption($option) && $input->getOption($option); } $apps = $input->getArgument('app'); $builder = new LocalBuild($settings, $this->stdErr); $success = $builder->build($sourceDir, $destination, $apps); return $success ? 0 : 1; }