Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/devtools_app_shared/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ resolution: workspace
dependencies:
collection: ^1.15.0
dds_service_extensions: ^2.0.0
devtools_shared: ^13.0.0
devtools_shared: ^14.0.0
dtd: ^4.0.0
flutter:
sdk: flutter
Expand Down
2 changes: 1 addition & 1 deletion packages/devtools_extensions/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ executables:

dependencies:
args: ^2.4.2
devtools_shared: ^13.0.0
devtools_shared: ^14.0.0
devtools_app_shared: ^0.5.1
flutter:
sdk: flutter
Expand Down
24 changes: 23 additions & 1 deletion packages/devtools_shared/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,31 @@ Copyright 2025 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.
-->
# 13.0.2-wip
# 14.0.0-wip
* The minimum Dart SDK version is bumped to 3.11.0.
* The minimum Flutter SDK version is bumped to 3.41.0.
* **Braking changes**: `LocalFileSystem`, an extension which provided some handy
Comment thread
srawlins marked this conversation as resolved.
Outdated
helpers, has been refactored into an extension on the `file` package's
`FileSystem` abstraction. In detail:
* `fileSystem` is a new top-level constant which represents the local (real)
file system.
* `LocalFileSystem.devToolsDir()` is now a static getter,
`FileSystemExtension.devToolsDir`.
* `LocalFileSystem.maybeMoveLegacyDevToolsStore()` is now an instance method,
`FileSystemExtension.maybeMoveLegacyDevToolsStore()`.
* `LocalFileSystem.devToolsStoreLocation()` is now a static getter,
`FileSystemExtension.devToolsStoreLocation`.
* `LocalFileSystem.ensureDevToolsDirectory()` is now private.
* `LocalFileSystem.devToolsFileFromPath()` is now an instance method,
`FileSystemExtension.devToolsFileFromPath()`.
* `LocalFileSystem.devToolsFileAsJson()` is now an instance method,
`FileSystemExtension.devToolsFileAsJson()`.
* `LocalFileSystem.flutterStoreExists()` is now an instance getter,
`FileSystemExtension.flutterStoreExists`.
* `IOPersistentProperties.new` accepts a new optional `FileSystem fs`
argument.
* Update `LocalFileSystem` and `IOPersistentProperties` to use `package:file`
instead of `dart:io` to allow mocking the file system.

