| // Copyright (c) 2023, 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 'package:extension_discovery/extension_discovery.dart'; | |
| import 'package:path/path.dart' as path; | |
| class VsCodeExtensionsManager { | |
| /// Reads metadata about the VS Code extensions for the packages used by the | |
| /// project at [rootPath]. | |
| Future<VsCodeExtensionResults> findVsCodeExtensions(String rootPath) async { | |
| const targetName = 'vs_code'; | |
| final packageConfig = | |
| Uri.file(path.join(rootPath, '.dart_tool', 'package_config.json')); | |
| final results = VsCodeExtensionResults(); | |
| try { | |
| final extensions = await findExtensions( | |
| targetName, | |
| packageConfig: packageConfig, | |
| ); | |
| for (final extension in extensions) { | |
| try { | |
| results.extensions.add( | |
| VsCodeExtensionConfig.parse(extension.package, extension.config), | |
| ); | |
| } on Error catch (e) { | |
| results.parseErrors.add(VsCodeExtensionParseError( | |
| packageName: extension.package, error: e.toString())); | |
| } | |
| } | |
| } on PackageConfigException { | |
| // If the package_config doesn't exist or is invalid, we'll just return | |
| // the empty results. | |
| } | |
| return results; | |
| } | |
| } | |
| class VsCodeExtensionResults { | |
| final extensions = <VsCodeExtensionConfig>[]; | |
| final parseErrors = <VsCodeExtensionParseError>[]; | |
| Map<String, Object?> toJson() => { | |
| 'extensions': extensions, | |
| 'parseErrors': parseErrors, | |
| }; | |
| } | |
| class VsCodeExtensionParseError { | |
| VsCodeExtensionParseError({required this.packageName, required this.error}); | |
| final String packageName; | |
| final String error; | |
| Map<String, Object?> toJson() => { | |
| 'packageName': packageName, | |
| 'error': error, | |
| }; | |
| } | |
| class VsCodeExtensionConfig { | |
| VsCodeExtensionConfig._({ | |
| required this.packageName, | |
| required this.vsCodeExtensionId, | |
| }); | |
| factory VsCodeExtensionConfig.parse( | |
| String packageName, | |
| Map<String, Object?> json, | |
| ) { | |
| if (json | |
| case { | |
| vsCodeExtensionIdKey: final String vsCodeExtensionId, | |
| }) { | |
| return VsCodeExtensionConfig._( | |
| packageName: packageName, | |
| vsCodeExtensionId: vsCodeExtensionId, | |
| ); | |
| } else { | |
| const requiredKeysFromConfigFile = <String>{ | |
| vsCodeExtensionIdKey, | |
| }; | |
| final missing = requiredKeysFromConfigFile.difference(json.keys.toSet()); | |
| if (missing.isNotEmpty) { | |
| throw StateError( | |
| 'Missing required fields $missing in the extension ' | |
| 'config.yaml.', | |
| ); | |
| } else { | |
| // All the required keys are present, but the value types did not match. | |
| final sb = StringBuffer(); | |
| for (final entry in json.entries) { | |
| sb.writeln( | |
| ' ${entry.key}: ${entry.value} (${entry.value.runtimeType})', | |
| ); | |
| } | |
| throw StateError( | |
| 'Unexpected value types in the extension config.yaml. Expected all ' | |
| 'values to be of type String, but one or more had a different type:\n' | |
| '$sb', | |
| ); | |
| } | |
| } | |
| } | |
| Map<String, Object?> toJson() => { | |
| 'packageName': packageName, | |
| VsCodeExtensionConfig.vsCodeExtensionIdKey: vsCodeExtensionId, | |
| }; | |
| /// The YAML key for the ID of the VS Code extension. | |
| /// | |
| /// This is 'extension' in the YAML because users have to type it and it's | |
| /// already in a VS-Code-specific file, but in code we use a more descriptive | |
| /// name. | |
| static const vsCodeExtensionIdKey = 'extension'; | |
| /// The name of the package promoting the extension. | |
| final String packageName; | |
| /// The ID of the VS Code extension being promoted. | |
| /// | |
| /// The format should be 'publisher.extension' matching what's shown inside | |
| /// VS Code and on the market place. | |
| final String vsCodeExtensionId; | |
| } |