Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add package:trebuchet to repo. #289

Merged
merged 15 commits into from
Aug 26, 2024
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ This repository is home to general Dart Ecosystem tools and packages.
| [firehose](pkgs/firehose/) | A tool to automate publishing of Pub packages from GitHub actions. | [![pub package](https://img.shields.io/pub/v/firehose.svg)](https://pub.dev/packages/firehose) |
| [repo_manage](pkgs/repo_manage/) | Miscellaneous issue, repo, and PR query tools. | |
| [sdk_triage_bot](pkgs/sdk_triage_bot/) | A triage automation tool for dart-lang/sdk issues. | |
| [trebuchet](pkgs/trebuchet/) | A tool for moving existing packages into monorepos. | |

## Publishing automation

Expand Down
16 changes: 16 additions & 0 deletions pkgs/trebuchet/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
## What's this?

This is a tool to move existing packages into monorepos.

## Running this tool

```bash
dart run bin/trebuchet.dart \
--input-name coverage \
--branch-name master \
--input-path ~/projects/coverage/ \
--target-path ~/projects/tools/ \
--git-filter-repo ~/tools/git-filter-repo
```

This basically executes the instructions at https://github.com/dart-lang/ecosystem/wiki/Merging-existing-repos-into-a-monorepo
5 changes: 5 additions & 0 deletions pkgs/trebuchet/analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
include: package:dart_flutter_team_lints/analysis_options.yaml

linter:
rules:
- prefer_final_locals
219 changes: 219 additions & 0 deletions pkgs/trebuchet/bin/trebuchet.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
// Copyright (c) 2024, 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:io';

import 'package:args/args.dart';
import 'package:path/path.dart' as p;

Future<void> main(List<String> arguments) async {
final argParser = ArgParser()
..addOption(
'input-name',
help: 'Name of the package which should be transferred to a mono-repo',
)
..addOption(
'input-path',
help: 'Path to the package which should be transferred to a mono-repo',
)
..addOption(
'target-path',
help: 'Path to the mono-repo',
)
..addOption(
'branch-name',
help: 'The name of the main branch on the input repo',
defaultsTo: 'main',
)
..addOption(
'git-filter-repo',
help: 'Path to the git-filter-repo tool',
)
..addFlag(
'dry-run',
help: 'Do not actually execute any of the steps',
defaultsTo: false,
)
..addFlag(
'help',
abbr: 'h',
help: 'Prints usage info',
negatable: false,
);

String? input;
String? inputPath;
String? targetPath;
String? branchName;
String? gitFilterRepo;
bool dryRun;
try {
final parsed = argParser.parse(arguments);
if (parsed.flag('help')) {
print(argParser.usage);
exit(0);
}

input = parsed.option('input-name')!;
inputPath = parsed.option('input-path')!;
targetPath = parsed.option('target-path')!;
branchName = parsed.option('branch-name')!;
gitFilterRepo = parsed.option('git-filter-repo')!;
dryRun = parsed.flag('dry-run');
} catch (e) {
print(e);
print('');
print(argParser.usage);
exit(1);
}

final trebuchet = Trebuchet(
input: input,
inputPath: inputPath,
targetPath: targetPath,
branchName: branchName,
gitFilterRepo: gitFilterRepo,
dryRun: dryRun,
);

await trebuchet.hurl();
}

class Trebuchet {
final String input;
final String inputPath;
final String targetPath;
final String branchName;
final String gitFilterRepo;
final bool dryRun;

Trebuchet({
required this.input,
required this.inputPath,
required this.targetPath,
required this.branchName,
required this.gitFilterRepo,
required this.dryRun,
});

Future<void> hurl() async {
print('Check existence of python3 on path');
await runProcess(
'python3',
['--version'],
inTarget: false,
);

print('Start moving package');

print('Rename to `pkgs/`');
await filterRepo(['--path-rename', ':pkgs/$input/']);

print('Prefix tags');
await filterRepo(['--tag-rename', ':$input-']);

print('Replace issue references in commit messages');
await inTempDir((tempDirectory) async {
final regexFile = File(p.join(tempDirectory.path, 'expressions.txt'));
await regexFile.create();
await regexFile.writeAsString('regex:#(\\d)==>dart-lang/$input#\\1');
await filterRepo(['--replace-message', regexFile.path]);
});

print('Create branch at target');
await runProcess('git', ['checkout', '-b', 'merge-$input-package']);

print('Add a remote for the local clone of the moving package');
await runProcess(
'git',
['remote', 'add', '${input}_package', inputPath],
);
await runProcess('git', ['fetch', '${input}_package']);

print('Merge branch into monorepo');
await runProcess(
'git',
[
'merge',
'--allow-unrelated-histories',
'${input}_package/$branchName',
'-m',
'Merge package:$input into shared tool repository'
],
);

final shouldPush = getInput('Push to remote? (y/N)');

if (shouldPush) {
print('Push to remote');
await runProcess(
'git',
['push', '--set-upstream', 'origin', 'merge-$input-package'],
);
}

print('DONE!');
print('''
Steps left to do:

- Move and fix workflow files
${shouldPush ? '' : '- Run `git push --set-upstream origin merge-$input-package` in the monorepo directory'}
- Disable squash-only in GitHub settings, and merge with a fast forward merge to the main branch, enable squash-only in GitHub settings.
- Push tags to github using `git tag --list '$input*' | xargs git push origin`
- Follow up with a PR adding links to the top-level readme table.
- Add a commit to https://github.com/dart-lang/$input/ with it's readme pointing to the monorepo.
- Update the auto-publishing settings on pub.dev/packages/$input.
- Archive https://github.com/dart-lang/$input/.
''');
}

bool getInput(String question) {
print(question);
final line = stdin.readLineSync()?.toLowerCase();
return line == 'y' || line == 'yes';
}

Future<void> runProcess(
String executable,
List<String> arguments, {
bool inTarget = true,
}) async {
final workingDirectory = inTarget ? targetPath : inputPath;
print('----------');
print('Running `$executable $arguments` in $workingDirectory');
if (!dryRun) {
final processResult = await Process.run(
executable,
arguments,
workingDirectory: workingDirectory,
);
print('stdout:');
print(processResult.stdout);
if ((processResult.stderr as String).isNotEmpty) {
print('stderr:');
print(processResult.stderr);
}
if (processResult.exitCode != 0) {
throw ProcessException(executable, arguments);
}
} else {
print('Not running, as --dry-run is set.');
}
print('==========');
}

Future<void> filterRepo(List<String> args) async {
await runProcess(
'python3',
mosuem marked this conversation as resolved.
Show resolved Hide resolved
[p.relative(gitFilterRepo, from: inputPath), ...args],
inTarget: false,
);
}
}

Future<void> inTempDir(Future<void> Function(Directory temp) f) async {
final tempDirectory = await Directory.systemTemp.createTemp();
await f(tempDirectory);
await tempDirectory.delete(recursive: true);
}
16 changes: 16 additions & 0 deletions pkgs/trebuchet/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
name: trebuchet
description: A tool for hurling packages into monorepos.

publish_to: none

environment:
sdk: ^3.3.0

dependencies:
args: ^2.5.0
path: ^1.9.0

dev_dependencies:
dart_flutter_team_lints: ^3.2.0
lints: ^4.0.0
test: ^1.24.0