Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions config/conf.php.dist
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,31 @@ $conf['horde_pass'] = '';
/* From: address for announcements. */
$conf['from'] = 'Full name <user@horde.org>';

/**
* GitHub API Personal Access Token
*
* Used for GitHub API operations including:
* - Creating releases
* - Managing pull requests
* - Checking CI status
* - Uploading release assets
*
* Generate a token at: https://github.com/settings/tokens
* Required scopes: repo, read:org
*
* SECURITY NOTE: This token grants access to your GitHub repositories.
* Keep it secure and never commit it to version control.
*
* PRECEDENCE: This setting can be overridden by:
* 1. CLI argument: --github-token=ghp_xxx (highest precedence)
* 2. Environment variable: GITHUB_TOKEN
* 3. User config: ~/.config/horde/components.php
* 4. This file: config/conf.php (if copied from .dist)
*
* Leave empty to disable GitHub integration or rely on GITHUB_TOKEN env var.
*/
$conf['github.token'] = '';

/* Path to a checkout of the horde-web repository. */
$conf['web_dir'] = '/var/www/horde-web';

Expand Down
2 changes: 2 additions & 0 deletions src/Cli/ModuleProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use Horde\Components\Module\Composer;
use Horde\Components\Module\ConventionalCommit;
use Horde\Components\Module\Change;
use Horde\Components\Module\Init;
use Horde\Components\Module\InstallModule;
use Horde\Components\Module\Package;
use Horde\Components\Module\Pullrequest;
Expand Down Expand Up @@ -54,6 +55,7 @@ public function getModules(): Modules
$this->injector->get(Git::class),
$this->injector->get(ConventionalCommit::class),
$this->injector->get(Help::class),
$this->injector->get(Init::class),
$this->injector->get(InstallModule::class),
$this->injector->get(Package::class),
$this->injector->get(Pullrequest::class),
Expand Down
12 changes: 9 additions & 3 deletions src/Component/Source.php
Original file line number Diff line number Diff line change
Expand Up @@ -1273,9 +1273,15 @@ public function getWrapper($file)
);
break;
case 'ChangelogYml':
$this->_wrappers[$file] = new WrapperChangelogYml(
$this->getDocDirectory()
);
// changelog.yml is always in the root doc/ directory, not in library-specific subdirs
if (is_dir($this->directory . '/doc')) {
$baseDocDir = $this->directory . '/doc';
} elseif (is_dir($this->directory . '/docs')) {
$baseDocDir = $this->directory . '/docs';
} else {
$baseDocDir = $this->directory . '/doc';
}
$this->_wrappers[$file] = new WrapperChangelogYml($baseDocDir);
break;
case 'Changes':
$this->_wrappers[$file] = new WrapperChanges(
Expand Down
116 changes: 85 additions & 31 deletions src/Components.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,14 @@

use Horde\Cli\Modular\ModularCli;
use Horde\Components\Component\Identify;
use Horde\Components\Config\CliConfig;
use Horde\Components\Config\File as ConfigFile;
use Horde\Components\Config;
use Horde\Components\Config\MinimalConfig;
use Horde\Components\ConfigProvider\BuiltinConfigProvider;
use Horde\Components\ConfigProvider\CliConfigProvider;
use Horde\Components\ConfigProvider\EnvironmentConfigProvider;
use Horde\Components\ConfigProvider\PhpConfigFileProvider;
use Horde\Components\ConfigProvider\ConfigProviderFactory;
use Horde\Components\Module;
//use Horde\Components\Dependencies\Injector;
use Horde\Injector\TopLevel;
use Horde\Injector\Injector;
use Horde\EventDispatcher\EventDispatcher;
Expand Down Expand Up @@ -117,25 +118,82 @@ public function __construct(Injector $injector, $parameters)
$configFileLocation = $finder->find();
$phpConfig = new PhpConfigFileProvider($configFileLocation);
$injector->setInstance(PhpConfigFileProvider::class, $phpConfig);

// Check for legacy config file (config/conf.php)
$legacyConfigPath = dirname(__DIR__) . '/config/conf.php';
$legacyConfig = null;
if (file_exists($legacyConfigPath) && is_readable($legacyConfigPath)) {
$legacyConfig = new PhpConfigFileProvider($legacyConfigPath);
}

// Set up ConfigProviderFactory with all layers
// Note: CLI provider will be added in _prepareModular after parser is ready
$configFactory = new ConfigProviderFactory(
$environmentConfig,
$phpConfig,
$legacyConfig,
$injector->get(BuiltinConfigProvider::class),
null // CLI provider added later
);
$injector->setInstance(ConfigProviderFactory::class, $configFactory);

Dependencies\Injector::registerAppDependencies($injector);
// Identify if we are in a component dir or have provided one with variable
$modular = self::_prepareModular($injector, $parameters);
// If we don't do this, help introspection is broken.
$injector->setInstance(ModularCli::class, $modular);
// TODO: Get rid of this "config" here.
$argv = $injector->get(ArgvWrapper::class);
$config = self::_prepareConfig($argv);
$injector->setInstance(Config::class, $config);

// NOW that parser is ready, we can create CliConfigProvider and update factory
$parser = $modular->getParser();
list($parsedOptions, $parsedArgs) = $parser->parseArgs();

// Convert Horde\Argv\Values object to array
$optionsArray = [];
foreach ($parsedOptions as $key => $value) {
$optionsArray[$key] = $value;
}

$cliProvider = new CliConfigProvider($optionsArray);

// Create new factory WITH CLI provider
$configFactoryWithCli = new ConfigProviderFactory(
$environmentConfig,
$phpConfig,
$legacyConfig,
$injector->get(BuiltinConfigProvider::class),
$cliProvider // NOW we have CLI options!
);
// Replace the old factory
$injector->setInstance(ConfigProviderFactory::class, $configFactoryWithCli);

// Store parsed options for Output factory
$injector->setInstance('parsed_options', $optionsArray);

// Create minimal Config for Component classes (legacy compatibility)
$minimalConfig = new MinimalConfig($optionsArray, $parsedArgs);
$injector->setInstance(Config::class, $minimalConfig);

// Always set path to current working directory
$minimalConfig->setPath(getcwd());

// Identify component if working in a component directory
$component = null;
try {
$identify = $injector->getInstance(Identify::class);
$component = $identify->identifyComponent(getcwd());
// Set component in Config for legacy compatibility
$minimalConfig->setComponent($component);
} catch (\Exception $e) {
// No component in current directory - that's fine for many commands
}

/**
* By this point the modular CLI is setup to cycle through "handle"
*/
try {
$ran = false;
foreach (clone $modular->getModules() as $module) {
// Re-initialize the config for each module to avoid spill
$config = self::_prepareConfig($argv, $module);
$ran |= $module->handle($config);
$ran |= $module->handle($optionsArray, $parsedArgs, $component);
}
} catch (Exception $e) {
$injector->getInstance(Output::class)->fail($e);
Expand Down Expand Up @@ -177,8 +235,23 @@ protected static function _prepareModular(
$injector->setInstance(Horde_Argv_Parser::class, $parser);
$injector->setInstance(ClientInterface::class, new CurlClient(new ResponseFactory(), new StreamFactory(), new Options()));
$injector->setInstance(RequestFactoryInterface::class, new RequestFactory());
$strGithubApiToken = (string) getenv('GITHUB_TOKEN') ?? '';
$injector->setInstance(GithubApiConfig::class, new GithubApiConfig(accessToken: $strGithubApiToken));

// Get GitHub token from ConfigProvider hierarchy
// Precedence: CLI args > GITHUB_TOKEN env var > github.token config key
$configFactory = $injector->getInstance(ConfigProviderFactory::class);
$config = $configFactory->createDefault();

$githubToken = '';
// First check GITHUB_TOKEN environment variable (backward compatibility)
if ($config->hasSetting('GITHUB_TOKEN')) {
$githubToken = $config->getSetting('GITHUB_TOKEN');
}
// Then check github.token config key (new way)
elseif ($config->hasSetting('github.token')) {
$githubToken = $config->getSetting('github.token');
}

$injector->setInstance(GithubApiConfig::class, new GithubApiConfig(accessToken: $githubToken));
return $modularCli;
}

Expand All @@ -199,27 +272,8 @@ protected static function _prepareDependencies($parameters)
}
}

