-
Notifications
You must be signed in to change notification settings - Fork 0
feat: Global Component Discovery System #80
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
- Add GlobalComponentDiscovery service to scan ~/.composer/vendor - Auto-load globally installed Conduit components on boot - Support provider detection from both conduit.json and composer.json - Enable components like conduit-env-manager to work globally - No breaking changes - purely additive feature This allows users to install Conduit components globally via: composer global require vendor/conduit-component And have them automatically discovered and loaded by Conduit. Tested with jordanpartridge/conduit-env-manager which successfully loaded its commands when installed globally.
WalkthroughThese changes introduce a mechanism for discovering and loading globally installed Composer components into the application during the boot process. A new service, Changes
Sequence Diagram(s)sequenceDiagram
participant AppServiceProvider
participant GlobalComponentDiscovery
participant Application
AppServiceProvider->>GlobalComponentDiscovery: discover()
GlobalComponentDiscovery-->>AppServiceProvider: Collection of components
loop For each component
AppServiceProvider->>GlobalComponentDiscovery: loadComponent(component)
GlobalComponentDiscovery->>Application: Register service providers
end
AppServiceProvider->>AppServiceProvider: Log results (if verbose)
Estimated code review effortπ― 3 (Moderate) | β±οΈ ~15 minutes Poem
Note β‘οΈ Unit Test Generation is now available in beta!Learn more here, or try it out under "Finishing Touches" below. π Recent review detailsConfiguration used: CodeRabbit UI π Files selected for processing (2)
π§ Files skipped from review as they are similar to previous changes (2)
β¨ Finishing Touches
π§ͺ Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. πͺ§ TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
- Fix concatenation spacing - Remove unused imports - Consistent code style
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
π§Ή Nitpick comments (1)
app/Providers/AppServiceProvider.php (1)
271-291: Improve logging and console output using Laravel conventions.The error handling approach is good, but consider these improvements:
- Use Laravel's console facilities: Replace manual
$_SERVER['argv']checking andechowith proper Laravel console/logging methods.- Improve readability: Consider early returns to reduce nesting.
Apply this diff to use Laravel conventions:
private function loadGlobalComponents(): void { + if (!$this->app->runningInConsole()) { + return; + } + + $isVerbose = in_array('-v', $_SERVER['argv'] ?? []) || in_array('--verbose', $_SERVER['argv'] ?? []); + try { $discovery = $this->app->make(\App\Services\GlobalComponentDiscovery::class); $components = $discovery->discover(); foreach ($components as $component) { $discovery->loadComponent($component); } - // Log discovery info if in verbose mode - if ($this->app->runningInConsole() && in_array('-v', $_SERVER['argv'] ?? [])) { - echo "Discovered {$components->count()} global components\n"; + if ($isVerbose && $components->count() > 0) { + $this->app->make('log')->info("Discovered {$components->count()} global components"); } } catch (\Exception $e) { - // Fail gracefully - global components are optional - if ($this->app->runningInConsole() && in_array('-v', $_SERVER['argv'] ?? [])) { - echo 'Failed to load global components: ' . $e->getMessage() . "\n"; + if ($isVerbose) { + $this->app->make('log')->warning('Failed to load global components: ' . $e->getMessage()); } } }
π Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
π Files selected for processing (2)
app/Providers/AppServiceProvider.php(3 hunks)app/Services/GlobalComponentDiscovery.php(1 hunks)
π§° Additional context used
π Path-based instructions (1)
app/Services/**/*.php
π CodeRabbit Inference Engine (CLAUDE.md)
Service layer should use trait composition (e.g., ManagesBranches, ManagesReviewers)
Files:
app/Services/GlobalComponentDiscovery.php
π§ Learnings (3)
π Common learnings
Learnt from: CR
PR: conduit-ui/conduit#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-27T19:26:35.931Z
Learning: Components in conduit-components/ should support standalone architecture (work independently or within Conduit)
Learnt from: CR
PR: conduit-ui/conduit#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-27T19:26:35.931Z
Learning: Applies to conduit-components/**/*.php : Components in conduit-components/ should register Laravel service providers
Learnt from: CR
PR: conduit-ui/conduit#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-27T19:26:35.931Z
Learning: Applies to tests/Integration/**/*.php : Integration tests should cover component installation and interaction
app/Services/GlobalComponentDiscovery.php (3)
Learnt from: CR
PR: conduit-ui/conduit#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-27T19:26:35.931Z
Learning: Applies to conduit-components/**/*.php : Components in conduit-components/ should register Laravel service providers
Learnt from: CR
PR: conduit-ui/conduit#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-27T19:26:35.931Z
Learning: Applies to conduit-components/**/GitHub.php : Components in conduit-components/ should use AbstractGitHubComponent as the base class for GitHub integrations
Learnt from: CR
PR: conduit-ui/conduit#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-27T19:26:35.931Z
Learning: Applies to app/Services/**/*.php : Service layer should use trait composition (e.g., ManagesBranches, ManagesReviewers)
app/Providers/AppServiceProvider.php (2)
Learnt from: CR
PR: conduit-ui/conduit#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-27T19:26:35.931Z
Learning: Applies to conduit-components/**/*.php : Components in conduit-components/ should register Laravel service providers
Learnt from: CR
PR: conduit-ui/conduit#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-27T19:26:35.931Z
Learning: Applies to tests/Integration/**/*.php : Integration tests should cover component installation and interaction
𧬠Code Graph Analysis (1)
app/Services/GlobalComponentDiscovery.php (1)
app/Providers/AppServiceProvider.php (1)
register(122-175)
π Additional comments (2)
app/Providers/AppServiceProvider.php (2)
170-171: LGTM!The singleton registration follows the established pattern and is correctly placed with other service registrations.
72-73: LGTM!Good placement of global component loading after optional components in the boot sequence.
| public function __construct() | ||
| { | ||
| $home = getenv('HOME'); | ||
| $this->searchPaths = [ | ||
| $home . '/.composer/vendor/*/*/conduit.json', | ||
| $home . '/.config/composer/vendor/*/*/conduit.json', | ||
| ]; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
π οΈ Refactor suggestion
Improve cross-platform compatibility and follow coding guidelines.
Several issues need attention:
- Cross-platform compatibility:
getenv('HOME')doesn't work on Windows systems. Use Laravel's helper or more robust detection. - Coding guidelines violation: Services in
app/Services/**/*.phpshould use trait composition (e.g., ManagesBranches, ManagesReviewers) per the coding guidelines. - Missing validation: No validation if HOME environment variable exists.
Apply this diff to improve cross-platform support:
public function __construct()
{
- $home = getenv('HOME');
+ $home = $_SERVER['HOME'] ?? $_SERVER['USERPROFILE'] ?? sys_get_temp_dir();
$this->searchPaths = [
$home . '/.composer/vendor/*/*/conduit.json',
$home . '/.config/composer/vendor/*/*/conduit.json',
];
}Consider refactoring to use trait composition to align with coding guidelines for the Services layer.
π€ Prompt for AI Agents
In app/Services/GlobalComponentDiscovery.php around lines 11 to 18, replace
getenv('HOME') with Laravel's helper like env('HOME') or use a cross-platform
method such as base_path() or config paths to ensure compatibility on Windows.
Add validation to check if the resolved home path exists before using it.
Refactor the class to use trait composition for functionalities like branch and
reviewer management according to coding guidelines for Services in app/Services,
moving relevant methods into traits and composing them in this class.
| public function discover(): Collection | ||
| { | ||
| $components = collect(); | ||
|
|
||
| foreach ($this->searchPaths as $pattern) { | ||
| $manifests = glob($pattern); | ||
|
|
||
| if (!$manifests) { | ||
| continue; | ||
| } | ||
|
|
||
| foreach ($manifests as $manifestPath) { | ||
| try { | ||
| $manifest = json_decode(file_get_contents($manifestPath), true); | ||
|
|
||
| if (!$manifest || !isset($manifest['name'])) { | ||
| continue; | ||
| } | ||
|
|
||
| // Extract package info from path | ||
| preg_match('|vendor/([^/]+)/([^/]+)/conduit\.json$|', $manifestPath, $matches); | ||
| $vendor = $matches[1] ?? 'unknown'; | ||
| $package = $matches[2] ?? 'unknown'; | ||
|
|
||
| // Get providers from manifest or try to detect from composer.json | ||
| $providers = $manifest['providers'] ?? []; | ||
|
|
||
| if (empty($providers)) { | ||
| // Try to get from composer.json Laravel extra section | ||
| $composerPath = dirname($manifestPath) . '/composer.json'; | ||
| if (file_exists($composerPath)) { | ||
| $composer = json_decode(file_get_contents($composerPath), true); | ||
| $providers = $composer['extra']['laravel']['providers'] ?? []; | ||
| } | ||
| } | ||
|
|
||
| $components->push([ | ||
| 'name' => $manifest['name'], | ||
| 'package' => "$vendor/$package", | ||
| 'description' => $manifest['description'] ?? '', | ||
| 'version' => $manifest['version'] ?? 'unknown', | ||
| 'path' => dirname($manifestPath), | ||
| 'global' => true, | ||
| 'providers' => $providers, | ||
| ]); | ||
| } catch (\Exception $e) { | ||
| continue; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return $components; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
π οΈ Refactor suggestion
Address security vulnerabilities and improve error handling.
Several critical issues need attention:
- Security vulnerability: Direct
file_get_contents()without size limits or validation could be exploited with large files or malicious content. - Fragile path parsing: The regex assumes a specific directory structure that might not always hold.
- Silent failures: Exception handling that just continues could hide legitimate configuration errors.
- Missing JSON validation: No validation that decoded JSON has expected structure.
Apply this diff to improve security and robustness:
foreach ($manifests as $manifestPath) {
try {
+ // Validate file size (max 1MB for manifest files)
+ if (filesize($manifestPath) > 1024 * 1024) {
+ continue;
+ }
+
+ // Validate file path to prevent directory traversal
+ if (!realpath($manifestPath) || strpos(realpath($manifestPath), realpath(dirname($manifestPath))) !== 0) {
+ continue;
+ }
+
$manifest = json_decode(file_get_contents($manifestPath), true);
- if (!$manifest || !isset($manifest['name'])) {
+ if (!$manifest || json_last_error() !== JSON_ERROR_NONE || !isset($manifest['name'])) {
continue;
}Consider adding logging for skipped manifests in verbose mode to aid debugging.
π€ Prompt for AI Agents
In app/Services/GlobalComponentDiscovery.php between lines 20 and 72, improve
security and robustness by limiting the size of files read with
file_get_contents to prevent large file exploits, validate file contents before
decoding JSON, and ensure the decoded JSON matches the expected structure before
processing. Replace the fragile regex path parsing with a more reliable method
that does not assume a fixed directory structure. Enhance error handling by
logging exceptions or skipped manifests instead of silently continuing,
optionally controlled by a verbose mode flag to aid debugging. This will prevent
silent failures and improve maintainability.
| public function loadComponent(array $component): void | ||
| { | ||
| $componentPath = $component['path']; | ||
|
|
||
| // Load component's autoloader if it exists | ||
| $autoloadPath = $componentPath . '/vendor/autoload.php'; | ||
| if (file_exists($autoloadPath)) { | ||
| require_once $autoloadPath; | ||
| } | ||
|
|
||
| // Register service providers | ||
| foreach ($component['providers'] as $provider) { | ||
| if (class_exists($provider)) { | ||
| app()->register($provider); | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical security vulnerabilities in component loading.
This method has several serious security issues:
- Code execution vulnerability:
require_onceon user-controlled paths could execute malicious code. - Path traversal: No validation prevents loading autoloaders from outside intended directories.
- Missing validation: No checks on provider class names or autoloader paths.
- Poor dependency injection: Using
app()helper instead of proper DI.
Apply this diff to improve security:
public function loadComponent(array $component): void
{
$componentPath = $component['path'];
+
+ // Validate component path to prevent traversal attacks
+ $realComponentPath = realpath($componentPath);
+ if (!$realComponentPath || !is_dir($realComponentPath)) {
+ return;
+ }
// Load component's autoloader if it exists
- $autoloadPath = $componentPath . '/vendor/autoload.php';
- if (file_exists($autoloadPath)) {
- require_once $autoloadPath;
+ $autoloadPath = $realComponentPath . '/vendor/autoload.php';
+ $realAutoloadPath = realpath($autoloadPath);
+
+ if ($realAutoloadPath &&
+ file_exists($realAutoloadPath) &&
+ strpos($realAutoloadPath, $realComponentPath) === 0) {
+ require_once $realAutoloadPath;
}
// Register service providers
foreach ($component['providers'] as $provider) {
- if (class_exists($provider)) {
+ // Validate provider class name format
+ if (is_string($provider) &&
+ preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff\\\\]*$/', $provider) &&
+ class_exists($provider)) {
app()->register($provider);
}
}
}Consider injecting the Application instance instead of using the app() helper for better testability.
π Committable suggestion
βΌοΈ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| public function loadComponent(array $component): void | |
| { | |
| $componentPath = $component['path']; | |
| // Load component's autoloader if it exists | |
| $autoloadPath = $componentPath . '/vendor/autoload.php'; | |
| if (file_exists($autoloadPath)) { | |
| require_once $autoloadPath; | |
| } | |
| // Register service providers | |
| foreach ($component['providers'] as $provider) { | |
| if (class_exists($provider)) { | |
| app()->register($provider); | |
| } | |
| } | |
| } | |
| public function loadComponent(array $component): void | |
| { | |
| $componentPath = $component['path']; | |
| // Validate component path to prevent traversal attacks | |
| $realComponentPath = realpath($componentPath); | |
| if (!$realComponentPath || !is_dir($realComponentPath)) { | |
| return; | |
| } | |
| // Load component's autoloader if it exists | |
| $autoloadPath = $realComponentPath . '/vendor/autoload.php'; | |
| $realAutoloadPath = realpath($autoloadPath); | |
| if ($realAutoloadPath && | |
| file_exists($realAutoloadPath) && | |
| strpos($realAutoloadPath, $realComponentPath) === 0) { | |
| require_once $realAutoloadPath; | |
| } | |
| // Register service providers | |
| foreach ($component['providers'] as $provider) { | |
| // Validate provider class name format | |
| if (is_string($provider) && | |
| preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff\\\\]*$/', $provider) && | |
| class_exists($provider)) { | |
| app()->register($provider); | |
| } | |
| } | |
| } |
π€ Prompt for AI Agents
In app/Services/GlobalComponentDiscovery.php around lines 74 to 90, the
loadComponent method has security risks due to unvalidated require_once on
user-controlled paths, lack of path validation, and direct use of the app()
helper. To fix this, validate and sanitize the component path to prevent path
traversal and ensure it is within allowed directories before requiring the
autoloader. Validate provider class names to confirm they exist and are safe to
load. Refactor the method to accept the Application instance via dependency
injection instead of calling the app() helper directly for better security and
testability.
- Use Laravel logging conventions instead of echo statements - Add cross-platform home directory detection with Windows support - Implement security validation for component loading: - Validate paths to prevent directory traversal - Sanitize autoloader paths - Validate provider class names before registration
π Global Component Discovery
This PR introduces a global component discovery system that enables Conduit to automatically find and load components installed via Composer's global directory.
π― Problem Solved
Currently, components must be installed locally into Conduit's vendor directory, which:
β¨ Solution
This PR adds automatic discovery of globally installed Conduit components, allowing users to:
composer global require jordanpartridge/conduit-spotify composer global require someone/conduit-docker # Components are automatically available in Conduit\!π§ Implementation Details
GlobalComponentDiscovery Service
~/.composer/vendor/*/*/conduit.json~/.config/composer/vendor/*/*/conduit.jsonIntegration
β Testing
Tested with
jordanpartridge/conduit-env-manager:composer global require jordanpartridge/conduit-env-managerphp conduit listenv:init,env:get,env:set,env:backupπ Future Enhancements
This lays the foundation for:
conduit components install spotify --globalflagπ Impact
π§ͺ How to Test
composer global require jordanpartridge/conduit-env-managerphp conduit list | grep env:This is the first step toward the modular component architecture discussed in the VISION.md!
Summary by CodeRabbit
New Features
Bug Fixes