| // Copyright (c) 2026, 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:async'; | |
| import 'package:analyzer/file_system/file_system.dart'; | |
| import 'package:args/command_runner.dart'; | |
| import 'package:file/file.dart' as f; | |
| import 'package:http/http.dart' as http; | |
| import 'package:pub/pub.dart'; | |
| import '../resource_provider/resource_provider_file.dart'; | |
| import '../resource_provider/resource_provider_wrap_cwd.dart'; | |
| import '../shared.dart'; | |
| import '../util/stdout_recorder.dart'; | |
| const supportedPubCommands = [ | |
| 'get', | |
| 'add', | |
| 'downgrade', | |
| 'outdated', | |
| 'upgrade', | |
| 'remove', | |
| 'unpack', | |
| ]; | |
| Future<({String log})> pub({ | |
| required ResourceProvider resourceProvider, | |
| required String currentWorkingDirectory, | |
| required String command, | |
| required List<String> args, | |
| required DartPadConfig config, | |
| required VersionInfo version, | |
| }) async { | |
| if (!supportedPubCommands.contains(command)) { | |
| throw ArgumentError.value( | |
| command, | |
| 'command', | |
| 'must be one of $supportedPubCommands', | |
| ); | |
| } | |
| final stdout = StdoutRecorder(); | |
| final exitCode = await Runner( | |
| fileSystem: resourceProviderAsFileFileSystem( | |
| resourceProviderWithCurrentWorkingDirectory( | |
| resourceProvider, | |
| currentWorkingDirectory, | |
| ), | |
| ), | |
| stdout: stdout.sink, | |
| stderr: stdout.sink, | |
| stdin: const Stream.empty(), | |
| platformVersion: version.dartVersion, | |
| environment: { | |
| 'PUB_CACHE': '/pub-cache', | |
| 'DART_ROOT': config.dartSdkPath, | |
| 'FLUTTER_ROOT': ?config.flutterSdkPath, | |
| if (config.pubHostedUrl?.isNotEmpty == true) | |
| 'PUB_HOSTED_URL': ?config.pubHostedUrl, | |
| }, | |
| httpClient: _HeaderStrippingClient(http.Client()), | |
| ).run(['pub', command, ...args]); | |
| if (exitCode != 0) { | |
| final makeException = | |
| _pubExceptionMap[exitCode] ?? PubCommandFailedException.new; | |
| throw makeException(stdout.log, data: {'exitCode': exitCode}); | |
| } | |
| return (log: stdout.log); | |
| } | |
| /// An [http.Client] that strips harmless request headers that would otherwise | |
| /// trigger a CORS preflight (`OPTIONS`) request in browsers. | |
| /// | |
| /// `package:pub` attaches headers such as `User-Agent: Dart pub ...` and | |
| /// `Accept: application/vnd.pub.v2+json`, which trigger CORS preflight requests | |
| /// in Firefox and Safari. | |
| final class _HeaderStrippingClient extends http.BaseClient { | |
| static const _headersToStrip = [ | |
| 'user-agent', | |
| 'accept', | |
| 'if-none-match', | |
| 'if-modified-since', | |
| ]; | |
| final http.Client _inner; | |
| _HeaderStrippingClient(this._inner); | |
| @override | |
| Future<http.StreamedResponse> send(http.BaseRequest request) { | |
| for (final header in _headersToStrip) { | |
| request.headers.remove(header); | |
| } | |
| return _inner.send(request); | |
| } | |
| @override | |
| void close() => _inner.close(); | |
| } | |
| typedef _MakeException = Exception Function(String message, {Object? data}); | |
| /// Pub uses exit codes from http://www.freebsd.org/cgi/man.cgi?query=sysexits | |
| /// | |
| /// See: sdk/third_party/pkg/pub/lib/src/exit_codes.dart | |
| final _pubExceptionMap = <int, _MakeException>{ | |
| 64: PubUsageException.new, | |
| 65: PubDataException.new, | |
| 66: PubNoInputException.new, | |
| 67: PubNoUserException.new, | |
| 68: PubNoHostException.new, | |
| 69: PubUnavailableException.new, | |
| 70: PubSoftwareException.new, | |
| 71: PubOsException.new, | |
| 72: PubOsFileException.new, | |
| 73: PubCantCreateException.new, | |
| 74: PubIoException.new, | |
| 75: PubTempFailException.new, | |
| 76: PubProtocolException.new, | |
| 77: PubNoPermException.new, | |
| 78: PubConfigException.new, | |
| }; | |
| class Runner extends CommandRunner<int> { | |
| final f.FileSystem fileSystem; | |
| final Map<String, String> environment; | |
| final String platformVersion; | |
| final Stream<List<int>> stdin; | |
| final StreamSink<List<int>> stdout; | |
| final StreamSink<List<int>> stderr; | |
| final http.Client httpClient; | |
| Runner({ | |
| required this.fileSystem, | |
| required this.environment, | |
| required this.platformVersion, | |
| required this.stdin, | |
| required this.stdout, | |
| required this.stderr, | |
| required this.httpClient, | |
| }) : super('dart', 'dart pub emulator') { | |
| addCommand( | |
| pubCommand( | |
| isVerbose: () => false, | |
| fileSystem: fileSystem, | |
| environment: environment, | |
| platformVersion: platformVersion, | |
| stdin: stdin, | |
| stdout: stdout, | |
| stderr: stderr, | |
| httpClient: httpClient, | |
| ), | |
| ); | |
| } | |
| } |