feat: Add a cutler tool for managing our Flutter forks. (#336)

This commit is contained in:
Eric Seidel
2023-04-20 15:02:42 -04:00
committed by GitHub
parent 4a5dc63247
commit 41fcb06b71
9 changed files with 928 additions and 2 deletions
+2 -2
View File
@@ -128,7 +128,7 @@ And save off that hash:
We do not need to update our engine fork commits at this time since it already
pointed to the (unchanged) forked buildroot in `DEPS`.
Because this kind of rebase is a non-fastforward commit, we will need to
Because this kind of rebase is a non-fast-forward commit, we will need to
force push to our fork. A better solution will be for us to tag or branch
each of these releases instead of keeping a single release branch.
```
@@ -164,7 +164,7 @@ git push origin --force
https://github.com/shorebirdtech/shorebird/blob/main/packages/shorebird_cli/lib/src/engine_revision.dart
12. If there were changes to the `patch` binary in the `updater` library we
will need to tigger github actions before we can publish the new version of
will need to trigger github actions before we can publish the new version of
the shorebird engine.
13. Before we can publish the new version of Shorebird, we need to build the
+3
View File
@@ -0,0 +1,3 @@
# https://dart.dev/guides/libraries/private-files
# Created by `dart pub`
.dart_tool/
+6
View File
@@ -0,0 +1,6 @@
# Cutler
A tool for managing our fork of Flutter
"Someone who makes or sells cutlery is a cutler." - Wikipedia
"Forks are considered cutlery, right?" - Me
+5
View File
@@ -0,0 +1,5 @@
include: package:very_good_analysis/analysis_options.4.0.0.yaml
linter:
rules:
public_member_api_docs: false
avoid_print: false
+364
View File
@@ -0,0 +1,364 @@
import 'dart:io';
import 'package:cutler/config.dart';
import 'package:cutler/model.dart';
import 'package:path/path.dart' as p;
/// Our path constants.
enum Paths {
engineDEPS('DEPS'),
flutterEngineVersion('bin/internal/engine.version'),
shorebirdFlutterVersion('bin/internal/flutter.version');
const Paths(this.path);
final String path;
}
String runCommand(
String executable,
List<String> arguments, {
String? workingDirectory,
}) {
if (config.verbose) {
final workingDirectoryString = workingDirectory == null ||
p.equals(workingDirectory, Directory.current.path)
? ''
: ' (in $workingDirectory)';
print("$executable ${arguments.join(' ')}$workingDirectoryString");
}
final result = Process.runSync(
executable,
arguments,
workingDirectory: workingDirectory,
);
if (result.exitCode != 0) {
throw Exception('Failed to run $executable $arguments: ${result.stderr}');
}
return result.stdout.toString().trim();
}
void dryRunCommand(
String executable,
List<String> arguments, {
String? workingDirectory,
}) {
print("$executable ${arguments.join(' ')}");
}
extension RepoCommands on Repo {
Version versionFrom(String hash, {bool lookupTags = true}) {
return Version(
hash: hash,
repo: this,
aliases: lookupTags ? getTagsFor(hash) : [],
);
}
String get _workingDirectory => '${config.checkoutsRoot}/$path';
String getLatestCommit(String branch) {
return runCommand(
'git',
['log', '-1', '--pretty=%H', branch],
workingDirectory: _workingDirectory,
);
}
List<String> getTagsFor(String commit) {
final output = runCommand(
'git',
['tag', '--points-at', commit],
workingDirectory: _workingDirectory,
);
if (output.isEmpty) {
return [];
}
return output.split('\n');
}
Version getForkPoint() {
final hash = runCommand(
'git',
['merge-base', '--fork-point', upstreamBranch, releaseBranch],
workingDirectory: _workingDirectory,
);
return versionFrom(hash);
}
String contentsAtPath(String commit, String path) {
return runCommand(
'git',
['show', '$commit:$path'],
workingDirectory: _workingDirectory,
);
}
void writeFile(String path, String contents) {
File(path).writeAsStringSync(contents);
}
void commit(String message) {
runCommand(
'git',
['commit', '-a', '-m', message],
workingDirectory: _workingDirectory,
);
}
Version localHead() {
return versionFrom(
runCommand(
'git',
['rev-parse', 'HEAD'],
workingDirectory: _workingDirectory,
),
);
}
}
extension VersionCommands on Version {
String contentsAtPath(String path) {
return repo.contentsAtPath(hash, path);
}
}
String printLatestForBranch(Repo repo, String branch) {
final hash = repo.getLatestCommit(branch);
final tags = repo.getTagsFor(hash);
final tagsString = tags.isEmpty ? '' : " (${tags.join(', ')})";
print('${repo.name.padRight(10)} ${branch.padRight(25)} $hash$tagsString');
return hash;
}
void printVersions(VersionSet versions, int indent) {
print("${' ' * indent}engine ${versions.engine}");
print("${' ' * indent}flutter ${versions.flutter}");
print("${' ' * indent}buildroot ${versions.buildroot}");
}
VersionSet getFlutterVersions(String flutterHash) {
final engineHash = Repo.flutter
.contentsAtPath(flutterHash, 'bin/internal/engine.version')
.trim();
final depsContents =
Repo.engine.contentsAtPath(engineHash, Paths.engineDEPS.path);
final buildrootVersion = parseBuildRoot(depsContents);
return VersionSet(
engine: Repo.engine.versionFrom(engineHash),
flutter: Repo.flutter.versionFrom(flutterHash),
buildroot: Repo.buildroot.versionFrom(buildrootVersion),
);
}
String parseBuildRoot(String depsContents) {
final lines = depsContents.split('\n');
// Example:
// 'src': 'https://github.com/flutter/buildroot.git' + '@' + '059d155b4d452efd9c4427c45cddfd9445144869',
final buildrootLine = lines.firstWhere((line) => line.contains("'src': "));
final regexp = RegExp('([0-9a-f]{40})');
final match = regexp.firstMatch(buildrootLine);
if (match == null) {
throw Exception('Failed to parse buildroot version from $buildrootLine');
}
return match.group(0)!;
}
VersionSet getForkpoints() {
final flutterForkpoint = Repo.flutter.getForkPoint();
// final engineForkpoint = Repo.engine.getForkPoint();
// final buildrootForkpoint = Repo.buildroot.getForkPoint();
// return VersionSet(
// engine: engineForkpoint,
// flutter: flutterForkpoint,
// buildroot: buildrootForkpoint,
// );
// This is slightly error-prone in that we're assuming that our engine and
// buildroot forks started from the correct commit. But I'm not sure how
// to determine the forkpoint otherwise. engine and buildroot don't have
// a stable branch, yet they do seem to "branch" for stable releases at the
// x.x.0 release.
return getFlutterVersions(flutterForkpoint.hash);
}
/// Generate rebase commands for the repo given the version sets.
String rebaseRepo(
Repo repo, {
required VersionSet forkpoints,
required VersionSet upstream,
required VersionSet shorebird,
bool dryRun = true,
}) {
final run = dryRun ? dryRunCommand : runCommand;
if (upstream[repo] != forkpoints[repo]) {
print('Rebasing ${repo.name}...');
run(
'git',
[
'rebase',
'--onto',
upstream[repo].ref,
forkpoints[repo].ref,
shorebird[repo].ref
],
workingDirectory: repo._workingDirectory,
);
return dryRun ? 'new-${repo.name}-hash' : repo.getLatestCommit('HEAD');
} else {
print('Skipping ${repo.name} (unchanged: ${upstream[repo].ref})');
}
return shorebird[repo].ref;
}
/// Cutler will always try to upgrade to the latest flutter version
/// on the stable branch.
void main(List<String> args) {
config = parseArgs(args);
if (config.doUpdate) {
for (final repo in Repo.values) {
print('Updating ${repo.name}...');
runCommand(
'git',
['fetch', '--all'],
workingDirectory: repo._workingDirectory,
);
}
}
// This prints the latest versions on our branches, not necessarily
// the ones shorebird depends on.
// for (final repo in Repo.values) {
// printLatestForBranch(repo, repo.releaseBranch);
// }
// FIXME: This is wrong, but 0.0.7 doesn't have a flutter.version file yet.
final shorebirdFlutter =
Repo.flutter.getLatestCommit(Repo.flutter.releaseBranch);
// final shorebirdStable =
// Repo.shorebird.getLatestCommit(Repo.shorebird.releaseBranch);
// final shorebirdFlutter = Repo.flutter
// .contentsAtPath(shorebirdStable, 'bin/internal/flutter.version');
final shorebird = getFlutterVersions(shorebirdFlutter);
print('Shorebird stable:');
printVersions(shorebird, 2);
final forkpoints = getForkpoints();
print('Forkpoints:');
printVersions(forkpoints, 2);
// Figure out the latest version of Flutter.
final upstreamFlutter =
Repo.flutter.getLatestCommit(Repo.flutter.upstreamBranch);
// Figure out what versions that Flutter depends on.
final upstream = getFlutterVersions(upstreamFlutter);
print('Upstream stable:');
printVersions(upstream, 2);
Version doRebase(Repo repo) {
final newHash = rebaseRepo(
repo,
forkpoints: forkpoints,
upstream: upstream,
shorebird: shorebird,
dryRun: config.dryRun,
);
return repo.versionFrom(newHash, lookupTags: !config.dryRun);
}
// Rebase our repos.
var newHead = VersionSet(
buildroot: doRebase(Repo.buildroot),
engine: doRebase(Repo.engine),
flutter: doRebase(Repo.flutter),
);
// Make sure engine points to this buildroot.
// If not, update it and commit.
if (shorebird[Repo.buildroot] != newHead.buildroot) {
print('Updating engine DEPS...');
final depsContents =
shorebird[Repo.engine].contentsAtPath(Paths.engineDEPS.path);
final newDepsContents = depsContents.replaceAll(
shorebird[Repo.buildroot].hash,
newHead.buildroot.hash,
);
if (newDepsContents != depsContents) {
if (config.dryRun) {
print('Would have changed DEPS lines:');
final changes = newDepsContents.split('\n').where((line) {
return line.contains(newHead.buildroot.hash);
});
print(changes);
} else {
Repo.engine.writeFile(Paths.engineDEPS.path, newDepsContents);
Repo.engine.commit('Update DEPS.');
}
} else {
print('ERROR: engine DEPS is already up to date?');
exit(1);
}
newHead = newHead.copyWith(engine: Repo.engine.localHead());
}
// Update our forked flutter's engine version.
if (shorebird[Repo.engine] != newHead.engine) {
print('Updating flutter engine version...');
final existingEngineVersion = shorebird[Repo.flutter]
.contentsAtPath(Paths.flutterEngineVersion.path)
.trim();
if (newHead.engine.hash != existingEngineVersion) {
if (config.dryRun) {
print(
' Change engine.version: ${newHead.engine.hash} from '
'$existingEngineVersion',
);
} else {
Repo.flutter
.writeFile(Paths.flutterEngineVersion.path, newHead.engine.hash);
Repo.flutter.commit('Update engine.version');
}
} else {
print('ERROR: flutter engine.version is already up to date?');
exit(1);
}
newHead = newHead.copyWith(flutter: Repo.flutter.localHead());
}
// Update Shorebird's version of Flutter.
if (shorebird[Repo.flutter] != newHead.flutter) {
print('Updating shorebird flutter version...');
if (config.dryRun) {
print(
' Change flutter.version: ${newHead.flutter.hash} from '
'${shorebird[Repo.flutter].hash}',
);
} else {
Repo.shorebird
.writeFile(Paths.shorebirdFlutterVersion.path, newHead.flutter.hash);
Repo.shorebird.commit('Update flutter.version');
}
}
// To push a new engine:
// git branch stable_codepush HEAD --force
// git push origin stable_codepush --force
// Engine rev: d470ae25d21f583abe128f7b838476afd5e45bde
// To push new flutter:
// % git rebase --onto 3.7.12 3.7.10 \
// 45fc514f1a9c347a3af76b02baf980a4d88b7879
// Auto-merging bin/internal/engine.version
// CONFLICT (content): Merge conflict in bin/internal/engine.version
// error: could not apply c2185f5f6c... chore: Update engine version to
// shorebird-3.7.10
// hint: Resolve all conflicts manually, mark them as resolved with
// hint: "git add/rm <conflicted_files>", then run "git rebase --continue".
// hint: You can instead skip this commit: run "git rebase --skip".
// hint: To abort and get back to the state before "git rebase", run "git
// rebase --abort".
// Could not apply c2185f5f6c... chore: Update engine version to
// shorebird-3.7.10
// Flutter revision: 58dff390738f9c512ab7e0638af9573515b0409c
// To push new shorebird:
}
+56
View File
@@ -0,0 +1,56 @@
import 'dart:io';
import 'package:args/args.dart';
// https://github.com/dart-lang/sdk/issues/18466
// https://github.com/dart-lang/path/issues/117#issuecomment-1034313012
String expandUser(String path) {
// This is not "well written", but eventually I guess we should
// write one and publish it if Dart doesn't add an equivalent method.
// If we're in CMD or PowerShell, we need to expand %USERPROFILE%. If we're
// in WSL, we need to expand $HOME.
// This should handle ~ and ~user.
if (path.startsWith('~')) {
final home = Platform.environment['HOME'];
if (home == null) {
throw Exception('Failed to expand $path');
}
return path.replaceFirst('~', home);
}
return path;
}
// Config is basically just our typed ArgResults held as a global.
class Config {
Config({
required this.checkoutsRoot,
required this.verbose,
required this.dryRun,
required this.doUpdate,
});
final String checkoutsRoot;
final bool verbose;
final bool dryRun;
final bool doUpdate;
}
late final Config config;
Config parseArgs(List<String> args) {
final parser = ArgParser()
..addFlag('verbose', abbr: 'v')
..addOption(
'root',
defaultsTo: '.',
help: 'Directory in which to find checkouts.',
)
..addFlag('dry-run', defaultsTo: true, help: 'Do not actually run git.')
..addFlag('update', defaultsTo: true, help: 'Update checkouts.');
final results = parser.parse(args);
return Config(
verbose: results['verbose'] as bool,
checkoutsRoot: expandUser(results['root'] as String),
dryRun: results['dry-run'] as bool,
doUpdate: results['update'] as bool,
);
}
+103
View File
@@ -0,0 +1,103 @@
import 'package:meta/meta.dart';
enum Repo {
shorebird(
name: 'shorebird',
path: '_shorebird/shorebird',
url: 'https://github.com/shorebirdtech/shorebird.git',
releaseBranch: 'origin/stable',
upstreamBranch: 'origin/main',
),
flutter(
name: 'flutter',
path: 'flutter',
url: 'https://github.com/shorebirdtech/flutter.git',
releaseBranch: 'origin/stable',
upstreamBranch: 'upstream/stable',
),
engine(
name: 'engine',
path: 'engine/src/flutter',
url: 'https://github.com/shorebirdtech/engine.git',
releaseBranch: 'origin/stable_codepush',
upstreamBranch: 'upstream/master',
),
buildroot(
name: 'buildroot',
path: 'engine/src',
url: 'https://github.com/shorebirdtech/builddoor.git',
releaseBranch: 'origin/stable_codepush',
upstreamBranch: 'upstream/master',
);
const Repo({
required this.name,
required this.path,
required this.url,
required this.releaseBranch,
required this.upstreamBranch,
});
final String name;
final String path;
final String url;
final String releaseBranch;
final String upstreamBranch;
}
@immutable
class Version {
const Version({
required this.hash,
required this.repo,
this.aliases = const [],
});
final String hash;
final List<String> aliases;
final Repo repo;
String get ref => aliases.isEmpty ? hash : aliases.first;
@override
String toString() {
final aliasesString = aliases.isEmpty ? '' : " (${aliases.join(', ')})";
return '$hash$aliasesString';
}
@override
bool operator ==(Object other) {
if (other is! Version) {
return false;
}
return other.hash == hash && other.repo == repo;
}
@override
int get hashCode => Object.hashAll([hash, repo]);
}
class VersionSet {
const VersionSet({
required this.engine,
required this.flutter,
required this.buildroot,
});
final Version engine;
final Version flutter;
final Version buildroot;
Version operator [](Repo repo) => {
Repo.engine: engine,
Repo.flutter: flutter,
Repo.buildroot: buildroot,
}[repo]!;
VersionSet copyWith({Version? engine, Version? flutter, Version? buildroot}) {
return VersionSet(
engine: engine ?? this.engine,
flutter: flutter ?? this.flutter,
buildroot: buildroot ?? this.buildroot,
);
}
}
+373
View File
@@ -0,0 +1,373 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
_fe_analyzer_shared:
dependency: transitive
description:
name: _fe_analyzer_shared
sha256: a36ec4843dc30ea6bf652bf25e3448db6c5e8bcf4aa55f063a5d1dad216d8214
url: "https://pub.dev"
source: hosted
version: "58.0.0"
analyzer:
dependency: transitive
description:
name: analyzer
sha256: cc4242565347e98424ce9945c819c192ec0838cb9d1f6aa4a97cc96becbc5b27
url: "https://pub.dev"
source: hosted
version: "5.10.0"
args:
dependency: "direct main"
description:
name: args
sha256: "4cab82a83ffef80b262ddedf47a0a8e56ee6fbf7fe21e6e768b02792034dd440"
url: "https://pub.dev"
source: hosted
version: "2.4.0"
async:
dependency: transitive
description:
name: async
sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
url: "https://pub.dev"
source: hosted
version: "2.11.0"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
collection:
dependency: transitive
description:
name: collection
sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c"
url: "https://pub.dev"
source: hosted
version: "1.17.1"
convert:
dependency: transitive
description:
name: convert
sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592"
url: "https://pub.dev"
source: hosted
version: "3.1.1"
coverage:
dependency: transitive
description:
name: coverage
sha256: "2fb815080e44a09b85e0f2ca8a820b15053982b2e714b59267719e8a9ff17097"
url: "https://pub.dev"
source: hosted
version: "1.6.3"
crypto:
dependency: transitive
description:
name: crypto
sha256: aa274aa7774f8964e4f4f38cc994db7b6158dd36e9187aaceaddc994b35c6c67
url: "https://pub.dev"
source: hosted
version: "3.0.2"
file:
dependency: transitive
description:
name: file
sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d"
url: "https://pub.dev"
source: hosted
version: "6.1.4"
frontend_server_client:
dependency: transitive
description:
name: frontend_server_client
sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612"
url: "https://pub.dev"
source: hosted
version: "3.2.0"
glob:
dependency: transitive
description:
name: glob
sha256: "4515b5b6ddb505ebdd242a5f2cc5d22d3d6a80013789debfbda7777f47ea308c"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
http_multi_server:
dependency: transitive
description:
name: http_multi_server
sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b"
url: "https://pub.dev"
source: hosted
version: "3.2.1"
http_parser:
dependency: transitive
description:
name: http_parser
sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b"
url: "https://pub.dev"
source: hosted
version: "4.0.2"
io:
dependency: transitive
description:
name: io
sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e"
url: "https://pub.dev"
source: hosted
version: "1.0.4"
js:
dependency: transitive
description:
name: js
sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
url: "https://pub.dev"
source: hosted
version: "0.6.7"
logging:
dependency: transitive
description:
name: logging
sha256: "04094f2eb032cbb06c6f6e8d3607edcfcb0455e2bb6cbc010cb01171dcb64e6d"
url: "https://pub.dev"
source: hosted
version: "1.1.1"
matcher:
dependency: transitive
description:
name: matcher
sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb"
url: "https://pub.dev"
source: hosted
version: "0.12.15"
meta:
dependency: "direct main"
description:
name: meta
sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3"
url: "https://pub.dev"
source: hosted
version: "1.9.1"
mime:
dependency: transitive
description:
name: mime
sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e
url: "https://pub.dev"
source: hosted
version: "1.0.4"
node_preamble:
dependency: transitive
description:
name: node_preamble
sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db"
url: "https://pub.dev"
source: hosted
version: "2.0.2"
package_config:
dependency: transitive
description:
name: package_config
sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd"
url: "https://pub.dev"
source: hosted
version: "2.1.0"
path:
dependency: "direct main"
description:
name: path
sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917"
url: "https://pub.dev"
source: hosted
version: "1.8.3"
pool:
dependency: transitive
description:
name: pool
sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a"
url: "https://pub.dev"
source: hosted
version: "1.5.1"
pub_semver:
dependency: transitive
description:
name: pub_semver
sha256: "307de764d305289ff24ad257ad5c5793ce56d04947599ad68b3baa124105fc17"
url: "https://pub.dev"
source: hosted
version: "2.1.3"
shelf:
dependency: transitive
description:
name: shelf
sha256: c24a96135a2ccd62c64b69315a14adc5c3419df63b4d7c05832a346fdb73682c
url: "https://pub.dev"
source: hosted
version: "1.4.0"
shelf_packages_handler:
dependency: transitive
description:
name: shelf_packages_handler
sha256: aef74dc9195746a384843102142ab65b6a4735bb3beea791e63527b88cc83306
url: "https://pub.dev"
source: hosted
version: "3.0.1"
shelf_static:
dependency: transitive
description:
name: shelf_static
sha256: e792b76b96a36d4a41b819da593aff4bdd413576b3ba6150df5d8d9996d2e74c
url: "https://pub.dev"
source: hosted
version: "1.1.1"
shelf_web_socket:
dependency: transitive
description:
name: shelf_web_socket
sha256: a988c0e8d8ffbdb8a28aa7ec8e449c260f3deb808781fe1284d22c5bba7156e8
url: "https://pub.dev"
source: hosted
version: "1.0.3"
source_map_stack_trace:
dependency: transitive
description:
name: source_map_stack_trace
sha256: "84cf769ad83aa6bb61e0aa5a18e53aea683395f196a6f39c4c881fb90ed4f7ae"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
source_maps:
dependency: transitive
description:
name: source_maps
sha256: "708b3f6b97248e5781f493b765c3337db11c5d2c81c3094f10904bfa8004c703"
url: "https://pub.dev"
source: hosted
version: "0.10.12"
source_span:
dependency: transitive
description:
name: source_span
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
url: "https://pub.dev"
source: hosted
version: "1.10.0"
stack_trace:
dependency: transitive
description:
name: stack_trace
sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5
url: "https://pub.dev"
source: hosted
version: "1.11.0"
stream_channel:
dependency: transitive
description:
name: stream_channel
sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
string_scanner:
dependency: transitive
description:
name: string_scanner
sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
url: "https://pub.dev"
source: hosted
version: "1.2.0"
term_glyph:
dependency: transitive
description:
name: term_glyph
sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
url: "https://pub.dev"
source: hosted
version: "1.2.1"
test:
dependency: "direct dev"
description:
name: test
sha256: "4f92f103ef63b1bbac6f4bd1930624fca81b2574464482512c4f0896319be575"
url: "https://pub.dev"
source: hosted
version: "1.24.2"
test_api:
dependency: transitive
description:
name: test_api
sha256: daadc9baabec998b062c9091525aa95786508b1c48e9c30f1f891b8bf6ff2e64
url: "https://pub.dev"
source: hosted
version: "0.5.2"
test_core:
dependency: transitive
description:
name: test_core
sha256: "3642b184882f79e76ca57a9230fb971e494c3c1fd09c21ae3083ce891bcc0aa1"
url: "https://pub.dev"
source: hosted
version: "0.5.2"
typed_data:
dependency: transitive
description:
name: typed_data
sha256: "26f87ade979c47a150c9eaab93ccd2bebe70a27dc0b4b29517f2904f04eb11a5"
url: "https://pub.dev"
source: hosted
version: "1.3.1"
very_good_analysis:
dependency: "direct dev"
description:
name: very_good_analysis
sha256: ebc48c51db35beeeec8c414e32f7bd78e612bd7f5992ccb0d46e19edaeb40b08
url: "https://pub.dev"
source: hosted
version: "4.0.0+1"
vm_service:
dependency: transitive
description:
name: vm_service
sha256: f6deed8ed625c52864792459709183da231ebf66ff0cf09e69b573227c377efe
url: "https://pub.dev"
source: hosted
version: "11.3.0"
watcher:
dependency: transitive
description:
name: watcher
sha256: "6a7f46926b01ce81bfc339da6a7f20afbe7733eff9846f6d6a5466aa4c6667c0"
url: "https://pub.dev"
source: hosted
version: "1.0.2"
web_socket_channel:
dependency: transitive
description:
name: web_socket_channel
sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b
url: "https://pub.dev"
source: hosted
version: "2.4.0"
webkit_inspection_protocol:
dependency: transitive
description:
name: webkit_inspection_protocol
sha256: "67d3a8b6c79e1987d19d848b0892e582dbb0c66c57cc1fef58a177dd2aa2823d"
url: "https://pub.dev"
source: hosted
version: "1.2.0"
yaml:
dependency: transitive
description:
name: yaml
sha256: "23812a9b125b48d4007117254bca50abb6c712352927eece9e155207b1db2370"
url: "https://pub.dev"
source: hosted
version: "3.1.1"
sdks:
dart: ">=2.19.6 <3.0.0"
+16
View File
@@ -0,0 +1,16 @@
name: cutler
description: A tool for managing Flutter forks.
version: 1.0.0
repository: https://github.com/shorebirdtech/shorebird
environment:
sdk: '>=2.19.6 <3.0.0'
dev_dependencies:
test: ^1.21.0
very_good_analysis: ^4.0.0
dependencies:
args: ^2.4.0
meta: ^1.9.1
path: ^1.8.3