blob: 89cda33b1e855a1c1f4c007b971f0dd249c56621 [file] [log] [blame]
// Copyright 2024 The Flutter Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd.
import 'dart:io';
import 'package:args/command_runner.dart';
import 'package:devtools_shared/devtools_extensions.dart';
import 'package:devtools_shared/devtools_extensions_io.dart';
import 'package:path/path.dart' as path;
import 'package:yaml/yaml.dart';
/// Command that validates that a DevTools extension meets the requirements for
/// loading successfully in DevTools.
///
/// Example usage:
///
/// dart run devtools_extensions validate --package=../your_pub_package
class ValidateExtensionCommand extends Command {
ValidateExtensionCommand() {
argParser.addOption(
_packageKey,
help: 'The location of the package that this extension is published with',
abbr: 'p',
valueHelp: 'path/to/foo/packages/foo',
mandatory: true,
);
}
static const _packageKey = 'package';
@override
String get name => 'validate';
@override
String get description =>
'Command that validates that a DevTools extension meets the '
'requirements for loading successfully in DevTools.';
static const docUrl = 'https://docs.flutter.dev/tools/devtools/extensions';
@override
Future<void> run() async {
final packagePath = argResults?[_packageKey]! as String;
try {
// TODO(kenz): try to use the the existing pub validator for this check. See
// https://github.com/dart-lang/pub/blob/master/lib/src/validator/devtools_extension.dart.
_validateDirectoryContents(packagePath);
// Try to parse the config.yaml file. This will throw an exception if there
// are parsing errors.
DevToolsExtensionConfig.parse(
{
..._configAsMap(packagePath),
// These are generated on the DevTools server, so pass in stubbed
// values for the sake of validation.
DevToolsExtensionConfig.extensionAssetsPathKey: '',
DevToolsExtensionConfig.devtoolsOptionsUriKey: '',
DevToolsExtensionConfig.isPubliclyHostedKey: 'false',
DevToolsExtensionConfig.detectedFromStaticContextKey: 'false',
},
);
// If there are no exceptions at this point, the extension has successfully
// been validated.
stdout.writeln('Extension validation successful');
} on StateError catch (e) {
_logError(e.message);
} on FileSystemException catch (e) {
_logError(e.message);
} catch (e) {
_logError(e.toString());
}
}
}
void _validateDirectoryContents(String packagePath) {
final packageDirectory = Directory(packagePath);
if (!packageDirectory.existsSync()) {
throw FileSystemException('${packageDirectory.path} directory not found');
}
final devtoolsExtensionDir = Directory(
path.join(packageDirectory.path, 'extension', 'devtools'),
);
if (!devtoolsExtensionDir.existsSync()) {
throw const FileSystemException(
'''
An extension/devtools directory is required, but none was found.
See ${ValidateExtensionCommand.docUrl}.
''',
);
}
final buildDir = Directory(path.join(devtoolsExtensionDir.path, 'build'));
if (!buildDir.existsSync()) {
throw const FileSystemException(
'''
An extension/devtools/build directory is required, but none was found.
See ${ValidateExtensionCommand.docUrl}.
''',
);
}
if (buildDir.listSync().isEmpty) {
throw const FileSystemException(
'''
A non-empty extension/devtools/build directory is required, but the directory is empty.
See ${ValidateExtensionCommand.docUrl}.
''',
);
}
final configFile = _lookupConfigFile(packagePath);
if (!configFile.existsSync()) {
throw const FileSystemException(
'''
An extension/devtools/config.yaml file is required, but none was found.
See ${ValidateExtensionCommand.docUrl}.
''',
);
}
}
Map<String, Object?> _configAsMap(String packagePath) {
final configFile = _lookupConfigFile(packagePath);
// At this point, we know the config.yaml file exists.
assert(configFile.existsSync());
final yamlMap = loadYaml(configFile.readAsStringSync()) as YamlMap;
return yamlMap.toDartMap();
}
File _lookupConfigFile(String packagePath) {
return File(
path.join(packagePath, 'extension', 'devtools', 'config.yaml'),
);
}
void _logError(String error) {
stderr.writeln('Validation error: $error');
}