protected static function _prepareConfig(ArgvWrapper $argv, ?Module $module = null): \Horde\Components\Configs
{
$config = new Configs();
$config->addConfigurationType(
new CliConfig(
$argv,
$module
)
);
$config->unshiftConfigurationType(
new ConfigFile(
$config->getOption('config')
)
);
return $config;
}

/**
* Provide a list of available action arguments.
*
* @param Config $config The active configuration.
*/
protected static function _getActionArguments(\Horde_Cli_Modular $modular): array
{
Expand Down
110 changes: 16 additions & 94 deletions src/Config.php
Original file line number Diff line number Diff line change
@@ -1,114 +1,36 @@
<?php

/**
* Components_Config:: interface represents a configuration type for the Horde
* component tool.
* Minimal Config interface for Component class compatibility
*
* PHP Version 7
* PHP version 8.2+
*
* @category Horde
* @package Components
* @author Gunnar Wrobel <wrobel@pardus.de>
* @author Ralf Lang <ralf.lang@ralf-lang.de>
* @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
*/

declare(strict_types=1);

namespace Horde\Components;

/**
* Components_Config:: interface represents a configuration type for the Horde
* component tool.
*
* Copyright 2009-2024 Horde LLC (http://www.horde.org/)
* Minimal Config interface.
*
* See the enclosed file LICENSE for license information (LGPL). If you
* did not receive this file, see http://www.horde.org/licenses/lgpl21.
*
* @category Horde
* @package Components
* @author Gunnar Wrobel <wrobel@pardus.de>
* @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
* Component classes still depend on Config interface.
* This provides the minimal interface definition.
*/
interface Config
{
/**
* Set an additional option value.
*
* @param string $key The option to set.
* @param string $value The value of the option.
*
* @return void
*/
public function setOption($key, $value);

/**
* Return the specified option.
*
* @param string $option The name of the option.
*
* @return mixed The option value or NULL if it is not defined.
*/
public function setOption($key, $value): void;
public function getOption($option);

/**
* Return the options provided by the configuration handlers.
*
* @return array An array of options.
*/
public function getOptions();

/**
* Shift an element from the argument list.
*
* @return mixed The shifted element.
*/
public function getOptions(): array;
public function shiftArgument();

/**
* Unshift an element to the argument list.
*
* @param string $element The element to unshift.
*
* @return void
*/
public function unshiftArgument($element);

/**
* Return the arguments provided by the configuration handlers.
*
* @return array An array of arguments.
*/
public function getArguments();

/**
* Set the selected component.
*
* @param Component $component The selected component.
*
* @return void
*/
public function setComponent(Component $component);

/**
* Return the selected component.
*
* @return Component The selected component.
*/
public function getComponent();

/**
* Set the path to the directory of the selected source component.
*
* @param string $path The path to the component directory.
*
* @return void
*/
public function setPath($path);

/**
* Get the path to the directory of the selected component (in case it was a
* source component).
*
* @return string The path to the component directory.
*/
public function getPath();
public function unshiftArgument($element): void;
public function getArguments(): array;
public function setComponent(Component $component): void;
public function getComponent(): ?Component;
public function setPath($path): void;
public function getPath(): ?string;
}
Loading
Loading