From 6bb716f18d9fe325389086017340ddffc20be149 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Wed, 15 Jul 2026 09:35:32 -0700 Subject: [PATCH 1/3] Cut devtools_shared 13.1.0 with new FileSystemExtension API from 14.0.0 --- packages/devtools_shared/CHANGELOG.md | 20 ++++ .../lib/src/server/file_system.dart | 110 ++++++++++++++++-- packages/devtools_shared/pubspec.yaml | 3 +- pubspec.lock | 4 +- 4 files changed, 127 insertions(+), 10 deletions(-) diff --git a/packages/devtools_shared/CHANGELOG.md b/packages/devtools_shared/CHANGELOG.md index eaf9a53f5df..aef5175c04d 100644 --- a/packages/devtools_shared/CHANGELOG.md +++ b/packages/devtools_shared/CHANGELOG.md @@ -4,6 +4,26 @@ 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. --> +# 13.1.0 +* The `LocalFileSystem` extension APIs are deprecated in favor of a new + `FileSystemExtension` extension, which is based on the `FileSystem` class + from the `file` package. In detail: + * `fileSystem` is a new top-level constant which represents the local (real) + file system. + * `LocalFileSystem.devToolsDir()` is replaced by a static getter, + `FileSystemExtension.devToolsDir`. + * `LocalFileSystem.maybeMoveLegacyDevToolsStore()` is replaced by an instance + method, `FileSystemExtension.maybeMoveLegacyDevToolsStore()`. + * `LocalFileSystem.devToolsStoreLocation()` is replaced by a static getter, + `FileSystemExtension.devToolsStoreLocation`. + * `LocalFileSystem.ensureDevToolsDirectory()` will be removed. + * `LocalFileSystem.devToolsFileFromPath()` is replaced by an instance method, + `FileSystemExtension.devToolsFileFromPath()`. + * `LocalFileSystem.devToolsFileAsJson()` is replaced by an instance method, + `FileSystemExtension.devToolsFileAsJson()`. + * `LocalFileSystem.flutterStoreExists()` is replaced by an instance getter, + `FileSystemExtension.flutterStoreExists`. + # 13.0.2 * Validate the `devtoolsOptionsUri` query parameter in the extension enabled state handler so it must be a `file:` URI named `devtools_options.yaml`, diff --git a/packages/devtools_shared/lib/src/server/file_system.dart b/packages/devtools_shared/lib/src/server/file_system.dart index 6a2036f1de9..10d21f1e483 100644 --- a/packages/devtools_shared/lib/src/server/file_system.dart +++ b/packages/devtools_shared/lib/src/server/file_system.dart @@ -3,26 +3,35 @@ // found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd. import 'dart:convert'; -import 'dart:io'; +import 'dart:io' as io show Platform, File; +import 'package:file/file.dart' as file; +import 'package:file/local.dart' as file; import 'package:path/path.dart' as path; import 'devtools_store.dart'; +/// The real, local file system, which can be avoided in tests. +const file.FileSystem fileSystem = file.LocalFileSystem(); + /// A namespace local file system utlities. extension LocalFileSystem on Never { static String _userHomeDir() { - final envKey = Platform.operatingSystem == 'windows' ? 'APPDATA' : 'HOME'; - return Platform.environment[envKey] ?? '.'; + final envKey = io.Platform.operatingSystem == 'windows' + ? 'APPDATA' + : 'HOME'; + return io.Platform.environment[envKey] ?? '.'; } /// Returns the path to the DevTools storage directory. + @Deprecated("Replaced by 'FileSystemExtension.devToolsDir'") static String devToolsDir() { return path.join(_userHomeDir(), '.flutter-devtools'); } /// Moves the .devtools file to ~/.flutter-devtools/.devtools if the .devtools /// file exists in the user's home directory. + @Deprecated("Replaced by 'FileSystemExtension.maybeMoveLegacyDevToolsStore'") static void maybeMoveLegacyDevToolsStore() { final file = File(path.join(_userHomeDir(), DevToolsUsage.storeName)); if (file.existsSync()) { @@ -32,11 +41,13 @@ extension LocalFileSystem on Never { } } + @Deprecated("Replaced by 'FileSystemExtension.devToolsStoreLocation'") static String devToolsStoreLocation() { return path.join(devToolsDir(), DevToolsUsage.storeName); } /// Creates the ~/.flutter-devtools directory if it does not already exist. + @Deprecated('To be removed') static void ensureDevToolsDirectory() { Directory(devToolsDir()).createSync(); } @@ -44,7 +55,8 @@ extension LocalFileSystem on Never { /// Returns a DevTools file from the given path. /// /// Only files within ~/.flutter-devtools/ can be accessed. - static File? devToolsFileFromPath(String pathFromDevToolsDir) { + @Deprecated("Replaced by 'FileSystemExtension.devToolsFileFromPath'") + static io.File? devToolsFileFromPath(String pathFromDevToolsDir) { if (pathFromDevToolsDir.contains('..') || path.isAbsolute(pathFromDevToolsDir)) { // The passed in path should not be able to walk up the directory tree @@ -72,6 +84,7 @@ extension LocalFileSystem on Never { /// Returns a DevTools file from the given path as encoded json. /// /// Only files within ~/.flutter-devtools/ can be accessed. + @Deprecated("Replaced by 'FileSystemExtension.devToolsFileAsJson'") static String? devToolsFileAsJson(String pathFromDevToolsDir) { final file = devToolsFileFromPath(pathFromDevToolsDir); if (file == null) return null; @@ -86,24 +99,107 @@ extension LocalFileSystem on Never { } /// Whether the flutter store file exists. + @Deprecated("Replaced by 'FileSystemExtension.flutterStoreExists'") static bool flutterStoreExists() { final flutterStore = File(path.join(_userHomeDir(), '.flutter')); return flutterStore.existsSync(); } } +extension FileSystemExtension on file.FileSystem { + static String get _userHomeDir { + final envKey = io.Platform.operatingSystem == 'windows' + ? 'APPDATA' + : 'HOME'; + return io.Platform.environment[envKey] ?? '.'; + } + + /// The path to the DevTools storage directory. + static String get devToolsDir { + return path.join(_userHomeDir, '.flutter-devtools'); + } + + /// Moves the `.devtools` file to `~/.flutter-devtools/.devtools` if the + /// `.devtools` file exists in the user's home directory. + void maybeMoveLegacyDevToolsStore() { + final storeFile = this.file( + path.join(_userHomeDir, DevToolsUsage.storeName), + ); + if (storeFile.existsSync()) { + _ensureDevToolsDirectory(); + storeFile.copySync(devToolsStoreLocation); + storeFile.deleteSync(); + } + } + + static String get devToolsStoreLocation { + return path.join(devToolsDir, DevToolsUsage.storeName); + } + + /// Creates the `~/.flutter-devtools` directory if it does not already exist. + void _ensureDevToolsDirectory() { + directory(devToolsDir).createSync(); + } + + /// Returns a DevTools file from the given path. + /// + /// Only files within ~/.flutter-devtools/ can be accessed. + file.File? devToolsFileFromPath(String relativePath) { + if (relativePath.contains('..') || path.isAbsolute(relativePath)) { + // The passed in path should not be able to walk up the directory tree + // outside of the `~/.flutter-devtools/` directory. It must also not be an + // absolute path: `path.join()` discards the base directory when its + // second argument is absolute, which would otherwise allow reading an + // arbitrary file on disk (e.g. an absolute path to a credentials `.json` + // file). + return null; + } + + _ensureDevToolsDirectory(); + final targetFile = this.file(path.join(devToolsDir, relativePath)); + // Defense in depth: ensure the resolved path is actually contained within + // the DevTools directory. + if (!path.isWithin(devToolsDir, targetFile.path)) return null; + if (!targetFile.existsSync()) return null; + return targetFile; + } + + /// Returns a DevTools file from the given path as encoded JSON. + /// + /// Only files within `~/.flutter-devtools/` can be accessed. + String? devToolsFileAsJson(String relativePath) { + final targetFile = devToolsFileFromPath(relativePath); + if (targetFile == null) return null; + + final fileName = path.basename(targetFile.path); + if (!fileName.endsWith('.json')) return null; + + final content = targetFile.readAsStringSync(); + final json = jsonDecode(content) as Map; + json['lastModifiedTime'] = targetFile.lastModifiedSync().toString(); + return jsonEncode(json); + } + + /// Whether the flutter store file exists. + bool get flutterStoreExists { + final flutterStore = this.file(path.join(_userHomeDir, '.flutter')); + return flutterStore.existsSync(); + } +} + class IOPersistentProperties { IOPersistentProperties(this.name, {String? documentDirPath}) { final fileName = name.replaceAll(' ', '_'); documentDirPath ??= LocalFileSystem._userHomeDir(); - _file = File(path.join(documentDirPath, fileName)); + _file = io.File(path.join(documentDirPath, fileName)); if (!_file.existsSync()) { _file.createSync(recursive: true); } syncSettings(); } - IOPersistentProperties.fromFile(File file) : name = path.basename(file.path) { + IOPersistentProperties.fromFile(io.File file) + : name = path.basename(file.path) { _file = file; if (!_file.existsSync()) { _file.createSync(recursive: true); @@ -113,7 +209,7 @@ class IOPersistentProperties { final String name; - late File _file; + late io.File _file; late Map _map; diff --git a/packages/devtools_shared/pubspec.yaml b/packages/devtools_shared/pubspec.yaml index 332adaa9421..16cd6c8a5cc 100644 --- a/packages/devtools_shared/pubspec.yaml +++ b/packages/devtools_shared/pubspec.yaml @@ -4,7 +4,7 @@ name: devtools_shared description: Package of shared Dart structures between devtools_app, dds, and other tools. -version: 13.0.2 +version: 13.1.0 repository: https://github.com/flutter/devtools/tree/master/packages/devtools_shared @@ -18,6 +18,7 @@ dependencies: collection: ^1.15.0 dtd: ^4.0.0 extension_discovery: ^2.1.0 + file: ^7.0.0 meta: ^1.9.1 path: ^1.8.0 shelf: ^1.1.0 diff --git a/pubspec.lock b/pubspec.lock index cc8b58e6e9b..1c67b639f11 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -555,10 +555,10 @@ packages: dependency: transitive description: name: meta - sha256: c82594181e3312f3d0695fc95aaaf7758d75b8d4ae2bbecf223b9fd5109a059d + sha256: "307249ce4ff29d58a18e97f6345f539382eb9c9c29ecda628900f31de0443dd9" url: "https://pub.dev" source: hosted - version: "1.18.3" + version: "1.19.0" mime: dependency: transitive description: From 70fb9be81885150bec4f9e04eb3c98a83873853b Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Wed, 15 Jul 2026 09:52:29 -0700 Subject: [PATCH 2/3] more io --- packages/devtools_shared/lib/src/server/file_system.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/devtools_shared/lib/src/server/file_system.dart b/packages/devtools_shared/lib/src/server/file_system.dart index 10d21f1e483..ada89b0d06d 100644 --- a/packages/devtools_shared/lib/src/server/file_system.dart +++ b/packages/devtools_shared/lib/src/server/file_system.dart @@ -3,7 +3,7 @@ // found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd. import 'dart:convert'; -import 'dart:io' as io show Platform, File; +import 'dart:io' as io show Platform, File, Directory; import 'package:file/file.dart' as file; import 'package:file/local.dart' as file; @@ -49,7 +49,7 @@ extension LocalFileSystem on Never { /// Creates the ~/.flutter-devtools directory if it does not already exist. @Deprecated('To be removed') static void ensureDevToolsDirectory() { - Directory(devToolsDir()).createSync(); + io.Directory(devToolsDir()).createSync(); } /// Returns a DevTools file from the given path. @@ -69,7 +69,7 @@ extension LocalFileSystem on Never { ensureDevToolsDirectory(); final devToolsDirPath = devToolsDir(); - final file = File(path.join(devToolsDirPath, pathFromDevToolsDir)); + final file = io.File(path.join(devToolsDirPath, pathFromDevToolsDir)); // Defense in depth: ensure the resolved path is actually contained within // the DevTools directory. if (!path.isWithin(devToolsDirPath, file.path)) { @@ -101,7 +101,7 @@ extension LocalFileSystem on Never { /// Whether the flutter store file exists. @Deprecated("Replaced by 'FileSystemExtension.flutterStoreExists'") static bool flutterStoreExists() { - final flutterStore = File(path.join(_userHomeDir(), '.flutter')); + final flutterStore = io.File(path.join(_userHomeDir(), '.flutter')); return flutterStore.existsSync(); } } From dc230bcd6dbaad3a402b95dc0195768675fb948a Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Wed, 15 Jul 2026 10:09:11 -0700 Subject: [PATCH 3/3] one-more --- packages/devtools_shared/lib/src/server/file_system.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/devtools_shared/lib/src/server/file_system.dart b/packages/devtools_shared/lib/src/server/file_system.dart index ada89b0d06d..620227a1aa4 100644 --- a/packages/devtools_shared/lib/src/server/file_system.dart +++ b/packages/devtools_shared/lib/src/server/file_system.dart @@ -33,7 +33,7 @@ extension LocalFileSystem on Never { /// file exists in the user's home directory. @Deprecated("Replaced by 'FileSystemExtension.maybeMoveLegacyDevToolsStore'") static void maybeMoveLegacyDevToolsStore() { - final file = File(path.join(_userHomeDir(), DevToolsUsage.storeName)); + final file = io.File(path.join(_userHomeDir(), DevToolsUsage.storeName)); if (file.existsSync()) { ensureDevToolsDirectory(); file.copySync(devToolsStoreLocation());