diff --git a/pkgs/dart_mcp_server/lib/src/utils/cli_utils.dart b/pkgs/dart_mcp_server/lib/src/utils/cli_utils.dart index 7ae8ae02..80926e5a 100644 --- a/pkgs/dart_mcp_server/lib/src/utils/cli_utils.dart +++ b/pkgs/dart_mcp_server/lib/src/utils/cli_utils.dart @@ -26,13 +26,16 @@ enum ProjectKind { unknown, } -/// Infers the [ProjectKind] of a given [Root]. +/// Infers the [ProjectKind] of a given project at [rootUri]. /// /// Currently, this is done by checking for the existence of a `pubspec.yaml` /// file and whether it contains a Flutter SDK dependency. -Future inferProjectKind(Root root, FileSystem fileSystem) async { +Future inferProjectKind( + String rootUri, + FileSystem fileSystem, +) async { final pubspecFile = fileSystem - .directory(Uri.parse(root.uri)) + .directory(Uri.parse(rootUri)) .childFile('pubspec.yaml'); if (!await pubspecFile.exists()) { return ProjectKind.unknown; @@ -73,7 +76,7 @@ Future inferProjectKind(Root root, FileSystem fileSystem) async { /// root's 'paths'. Future runCommandInRoots( CallToolRequest request, { - FutureOr Function(Root, FileSystem, Sdk) commandForRoot = + FutureOr Function(String, FileSystem, Sdk) commandForRoot = defaultCommandForRoot, List arguments = const [], required String commandDescription, @@ -138,7 +141,7 @@ Future runCommandInRoots( Future runCommandInRoot( CallToolRequest request, { Map? rootConfig, - FutureOr Function(Root, FileSystem, Sdk) commandForRoot = + FutureOr Function(String, FileSystem, Sdk) commandForRoot = defaultCommandForRoot, List arguments = const [], required String commandDescription, @@ -192,7 +195,7 @@ Future runCommandInRoot( final projectRoot = fileSystem.directory(rootUri); final commandWithPaths = [ - await commandForRoot(root, fileSystem, sdk), + await commandForRoot(rootUriString, fileSystem, sdk), ...arguments, ]; final paths = @@ -247,17 +250,17 @@ Future runCommandInRoot( /// /// Throws an [ArgumentError] if there is no pubspec. Future defaultCommandForRoot( - Root root, + String rootUri, FileSystem fileSystem, Sdk sdk, -) async => switch (await inferProjectKind(root, fileSystem)) { +) async => switch (await inferProjectKind(rootUri, fileSystem)) { ProjectKind.dart => sdk.dartExecutablePath, ProjectKind.flutter => sdk.flutterExecutablePath, ProjectKind.unknown => throw ArgumentError.value( - root.uri, - 'root.uri', - 'Unknown project kind at root ${root.uri}. All projects must have a ' + rootUri, + 'rootUri', + 'Unknown project kind at root $rootUri. All projects must have a ' 'pubspec.', ), }; diff --git a/pkgs/dart_mcp_server/test/tools/pub_test.dart b/pkgs/dart_mcp_server/test/tools/pub_test.dart index 693b99e6..19ba59b7 100644 --- a/pkgs/dart_mcp_server/test/tools/pub_test.dart +++ b/pkgs/dart_mcp_server/test/tools/pub_test.dart @@ -141,6 +141,33 @@ void main() { ]); }); + test('in a subdir of an a root', () async { + fileSystem.file(p.join(fakeAppPath, 'subdir', 'pubspec.yaml')) + ..createSync(recursive: true) + ..writeAsStringSync( + appKind == 'flutter' ? flutterPubspec : dartPubspec, + ); + final request = CallToolRequest( + name: dartPubTool.name, + arguments: { + ParameterNames.command: 'get', + ParameterNames.roots: [ + {ParameterNames.root: p.join(testRoot.uri, 'subdir')}, + ], + }, + ); + final result = await testHarness.callToolWithRetry(request); + + // Verify the command was sent to the process manager without error. + expect(result.isError, isNot(true)); + expect(testProcessManager.commandsRan, [ + equalsCommand(( + command: [endsWith(appKind), 'pub', 'get'], + workingDirectory: p.join(fakeAppPath, 'subdir'), + )), + ]); + }); + group('returns error', () { test('for missing command', () async { final request = CallToolRequest(name: dartPubTool.name);