# 13.0.1
* Handle null values for `FlutterStore.flutterClientId`.
Expand Down
4 changes: 2 additions & 2 deletions packages/devtools_shared/lib/src/server/devtools_store.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ enum DevToolsStoreKeys {
/// Provides access to the local DevTools store (~/.flutter-devtools/.devtools).
class DevToolsUsage {
DevToolsUsage() {
LocalFileSystem.maybeMoveLegacyDevToolsStore();
fileSystem.maybeMoveLegacyDevToolsStore();
properties = IOPersistentProperties(
storeName,
documentDirPath: LocalFileSystem.devToolsDir(),
documentDirPath: FileSystemExtension.devToolsDir,
);
_removeLegacyKeys();
}
Comment thread
srawlins marked this conversation as resolved.
Expand Down
121 changes: 66 additions & 55 deletions packages/devtools_shared/lib/src/server/file_system.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,108 +3,117 @@
// 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';
import 'package:file/local.dart';
import 'package:path/path.dart' as path;

import 'devtools_store.dart';

/// A namespace local file system utlities.
extension LocalFileSystem on Never {
static String _userHomeDir() {
final envKey = Platform.operatingSystem == 'windows' ? 'APPDATA' : 'HOME';
return Platform.environment[envKey] ?? '.';
/// The real, local file system, which can be avoided in tests.
const FileSystem fileSystem = LocalFileSystem();

extension FileSystemExtension on FileSystem {
static String get _userHomeDir {
final envKey = io.Platform.operatingSystem == 'windows'
? 'APPDATA'
: 'HOME';
return io.Platform.environment[envKey] ?? '.';
}

/// Returns the path to the DevTools storage directory.
static String devToolsDir() {
return path.join(_userHomeDir(), '.flutter-devtools');
/// 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.
static void maybeMoveLegacyDevToolsStore() {
final file = File(path.join(_userHomeDir(), DevToolsUsage.storeName));
if (file.existsSync()) {
ensureDevToolsDirectory();
file.copySync(devToolsStoreLocation());
file.deleteSync();
/// Moves the `.devtools` file to `~/.flutter-devtools/.devtools` if the
/// `.devtools` file exists in the user's home directory.
void maybeMoveLegacyDevToolsStore() {
final storeFile = file(path.join(_userHomeDir, DevToolsUsage.storeName));
if (storeFile.existsSync()) {
_ensureDevToolsDirectory();
storeFile.copySync(devToolsStoreLocation);
storeFile.deleteSync();
}
}

static String devToolsStoreLocation() {
return path.join(devToolsDir(), DevToolsUsage.storeName);
static String get devToolsStoreLocation {
return path.join(devToolsDir, DevToolsUsage.storeName);
}

/// Creates the ~/.flutter-devtools directory if it does not already exist.
static void ensureDevToolsDirectory() {
Directory(devToolsDir()).createSync();
/// 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.
static File? devToolsFileFromPath(String pathFromDevToolsDir) {
if (pathFromDevToolsDir.contains('..') ||
path.isAbsolute(pathFromDevToolsDir)) {
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).
// 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 devToolsDirPath = devToolsDir();
final file = File(path.join(devToolsDirPath, pathFromDevToolsDir));
_ensureDevToolsDirectory();
final targetFile = file(path.join(devToolsDir, relativePath));
// Defense in depth: ensure the resolved path is actually contained within
// the DevTools directory.
if (!path.isWithin(devToolsDirPath, file.path)) {
return null;
}
if (!file.existsSync()) {
return null;
}
return file;
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.
/// Returns a DevTools file from the given path as encoded JSON.
///
/// Only files within ~/.flutter-devtools/ can be accessed.
static String? devToolsFileAsJson(String pathFromDevToolsDir) {
final file = devToolsFileFromPath(pathFromDevToolsDir);
if (file == null) return null;
/// 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(file.path);
final fileName = path.basename(targetFile.path);
if (!fileName.endsWith('.json')) return null;

final content = file.readAsStringSync();
final content = targetFile.readAsStringSync();
final json = jsonDecode(content) as Map;
json['lastModifiedTime'] = file.lastModifiedSync().toString();
json['lastModifiedTime'] = targetFile.lastModifiedSync().toString();
Comment thread
srawlins marked this conversation as resolved.
return jsonEncode(json);
}

/// Whether the flutter store file exists.
static bool flutterStoreExists() {
final flutterStore = File(path.join(_userHomeDir(), '.flutter'));
bool get flutterStoreExists {
final flutterStore = file(path.join(_userHomeDir, '.flutter'));
return flutterStore.existsSync();
}
}

class IOPersistentProperties {
IOPersistentProperties(this.name, {String? documentDirPath}) {
IOPersistentProperties(
this.name, {
String? documentDirPath,
FileSystem fs = fileSystem,
}) : _fs = fs {
final fileName = name.replaceAll(' ', '_');
documentDirPath ??= LocalFileSystem._userHomeDir();
_file = File(path.join(documentDirPath, fileName));
documentDirPath ??= FileSystemExtension._userHomeDir;
_file = _fs.file(path.join(documentDirPath, fileName));
if (!_file.existsSync()) {
_file.createSync(recursive: true);
}
syncSettings();
}

IOPersistentProperties.fromFile(File file) : name = path.basename(file.path) {
_file = file;
// TODO(srawlins): This is unused in any devtools code. Even in tests. Either
// test it, if it is needed by users, or remove it.
IOPersistentProperties.fromFile(io.File file)
: name = path.basename(file.path),
_fs = file is File ? file.fileSystem : fileSystem {
_file = file is File ? file : _fs.file(file.path);
if (!_file.existsSync()) {
_file.createSync(recursive: true);
}
Expand All @@ -113,7 +122,9 @@ class IOPersistentProperties {

final String name;

late File _file;
final FileSystem _fs;

late final File _file;

late Map<String, Object?> _map;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ extension _AppSizeHandler on Never {
if (missingRequiredParams != null) return missingRequiredParams;

final filePath = queryParams[AppSizeApi.baseAppSizeFilePropertyName]!;
final fileJson = LocalFileSystem.devToolsFileAsJson(filePath);
final fileJson = fileSystem.devToolsFileAsJson(filePath);
if (fileJson == null) {
return api.badRequest('No JSON file available at $filePath.');
}
Expand All @@ -39,7 +39,7 @@ extension _AppSizeHandler on Never {
if (missingRequiredParams != null) return missingRequiredParams;

final filePath = queryParams[AppSizeApi.testAppSizeFilePropertyName]!;
final fileJson = LocalFileSystem.devToolsFileAsJson(filePath);
final fileJson = fileSystem.devToolsFileAsJson(filePath);
if (fileJson == null) {
return api.badRequest('No JSON file available at $filePath.');
}
Expand Down
10 changes: 4 additions & 6 deletions packages/devtools_shared/lib/src/server/server_api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,7 @@ class ServerApi {
// ----- Flutter Tool GA store. -----
case apiGetFlutterGAClientId:
return _encodeResponse(
LocalFileSystem.flutterStoreExists()
? _flutterStore.flutterClientId
: '',
fileSystem.flutterStoreExists ? _flutterStore.flutterClientId : '',
api: api,
);

Expand Down Expand Up @@ -222,16 +220,16 @@ class ServerApi {
: null;
}

/// Accessing DevTools store file e.g., ~/.flutter-devtools/.devtools
/// Accessing DevTools store file e.g., `~/.flutter-devtools/.devtools`.
static final _devToolsStore = DevToolsUsage();

/// Accessing Flutter store file e.g., ~/.flutter
/// Accessing Flutter store file e.g., `~/.flutter`.
static final _flutterStore = FlutterStore();

static DevToolsUsage get devToolsPreferences => _devToolsStore;

/// Provides read and write access to DevTools options files
/// (e.g. path/to/app/root/devtools_options.yaml).
/// (e.g. `path/to/app/root/devtools_options.yaml`).
static final _devToolsOptions = DevToolsOptions();

/// Logs a page view in the DevTools server.
Expand Down
3 changes: 2 additions & 1 deletion packages/devtools_shared/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
name: devtools_shared
description: Package of shared Dart structures between devtools_app, dds, and other tools.

version: 13.0.2-dev
version: 14.0.0-wip

repository: https://github.com/flutter/devtools/tree/master/packages/devtools_shared

Expand All @@ -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
Expand Down
Loading
Loading