// 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 'dart:convert' show jsonEncode; import 'dart:io' show File, FileSystemEntityType, IOException; import 'expect_json.dart'; import 'io.dart'; // TODO: Convert the 'rootUri' reference below to a doc comment reference once // https://github.com/dart-lang/linter/issues/4645 is addressed. /// Entry in the `.dart_tool/extension_discovery/.json` file. /// /// If the `rootUri` is not an absolute path, then we will assume that the /// package is mutable (either it's the root package or a path dependency). /// If there is no extension config file for a mutable package, then we will /// still store a [RegistryEntry] with `config = null`. Because everytime we /// load the registry, we still need to check if a configuration file has been /// added to the mutable package. typedef RegistryEntry = ({ String package, Uri rootUri, Uri packageUri, Map? config, }); typedef Registry = List; Future loadRegistry(File registryFile) async { try { final registryJson = decodeJsonMap(await registryFile.readAsString()); if (registryJson.expectNumber('version') != 2) { throw const FormatException('"version" must be 2'); } return registryJson .expectListObjects('entries') .map((e) => ( package: e.expectString('package'), rootUri: e.expectUri('rootUri'), packageUri: e.expectUri('packageUri'), config: e.optionalMap('config'), )) .toList(growable: false); } on IOException { return null; // pass } on FormatException { await registryFile.tryDelete(); return null; } } Future saveRegistry( File registryFile, Registry registry, ) async { try { if (!registryFile.parent.existsSync()) { await registryFile.parent.create(); } final tmpFile = File('${registryFile.path}.tmp'); final tmpFileStat = tmpFile.statSync(); if (tmpFileStat.type != FileSystemEntityType.notFound) { final tmpAge = DateTime.now().difference(tmpFileStat.modified); if (tmpAge.inSeconds < 5) { // Don't try to write registry, if there is an abandoned temporary file // no older than 5 seconds. Otherwise, we could have race conditions! // Note: That saving the registry is a performance improvement, not a // strict necessity! return; } else { await tmpFile.delete(); } } await tmpFile.writeAsString(jsonEncode({ 'version': 2, 'entries': registry .map((e) => { 'package': e.package, 'rootUri': e.rootUri.toString(), 'packageUri': e.packageUri.toString(), if (e.config != null) 'config': e.config, }) .toList(), })); await tmpFile.reliablyRename(registryFile.path); await _ensureReadme( File.fromUri(registryFile.parent.uri.resolve('README.md')), ); } on IOException { // pass } } Future _ensureReadme(File readmeFile) async { try { final stat = readmeFile.statSync(); if (stat.type != FileSystemEntityType.notFound) { final age = DateTime.now().difference(stat.modified); if (age.inDays < 5) { return; // don't update README.md, if it's less than 5 days old } } await readmeFile.writeAsString(_readmeContents); } on IOException { // pass } } const _readmeContents = ''' Extension Discovery Cache ========================= This folder is used by `package:extension_discovery` to cache lists of packages that contains extensions for other packages. DO NOT USE THIS FOLDER ---------------------- * Do not read (or rely) the contents of this folder. * Do write to this folder. If you're interested in the lists of extensions stored in this folder use the API offered by package `extension_discovery` to get this information. If this package doesn't work for your use-case, then don't try to read the contents of this folder. It may change, and will not remain stable. Use package `extension_discovery` --------------------------------- If you want to access information from this folder. Feel free to delete this folder ------------------------------- Files in this folder act as a cache, and the cache is discarded if the files are older than the modification time of `.dart_tool/package_config.json`. Hence, it should never be necessary to clear this cache manually, if you find a need to do please file a bug. ''';