@@ -155,6 +155,68 @@ describe('runUpdateDeps', () => {
155155 expect ( result . has_dep_changes ) . toBe ( false ) ;
156156 } ) ;
157157
158+ it ( 'should scan all package.json files in non-workspace target repos' , async ( ) => {
159+ // Source workspace has one package
160+ mockedFs . readFile . mockImplementation ( async ( filePath : any ) => {
161+ const p = filePath . toString ( ) ;
162+ if ( p . endsWith ( 'pnpm-workspace.yaml' ) && p . includes ( 'source' ) ) {
163+ return WORKSPACE_YAML ;
164+ }
165+ if ( p . endsWith ( 'pnpm-workspace.yaml' ) && p . includes ( 'boilerplate' ) ) {
166+ throw new Error ( 'ENOENT' ) ;
167+ }
168+ // Source package
169+ if ( p . includes ( 'source' ) && p . includes ( 'packages/foo/package.json' ) ) {
170+ return makePkg ( '@scope/foo' , '3.0.0' ) ;
171+ }
172+ // Boilerplate target — multiple independent package.json files, no workspace
173+ if ( p . includes ( 'boilerplate' ) && p . includes ( 'graphql/codegen/package.json' ) ) {
174+ return makePkg ( 'codegen-template' , '0.0.1' , { '@scope/foo' : '^2.0.0' } ) ;
175+ }
176+ if ( p . includes ( 'boilerplate' ) && p . includes ( 'nextjs/app/package.json' ) ) {
177+ return makePkg ( 'nextjs-template' , '0.0.1' , { } , { '@scope/foo' : '^3.0.0' } ) ;
178+ }
179+ if ( p . includes ( 'boilerplate' ) && p . endsWith ( 'package.json' ) && ! p . includes ( 'graphql' ) && ! p . includes ( 'nextjs' ) ) {
180+ return makePkg ( 'boilerplate-root' , '1.0.0' ) ;
181+ }
182+ throw new Error ( `ENOENT: ${ p } ` ) ;
183+ } ) ;
184+
185+ mockedGlob . mockImplementation ( async ( patterns : any , opts : any ) => {
186+ const cwd = opts ?. cwd || '' ;
187+ if ( cwd . includes ( 'source' ) ) {
188+ return [ 'packages/foo/package.json' ] ;
189+ }
190+ // Non-workspace target — glob returns all nested package.json files
191+ if ( cwd . includes ( 'boilerplate' ) ) {
192+ return [ 'package.json' , 'graphql/codegen/package.json' , 'nextjs/app/package.json' ] ;
193+ }
194+ return [ ] ;
195+ } ) ;
196+
197+ const result = await runUpdateDeps ( [ '--from' , '/source' , '--in' , '/boilerplate' ] ) ;
198+
199+ // Should find the source package
200+ expect ( result . sourcePackages ) . toHaveLength ( 1 ) ;
201+ expect ( result . sourcePackages [ 0 ] . name ) . toBe ( '@scope/foo' ) ;
202+
203+ // Should match deps in both nested templates (not root — root has no matching deps)
204+ expect ( result . matchedPackages ) . toHaveLength ( 2 ) ;
205+
206+ // codegen-template has @scope/foo ^2.0.0 -> 3.0.0 (outdated)
207+ const codegenMatch = result . matchedPackages . find ( p => p . consumer === 'codegen-template' ) ;
208+ expect ( codegenMatch ?. outdated ) . toBe ( true ) ;
209+ expect ( codegenMatch ?. depType ) . toBe ( 'dependencies' ) ;
210+
211+ // nextjs-template has @scope/foo ^3.0.0 -> 3.0.0 (up to date)
212+ const nextjsMatch = result . matchedPackages . find ( p => p . consumer === 'nextjs-template' ) ;
213+ expect ( nextjsMatch ?. outdated ) . toBe ( false ) ;
214+ expect ( nextjsMatch ?. depType ) . toBe ( 'devDependencies' ) ;
215+
216+ expect ( result . has_dep_changes ) . toBe ( true ) ;
217+ expect ( result . outdatedPackages ) . toHaveLength ( 1 ) ;
218+ } ) ;
219+
158220 it ( 'should handle workspace: protocol as not outdated' , async ( ) => {
159221 mockedFs . readFile . mockImplementation ( async ( filePath : any ) => {
160222 const p = filePath . toString ( ) ;
0 commit comments