@@ -57,8 +57,9 @@ private static function tokenizeCommand(string $input): array
5757 }
5858
5959 /**
60- * Discover providers by scanning vendor composer.json
61- * No reliance on composer/composer installed.json or installed.php.
60+ * Discover providers by scanning project and vendor package composer.json files.
61+ * This works both when developing this package standalone and when it's used
62+ * inside a consumer project that requires other packages.
6263 */
6364 private function discoverExternal (): void
6465 {
@@ -67,39 +68,63 @@ private function discoverExternal(): void
6768 return ;
6869 }
6970
70- // Scan all package composer.json files
71- $ pattern = $ vendorPath . DIRECTORY_SEPARATOR . 'composer.json ' ;
72- $ composerFiles = glob ($ pattern ) ?: [];
71+ $ composerFiles = [];
72+
73+ // 1) Include root project's composer.json (may declare providers)
74+ $ projectRoot = \dirname ($ vendorPath );
75+ $ rootComposer = $ projectRoot . DIRECTORY_SEPARATOR . 'composer.json ' ;
76+ if (is_file ($ rootComposer )) {
77+ $ composerFiles [] = $ rootComposer ;
78+ }
79+
80+ // 2) Include all vendor package composer.json files: vendor/*/*/composer.json
81+ $ packagesPattern = $ vendorPath . DIRECTORY_SEPARATOR . '* ' . DIRECTORY_SEPARATOR . '* ' . DIRECTORY_SEPARATOR . 'composer.json ' ;
82+ $ found = glob ($ packagesPattern ) ?: [];
83+ if (!empty ($ found )) {
84+ $ composerFiles = array_merge ($ composerFiles , $ found );
85+ }
86+
87+ // De-duplicate paths just in case
88+ $ composerFiles = array_values (array_unique ($ composerFiles ));
7389
7490 foreach ($ composerFiles as $ composerJson ) {
75- $ json = @ File::get ($ composerJson );
91+ $ json = File::get ($ composerJson );
7692 if ($ json === false ) {
7793 continue ;
7894 }
7995 $ meta = json_decode ($ json , true );
8096 if (!is_array ($ meta )) {
8197 continue ;
8298 }
99+
83100 $ extra = $ meta ['extra ' ]['tamedevelopers ' ] ?? null ;
84101 if (!$ extra ) {
85102 continue ;
86103 }
104+
87105 $ providers = $ extra ['providers ' ] ?? [];
88106 foreach ((array ) $ providers as $ fqcn ) {
89- if (!is_string ($ fqcn ) || !class_exists ($ fqcn )) {
107+ if (!is_string ($ fqcn )) {
108+ continue ;
109+ }
110+
111+ // Rely on Composer autoload being present (vendor/autoload.php loaded by entry script)
112+ if (!class_exists ($ fqcn )) {
90113 continue ;
91114 }
115+
92116 if (isset (self ::$ registeredProviders [$ fqcn ])) {
93117 continue ; // already registered in this process
94118 }
119+
95120 try {
96121 $ provider = new $ fqcn ();
97122 if (method_exists ($ provider , 'register ' )) {
98123 $ provider ->register ($ this );
99124 self ::$ registeredProviders [$ fqcn ] = true ;
100125 }
101126 } catch (\Throwable $ e ) {
102- // ignore provider instantiation/registration failures
127+ // ignore provider instantiation/registration failures, but do not abort discovery
103128 }
104129 }
105130 }
0 commit comments