diff --git a/FORKING_FLUTTER.md b/FORKING_FLUTTER.md index a10122f6..4008517b 100644 --- a/FORKING_FLUTTER.md +++ b/FORKING_FLUTTER.md @@ -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 diff --git a/packages/cutler/.gitignore b/packages/cutler/.gitignore new file mode 100644 index 00000000..3a857904 --- /dev/null +++ b/packages/cutler/.gitignore @@ -0,0 +1,3 @@ +# https://dart.dev/guides/libraries/private-files +# Created by `dart pub` +.dart_tool/ diff --git a/packages/cutler/README.md b/packages/cutler/README.md new file mode 100644 index 00000000..4da7a39c --- /dev/null +++ b/packages/cutler/README.md @@ -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 \ No newline at end of file diff --git a/packages/cutler/analysis_options.yaml b/packages/cutler/analysis_options.yaml new file mode 100644 index 00000000..4369e472 --- /dev/null +++ b/packages/cutler/analysis_options.yaml @@ -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 diff --git a/packages/cutler/bin/cutler.dart b/packages/cutler/bin/cutler.dart new file mode 100644 index 00000000..92c029a2 --- /dev/null +++ b/packages/cutler/bin/cutler.dart @@ -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 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 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 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 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 ", 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: +} diff --git a/packages/cutler/lib/config.dart b/packages/cutler/lib/config.dart new file mode 100644 index 00000000..5a5f2d80 --- /dev/null +++ b/packages/cutler/lib/config.dart @@ -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 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, + ); +} diff --git a/packages/cutler/lib/model.dart b/packages/cutler/lib/model.dart new file mode 100644 index 00000000..a25f7387 --- /dev/null +++ b/packages/cutler/lib/model.dart @@ -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 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, + ); + } +} diff --git a/packages/cutler/pubspec.lock b/packages/cutler/pubspec.lock new file mode 100644 index 00000000..e938c439 --- /dev/null +++ b/packages/cutler/pubspec.lock @@ -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" diff --git a/packages/cutler/pubspec.yaml b/packages/cutler/pubspec.yaml new file mode 100644 index 00000000..fcfeea40 --- /dev/null +++ b/packages/cutler/pubspec.yaml @@ -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