0 follower

Final Class Yiisoft\YiiDevTool\App\Command\Release\MakeCommand

InheritanceYiisoft\YiiDevTool\App\Command\Release\MakeCommand » Yiisoft\YiiDevTool\App\Component\Console\PackageCommand » Symfony\Component\Console\Command\Command
Uses TraitsYiisoft\YiiDevTool\App\Component\GitHubTokenAware

Protected Methods

Hide inherited methods

Method Description Defined By
afterProcessingPackages() Override this method in a subclass if you want to do something after processing the packages. Yiisoft\YiiDevTool\App\Component\Console\PackageCommand
areTargetPackagesSpecifiedExplicitly() Yiisoft\YiiDevTool\App\Component\Console\PackageCommand
beforeProcessingPackages() Yiisoft\YiiDevTool\App\Command\Release\MakeCommand
checkSSHConnection() Yiisoft\YiiDevTool\App\Component\Console\PackageCommand
configure() Yiisoft\YiiDevTool\App\Command\Release\MakeCommand
doesPackageContainErrors() Yiisoft\YiiDevTool\App\Component\Console\PackageCommand
execute() Yiisoft\YiiDevTool\App\Component\Console\PackageCommand
getAppRootDir() Use this method to get a root directory of the tool. Yiisoft\YiiDevTool\App\Component\Console\PackageCommand
getErrorsList() Yiisoft\YiiDevTool\App\Component\Console\PackageCommand
getExampleCommandPrefix() Yiisoft\YiiDevTool\App\Component\Console\PackageCommand
getIO() Yiisoft\YiiDevTool\App\Component\Console\PackageCommand
getMessageWhenNothingHasBeenOutput() Yiisoft\YiiDevTool\App\Command\Release\MakeCommand
getPackageList() Yiisoft\YiiDevTool\App\Component\Console\PackageCommand
getTargetPackages() Yiisoft\YiiDevTool\App\Component\Console\PackageCommand
initPackageList() Yiisoft\YiiDevTool\App\Component\Console\PackageCommand
initTargetPackages() Yiisoft\YiiDevTool\App\Component\Console\PackageCommand
initialize() Yiisoft\YiiDevTool\App\Component\Console\PackageCommand
processPackage() Yiisoft\YiiDevTool\App\Command\Release\MakeCommand
registerPackageError() Yiisoft\YiiDevTool\App\Component\Console\PackageCommand

Constants

Hide inherited constants

Constant Value Description Defined By
MAIN_BRANCHES [ 'master', 'main', ] Yiisoft\YiiDevTool\App\Command\Release\MakeCommand

Method Details

Hide inherited methods

afterProcessingPackages() protected method

Defined in: Yiisoft\YiiDevTool\App\Component\Console\PackageCommand::afterProcessingPackages()

Override this method in a subclass if you want to do something after processing the packages.

For example, link the packages with each other.

protected void afterProcessingPackages ( \Symfony\Component\Console\Input\InputInterface $input )
$input \Symfony\Component\Console\Input\InputInterface

                protected function afterProcessingPackages(InputInterface $input): void
{
}

            
areTargetPackagesSpecifiedExplicitly() protected method
protected boolean areTargetPackagesSpecifiedExplicitly ( )

                protected function areTargetPackagesSpecifiedExplicitly(): bool
{
    if ($this->targetPackagesSpecifiedExplicitly === null) {
        throw new RuntimeException('Target packages are not initialized.');
    }
    return $this->targetPackagesSpecifiedExplicitly;
}

            
beforeProcessingPackages() protected method

protected void beforeProcessingPackages ( \Symfony\Component\Console\Input\InputInterface $input )
$input \Symfony\Component\Console\Input\InputInterface

                protected function beforeProcessingPackages(InputInterface $input): void
{
    $io = $this->getIO();
    $this->tag = $input->getOption('tag');
    if ($io->getVerbosity() < OutputInterface::VERBOSITY_VERBOSE) {
        $io->setVerbosity(OutputInterface::VERBOSITY_VERBOSE);
    }
}

            
checkSSHConnection() protected method
protected boolean checkSSHConnection ( )

                protected function checkSSHConnection(): bool
{
    $process = new Process(['ssh', '-T', 'git@github.com']);
    $process
        ->setTimeout(null)
        ->run();
    if ($process->getExitCode() !== 1) {
        $this
            ->getIO()
            ->error([
                'Checking access to github.com ... DENIED',
                'Error: ' . $process->getErrorOutput(),
                'Seems like you have not installed SSH key to you Github account.',
                'Key is required to work with repository via SSH.',
                'See here for instructions: https://docs.github.com/en/github/authenticating-to-github/adding-a-new-ssh-key-to-your-github-account',
            ]);
        return false;
    }
    return true;
}

            
configure() protected method

