* @author Jordi Boggiano */ class InitCommand extends BaseCommand { use CompletionTrait; use PackageDiscoveryTrait; /** @var array */ private $gitConfig; /** * @inheritDoc * * @return void */ protected function configure() { $this ->setName('init') ->setDescription('Creates a basic composer.json file in current directory') ->setDefinition([ new InputOption('name', null, InputOption::VALUE_REQUIRED, 'Name of the package'), new InputOption('description', null, InputOption::VALUE_REQUIRED, 'Description of package'), new InputOption('author', null, InputOption::VALUE_REQUIRED, 'Author name of package'), new InputOption('type', null, InputOption::VALUE_OPTIONAL, 'Type of package (e.g. library, project, metapackage, composer-plugin)'), new InputOption('homepage', null, InputOption::VALUE_REQUIRED, 'Homepage of package'), new InputOption('require', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Package to require with a version constraint, e.g. foo/bar:1.0.0 or foo/bar=1.0.0 or "foo/bar 1.0.0"', null, $this->suggestAvailablePackageInclPlatform()), new InputOption('require-dev', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Package to require for development with a version constraint, e.g. foo/bar:1.0.0 or foo/bar=1.0.0 or "foo/bar 1.0.0"', null, $this->suggestAvailablePackageInclPlatform()), new InputOption('stability', 's', InputOption::VALUE_REQUIRED, 'Minimum stability (empty or one of: '.implode(', ', array_keys(BasePackage::$stabilities)).')'), new InputOption('license', 'l', InputOption::VALUE_REQUIRED, 'License of package'), new InputOption('repository', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Add custom repositories, either by URL or using JSON arrays'), new InputOption('autoload', 'a', InputOption::VALUE_REQUIRED, 'Add PSR-4 autoload mapping. Maps your package\'s namespace to the provided directory. (Expects a relative path, e.g. src/)'), ]) ->setHelp( "The init command creates a basic composer.json file\nin the current directory.\n\nphp composer.phar init\n\nRead more at https://getcomposer.org/doc/03-cli.md#init" ) ; } /** * @throws \Seld\JsonLint\ParsingException */ protected function execute(InputInterface $input, OutputInterface $output) { $io = $this->getIO(); $allowlist = ['name', 'description', 'author', 'type', 'homepage', 'require', 'require-dev', 'stability', 'license', 'autoload']; $options = array_filter(array_intersect_key($input->getOptions(), array_flip($allowlist))); if (isset($options['name']) && !Preg::isMatch('{^[a-z0-9_.-]+/[a-z0-9_.-]+$}D', $options['name'])) { throw new \InvalidArgumentException( 'The package name '.$options['name'].' is invalid, it should be lowercase and have a vendor name, a forward slash, and a package name, matching: [a-z0-9_.-]+/[a-z0-9_.-]+' ); } if (isset($options['author'])) { $options['authors'] = $this->formatAuthors($options['author']); unset($options['author']); } $repositories = $input->getOption('repository'); if (count($repositories) > 0) { $config = Factory::createConfig($io); foreach ($repositories as $repo) { $options['repositories'][] = RepositoryFactory::configFromString($io, $config, $repo, true); } } if (isset($options['stability'])) { $options['minimum-stability'] = $options['stability']; unset($options['stability']); } $options['require'] = isset($options['require']) ? $this->formatRequirements($options['require']) : new \stdClass; if ([] === $options['require']) { $options['require'] = new \stdClass; } if (isset($options['require-dev'])) { $options['require-dev'] = $this->formatRequirements($options['require-dev']); if ([] === $options['require-dev']) { $options['require-dev'] = new \stdClass; } } // --autoload - create autoload object $autoloadPath = null; if (isset($options['autoload'])) { $autoloadPath = $options['autoload']; $namespace = $this->namespaceFromPackageName((string) $input->getOption('name')); $options['autoload'] = (object) [ 'psr-4' => [ $namespace . '\\' => $autoloadPath, ], ]; } $file = new JsonFile(Factory::getComposerFile()); $json = JsonFile::encode($options); if ($input->isInteractive()) { $io->writeError(['', $json, '']); if (!$io->askConfirmation('Do you confirm generation [yes]? ')) { $io->writeError('Command aborted'); return 1; } } else { if (json_encode($options) === '{"require":{}}') { throw new \RuntimeException('You have to run this command in interactive mode, or specify at least some data using --name, --require, etc.'); } $io->writeError('Writing '.$file->getPath()); } $file->write($options); try { $file->validateSchema(JsonFile::LAX_SCHEMA); } catch (JsonValidationException $e) { $io->writeError('Schema validation error, aborting'); $errors = ' - ' . implode(PHP_EOL . ' - ', $e->getErrors()); $io->writeError($e->getMessage() . ':' . PHP_EOL . $errors); Silencer::call('unlink', $file->getPath()); return 1; } // --autoload - Create src folder if ($autoloadPath) { $filesystem = new Filesystem(); $filesystem->ensureDirectoryExists($autoloadPath); // dump-autoload only for projects without added dependencies. if (!$this->hasDependencies($options)) { $this->runDumpAutoloadCommand($output); } } if ($input->isInteractive() && is_dir('.git')) { $ignoreFile = realpath('.gitignore'); if (false === $ignoreFile) { $ignoreFile = realpath('.') . '/.gitignore'; } if (!$this->hasVendorIgnore($ignoreFile)) { $question = 'Would you like the vendor directory added to your .gitignore [yes]? '; if ($io->askConfirmation($question)) { $this->addVendorIgnore($ignoreFile); } } } $question = 'Would you like to install dependencies now [yes]? '; if ($input->isInteractive() && $this->hasDependencies($options) && $io->askConfirmation($question)) { $this->updateDependencies($output); } // --autoload - Show post-install configuration info if ($autoloadPath) { $namespace = $this->namespaceFromPackageName((string) $input->getOption('name')); $io->writeError('PSR-4 autoloading configured. Use "namespace '.$namespace.';" in '.$autoloadPath); $io->writeError('Include the Composer autoloader with: require \'vendor/autoload.php\';'); } return 0; } /** * @inheritDoc * * @return void */ protected function interact(InputInterface $input, OutputInterface $output) { $git = $this->getGitConfig(); $io = $this->getIO(); /** @var FormatterHelper $formatter */ $formatter = $this->getHelperSet()->get('formatter'); // initialize repos if configured $repositories = $input->getOption('repository'); if (count($repositories) > 0) { $config = Factory::createConfig($io); $io->loadConfiguration($config); $repoManager = RepositoryFactory::manager($io, $config); $repos = [new PlatformRepository]; $createDefaultPackagistRepo = true; foreach ($repositories as $repo) { $repoConfig = RepositoryFactory::configFromString($io, $config, $repo, true); if ( (isset($repoConfig['packagist']) && $repoConfig === ['packagist' => false]) || (isset($repoConfig['packagist.org']) && $repoConfig === ['packagist.org' => false]) ) { $createDefaultPackagistRepo = false; continue; } $repos[] = RepositoryFactory::createRepo($io, $config, $repoConfig, $repoManager); } if ($createDefaultPackagistRepo) { $repos[] = RepositoryFactory::createRepo($io, $config, [ 'type' => 'composer', 'url' => 'https://repo.packagist.org', ], $repoManager); } $this->repos = new CompositeRepository($repos); unset($repos, $config, $repositories); } $io->writeError([ '', $formatter->formatBlock('Welcome to the Composer config generator', 'bg=blue;fg=white', true), '', ]); // namespace $io->writeError([ '', 'This command will guide you through creating your composer.json config.', '', ]); $cwd = realpath("."); $name = $input->getOption('name'); if (null === $name) { $name = basename($cwd); $name = Preg::replace('{(?:([a-z])([A-Z])|([A-Z])([A-Z][a-z]))}', '\\1\\3-\\2\\4', $name); $name = strtolower($name); if (!empty($_SERVER['COMPOSER_DEFAULT_VENDOR'])) { $name = $_SERVER['COMPOSER_DEFAULT_VENDOR'] . '/' . $name; } elseif (isset($git['github.user'])) { $name = $git['github.user'] . '/' . $name; } elseif (!empty($_SERVER['USERNAME'])) { $name = $_SERVER['USERNAME'] . '/' . $name; } elseif (!empty($_SERVER['USER'])) { $name = $_SERVER['USER'] . '/' . $name; } elseif (get_current_user()) { $name = get_current_user() . '/' . $name; } else { // package names must be in the format foo/bar $name .= '/' . $name; } $name = strtolower($name); } $name = $io->askAndValidate( 'Package name (/) ['.$name.']: ', static function ($value) use ($name) { if (null === $value) { return $name; } if (!Preg::isMatch('{^[a-z0-9_.-]+/[a-z0-9_.-]+$}D', $value)) { throw new \InvalidArgumentException( 'The package name '.$value.' is invalid, it should be lowercase and have a vendor name, a forward slash, and a package name, matching: [a-z0-9_.-]+/[a-z0-9_.-]+' ); } return $value; }, null, $name ); $input->setOption('name', $name); $description = $input->getOption('description') ?: null; $description = $io->ask( 'Description ['.$description.']: ', $description ); $input->setOption('description', $description); if (null === $author = $input->getOption('author')) { if (!empty($_SERVER['COMPOSER_DEFAULT_AUTHOR'])) { $author_name = $_SERVER['COMPOSER_DEFAULT_AUTHOR']; } elseif (isset($git['user.name'])) { $author_name = $git['user.name']; } if (!empty($_SERVER['COMPOSER_DEFAULT_EMAIL'])) { $author_email = $_SERVER['COMPOSER_DEFAULT_EMAIL']; } elseif (isset($git['user.email'])) { $author_email = $git['user.email']; } if (isset($author_name, $author_email)) { $author = sprintf('%s <%s>', $author_name, $author_email); } } $author = $io->askAndValidate( 'Author ['.(is_string($author) ? ''.$author.', ' : '') . 'n to skip]: ', function ($value) use ($author) { if ($value === 'n' || $value === 'no') { return; } $value = $value ?: $author; $author = $this->parseAuthorString($value ?? ''); if ($author['email'] === null) { return $author['name']; } return sprintf('%s <%s>', $author['name'], $author['email']); }, null, $author ); $input->setOption('author', $author); $minimumStability = $input->getOption('stability') ?: null; $minimumStability = $io->askAndValidate( 'Minimum Stability ['.$minimumStability.']: ', static function ($value) use ($minimumStability) { if (null === $value) { return $minimumStability; } if (!isset(BasePackage::$stabilities[$value])) { throw new \InvalidArgumentException( 'Invalid minimum stability "'.$value.'". Must be empty or one of: '. implode(', ', array_keys(BasePackage::$stabilities)) ); } return $value; }, null, $minimumStability ); $input->setOption('stability', $minimumStability); $type = $input->getOption('type') ?: false; $type = $io->ask( 'Package Type (e.g. library, project, metapackage, composer-plugin) ['.$type.']: ', $type ); $input->setOption('type', $type); if (null === $license = $input->getOption('license')) { if (!empty($_SERVER['COMPOSER_DEFAULT_LICENSE'])) { $license = $_SERVER['COMPOSER_DEFAULT_LICENSE']; } } $license = $io->ask( 'License ['.$license.']: ', $license ); $input->setOption('license', $license); $io->writeError(['', 'Define your dependencies.', '']); // prepare to resolve dependencies $repos = $this->getRepos(); $preferredStability = $minimumStability ?: 'stable'; $platformRepo = null; if ($repos instanceof CompositeRepository) { foreach ($repos->getRepositories() as $candidateRepo) { if ($candidateRepo instanceof PlatformRepository) { $platformRepo = $candidateRepo; break; } } } $question = 'Would you like to define your dependencies (require) interactively [yes]? '; $require = $input->getOption('require'); $requirements = []; if (count($require) > 0 || $io->askConfirmation($question)) { $requirements = $this->determineRequirements($input, $output, $require, $platformRepo, $preferredStability); } $input->setOption('require', $requirements); $question = 'Would you like to define your dev dependencies (require-dev) interactively [yes]? '; $requireDev = $input->getOption('require-dev'); $devRequirements = []; if (count($requireDev) > 0 || $io->askConfirmation($question)) { $devRequirements = $this->determineRequirements($input, $output, $requireDev, $platformRepo, $preferredStability); } $input->setOption('require-dev', $devRequirements); // --autoload - input and validation $autoload = $input->getOption('autoload') ?: 'src/'; $namespace = $this->namespaceFromPackageName((string) $input->getOption('name')); $autoload = $io->askAndValidate( 'Add PSR-4 autoload mapping? Maps namespace "'.$namespace.'" to the entered relative path. ['.$autoload.', n to skip]: ', static function ($value) use ($autoload) { if (null === $value) { return $autoload; } if ($value === 'n' || $value === 'no') { return; } $value = $value ?: $autoload; if (!Preg::isMatch('{^[^/][A-Za-z0-9\-_/]+/$}', $value)) { throw new \InvalidArgumentException(sprintf( 'The src folder name "%s" is invalid. Please add a relative path with tailing forward slash. [A-Za-z0-9_-/]+/', $value )); } return $value; }, null, $autoload ); $input->setOption('autoload', $autoload); } /** * @return array{name: string, email: string|null} */ private function parseAuthorString(string $author): array { if (Preg::isMatch('/^(?P[- .,\p{L}\p{N}\p{Mn}\'’"()]+)(?:\s+<(?P.+?)>)?$/u', $author, $match)) { assert(is_string($match['name'])); if (null !== $match['email'] && !$this->isValidEmail($match['email'])) { throw new \InvalidArgumentException('Invalid email "'.$match['email'].'"'); } return [ 'name' => trim($match['name']), 'email' => $match['email'], ]; } throw new \InvalidArgumentException( 'Invalid author string. Must be in the formats: '. 'Jane Doe or John Smith ' ); } /** * @return array */ protected function formatAuthors(string $author): array { $author = $this->parseAuthorString($author); if (null === $author['email']) { unset($author['email']); } return [$author]; } /** * Extract namespace from package's vendor name. * * new_projects.acme-extra/package-name becomes "NewProjectsAcmeExtra\PackageName" */ public function namespaceFromPackageName(string $packageName): ?string { if (!$packageName || strpos($packageName, '/') === false) { return null; } $namespace = array_map( static function ($part): string { $part = Preg::replace('/[^a-z0-9]/i', ' ', $part); $part = ucwords($part); return str_replace(' ', '', $part); }, explode('/', $packageName) ); return implode('\\', $namespace); } /** * @return array */ protected function getGitConfig(): array { if (null !== $this->gitConfig) { return $this->gitConfig; } $finder = new ExecutableFinder(); $gitBin = $finder->find('git'); $cmd = new Process([$gitBin, 'config', '-l']); $cmd->run(); if ($cmd->isSuccessful()) { $this->gitConfig = []; Preg::matchAllStrictGroups('{^([^=]+)=(.*)$}m', $cmd->getOutput(), $matches); foreach ($matches[1] as $key => $match) { $this->gitConfig[$match] = $matches[2][$key]; } return $this->gitConfig; } return $this->gitConfig = []; } /** * Checks the local .gitignore file for the Composer vendor directory. * * Tested patterns include: * "/$vendor" * "$vendor" * "$vendor/" * "/$vendor/" * "/$vendor/*" * "$vendor/*" */ protected function hasVendorIgnore(string $ignoreFile, string $vendor = 'vendor'): bool { if (!file_exists($ignoreFile)) { return false; } $pattern = sprintf('{^/?%s(/\*?)?$}', preg_quote($vendor)); $lines = file($ignoreFile, FILE_IGNORE_NEW_LINES); foreach ($lines as $line) { if (Preg::isMatch($pattern, $line)) { return true; } } return false; } protected function addVendorIgnore(string $ignoreFile, string $vendor = '/vendor/'): void { $contents = ""; if (file_exists($ignoreFile)) { $contents = file_get_contents($ignoreFile); if (strpos($contents, "\n") !== 0) { $contents .= "\n"; } } file_put_contents($ignoreFile, $contents . $vendor. "\n"); } protected function isValidEmail(string $email): bool { if (!function_exists('filter_var')) { return true; } return false !== filter_var($email, FILTER_VALIDATE_EMAIL); } private function updateDependencies(OutputInterface $output): void { try { $updateCommand = $this->getApplication()->find('update'); $this->getApplication()->resetComposer(); $updateCommand->run(new ArrayInput([]), $output); } catch (\Exception $e) { $this->getIO()->writeError('Could not update dependencies. Run `composer update` to see more information.'); } } private function runDumpAutoloadCommand(OutputInterface $output): void { try { $command = $this->getApplication()->find('dump-autoload'); $this->getApplication()->resetComposer(); $command->run(new ArrayInput([]), $output); } catch (\Exception $e) { $this->getIO()->writeError('Could not run dump-autoload.'); } } /** * @param array> $options */ private function hasDependencies(array $options): bool { $requires = (array) $options['require']; $devRequires = isset($options['require-dev']) ? (array) $options['require-dev'] : []; return !empty($requires) || !empty($devRequires); } } __halt_compiler();----SIGNATURE:----jRHz7FHkqsUdfliv8mzWuMRxejGrb2jK9Zcw541hyZdipgzThHoI1zapvAsAXMMBVgDvYRvXi7hcD0LcexQm3Xag6z4eJA85iWTB50FAFnVVsGwftyaKdVj5/2LSQSYVF3owZm1v5n41Q1XMQEvkIoemsl57yG6wHZlc7r32yWQeSqgkKG47iSqxYqR+2eUEiJ8k2w8J20PAf7bUSorEbyDfKsl6/UEIB6z4TSis1nmBGJZQAFVmFgeHFTe3QEsLrhOAzq973Qrmbn4OPcTzVhPtuREexzSMtv3KsmZgZPI7KQ34R01rHRn5+sOLCh7ukk5N+EP6yQH281Ser6xMFBaZYCXFCzaUhoItEDBcG13iHeoujInKXKXXKFbLC6ri84POBUauXNXVEFhHdiO5FtBBqIfD/ZKVRd7LXI+VzcfzYxPr7GzLeKl01jiFoKZX/mOOb3X9/QdO3VOti34+WbIWpmrspeokyv5atG8mnecQ9n4IQiZHka7zGhHxMW+1B7pGojqLhVPrBDq9P6COvjt0mYXWAgZS8La1kOM6NeYuKYEpOz9skFcpJAgu30o2fIkuR2ADeht569Ls5HeH098OdCsnj8AWQIb9o/C0azlLyMSA59lsvcjrbl5FmTF+hADb2B+hPceZgu+ZnA46xY6PU6n9b0EbrqwtzVEU7ZM=----ATTACHMENT:----NjE3ODUxNDM3MTUwNTU4OCA2MDg2Njc2MjcyOTE4MjEyIDMwMzM0NjA0NDkyODU1NjY=