public static function createConfig(IOInterface $io = null, $cwd = null) { $cwd = $cwd ?: getcwd(); $home = self::getHomeDir(); $cacheDir = self::getCacheDir($home); foreach (array($home, $cacheDir) as $dir) { if (!file_exists($dir . '/.htaccess')) { if (!is_dir($dir)) { @mkdir($dir, 0777, true); } @file_put_contents($dir . '/.htaccess', 'Deny from all'); } } $config = new Config(true, $cwd); $config->merge(array('config' => array('home' => $home, 'cache-dir' => $cacheDir))); $file = new JsonFile($config->get('home') . '/config.json'); if ($file->exists()) { if ($io && $io->isDebug()) { $io->writeError('Loading config file ' . $file->getPath()); } $config->merge($file->read()); } $config->setConfigSource(new JsonConfigSource($file)); $file = new JsonFile($config->get('home') . '/auth.json'); if ($file->exists()) { if ($io && $io->isDebug()) { $io->writeError('Loading config file ' . $file->getPath()); } $config->merge(array('config' => $file->read())); } $config->setAuthConfigSource(new JsonConfigSource($file, true)); return $config; }
protected function selectPackage(IOInterface $io, $packageName, $version = null) { $io->writeError('<info>Searching for the specified package.</info>'); if ($composer = $this->getComposer(false)) { $localRepo = $composer->getRepositoryManager()->getLocalRepository(); $repos = new CompositeRepository(array_merge(array($localRepo), $composer->getRepositoryManager()->getRepositories())); } else { $defaultRepos = Factory::createDefaultRepositories($this->getIO()); $io->writeError('No composer.json found in the current directory, searching packages from ' . implode(', ', array_keys($defaultRepos))); $repos = new CompositeRepository($defaultRepos); } $pool = new Pool(); $pool->addRepository($repos); $parser = new VersionParser(); $constraint = $version ? $parser->parseConstraints($version) : null; $packages = $pool->whatProvides($packageName, $constraint, true); if (count($packages) > 1) { $package = reset($packages); $io->writeError('<info>Found multiple matches, selected ' . $package->getPrettyString() . '.</info>'); $io->writeError('Alternatives were ' . implode(', ', array_map(function ($p) { return $p->getPrettyString(); }, $packages)) . '.'); $io->writeError('<comment>Please use a more specific constraint to pick a different package.</comment>'); } elseif ($packages) { $package = reset($packages); $io->writeError('<info>Found an exact match ' . $package->getPrettyString() . '.</info>'); } else { $io->writeError('<error>Could not find a package matching ' . $packageName . '.</error>'); return false; } return $package; }
/** * Register all managed paths with Composer. * * This function configures Composer to treat all Studio-managed paths as local path repositories, so that packages * therein will be symlinked directly. */ public function registerStudioPackages() { $repoManager = $this->composer->getRepositoryManager(); $composerConfig = $this->composer->getConfig(); foreach ($this->getManagedPaths() as $path) { $this->io->writeError("[Studio] Loading path {$path}"); $repoManager->prependRepository(new PathRepository(['url' => $path], $this->io, $composerConfig)); } }
/** * Write a message * * @param string $message */ protected function log($message) { if (method_exists($this->inputOutput, 'writeError')) { $this->inputOutput->writeError($message); } else { // @codeCoverageIgnoreStart // Backwards compatiblity for Composer before cb336a5 $this->inputOutput->write($message); // @codeCoverageIgnoreEnd } }
/** * Print an error line. * * @param string $message * @return bool Always false */ public function error($message) { if ($message && $this->verbosity > 0) { $tag = 'bg=red;fg=white;option=bold>'; $lines = $this->ensureLength($message); $this->io->write(''); foreach ($lines as $line) { $this->io->writeError(" <{$tag} " . $line . " </{$tag}"); } $this->io->write(''); } return false; }
/** * Log a debug message * * Messages will be output at the "verbose" logging level (eg `-v` needed * on the Composer command). * * @param string $message */ public function debug($message) { if ($this->inputOutput->isVerbose()) { $message = " <info>[{$this->name}]</info> {$message}"; if (method_exists($this->inputOutput, 'writeError')) { $this->inputOutput->writeError($message); } else { // @codeCoverageIgnoreStart // Backwards compatiblity for Composer before cb336a5 $this->inputOutput->write($message); // @codeCoverageIgnoreEnd } } }
public function process(Config\Template $tmpl) { /* process template if condition is not set or condition is valid */ if ($tmpl->getCondition() == null || $this->conditionValidator->isValid($tmpl->getCondition(), $this->vars)) { /* load source file */ if (is_file($tmpl->getSource())) { $content = file_get_contents($tmpl->getSource()); /* replace all vars by values */ if (is_array($this->vars)) { foreach ($this->vars as $key => $value) { $content = str_replace('${' . $key . '}', $value, $content); } } /* save destination file */ if (is_file($tmpl->getDestination()) && !$tmpl->isCanRewrite()) { $this->io->write(__CLASS__ . ": <comment>Destination file '{$tmpl->getDestination()}' is already exist and cannot be rewrote (rewrite = false).</comment>"); } else { $this->fileSaver->save($tmpl->getDestination(), $content); $this->io->write(__CLASS__ . ": <info>Destination file '{$tmpl->getDestination()}' is created from source template '{$tmpl->getSource()}'.</info>"); } } else { $this->io->writeError(__CLASS__ . ": <error>Cannot open source template ({$tmpl->getSource()}).</error>"); } } else { /* there is wrong condition for template */ $outSrc = $tmpl->getSource(); $cond = $tmpl->getCondition(); $outCond = '${' . $cond->getVar() . '}' . $cond->getOperation() . $cond->getValue(); $this->io->write(__CLASS__ . ": <comment>Skip processing of the template ({$outSrc}) because condition ({$outCond}) is 'false'.</comment>"); } }
/** * Clean a package, based on its rules. * * @param BasePackage $package The package to clean * @return bool True if cleaned * * @SuppressWarnings(PHPMD.NPathComplexity) */ protected function cleanPackage(BasePackage $package) { $vendorDir = $this->config->get('vendor-dir'); $targetDir = $package->getTargetDir(); $packageName = $package->getPrettyName(); $packageDir = $targetDir ? $packageName . '/' . $targetDir : $packageName; $rules = isset($this->rules[$packageName]) ? $this->rules[$packageName] : null; if (!$rules) { $this->io->writeError('Rules not found: ' . $packageName); return false; } $dir = $this->filesystem->normalizePath(realpath($vendorDir . '/' . $packageDir)); if (!is_dir($dir)) { $this->io->writeError('Vendor dir not found: ' . $vendorDir . '/' . $packageDir); return false; } //$this->io->write('Rules: ' . print_r($rules, true)); foreach ((array) $rules as $part) { // Split patterns for single globs (should be max 260 chars) $patterns = (array) $part; foreach ($patterns as $pattern) { try { foreach (glob($dir . '/' . $pattern) as $file) { $this->filesystem->remove($file); //$this->io->write('File removed: ' . $file); } } catch (\Exception $e) { $this->io->write("Could not parse {$packageDir} ({$pattern}): " . $e->getMessage()); } } } return true; }
/** * @param Request $request * @param bool $ignorePlatformReqs * @return array */ public function solve(Request $request, $ignorePlatformReqs = false) { $this->jobs = $request->getJobs(); $this->setupInstalledMap(); $this->rules = $this->ruleSetGenerator->getRulesFor($this->jobs, $this->installedMap, $ignorePlatformReqs); $this->checkForRootRequireProblems($ignorePlatformReqs); $this->decisions = new Decisions($this->pool); $this->watchGraph = new RuleWatchGraph(); foreach ($this->rules as $rule) { $this->watchGraph->insert(new RuleWatchNode($rule)); } /* make decisions based on job/update assertions */ $this->makeAssertionRuleDecisions(); $this->io->writeError('Resolving dependencies through SAT', true, IOInterface::DEBUG); $before = microtime(true); $this->runSat(true); $this->io->writeError(sprintf('Dependency resolution completed in %.3f seconds', microtime(true) - $before), true, IOInterface::VERBOSE); // decide to remove everything that's installed and undecided foreach ($this->installedMap as $packageId => $void) { if ($this->decisions->undecided($packageId)) { $this->decisions->decide(-$packageId, 1, null); } } if ($this->problems) { throw new SolverProblemsException($this->problems, $this->installedMap); } $transaction = new Transaction($this->policy, $this->pool, $this->installedMap, $this->decisions); return $transaction->getOperations(); }
/** * Registers an autoloader based on an autoload map returned by parseAutoloads * * @param array $autoloads see parseAutoloads return value * @return ClassLoader */ public function createLoader(array $autoloads) { $loader = new ClassLoader(); if (isset($autoloads['psr-0'])) { foreach ($autoloads['psr-0'] as $namespace => $path) { $loader->add($namespace, $path); } } if (isset($autoloads['psr-4'])) { foreach ($autoloads['psr-4'] as $namespace => $path) { $loader->addPsr4($namespace, $path); } } if (isset($autoloads['classmap'])) { foreach ($autoloads['classmap'] as $dir) { try { $loader->addClassMap($this->generateClassMap($dir, null, null, false)); } catch (\RuntimeException $e) { $this->io->writeError('<warning>'.$e->getMessage().'</warning>'); } } } return $loader; }
/** * Set permissions for files using extra->chmod from composer.json * * @return void */ private function setFilePermissions() { $packages = $this->composer->getRepositoryManager()->getLocalRepository()->getPackages(); $message = 'Check "chmod" section in composer.json of %s package.'; foreach ($packages as $package) { $extra = $package->getExtra(); if (!isset($extra['chmod']) || !is_array($extra['chmod'])) { continue; } $error = false; foreach ($extra['chmod'] as $chmod) { if (!isset($chmod['mask']) || !isset($chmod['path']) || strpos($chmod['path'], '..') !== false) { $error = true; continue; } $file = $this->installer->getTargetDir() . '/' . $chmod['path']; if (file_exists($file)) { chmod($file, octdec($chmod['mask'])); } else { $this->io->writeError(['File doesn\'t exist: ' . $chmod['path'], sprintf($message, $package->getName())]); } } if ($error) { $this->io->writeError(['Incorrect mask or file path.', sprintf($message, $package->getName())]); } } }
/** * Ensures backwards compatibility for packages which used helhum/class-alias-loader * * @param PackageInterface $package * @return array */ protected function handleDeprecatedConfigurationInPackage(PackageInterface $package) { $extraConfig = $package->getExtra(); $messages = array(); if (!isset($extraConfig['typo3/class-alias-loader'])) { if (isset($extraConfig['helhum/class-alias-loader'])) { $extraConfig['typo3/class-alias-loader'] = $extraConfig['helhum/class-alias-loader']; $messages[] = sprintf('<warning>The package "%s" uses "helhum/class-alias-loader" section to define class alias maps, which is deprecated. Please use "typo3/class-alias-loader" instead!</warning>', $package->getName()); } else { $extraConfig['typo3/class-alias-loader'] = array(); if (isset($extraConfig['class-alias-maps'])) { $extraConfig['typo3/class-alias-loader']['class-alias-maps'] = $extraConfig['class-alias-maps']; $messages[] = sprintf('<warning>The package "%s" uses "class-alias-maps" section on top level, which is deprecated. Please move this config below the top level key "typo3/class-alias-loader" instead!</warning>', $package->getName()); } if (isset($extraConfig['autoload-case-sensitivity'])) { $extraConfig['typo3/class-alias-loader']['autoload-case-sensitivity'] = $extraConfig['autoload-case-sensitivity']; $messages[] = sprintf('<warning>The package "%s" uses "autoload-case-sensitivity" section on top level, which is deprecated. Please move this config below the top level key "typo3/class-alias-loader" instead!</warning>', $package->getName()); } } } if (!empty($messages)) { $this->IO->writeError($messages); } return $extraConfig; }
/** * @return string[] */ private function fetch() { $this->io->write(sprintf(' - Updating <info>%s</info> tag list', $this->package)); if (isset($_ENV['HRDNS_PHANTOMJS_VERSION']) && !empty($_ENV['HRDNS_PHANTOMJS_VERSION'])) { $this->cache = [$_ENV['HRDNS_PHANTOMJS_VERSION']]; } if ($this->cache === null) { $this->cache = []; $this->io->writeError(" Downloading: <comment>Connecting...</comment>", false); $repo = new VcsRepository(['url' => $this->url, 'type' => $this->type], $this->io, $this->config); $this->cache = array_keys($repo->getDriver()->getTags()); $this->io->overwriteError('', false); $this->io->overwriteError(sprintf("\r Found <comment>%d</comment> versions\n", count($this->cache))); } return $this->cache; }
/** * Iterate over all files in the given directory searching for classes * * @param \Iterator|string $path The path to search in or an iterator * @param string $blacklist Regex that matches against the file path that exclude from the classmap. * @param IOInterface $io IO object * @param string $namespace Optional namespace prefix to filter by * * @throws \RuntimeException When the path is neither an existing file nor directory * @return array A class map array */ public static function createMap($path, $blacklist = null, IOInterface $io = null, $namespace = null) { if (is_string($path)) { if (is_file($path)) { $path = array(new \SplFileInfo($path)); } elseif (is_dir($path)) { $path = Finder::create()->files()->followLinks()->name('/\\.(php|inc|hh)$/')->in($path); } else { throw new \RuntimeException('Could not scan for classes inside "' . $path . '" which does not appear to be a file nor a folder'); } } $map = array(); foreach ($path as $file) { $filePath = $file->getRealPath(); if (!in_array(pathinfo($filePath, PATHINFO_EXTENSION), array('php', 'inc', 'hh'))) { continue; } if ($blacklist && preg_match($blacklist, strtr($filePath, '\\', '/'))) { continue; } $classes = self::findClasses($filePath); foreach ($classes as $class) { // skip classes not within the given namespace prefix if (null !== $namespace && 0 !== strpos($class, $namespace)) { continue; } if (!isset($map[$class])) { $map[$class] = $filePath; } elseif ($io && $map[$class] !== $filePath && !preg_match('{/(test|fixture|example|stub)s?/}i', strtr($map[$class] . ' ' . $filePath, '\\', '/'))) { $io->writeError('<warning>Warning: Ambiguous class resolution, "' . $class . '"' . ' was found in both "' . $map[$class] . '" and "' . $filePath . '", the first will be used.</warning>'); } } } return $map; }
/** * Plugin callback for this script event, which calls the previously implemented static method * * @throws \InvalidArgumentException * @throws \RuntimeException */ public function onPreAutoloadDump() { $composerConfig = $this->composer->getConfig(); $includeFile = $composerConfig->get('vendor-dir') . self::INCLUDE_FILE; $filesystem = new Filesystem(); $filesystem->ensureDirectoryExists(dirname($includeFile)); $includeFileContent = $this->getIncludeFileContent($includeFile); if (false !== @file_put_contents($includeFile, $includeFileContent)) { $rootPackage = $this->composer->getPackage(); $autoloadDefinition = $rootPackage->getAutoload(); $autoloadDefinition['files'][] = $includeFile; $rootPackage->setAutoload($autoloadDefinition); $this->io->writeError('<info>Registered helhum/dotenv-connector in composer autoload definition</info>'); } else { $this->io->writeError('<error>Could not dump helhum/dotenv-connector autoload include file</error>'); } }
private static function checkNpmExists(IOInterface $io) { $returnVal = shell_exec("which npm"); if ($returnVal === null) { $io->writeError("[41mCan't find `npm` in \$PATH, check `npm` and `node` are installed.[0m"); exit(1); } }
/** * {@inheritDoc} */ public function remove(PackageInterface $package, $path) { $this->io->writeError(" - Removing <info>" . $package->getName() . "</info> (<comment>" . $package->getPrettyVersion() . "</comment>)"); $this->cleanChanges($package, $path, false); if (!$this->filesystem->removeDirectory($path)) { throw new \RuntimeException('Could not completely delete ' . $path . ', aborting.'); } }
/** * Read extra config. * @param string $file * @return array */ protected function readExtensionConfig(PackageInterface $package, $file) { $path = $this->preparePath($package, $file); if (!file_exists($path)) { $this->io->writeError('<error>Non existent extension config file</error> ' . $file . ' in ' . $package->getName()); exit(1); } return require $path; }
/** * Write a message. * * @param string $message */ protected function log($message) { if (method_exists($this->io, 'writeError')) { $this->io->writeError($message); } else { // @codeCoverageIgnoreStart $this->io->write($message); // @codeCoverageIgnoreEn } }
/** * @param \Composer\IO\IOInterface $io * @param \Composer\Package\PackageInterface $package * @return bool */ public static function checkDependencies(IOInterface $io, PackageInterface $package) { $search = 'wp-cli/wp-cli'; $requires = $package->getRequires(); $requiresDev = $package->getDevRequires(); if (isset($requires[$search]) or isset($requiresDev[$search])) { return true; } $io->writeError('<info>This package is a dependency of `wp-cli/wp-cli`. Skipping.</info>'); return false; }
/** * @param $message * @param Exception|null $exception */ private function printWarning($message, Exception $exception = null) { if (!$exception) { $reasonPhrase = ''; } elseif ($this->io->isVerbose()) { $reasonPhrase = $exception instanceof PuliRunnerException ? $exception->getFullError() : $exception->getMessage() . "\n\n" . $exception->getTraceAsString(); } else { $reasonPhrase = $exception instanceof PuliRunnerException ? $exception->getShortError() : $exception->getMessage(); } $this->io->writeError(sprintf('<warning>Warning: %s%s</warning>', $message, $reasonPhrase ? ': ' . $reasonPhrase : '.')); }
private function addClassMapCode($filesystem, $basePath, $vendorPath, $dir, $whitelist = null, $namespaceFilter = null, array $classMap = array()) { foreach ($this->generateClassMap($dir, $whitelist, $namespaceFilter) as $class => $path) { $pathCode = $this->getPathCode($filesystem, $basePath, $vendorPath, $path) . ",\n"; if (!isset($classMap[$class])) { $classMap[$class] = $pathCode; } elseif ($this->io && $classMap[$class] !== $pathCode && !preg_match('{/(test|fixture|example|stub)s?/}i', strtr($classMap[$class] . ' ' . $path, '\\', '/'))) { $this->io->writeError('<warning>Warning: Ambiguous class resolution, "' . $class . '"' . ' was found in both "' . str_replace(array('$vendorDir . \'', "',\n"), array($vendorPath, ''), $classMap[$class]) . '" and "' . $path . '", the first will be used.</warning>'); } } return $classMap; }
/** * @return string[] */ private function fetch() { $this->io->write(sprintf(' - Updating <info>%s</info> tag list', $this->package)); if (isset($_ENV['HRDNS_SELENIUM_VERSION']) && !empty($_ENV['HRDNS_SELENIUM_VERSION'])) { $this->cache = [$_ENV['HRDNS_SELENIUM_VERSION']]; } if ($this->cache === null) { $this->cache = []; $this->io->writeError(" Downloading: <comment>Connecting...</comment>", false); $repo = new VcsRepository(['url' => $this->url, 'type' => $this->type], $this->io, $this->config); $this->cache = array_keys($repo->getDriver()->getTags()); $this->io->overwriteError(" Downloading: <comment>100%</comment>", false); $this->cache = array_filter($this->cache, function ($version) { return strpos($version, 'selenium-') === 0; }); array_walk($this->cache, function (&$value) { $value = substr($value, 9); }); $this->io->overwriteError(sprintf("\r Found <comment>%d</comment> versions\n", count($this->cache))); } return $this->cache; }
/** * Repositories requests credentials, let's put them in. * * @throws \RuntimeException * @return \Composer\Util\Svn */ protected function doAuthDance() { // cannot ask for credentials in non interactive mode if (!$this->io->isInteractive()) { throw new \RuntimeException('can not ask for authentication in non interactive mode'); } $this->io->writeError("The Subversion server ({$this->url}) requested credentials:"); $this->hasAuth = true; $this->credentials['username'] = $this->io->ask("Username: "******"Password: "******"Should Subversion cache these credentials? (yes/no) ", true); return $this; }
/** * Apply plugin modifications to composer * * See http://symfony.com/doc/current/components/console/introduction.html for more about $io * Available tags are: [info|comment|question|error] * * @param \Composer\Composer $composer * @param \Composer\IO\IOInterface $io */ public function activate(\Composer\Composer $composer, \Composer\IO\IOInterface $io) { $this->composer = $composer; $this->io = $io; $extra = $composer->getPackage()->getExtra(); if (isset($extra[self::EXTRA_PARAM])) { $files = $extra[self::EXTRA_PARAM]; /* parse configuration files */ if (!is_array($files)) { $this->configFileNames = [$files]; } else { $this->configFileNames = $files; } foreach ($this->configFileNames as $one) { if (file_exists($one)) { $config = new \Praxigento\Composer\Plugin\Templates\Config($one); if ($config->hasData()) { $io->write(__CLASS__ . ": <info>Configuration is read from '{$one}'.</info>", true); if (is_null(self::$config)) { self::$config = $config; } else { self::$config->merge($config); } } else { $io->writeError(__CLASS__ . ": <error>Cannot read valid JSON from configuration file '{$one}'. Plugin will be disabled.</error>", true); self::$config = null; break; } } else { $io->writeError(__CLASS__ . ": <error>Cannot open configuration file '{$one}'. Plugin will be disabled.</error>", true); self::$config = null; break; } } } else { $io->writeError(__CLASS__ . ": <error>Extra parameter '" . self::EXTRA_PARAM . "' is empty. Plugin is disabled.</error>", true); } }
private function unloadModule(PackageInterface $package, $moduleClass) { $modulesList = (include $this->root . '/config/modules.php'); if (!is_array($modulesList)) { $this->io->writeError('<warning> ** Bad config. "' . $this->root . '/config/modules.php" does not return an array.</warning>'); } if (!in_array($moduleClass, $modulesList)) { $this->io->write('<info> ** Module ' . $package->getPrettyName() . ' is already unloaded - Nothing to do.</info>'); } else { $index = array_search($moduleClass, $modulesList); unset($modulesList[$index]); file_put_contents($this->root . '/config/modules.php', '<?php return ' . var_export($modulesList, true) . ';'); } }
/** * @param string $formatter * * @throws CompilerException */ public function processFiles($formatter) { $this->scss->setFormatter($this->getFormatterClass($formatter)); $this->io->write("<info>use '{$formatter}' formatting</info>"); foreach ($this->files as $file) { $this->io->write("<info>processing</info>: {$file->getInputPath()}"); $this->fetchInputContextIntoFile($file); try { $this->processFile($file); } catch (CompilerException $e) { $this->io->writeError("<error>failed to process: {$file->getOutputPath()}</error>"); } } }
private function doCommit() { if (!$this->config->getCommitBinFile()) { $this->io->writeError('<error>No "commit-bin-file" for composer-changelogs plugin. Commit not done.</error>'); return; } $workingDirectory = getcwd(); $filename = tempnam(sys_get_temp_dir(), 'composer-changelogs-'); $message = $this->config->getCommitMessage() . PHP_EOL . PHP_EOL . strip_tags($this->outputter->getOutput()); file_put_contents($filename, $message); $command = $this->config->getCommitBinFile() . ' ' . escapeshellarg($workingDirectory) . ' ' . escapeshellarg($filename); $this->io->write(sprintf('Executing following command: %s', $command)); exec($command); }
/** * @param IO\IOInterface $io * @param CopyRequest[] $requests */ public function fetchAll(IO\IOInterface $io, array $requests) { $successCnt = $failureCnt = 0; $totalCnt = count($requests); $multi = new CurlMulti(); $multi->setRequests($requests); try { do { $multi->setupEventLoop(); $multi->wait(); $result = $multi->getFinishedResults(); $successCnt += $result['successCnt']; $failureCnt += $result['failureCnt']; foreach ($result['urls'] as $url) { $io->writeError(" <comment>{$successCnt}/{$totalCnt}</comment>:\t{$url}", true, IO\IOInterface::VERBOSE); } } while ($multi->remain()); } catch (FetchException $e) { // do nothing } $skippedCnt = $totalCnt - $successCnt - $failureCnt; $io->writeError(" Finished: <comment>success: {$successCnt}, skipped: {$skippedCnt}, failure: {$failureCnt}, total: {$totalCnt}</comment>", true, IO\IOInterface::VERBOSE); }
/** * {@inheritDoc} */ public function activate(Composer $composer, IOInterface $inputOutput) { if ($composer->getPackage()->getType() !== 'project') { $this->isProject = false; $inputOutput->writeError('Root package is not of type "project", we will not installing Contao extensions.'); return; } $this->composer = $composer; if (null === $this->runonceManager) { $this->runonceManager = new RunonceManager(dirname($composer->getConfig()->get('vendor-dir')) . '/app/Resources/contao/config/runonce.php'); } $installationManager = $composer->getInstallationManager(); $installationManager->addInstaller(new ContaoModuleInstaller($this->runonceManager, $inputOutput, $composer)); $installationManager->addInstaller(new LegacyContaoModuleInstaller($this->runonceManager, $inputOutput, $composer)); }