protected mixed configure ( )

                protected function configure()
{
    $this
        ->setName('release/make')
        ->setDescription('Make a package release')
        ->addOption('tag', null, InputArgument::OPTIONAL, description: 'Version to tag');
    parent::configure();
}

            
doesPackageContainErrors() protected method
protected boolean doesPackageContainErrors ( Yiisoft\YiiDevTool\App\Component\Package\Package $package )
$package Yiisoft\YiiDevTool\App\Component\Package\Package

                protected function doesPackageContainErrors(Package $package): bool
{
    return $this->errorList->has($package);
}

            
execute() protected method
protected integer execute ( \Symfony\Component\Console\Input\InputInterface $input, \Symfony\Component\Console\Output\OutputInterface $output )
$input \Symfony\Component\Console\Input\InputInterface
$output \Symfony\Component\Console\Output\OutputInterface

                protected function execute(InputInterface $input, OutputInterface $output): int
{
    $this->initPackageList();
    $this->initTargetPackages($input);
    $io = $this->getIO();
    $this->beforeProcessingPackages($input);
    $packages = $this->getTargetPackages();
    sort($packages);
    foreach ($packages as $package) {
        if ($this->isCurrentInstallationValid($package)) {
            $this->processPackage($package);
        }
    }
    $io->clearPreparedPackageHeader();
    $this->afterProcessingPackages($input);
    $this->showPackageErrors();
    if ($io->nothingHasBeenOutput()) {
        $message = $this->getMessageWhenNothingHasBeenOutput();
        if ($message !== null) {
            $io
                ->important()
                ->info($message);
        }
    }
    return Command::SUCCESS;
}

            
getAppRootDir() protected method

Defined in: Yiisoft\YiiDevTool\App\Component\Console\PackageCommand::getAppRootDir()

Use this method to get a root directory of the tool.

Commands and components can be moved as a result of refactoring, so you should not rely on their location in the file system.

protected string getAppRootDir ( )
return string

