Invoke dart/flutter in a more robust way (#143)
Closes https://github.com/dart-lang/ai/issues/33
Adds --dart-sdk and --flutter-sdk arguments to the CLI, falling back on DART_SDK and FLUTTER_SDK environment variables, and finally `Platform.resolvedExecutable`.
The flutter SDK will also be looked up relative to the Dart SDK, if a location is not given by command line arg or environment.
diff --git a/pkgs/dart_mcp_server/bin/main.dart b/pkgs/dart_mcp_server/bin/main.dart
index 6050251..351dc72 100644
--- a/pkgs/dart_mcp_server/bin/main.dart
+++ b/pkgs/dart_mcp_server/bin/main.dart
@@ -20,9 +20,14 @@
}
DartMCPServer? server;
- await runZonedGuarded(
- () async {
- server = await DartMCPServer.connect(
+ final dartSdkPath =
+ parsedArgs.option(dartSdkOption) ?? io.Platform.environment['DART_SDK'];
+ final flutterSdkPath =
+ parsedArgs.option(flutterSdkOption) ??
+ io.Platform.environment['FLUTTER_SDK'];
+ runZonedGuarded(
+ () {
+ server = DartMCPServer(
StreamChannel.withCloseGuarantee(io.stdin, io.stdout)
.transform(StreamChannelTransformer.fromCodec(utf8))
.transformStream(const LineSplitter())
@@ -34,6 +39,7 @@
),
),
forceRootsFallback: parsedArgs.flag(forceRootsFallback),
+ sdk: Sdk.find(dartSdkPath: dartSdkPath, flutterSdkPath: flutterSdkPath),
);
},
(e, s) {
@@ -65,6 +71,19 @@
final argParser =
ArgParser(allowTrailingOptions: false)
+ ..addOption(
+ dartSdkOption,
+ help:
+ 'The path to the root of the desired Dart SDK. Defaults to the '
+ 'DART_SDK environment variable.',
+ )
+ ..addOption(
+ flutterSdkOption,
+ help:
+ 'The path to the root of the desired Flutter SDK. Defaults to '
+ 'the FLUTTER_SDK environment variable, then searching up from the '
+ 'Dart SDK.',
+ )
..addFlag(
forceRootsFallback,
negatable: true,
@@ -77,5 +96,7 @@
)
..addFlag(help, abbr: 'h', help: 'Show usage text');
+const dartSdkOption = 'dart-sdk';
+const flutterSdkOption = 'flutter-sdk';
const forceRootsFallback = 'force-roots-fallback';
const help = 'help';
diff --git a/pkgs/dart_mcp_server/lib/dart_mcp_server.dart b/pkgs/dart_mcp_server/lib/dart_mcp_server.dart
index c1e1ead..badafe8 100644
--- a/pkgs/dart_mcp_server/lib/dart_mcp_server.dart
+++ b/pkgs/dart_mcp_server/lib/dart_mcp_server.dart
@@ -3,3 +3,4 @@
// BSD-style license that can be found in the LICENSE file.
export 'src/server.dart';
+export 'src/utils/sdk.dart' show Sdk;
diff --git a/pkgs/dart_mcp_server/lib/src/mixins/analyzer.dart b/pkgs/dart_mcp_server/lib/src/mixins/analyzer.dart
index da870e7..10ae558 100644
--- a/pkgs/dart_mcp_server/lib/src/mixins/analyzer.dart
+++ b/pkgs/dart_mcp_server/lib/src/mixins/analyzer.dart
@@ -14,13 +14,15 @@
import '../lsp/wire_format.dart';
import '../utils/constants.dart';
+import '../utils/sdk.dart';
/// Mix this in to any MCPServer to add support for analyzing Dart projects.
///
/// The MCPServer must already have the [ToolsSupport] and [LoggingSupport]
/// mixins applied.
base mixin DartAnalyzerSupport
- on ToolsSupport, LoggingSupport, RootsTrackingSupport {
+ on ToolsSupport, LoggingSupport, RootsTrackingSupport
+ implements SdkSupport {
/// The LSP server connection for the analysis server.
late final Peer _lspConnection;
@@ -52,9 +54,8 @@
if (!supportsRoots)
'Project analysis requires the "roots" capability which is not '
'supported. Analysis tools have been disabled.',
- if (Platform.environment['DART_SDK'] == null)
- 'Project analysis requires a "DART_SDK" environment variable to be set '
- '(this should be the path to the root of the dart SDK). Analysis '
+ if (sdk.dartSdkPath == null)
+ 'Project analysis requires a Dart SDK but none was given. Analysis '
'tools have been disabled.',
];
@@ -90,7 +91,7 @@
///
/// On failure, returns a reason for the failure.
Future<String?> _initializeAnalyzerLspServer() async {
- _lspServer = await Process.start('dart', [
+ _lspServer = await Process.start(sdk.dartExecutablePath, [
'language-server',
// Required even though it is documented as the default.
// https://github.com/dart-lang/sdk/issues/60574
diff --git a/pkgs/dart_mcp_server/lib/src/mixins/dash_cli.dart b/pkgs/dart_mcp_server/lib/src/mixins/dash_cli.dart
index 075540b..605ba1f 100644
--- a/pkgs/dart_mcp_server/lib/src/mixins/dash_cli.dart
+++ b/pkgs/dart_mcp_server/lib/src/mixins/dash_cli.dart
@@ -11,6 +11,7 @@
import '../utils/constants.dart';
import '../utils/file_system.dart';
import '../utils/process_manager.dart';
+import '../utils/sdk.dart';
/// Mix this in to any MCPServer to add support for running Dart or Flutter CLI
/// commands like `dart fix`, `dart format`, and `flutter test`.
@@ -18,14 +19,14 @@
/// The MCPServer must already have the [ToolsSupport] and [LoggingSupport]
/// mixins applied.
base mixin DashCliSupport on ToolsSupport, LoggingSupport, RootsTrackingSupport
- implements ProcessManagerSupport, FileSystemSupport {
+ implements ProcessManagerSupport, FileSystemSupport, SdkSupport {
@override
FutureOr<InitializeResult> initialize(InitializeRequest request) {
try {
return super.initialize(request);
} finally {
- // Can't call this until after `super.initialize`.
- if (supportsRoots) {
+ // Can't call `supportsRoots` until after `super.initialize`.
+ if (supportsRoots && sdk.dartSdkPath != null) {
registerTool(dartFixTool, _runDartFixTool);
registerTool(dartFormatTool, _runDartFormatTool);
registerTool(runTestsTool, _runTests);
@@ -38,12 +39,13 @@
Future<CallToolResult> _runDartFixTool(CallToolRequest request) async {
return runCommandInRoots(
request,
- commandForRoot: (_, _) => 'dart',
+ commandForRoot: (_, _, sdk) => sdk.dartExecutablePath,
arguments: ['fix', '--apply'],
commandDescription: 'dart fix',
processManager: processManager,
knownRoots: await roots,
fileSystem: fileSystem,
+ sdk: sdk,
);
}
@@ -51,13 +53,14 @@
Future<CallToolResult> _runDartFormatTool(CallToolRequest request) async {
return runCommandInRoots(
request,
- commandForRoot: (_, _) => 'dart',
+ commandForRoot: (_, _, sdk) => sdk.dartExecutablePath,
arguments: ['format'],
commandDescription: 'dart format',
processManager: processManager,
defaultPaths: ['.'],
knownRoots: await roots,
fileSystem: fileSystem,
+ sdk: sdk,
);
}
@@ -70,6 +73,7 @@
processManager: processManager,
knownRoots: await roots,
fileSystem: fileSystem,
+ sdk: sdk,
);
}
@@ -119,11 +123,19 @@
return runCommandInRoot(
request,
arguments: commandArgs,
- commandForRoot: (_, _) => projectType!,
+ commandForRoot:
+ (_, _, sdk) =>
+ switch (projectType) {
+ 'dart' => sdk.dartExecutablePath,
+ 'flutter' => sdk.flutterExecutablePath,
+ _ => StateError('Unknown project type: $projectType'),
+ }
+ as String,
commandDescription: '$projectType create',
fileSystem: fileSystem,
processManager: processManager,
knownRoots: await roots,
+ sdk: sdk,
);
}
diff --git a/pkgs/dart_mcp_server/lib/src/mixins/pub.dart b/pkgs/dart_mcp_server/lib/src/mixins/pub.dart
index eaca4bc..ab48500 100644
--- a/pkgs/dart_mcp_server/lib/src/mixins/pub.dart
+++ b/pkgs/dart_mcp_server/lib/src/mixins/pub.dart
@@ -10,6 +10,7 @@
import '../utils/constants.dart';
import '../utils/file_system.dart';
import '../utils/process_manager.dart';
+import '../utils/sdk.dart';
/// Mix this in to any MCPServer to add support for running Pub commands like
/// like `pub add` and `pub get`.
@@ -19,7 +20,7 @@
/// The MCPServer must already have the [ToolsSupport] and [LoggingSupport]
/// mixins applied.
base mixin PubSupport on ToolsSupport, LoggingSupport, RootsTrackingSupport
- implements ProcessManagerSupport, FileSystemSupport {
+ implements ProcessManagerSupport, FileSystemSupport, SdkSupport {
@override
FutureOr<InitializeResult> initialize(InitializeRequest request) {
try {
@@ -79,6 +80,7 @@
processManager: processManager,
knownRoots: await roots,
fileSystem: fileSystem,
+ sdk: sdk,
);
}
diff --git a/pkgs/dart_mcp_server/lib/src/server.dart b/pkgs/dart_mcp_server/lib/src/server.dart
index 072052e..d9d573d 100644
--- a/pkgs/dart_mcp_server/lib/src/server.dart
+++ b/pkgs/dart_mcp_server/lib/src/server.dart
@@ -2,14 +2,11 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
-import 'dart:async';
-
import 'package:dart_mcp/server.dart';
import 'package:file/file.dart';
import 'package:file/local.dart';
import 'package:meta/meta.dart';
import 'package:process/process.dart';
-import 'package:stream_channel/stream_channel.dart';
import 'mixins/analyzer.dart';
import 'mixins/dash_cli.dart';
@@ -19,6 +16,7 @@
import 'mixins/roots_fallback_support.dart';
import 'utils/file_system.dart';
import 'utils/process_manager.dart';
+import 'utils/sdk.dart';
/// An MCP server for Dart and Flutter tooling.
final class DartMCPServer extends MCPServer
@@ -33,9 +31,10 @@
PubSupport,
PubDevSupport,
DartToolingDaemonSupport
- implements ProcessManagerSupport, FileSystemSupport {
+ implements ProcessManagerSupport, FileSystemSupport, SdkSupport {
DartMCPServer(
super.channel, {
+ required this.sdk,
@visibleForTesting this.processManager = const LocalProcessManager(),
@visibleForTesting this.fileSystem = const LocalFileSystem(),
this.forceRootsFallback = false,
@@ -49,13 +48,6 @@
'their development tools and running applications.',
);
- static Future<DartMCPServer> connect(
- StreamChannel<String> mcpChannel, {
- bool forceRootsFallback = false,
- }) async {
- return DartMCPServer(mcpChannel, forceRootsFallback: forceRootsFallback);
- }
-
@override
final LocalProcessManager processManager;
@@ -64,4 +56,7 @@
@override
final bool forceRootsFallback;
+
+ @override
+ final Sdk sdk;
}
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 4fce8b7..9310546 100644
--- a/pkgs/dart_mcp_server/lib/src/utils/cli_utils.dart
+++ b/pkgs/dart_mcp_server/lib/src/utils/cli_utils.dart
@@ -11,6 +11,7 @@
import 'package:yaml/yaml.dart';
import 'constants.dart';
+import 'sdk.dart';
/// The supported kinds of projects.
enum ProjectKind {
@@ -71,7 +72,7 @@
/// root's 'paths'.
Future<CallToolResult> runCommandInRoots(
CallToolRequest request, {
- FutureOr<String> Function(Root, FileSystem) commandForRoot =
+ FutureOr<String> Function(Root, FileSystem, Sdk) commandForRoot =
defaultCommandForRoot,
List<String> arguments = const [],
required String commandDescription,
@@ -79,6 +80,7 @@
required ProcessManager processManager,
required List<Root> knownRoots,
List<String> defaultPaths = const <String>[],
+ required Sdk sdk,
}) async {
var rootConfigs =
(request.arguments?[ParameterNames.roots] as List?)
@@ -103,6 +105,7 @@
processManager: processManager,
knownRoots: knownRoots,
defaultPaths: defaultPaths,
+ sdk: sdk,
);
if (result.isError == true) return result;
outputs.addAll(result.content);
@@ -134,7 +137,7 @@
Future<CallToolResult> runCommandInRoot(
CallToolRequest request, {
Map<String, Object?>? rootConfig,
- FutureOr<String> Function(Root, FileSystem) commandForRoot =
+ FutureOr<String> Function(Root, FileSystem, Sdk) commandForRoot =
defaultCommandForRoot,
List<String> arguments = const [],
required String commandDescription,
@@ -142,6 +145,7 @@
required ProcessManager processManager,
required List<Root> knownRoots,
List<String> defaultPaths = const <String>[],
+ required Sdk sdk,
}) async {
rootConfig ??= request.arguments;
final rootUriString = rootConfig?[ParameterNames.root] as String?;
@@ -185,7 +189,7 @@
final projectRoot = fileSystem.directory(rootUri);
final commandWithPaths = <String>[
- await commandForRoot(root, fileSystem),
+ await commandForRoot(root, fileSystem, sdk),
...arguments,
];
final paths =
@@ -240,18 +244,21 @@
/// Returns 'dart' or 'flutter' based on the pubspec contents.
///
/// Throws an [ArgumentError] if there is no pubspec.
-Future<String> defaultCommandForRoot(Root root, FileSystem fileSystem) async =>
- switch (await inferProjectKind(root, fileSystem)) {
- ProjectKind.dart => 'dart',
- ProjectKind.flutter => 'flutter',
- ProjectKind.unknown =>
- throw ArgumentError.value(
- root.uri,
- 'root.uri',
- 'Unknown project kind at root ${root.uri}. All projects must have a '
- 'pubspec.',
- ),
- };
+Future<String> defaultCommandForRoot(
+ Root root,
+ FileSystem fileSystem,
+ Sdk sdk,
+) async => switch (await inferProjectKind(root, 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 '
+ 'pubspec.',
+ ),
+};
/// Returns whether or not [rootUri] is an allowed root, either exactly matching
/// or under on of the [knownRoots].
diff --git a/pkgs/dart_mcp_server/lib/src/utils/sdk.dart b/pkgs/dart_mcp_server/lib/src/utils/sdk.dart
new file mode 100644
index 0000000..f3f5523
--- /dev/null
+++ b/pkgs/dart_mcp_server/lib/src/utils/sdk.dart
@@ -0,0 +1,86 @@
+// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:io';
+
+import 'package:path/path.dart' as p;
+
+/// An interface class that provides a single getter of type [Sdk].
+///
+/// This provides information about the Dart and Flutter sdks, if available.
+abstract interface class SdkSupport {
+ Sdk get sdk;
+}
+
+/// Information about the Dart and Flutter SDKs, if available.
+class Sdk {
+ /// The path to the root of the Dart SDK.
+ final String? dartSdkPath;
+
+ /// The path to the root of the Flutter SDK.
+ final String? flutterSdkPath;
+
+ Sdk({this.dartSdkPath, this.flutterSdkPath});
+
+ /// Creates an [Sdk] from the path to the Dart SDK.
+ ///
+ /// If no [dartSdkPath] is given, this will attempt to find one using
+ /// [Platform.resolvedExecutable], assuming that is the `dart` binary
+ /// under the `bin` dir of a Dart SDK.
+ ///
+ /// Validates that the path is valid by checking for the `version` file.
+ ///
+ /// If no [flutterSdkPath] is given, this will search up from the resolved
+ /// Dart SDK path to see if it is nested inside a Flutter SDK.
+ factory Sdk.find({String? dartSdkPath, String? flutterSdkPath}) {
+ // Assume that we are running from the Dart SDK bin dir if not given any
+ // other configuration.
+ dartSdkPath ??= p.dirname(p.dirname(Platform.resolvedExecutable));
+
+ final versionFile = dartSdkPath.child('version');
+ if (!File(versionFile).existsSync()) {
+ throw ArgumentError('Invalid Dart SDK path: $dartSdkPath');
+ }
+
+ // Check if this is nested inside a Flutter SDK.
+ if (dartSdkPath.parent case final cacheDir
+ when cacheDir.basename == 'cache' && flutterSdkPath == null) {
+ if (cacheDir.parent case final binDir when binDir.basename == 'bin') {
+ final flutterExecutable = binDir.child('flutter');
+ if (File(flutterExecutable).existsSync()) {
+ flutterSdkPath = binDir.parent;
+ }
+ }
+ }
+
+ return Sdk(dartSdkPath: dartSdkPath, flutterSdkPath: flutterSdkPath);
+ }
+
+ /// The path to the `dart` executable.
+ ///
+ /// Throws an [ArgumentError] if [dartSdkPath] is `null`.
+ String get dartExecutablePath =>
+ dartSdkPath?.child('bin').child('dart') ??
+ (throw ArgumentError(
+ 'Dart SDK location unknown, try setting the DART_SDK environment '
+ 'variable.',
+ ));
+
+ /// The path to the `flutter` executable.
+ ///
+ /// Throws an [ArgumentError] if [flutterSdkPath] is `null`.
+ String get flutterExecutablePath =>
+ flutterSdkPath?.child('bin').child('flutter') ??
+ (throw ArgumentError(
+ 'Flutter SDK location unknown. To work on flutter projects, you must '
+ 'spawn the server using `dart` from the flutter SDK and not a Dart '
+ 'SDK, or set a FLUTTER_SDK environment variable.',
+ ));
+}
+
+extension on String {
+ String get basename => p.basename(this);
+ String child(String path) => p.join(this, path);
+ String get parent => p.dirname(this);
+}
diff --git a/pkgs/dart_mcp_server/test/test_harness.dart b/pkgs/dart_mcp_server/test/test_harness.dart
index 1a9bdae..a0c0f1c 100644
--- a/pkgs/dart_mcp_server/test/test_harness.dart
+++ b/pkgs/dart_mcp_server/test/test_harness.dart
@@ -11,6 +11,7 @@
import 'package:dart_mcp_server/src/mixins/dtd.dart';
import 'package:dart_mcp_server/src/server.dart';
import 'package:dart_mcp_server/src/utils/constants.dart';
+import 'package:dart_mcp_server/src/utils/sdk.dart';
import 'package:dtd/dtd.dart';
import 'package:file/file.dart';
import 'package:file/local.dart';
@@ -34,6 +35,7 @@
final DartToolingMCPClient mcpClient;
final ServerConnectionPair serverConnectionPair;
final FileSystem fileSystem;
+ final Sdk sdk;
ServerConnection get mcpServerConnection =>
serverConnectionPair.serverConnection;
@@ -43,6 +45,7 @@
this.serverConnectionPair,
this.fakeEditorExtension,
this.fileSystem,
+ this.sdk,
);
/// Starts a Dart Tooling Daemon as well as an MCP client and server, and
@@ -64,6 +67,10 @@
bool inProcess = false,
FileSystem? fileSystem,
}) async {
+ final sdk = Sdk.find(
+ dartSdkPath: Platform.environment['DART_SDK'],
+ flutterSdkPath: Platform.environment['FLUTTER_SDK'],
+ );
fileSystem ??= const LocalFileSystem();
final mcpClient = DartToolingMCPClient();
@@ -73,13 +80,14 @@
mcpClient,
inProcess,
fileSystem,
+ sdk,
);
final connection = serverConnectionPair.serverConnection;
connection.onLog.listen((log) {
printOnFailure('MCP Server Log: $log');
});
- final fakeEditorExtension = await FakeEditorExtension.connect();
+ final fakeEditorExtension = await FakeEditorExtension.connect(sdk);
addTearDown(fakeEditorExtension.shutdown);
return TestHarness._(
@@ -87,6 +95,7 @@
serverConnectionPair,
fakeEditorExtension,
fileSystem,
+ sdk,
);
}
@@ -102,6 +111,7 @@
appPath,
isFlutter: isFlutter,
args: args,
+ sdk: sdk,
);
await fakeEditorExtension.addDebugSession(session);
final root = rootForPath(projectRoot);
@@ -192,15 +202,20 @@
String appPath, {
List<String> args = const [],
required bool isFlutter,
+ required Sdk sdk,
}) async {
- final process = await TestProcess.start(isFlutter ? 'flutter' : 'dart', [
- 'run',
- '--no${isFlutter ? '' : '-serve'}-devtools',
- if (!isFlutter) '--enable-vm-service=0',
- if (isFlutter) ...['-d', 'flutter-tester'],
- appPath,
- ...args,
- ], workingDirectory: projectRoot);
+ final process = await TestProcess.start(
+ isFlutter ? sdk.flutterExecutablePath : sdk.dartExecutablePath,
+ [
+ 'run',
+ '--no${isFlutter ? '' : '-serve'}-devtools',
+ if (!isFlutter) '--enable-vm-service=0',
+ if (isFlutter) ...['-d', 'flutter-tester'],
+ appPath,
+ ...args,
+ ],
+ workingDirectory: projectRoot,
+ );
addTearDown(() async {
await kill(process, isFlutter);
@@ -284,8 +299,10 @@
static int get nextId => ++_nextId;
static int _nextId = 0;
- static Future<FakeEditorExtension> connect() async {
- final dtdProcess = await TestProcess.start('dart', ['tooling-daemon']);
+ static Future<FakeEditorExtension> connect(Sdk sdk) async {
+ final dtdProcess = await TestProcess.start(sdk.dartExecutablePath, [
+ 'tooling-daemon',
+ ]);
final dtdUri = await _getDTDUri(dtdProcess);
final dtd = await DartToolingDaemon.connect(Uri.parse(dtdUri));
final extension = FakeEditorExtension._(dtd, dtdProcess, dtdUri);
@@ -369,6 +386,7 @@
MCPClient client,
bool inProcess,
FileSystem fileSystem,
+ Sdk sdk,
) async {
ServerConnection connection;
DartMCPServer? server;
@@ -393,11 +411,12 @@
serverChannel,
processManager: TestProcessManager(),
fileSystem: fileSystem,
+ sdk: sdk,
);
addTearDown(server.shutdown);
connection = client.connectServer(clientChannel);
} else {
- connection = await client.connectStdioServer('dart', [
+ connection = await client.connectStdioServer(sdk.dartExecutablePath, [
'pub', // Using `pub` gives us incremental compilation
'run',
'bin/main.dart',
@@ -470,12 +489,9 @@
if (item.workingDirectory != value.workingDirectory) {
return false;
}
- if (item.command.length != value.command.length) {
+ if (!equals(value.command).matches(item.command, matchState)) {
return false;
}
- for (var i = 0; i < item.command.length; i++) {
- if (item.command[i] != value.command[i]) return false;
- }
return true;
}
}
diff --git a/pkgs/dart_mcp_server/test/tools/dart_cli_test.dart b/pkgs/dart_mcp_server/test/tools/dart_cli_test.dart
index 9bfdc93..98caf8d 100644
--- a/pkgs/dart_mcp_server/test/tools/dart_cli_test.dart
+++ b/pkgs/dart_mcp_server/test/tools/dart_cli_test.dart
@@ -76,7 +76,7 @@
expect(result.isError, isNot(true));
expect(testProcessManager.commandsRan, [
equalsCommand((
- command: ['dart', 'fix', '--apply'],
+ command: [endsWith('dart'), 'fix', '--apply'],
workingDirectory: exampleFlutterAppRoot.path,
)),
]);
@@ -98,7 +98,7 @@
expect(result.isError, isNot(true));
expect(testProcessManager.commandsRan, [
equalsCommand((
- command: ['dart', 'format', '.'],
+ command: [endsWith('dart'), 'format', '.'],
workingDirectory: exampleFlutterAppRoot.path,
)),
]);
@@ -123,7 +123,7 @@
expect(result.isError, isNot(true));
expect(testProcessManager.commandsRan, [
equalsCommand((
- command: ['dart', 'format', 'foo.dart', 'bar.dart'],
+ command: [endsWith('dart'), 'format', 'foo.dart', 'bar.dart'],
workingDirectory: exampleFlutterAppRoot.path,
)),
]);
@@ -154,11 +154,16 @@
expect(result.isError, isNot(true));
expect(testProcessManager.commandsRan, [
equalsCommand((
- command: ['flutter', 'test', 'foo_test.dart', 'bar_test.dart'],
+ command: [
+ endsWith('flutter'),
+ 'test',
+ 'foo_test.dart',
+ 'bar_test.dart',
+ ],
workingDirectory: exampleFlutterAppRoot.path,
)),
equalsCommand((
- command: ['dart', 'test', 'zip_test.dart'],
+ command: [endsWith('dart'), 'test', 'zip_test.dart'],
workingDirectory: dartCliAppRoot.path,
)),
]);
@@ -180,7 +185,13 @@
expect(testProcessManager.commandsRan, [
equalsCommand((
- command: ['dart', 'create', '--template', 'cli', 'new_app'],
+ command: [
+ endsWith('dart'),
+ 'create',
+ '--template',
+ 'cli',
+ 'new_app',
+ ],
workingDirectory: dartCliAppRoot.path,
)),
]);
@@ -201,7 +212,13 @@
expect(testProcessManager.commandsRan, [
equalsCommand((
- command: ['flutter', 'create', '--template', 'app', 'new_app'],
+ command: [
+ endsWith('flutter'),
+ 'create',
+ '--template',
+ 'app',
+ 'new_app',
+ ],
workingDirectory: exampleFlutterAppRoot.path,
)),
]);
diff --git a/pkgs/dart_mcp_server/test/tools/pub_test.dart b/pkgs/dart_mcp_server/test/tools/pub_test.dart
index 8f19130..693b99e 100644
--- a/pkgs/dart_mcp_server/test/tools/pub_test.dart
+++ b/pkgs/dart_mcp_server/test/tools/pub_test.dart
@@ -68,7 +68,7 @@
expect(result.isError, isNot(true));
expect(testProcessManager.commandsRan, [
equalsCommand((
- command: [appKind, 'pub', 'add', 'foo'],
+ command: [endsWith(appKind), 'pub', 'add', 'foo'],
workingDirectory: fakeAppPath,
)),
]);
@@ -91,7 +91,7 @@
expect(result.isError, isNot(true));
expect(testProcessManager.commandsRan, [
equalsCommand((
- command: [appKind, 'pub', 'remove', 'foo'],
+ command: [endsWith(appKind), 'pub', 'remove', 'foo'],
workingDirectory: fakeAppPath,
)),
]);
@@ -113,7 +113,7 @@
expect(result.isError, isNot(true));
expect(testProcessManager.commandsRan, [
equalsCommand((
- command: [appKind, 'pub', 'get'],
+ command: [endsWith(appKind), 'pub', 'get'],
workingDirectory: fakeAppPath,
)),
]);
@@ -135,7 +135,7 @@
expect(result.isError, isNot(true));
expect(testProcessManager.commandsRan, [
equalsCommand((
- command: [appKind, 'pub', 'upgrade'],
+ command: [endsWith(appKind), 'pub', 'upgrade'],
workingDirectory: fakeAppPath,
)),
]);
diff --git a/pkgs/dart_mcp_server/test/utils/cli_utils_test.dart b/pkgs/dart_mcp_server/test/utils/cli_utils_test.dart
index 36b97e1..2392d30 100644
--- a/pkgs/dart_mcp_server/test/utils/cli_utils_test.dart
+++ b/pkgs/dart_mcp_server/test/utils/cli_utils_test.dart
@@ -5,6 +5,7 @@
import 'package:dart_mcp/server.dart';
import 'package:dart_mcp_server/src/utils/cli_utils.dart';
import 'package:dart_mcp_server/src/utils/constants.dart';
+import 'package:dart_mcp_server/src/utils/sdk.dart';
import 'package:file/memory.dart';
import 'package:process/process.dart';
import 'package:test/fake.dart';
@@ -35,12 +36,13 @@
],
},
),
- commandForRoot: (_, _) => 'testCommand',
+ commandForRoot: (_, _, _) => 'testCommand',
arguments: ['a', 'b'],
commandDescription: '',
processManager: processManager,
knownRoots: [Root(uri: 'file:///bar/')],
fileSystem: fileSystem,
+ sdk: Sdk(),
);
expect(result.isError, isNot(true));
expect(processManager.commandsRan, [
@@ -63,11 +65,12 @@
],
},
),
- commandForRoot: (_, _) => 'testCommand',
+ commandForRoot: (_, _, _) => 'testCommand',
commandDescription: '',
processManager: processManager,
knownRoots: [Root(uri: 'file:///bar/')],
fileSystem: fileSystem,
+ sdk: Sdk(),
);
expect(result.isError, isNot(true));
expect(processManager.commandsRan, [
@@ -94,11 +97,12 @@
],
},
),
- commandForRoot: (_, _) => 'fake',
+ commandForRoot: (_, _, _) => 'fake',
commandDescription: '',
processManager: processManager,
knownRoots: [Root(uri: 'file:///foo/')],
fileSystem: fileSystem,
+ sdk: Sdk(),
);
expect(result.isError, isTrue);
expect(
@@ -130,11 +134,12 @@
],
},
),
- commandForRoot: (_, _) => 'fake',
+ commandForRoot: (_, _, _) => 'fake',
commandDescription: '',
processManager: processManager,
knownRoots: [Root(uri: 'file:///foo/')],
fileSystem: fileSystem,
+ sdk: Sdk(),
);
expect(result.isError, isTrue);
expect(