// Copyright (c) 2018, 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:checked_yaml/checked_yaml.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:pub_semver/pub_semver.dart'; import 'dependency.dart'; import 'screenshot.dart'; part 'pubspec.g.dart'; @JsonSerializable() class Pubspec { // TODO: executables final String name; @JsonKey(fromJson: _versionFromString) final Version? version; final String? description; /// This should be a URL pointing to the website for the package. final String? homepage; /// Specifies where to publish this package. /// /// Accepted values: `null`, `'none'` or an `http` or `https` URL. /// /// [More information](https://dart.dev/tools/pub/pubspec#publish_to). final String? publishTo; /// Optional field to specify the source code repository of the package. /// Useful when a package has both a home page and a repository. final Uri? repository; /// Optional field to a web page where developers can report new issues or /// view existing ones. final Uri? issueTracker; /// Optional field to list the URLs where the package authors accept /// support or funding. final List? funding; /// Optional field to list the topics that this packages belongs to. final List? topics; /// Optional field to list advisories to be ignored by the client. final List? ignoredAdvisories; /// Optional field for specifying included screenshot files. @JsonKey(fromJson: parseScreenshots) final List? screenshots; /// If there is exactly 1 value in [authors], returns it. /// /// If there are 0 or more than 1, returns `null`. @Deprecated( 'See https://dart.dev/tools/pub/pubspec#authorauthors', ) String? get author { if (authors.length == 1) { return authors.single; } return null; } @Deprecated( 'See https://dart.dev/tools/pub/pubspec#authorauthors', ) final List authors; final String? documentation; @JsonKey(fromJson: _environmentMap) final Map environment; @JsonKey(fromJson: parseDeps) final Map dependencies; @JsonKey(fromJson: parseDeps) final Map devDependencies; @JsonKey(fromJson: parseDeps) final Map dependencyOverrides; /// Optional configuration specific to [Flutter](https://flutter.io/) /// packages. /// /// May include /// [assets](https://flutter.io/docs/development/ui/assets-and-images) /// and other settings. final Map? flutter; /// Optional field to specify executables @JsonKey(fromJson: _executablesMap) final Map executables; /// If this package is a Pub Workspace, this field lists the sub-packages. final List? workspace; /// Specifies how to resolve dependencies with the surrounding Pub Workspace. final String? resolution; /// If [author] and [authors] are both provided, their values are combined /// with duplicates eliminated. Pubspec( this.name, { this.version, this.publishTo, @Deprecated( 'See https://dart.dev/tools/pub/pubspec#authorauthors', ) String? author, @Deprecated( 'See https://dart.dev/tools/pub/pubspec#authorauthors', ) List? authors, Map? environment, this.homepage, this.repository, this.issueTracker, this.funding, this.topics, this.ignoredAdvisories, this.screenshots, this.documentation, this.description, this.workspace, this.resolution, Map? dependencies, Map? devDependencies, Map? dependencyOverrides, this.flutter, Map? executables, }) : // ignore: deprecated_member_use_from_same_package authors = _normalizeAuthors(author, authors), environment = environment ?? const {}, dependencies = dependencies ?? const {}, devDependencies = devDependencies ?? const {}, executables = executables ?? const {}, dependencyOverrides = dependencyOverrides ?? const {} { if (name.isEmpty) { throw ArgumentError.value(name, 'name', '"name" cannot be empty.'); } if (publishTo != null && publishTo != 'none') { try { final targetUri = Uri.parse(publishTo!); if (!(targetUri.isScheme('http') || targetUri.isScheme('https'))) { throw const FormatException('Must be an http or https URL.'); } } on FormatException catch (e) { throw ArgumentError.value(publishTo, 'publishTo', e.message); } } } factory Pubspec.fromJson(Map json, {bool lenient = false}) { if (lenient) { while (json.isNotEmpty) { // Attempting to remove top-level properties that cause parsing errors. try { return _$PubspecFromJson(json); } on CheckedFromJsonException catch (e) { if (e.map == json && json.containsKey(e.key)) { json = Map.from(json)..remove(e.key); continue; } rethrow; } } } return _$PubspecFromJson(json); } /// Parses source [yaml] into [Pubspec]. /// /// When [lenient] is set, top-level property-parsing or type cast errors are /// ignored and `null` values are returned. factory Pubspec.parse(String yaml, {Uri? sourceUrl, bool lenient = false}) => checkedYamlDecode( yaml, (map) => Pubspec.fromJson(map!, lenient: lenient), sourceUrl: sourceUrl, ); static List _normalizeAuthors(String? author, List? authors) { final value = { if (author != null) author, ...?authors, }; return value.toList(); } } Version? _versionFromString(String? input) => input == null ? null : Version.parse(input); Map _environmentMap(Map? source) => source?.map((k, value) { final key = k as String; if (key == 'dart') { // github.com/dart-lang/pub/blob/d84173eeb03c3/lib/src/pubspec.dart#L342 // 'dart' is not allowed as a key! throw CheckedFromJsonException( source, 'dart', 'VersionConstraint', 'Use "sdk" to for Dart SDK constraints.', badKey: true, ); } VersionConstraint? constraint; if (value == null) { constraint = null; } else if (value is String) { try { constraint = VersionConstraint.parse(value); } on FormatException catch (e) { throw CheckedFromJsonException(source, key, 'Pubspec', e.message); } return MapEntry(key, constraint); } else { throw CheckedFromJsonException( source, key, 'VersionConstraint', '`$value` is not a String.', ); } return MapEntry(key, constraint); }) ?? {}; Map _executablesMap(Map? source) => source?.map((k, value) { final key = k as String; if (value == null) { return MapEntry(key, null); } else if (value is String) { return MapEntry(key, value); } else { throw CheckedFromJsonException( source, key, 'String', '`$value` is not a String.', ); } }) ?? {};