Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 14 additions & 11 deletions pkgs/dart_mcp_server/lib/src/utils/cli_utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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<ProjectKind> inferProjectKind(Root root, FileSystem fileSystem) async {
Future<ProjectKind> 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;
Expand Down Expand Up @@ -73,7 +76,7 @@ Future<ProjectKind> inferProjectKind(Root root, FileSystem fileSystem) async {
/// root's 'paths'.
Future<CallToolResult> runCommandInRoots(
CallToolRequest request, {
FutureOr<String> Function(Root, FileSystem, Sdk) commandForRoot =
FutureOr<String> Function(String, FileSystem, Sdk) commandForRoot =
defaultCommandForRoot,
List<String> arguments = const [],
required String commandDescription,
Expand Down Expand Up @@ -138,7 +141,7 @@ Future<CallToolResult> runCommandInRoots(
Future<CallToolResult> runCommandInRoot(
CallToolRequest request, {
Map<String, Object?>? rootConfig,
FutureOr<String> Function(Root, FileSystem, Sdk) commandForRoot =
FutureOr<String> Function(String, FileSystem, Sdk) commandForRoot =
defaultCommandForRoot,
List<String> arguments = const [],
required String commandDescription,
Expand Down Expand Up @@ -192,7 +195,7 @@ Future<CallToolResult> runCommandInRoot(
final projectRoot = fileSystem.directory(rootUri);

final commandWithPaths = <String>[
await commandForRoot(root, fileSystem, sdk),
await commandForRoot(rootUriString, fileSystem, sdk),
...arguments,
];
final paths =
Expand Down Expand Up @@ -247,17 +250,17 @@ Future<CallToolResult> runCommandInRoot(
///
/// Throws an [ArgumentError] if there is no pubspec.
Future<String> 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.',
),
};
Expand Down
27 changes: 27 additions & 0 deletions pkgs/dart_mcp_server/test/tools/pub_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down