Path to the root directory of the tool WITH a TRAILING SLASH.

                protected function getAppRootDir(): string
{
    return rtrim($this
            ->getApplication()
            ->getRootDir(), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
}

            
getErrorsList() protected method
protected Yiisoft\YiiDevTool\App\Component\Package\PackageErrorList getErrorsList ( )

                protected function getErrorsList(): PackageErrorList
{
    return $this->errorList;
}

            
getExampleCommandPrefix() protected method
protected string getExampleCommandPrefix ( )
return string

Console command prefix that works in current environment.

                protected function getExampleCommandPrefix(): string
{
    $shell = getenv('SHELL');
    $isBash = ($shell && stripos($shell, 'bash')) !== false;
    return $isBash ? './' : '';
}

            
getGitHubToken() public method
public string getGitHubToken ( )

                public function getGitHubToken(): string
{
    if ($this->gitHubToken !== null) {
        return $this->gitHubToken;
    }
    $io = $this->getIO();
    $tokenFile = $this->getAppRootDir() . 'config/github.token';
    if (!file_exists($tokenFile)) {
        $io->error([
            "There's no $tokenFile.",
            '<href=https://github.com/settings/tokens>Please create a GitHub token</> and put it there.',
        ]);
        exit(Command::FAILURE);
    }
    $token = trim(file_get_contents($tokenFile));
    if (empty($token)) {
        $io->error([
            "$tokenFile exists but is empty.",
            '<href=https://github.com/settings/tokens>Please create a GitHub token</> and put it there.',
        ]);
        exit(Command::FAILURE);
    }
    // Test the token by making an authenticated request
    $client = new Client();
    $client->authenticate($token, null, AuthMethod::ACCESS_TOKEN);
    try {
        $client->currentUser()->show();
    } catch (\Exception $e) {
        $io->error([
            "Failed to authenticate with GitHub using the provided token from $tokenFile.",
            '<href=https://github.com/settings/tokens>Please make sure the token is valid and has the required permissions</>.',
            'Error: ' . $e->getMessage(),
        ]);
        exit(Command::FAILURE);
    }
    $this->gitHubToken = $token;
    return $token;
}

            
getIO() protected method
protected Yiisoft\YiiDevTool\App\Component\Console\OutputManager getIO ( )

                protected function getIO(): OutputManager
{
    if ($this->io === null) {
        throw new RuntimeException('IO is not initialized.');
    }
    return $this->io;
}

            
getMessageWhenNothingHasBeenOutput() protected method

protected string|null getMessageWhenNothingHasBeenOutput ( )

                protected function getMessageWhenNothingHasBeenOutput(): ?string
{
    return '<success>✔ Done</success>';
}

            
getPackageList() protected method
protected Yiisoft\YiiDevTool\App\Component\Package\PackageList getPackageList ( )

                protected function getPackageList(): PackageList
{
    return $this->packageList;
}

            
getTargetPackages() protected method
protected Yiisoft\YiiDevTool\App\Component\Package\Package[] getTargetPackages ( )

                protected function getTargetPackages(): array
{
    if ($this->targetPackages === null) {
        throw new RuntimeException('Target packages are not initialized.');
    }
    return $this->targetPackages;
}

            
initPackageList() protected method
protected void initPackageList ( )

                protected function initPackageList(): void
{
    $io = $this->getIO();
    try {
        $ownerPackages = require $this->getAppRootDir() . 'owner-packages.php';
        if (!preg_match('/^[a-z0-9][a-z0-9-]*[a-z0-9]$/i', $ownerPackages)) {
            $io->error([
                'The packages owner can only contain the characters [a-z0-9-], and the character \'-\' cannot appear at the beginning or at the end.',
                'See <file>owner-packages.php</file> to set the packages owner.',
            ]);
            exit(1);
        }
        $packagesRootDir = $this->getApplication()->getConfig('packagesRootDir') ??  $this->getAppRootDir() . 'dev';
        $this->packageList = new PackageList(
            $ownerPackages,
            $this->getAppRootDir() . 'packages.php',
            packagesRootDir: $packagesRootDir,
        );
        $this->errorList = new PackageErrorList();
    } catch (InvalidArgumentException $e) {
        $io->error([
            'Invalid local package configuration <file>packages.local.php</file>',
            $e->getMessage(),
            'See <file>packages.local.php.example</file> for configuration examples.',
        ]);
        exit(1);
    }
}

            
initTargetPackages() protected method
protected void initTargetPackages ( \Symfony\Component\Console\Input\InputInterface $input )
$input \Symfony\Component\Console\Input\InputInterface

                protected function initTargetPackages(InputInterface $input): void
{
    if ($this->packageList === null) {
        throw new RuntimeException('Package list is not initialized.');
    }
    $io = $this->getIO();
    $commaSeparatedPackageIds = $input->getArgument('packages');
    if ($commaSeparatedPackageIds === null) {
        $this->targetPackagesSpecifiedExplicitly = false;
        $this->targetPackages = $this->packageList->getEnabledPackages();
        return;
    }
    $targetPackageIds = array_unique(explode(',', $commaSeparatedPackageIds));
    $problemsFound = false;
    $targetPackages = [];
    foreach ($targetPackageIds as $targetPackageId) {
        $package = $this->packageList->getPackage($targetPackageId);
        if ($package === null) {
            $io->error("Package <package>$targetPackageId</package> not found in <file>packages.php</file>");
            $problemsFound = true;
            continue;
        }
        if ($package->disabled()) {
            $io->error("Package <package>$targetPackageId</package> disabled in <file>packages.local.php</file>");
            $problemsFound = true;
            continue;
        }
        $targetPackages[] = $package;
    }
    if ($problemsFound) {
        exit(1);
    }
    $this->targetPackagesSpecifiedExplicitly = true;
    $this->targetPackages = $targetPackages;
}

            
initialize() protected method
protected mixed initialize ( \Symfony\Component\Console\Input\InputInterface $input, \Symfony\Component\Console\Output\OutputInterface $output )
$input \Symfony\Component\Console\Input\InputInterface
$output \Symfony\Component\Console\Output\OutputInterface

                protected function initialize(InputInterface $input, OutputInterface $output)
{
    $this->io = new OutputManager(new YiiDevToolStyle($input, $output));
}

            
processPackage() protected method

protected void processPackage ( Yiisoft\YiiDevTool\App\Component\Package\Package $package )
$package Yiisoft\YiiDevTool\App\Component\Package\Package

                protected function processPackage(Package $package): void
{
    $io = $this->getIO();
    $io->preparePackageHeader($package, 'Releasing {package}');
    $git = $package->getGitWorkingCopy();
    $gitHubToken = $this->getGitHubToken();
    if (!$package->composerConfigFileExists()) {
        $io->warning([
            "No <file>composer.json</file> in package <package>{$package->getName()}</package>.",
            'Release cancelled.',
        ]);
        return;
    }
    $composerPackage = new ComposerPackage($package->getName(), $package->getPath());
    $composerConfig = $composerPackage->getComposerConfig();
    $unstableFlags = ['dev', 'alpha', 'beta', 'rc'];
    $minimumStability = $composerConfig->getSection(ComposerConfig::SECTION_MINIMUM_STABILITY);
    if (in_array($minimumStability, $unstableFlags, true)) {
        $io->warning([
            "Minimum-stability of package <package>{$package->getName()}</package> is <em>$minimumStability</em>.",
            'Release is only possible for stable packages.',
            'Releasing skipped.',
        ]);
        return;
    }
    $dependencyList = $composerConfig->getDependencyList(ComposerConfig::SECTION_REQUIRE);
    foreach ($dependencyList->getDependencies() as $dependency) {
        if ($dependency->constraintContainsAnyOfStabilityFlags($unstableFlags)) {
            $io->warning([
                "Constraint of dependency <em>{$dependency->getPackageName()}</em> contains an unstable flag.",
                "The constraint is <em>{$dependency->getConstraint()}</em>.",
                'Release is only possible for packages with stable dependencies.',
                'Releasing skipped.',
            ]);
            return;
        }
    }
    $io->info("Hurray, another release is coming!\n");
    $currentVersion = $this->getCurrentVersion($git);
    if ($currentVersion->asString() === '') {
        $io->info('There is currently no release.');
    } else {
        $io->info("Current version is $currentVersion.");
    }
    $versionToRelease = $this->getVersionToRelease($currentVersion);
    $io->info("Going to release $versionToRelease.");
    if ($git->hasChanges()) {
        $changes = $git->getStatus();
        if ($this->confirm("You have uncommitted changes:\n" . $changes . "\nDiscard these?")) {
            $git->reset(['hard' => true]);
            $git->clean('-d', '-f');
        } else {
            $io->error('Can not continue with uncommitted changes.');
            return;
        }
    }
    $currentBranch = $this->getCurrentBranch($git);
    if (!in_array($currentBranch, self::MAIN_BRANCHES, true)) {
        $mainBranch = $this->getMainBranch($git);
        if ($mainBranch === null) {
            if (!$this->confirm("You are going to release from \"$currentBranch\" branch. OK?")) {
                return;
            }
        } elseif ($this->confirm("You are going to release from \"$currentBranch\" branch. Want to switch to \"$mainBranch\"?")) {
            $git->checkout($mainBranch);
        }
    }
    $io->info('Pulling latest changes.');
    $git->pull();
    $changelogPath = $package->getPath() . '/CHANGELOG.md';
    $changelog = new Changelog($changelogPath);
    $io->info("Sorting $changelogPath for $versionToRelease.");
    $changelog->resort();
    $io->info("Closing $changelogPath for $versionToRelease.");
    $changelog->close($versionToRelease);
    $io->info("Committing changes for $versionToRelease.");
    $git->commit([
        'S' => true,
        'a' => true,
        'm' => "Release version $versionToRelease",
    ]);
    $io->info("Adding a tag for $versionToRelease.");
    $git->tag([
        's' => (string) $versionToRelease,
        'm' => (string) $versionToRelease,
    ]);
    $nextVersion = $versionToRelease->getNext(Version::TYPE_PATCH);
    $io->info("Opening $changelogPath for $nextVersion.");
    $changelog->open($nextVersion);
    $io->info('Committing changes.');
    $git->commit([
        'a' => true,
        'm' => 'Prepare for next release',
    ]);
    if ($this->confirm('Push commits and tag, and release on GitHub?')) {
        $git->push();
        $git->pushTag((string) $versionToRelease);
        $this->releaseOnGithub($gitHubToken, $package, $versionToRelease);
        $io->done();
        $io->info('The following steps are left to do manually:');
        $io->info("- Close the $versionToRelease <href=https://github.com/{$package->getName()}/milestones/>milestone on GitHub</> and open new one for $nextVersion.");
        $io->info('- Release news and announcement.');
        $this->displayReleaseSummary(
            package: $package,
            composerConfig: $composerConfig,
            changelog: $changelog,
            versionToRelease: $versionToRelease
        );
    }
}

            
registerPackageError() protected method
protected void registerPackageError ( Yiisoft\YiiDevTool\App\Component\Package\Package $package, string $message, string $during )
$package Yiisoft\YiiDevTool\App\Component\Package\Package
$message string
$during string

                protected function registerPackageError(Package $package, string $message, string $during): void
{
    $this->errorList->set($package, $message, $during);
}