Skip to content

Commit 5da8a7a

Browse files
mosuemdevoncarew
andauthored
Add package:trebuchet to repo. (#289)
Moving over from https://github.com/mosuem/merger/. We might want to move the issue transfer tool from `repo_manage` to this tool as well, it would be cleaner. --- - [x] I’ve reviewed the contributor guide and applied the relevant portions to this PR. <details> <summary>Contribution guidelines:</summary><br> - See our [contributor guide](https://github.com/dart-lang/.github/blob/main/CONTRIBUTING.md) for general expectations for PRs. - Larger or significant changes should be discussed in an issue before creating a PR. - Contributions to our repos should follow the [Dart style guide](https://dart.dev/guides/language/effective-dart) and use `dart format`. - Most changes should add an entry to the changelog and may need to [rev the pubspec package version](https://github.com/dart-lang/sdk/blob/main/docs/External-Package-Maintenance.md#making-a-change). - Changes to packages require [corresponding tests](https://github.com/dart-lang/.github/blob/main/CONTRIBUTING.md#Testing). Note that many Dart repos have a weekly cadence for reviewing PRs - please allow for some latency before initial review feedback. </details> --------- Co-authored-by: Devon Carew <[email protected]>
1 parent dd8d12a commit 5da8a7a

File tree

5 files changed

+257
-0
lines changed

5 files changed

+257
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ This repository is home to general Dart Ecosystem tools and packages.
1414
| [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) |
1515
| [repo_manage](pkgs/repo_manage/) | Miscellaneous issue, repo, and PR query tools. | |
1616
| [sdk_triage_bot](pkgs/sdk_triage_bot/) | A triage automation tool for dart-lang/sdk issues. | |
17+
| [trebuchet](pkgs/trebuchet/) | A tool for moving existing packages into monorepos. | |
1718

1819
## Publishing automation
1920

pkgs/trebuchet/README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
## What's this?
2+
3+
This is a tool to move existing packages into monorepos.
4+
5+
## Running this tool
6+
7+
```bash
8+
dart run bin/trebuchet.dart \
9+
--input-name coverage \
10+
--branch-name master \
11+
--input-path ~/projects/coverage/ \
12+
--target-path ~/projects/tools/ \
13+
--git-filter-repo ~/tools/git-filter-repo
14+
```
15+
16+
This basically executes the instructions at https://github.com/dart-lang/ecosystem/wiki/Merging-existing-repos-into-a-monorepo
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
include: package:dart_flutter_team_lints/analysis_options.yaml
2+
3+
linter:
4+
rules:
5+
- prefer_final_locals

pkgs/trebuchet/bin/trebuchet.dart

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'dart:io';
6+
7+
import 'package:args/args.dart';
8+
import 'package:path/path.dart' as p;
9+
10+
Future<void> main(List<String> arguments) async {
11+
final argParser = ArgParser()
12+
..addOption(
13+
'input-name',
14+
help: 'Name of the package which should be transferred to a mono-repo',
15+
)
16+
..addOption(
17+
'input-path',
18+
help: 'Path to the package which should be transferred to a mono-repo',
19+
)
20+
..addOption(
21+
'target-path',
22+
help: 'Path to the mono-repo',
23+
)
24+
..addOption(
25+
'branch-name',
26+
help: 'The name of the main branch on the input repo',
27+
defaultsTo: 'main',
28+
)
29+
..addOption(
30+
'git-filter-repo',
31+
help: 'Path to the git-filter-repo tool',
32+
)
33+
..addFlag(
34+
'dry-run',
35+
help: 'Do not actually execute any of the steps',
36+
defaultsTo: false,
37+
)
38+
..addFlag(
39+
'help',
40+
abbr: 'h',
41+
help: 'Prints usage info',
42+
negatable: false,
43+
);
44+
45+
String? input;
46+
String? inputPath;
47+
String? targetPath;
48+
String? branchName;
49+
String? gitFilterRepo;
50+
bool dryRun;
51+
try {
52+
final parsed = argParser.parse(arguments);
53+
if (parsed.flag('help')) {
54+
print(argParser.usage);
55+
exit(0);
56+
}
57+
58+
input = parsed.option('input-name')!;
59+
inputPath = parsed.option('input-path')!;
60+
targetPath = parsed.option('target-path')!;
61+
branchName = parsed.option('branch-name')!;
62+
gitFilterRepo = parsed.option('git-filter-repo')!;
63+
dryRun = parsed.flag('dry-run');
64+
} catch (e) {
65+
print(e);
66+
print('');
67+
print(argParser.usage);
68+
exit(1);
69+
}
70+
71+
final trebuchet = Trebuchet(
72+
input: input,
73+
inputPath: inputPath,
74+
targetPath: targetPath,
75+
branchName: branchName,
76+
gitFilterRepo: gitFilterRepo,
77+
dryRun: dryRun,
78+
);
79+
80+
await trebuchet.hurl();
81+
}
82+
83+
class Trebuchet {
84+
final String input;
85+
final String inputPath;
86+
final String targetPath;
87+
final String branchName;
88+
final String gitFilterRepo;
89+
final bool dryRun;
90+
91+
Trebuchet({
92+
required this.input,
93+
required this.inputPath,
94+
required this.targetPath,
95+
required this.branchName,
96+
required this.gitFilterRepo,
97+
required this.dryRun,
98+
});
99+
100+
Future<void> hurl() async {
101+
print('Check existence of python3 on path');
102+
await runProcess(
103+
'python3',
104+
['--version'],
105+
inTarget: false,
106+
);
107+
108+
print('Start moving package');
109+
110+
print('Rename to `pkgs/`');
111+
await filterRepo(['--path-rename', ':pkgs/$input/']);
112+
113+
print('Prefix tags');
114+
await filterRepo(['--tag-rename', ':$input-']);
115+
116+
print('Replace issue references in commit messages');
117+
await inTempDir((tempDirectory) async {
118+
final regexFile = File(p.join(tempDirectory.path, 'expressions.txt'));
119+
await regexFile.create();
120+
await regexFile.writeAsString('regex:#(\\d)==>dart-lang/$input#\\1');
121+
await filterRepo(['--replace-message', regexFile.path]);
122+
});
123+
124+
print('Create branch at target');
125+
await runProcess('git', ['checkout', '-b', 'merge-$input-package']);
126+
127+
print('Add a remote for the local clone of the moving package');
128+
await runProcess(
129+
'git',
130+
['remote', 'add', '${input}_package', inputPath],
131+
);
132+
await runProcess('git', ['fetch', '${input}_package']);
133+
134+
print('Merge branch into monorepo');
135+
await runProcess(
136+
'git',
137+
[
138+
'merge',
139+
'--allow-unrelated-histories',
140+
'${input}_package/$branchName',
141+
'-m',
142+
'Merge package:$input into shared tool repository'
143+
],
144+
);
145+
146+
final shouldPush = getInput('Push to remote? (y/N)');
147+
148+
if (shouldPush) {
149+
print('Push to remote');
150+
await runProcess(
151+
'git',
152+
['push', '--set-upstream', 'origin', 'merge-$input-package'],
153+
);
154+
}
155+
156+
print('DONE!');
157+
print('''
158+
Steps left to do:
159+
160+
- Move and fix workflow files
161+
${shouldPush ? '' : '- Run `git push --set-upstream origin merge-$input-package` in the monorepo directory'}
162+
- Disable squash-only in GitHub settings, and merge with a fast forward merge to the main branch, enable squash-only in GitHub settings.
163+
- Push tags to github using `git tag --list '$input*' | xargs git push origin`
164+
- Follow up with a PR adding links to the top-level readme table.
165+
- Add a commit to https://github.com/dart-lang/$input/ with it's readme pointing to the monorepo.
166+
- Update the auto-publishing settings on pub.dev/packages/$input.
167+
- Archive https://github.com/dart-lang/$input/.
168+
''');
169+
}
170+
171+
bool getInput(String question) {
172+
print(question);
173+
final line = stdin.readLineSync()?.toLowerCase();
174+
return line == 'y' || line == 'yes';
175+
}
176+
177+
Future<void> runProcess(
178+
String executable,
179+
List<String> arguments, {
180+
bool inTarget = true,
181+
}) async {
182+
final workingDirectory = inTarget ? targetPath : inputPath;
183+
print('----------');
184+
print('Running `$executable $arguments` in $workingDirectory');
185+
if (!dryRun) {
186+
final processResult = await Process.run(
187+
executable,
188+
arguments,
189+
workingDirectory: workingDirectory,
190+
);
191+
print('stdout:');
192+
print(processResult.stdout);
193+
if ((processResult.stderr as String).isNotEmpty) {
194+
print('stderr:');
195+
print(processResult.stderr);
196+
}
197+
if (processResult.exitCode != 0) {
198+
throw ProcessException(executable, arguments);
199+
}
200+
} else {
201+
print('Not running, as --dry-run is set.');
202+
}
203+
print('==========');
204+
}
205+
206+
Future<void> filterRepo(List<String> args) async {
207+
await runProcess(
208+
'python3',
209+
[p.relative(gitFilterRepo, from: inputPath), ...args],
210+
inTarget: false,
211+
);
212+
}
213+
}
214+
215+
Future<void> inTempDir(Future<void> Function(Directory temp) f) async {
216+
final tempDirectory = await Directory.systemTemp.createTemp();
217+
await f(tempDirectory);
218+
await tempDirectory.delete(recursive: true);
219+
}

pkgs/trebuchet/pubspec.yaml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
name: trebuchet
2+
description: A tool for hurling packages into monorepos.
3+
4+
publish_to: none
5+
6+
environment:
7+
sdk: ^3.3.0
8+
9+
dependencies:
10+
args: ^2.5.0
11+
path: ^1.9.0
12+
13+
dev_dependencies:
14+
dart_flutter_team_lints: ^3.2.0
15+
lints: ^4.0.0
16+
test: ^1.24.0

0 commit comments

Comments
 (0)