Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
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
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 hurling packages into monorepos. | |

## Publishing automation

Expand Down
7 changes: 7 additions & 0 deletions pkgs/trebuchet/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Run

```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
212 changes: 212 additions & 0 deletions pkgs/trebuchet/bin/trebuchet.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
// 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(
'push',
help: 'Whether to push the branch to remote',
defaultsTo: true,
)
..addFlag(
'help',
abbr: 'h',
help: 'Prints usage info',
negatable: false,
)
..addFlag(
'dry-run',
help: 'Do not actually execute any of the steps',
defaultsTo: false,
);

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

input = parsed['input-name'] as String;
inputPath = parsed['input-path'] as String;
targetPath = parsed['target-path'] as String;
branchName = parsed['branch-name'] as String;
gitFilterRepo = parsed['git-filter-repo'] as String;
push = parsed.flag('push');
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,
push: push,
dryRun: dryRun,
);

await trebuchet.hurl();
}

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

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

Future<void> hurl() async {
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'
],
);

if (push) {
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
${push ? '' : '- 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/.
''');
}

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',
[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