Skip to content

Commit 5bc5a24

Browse files
committed
feat: scan all package.json files in non-workspace target repos
update-deps --in now recursively discovers all package.json files when the target repo has no pnpm-workspace.yaml, instead of only checking the root. This enables dependency detection in boilerplate repos like sandbox-templates and pgpm-boilerplates that have multiple independent template dirs with their own package.json files.
1 parent a06a5b0 commit 5bc5a24

2 files changed

Lines changed: 75 additions & 1 deletion

File tree

packages/makage/__tests__/updateDeps.test.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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();

packages/makage/src/commands/updateDeps.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,19 @@ async function getTargetPackageFiles(targetRoot: string): Promise<string[]> {
151151
// Not a monorepo — fall through
152152
}
153153

154-
return ['package.json'];
154+
// No workspace — scan for all package.json files recursively
155+
const allFiles = await glob('**/package.json', {
156+
cwd: targetRoot,
157+
absolute: false,
158+
ignore: ['**/node_modules/**']
159+
});
160+
161+
// Always include root package.json first if it exists
162+
if (allFiles.includes('package.json')) {
163+
return ['package.json', ...allFiles.filter(f => f !== 'package.json')];
164+
}
165+
166+
return allFiles.length > 0 ? allFiles : ['package.json'];
155167
}
156168

157169
export async function runUpdateDeps(args: string[]): Promise<UpdateDepsResult> {

0 commit comments

Comments
 (0)