Skip to content

Commit a1da087

Browse files
authoredMay 25, 2023
parse action versions out of workflow files (#420)
- parse action versions out of workflow files when the versions in that action file are managed by dependabot This will allow us to configure dependabot version upgrades for repos that use mono_repo to generate (and re-generate) their actions configurations. Code generate the hard coded versions from our own workflow file so that dependabot can manage that as well.
1 parent 69c4392 commit a1da087

16 files changed

+355
-58
lines changed
 

‎.github/workflows/dart.yml

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎mono_repo/CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## 6.5.7
2+
3+
- Updated `mono_repo` to use the existing action versions from the generated
4+
workflow file when dependabot is configured for the repo.
5+
16
## 6.5.6
27

38
- Restore the previous ordering behavior for jobs by using a secondary sort

‎mono_repo/README.md

+15
Original file line numberDiff line numberDiff line change
@@ -228,5 +228,20 @@ Look at these repositories for examples of `mono_repo` usage:
228228
* https://github.com/dart-lang/webdev
229229
* https://github.com/google/json_serializable.dart
230230

231+
## Mono_repo and Dependabot
232+
233+
Historically, package:mono_repo and Dependabot couldn't be used together -
234+
they both wanted to maintain your GitHub workflow file. We've adopted mono_repo
235+
so that you can now use both it and Dependabot in your repo.
236+
237+
When generating your workflow configuration (`mono_repo generate`) mono_repo
238+
will write out its current default action versions into the workflow file. If
239+
however it sees that the repo has a Dependabot configuration - has a file named
240+
`.github/dependabot.yaml` in the repo - mono_repo will instead parse the
241+
workflow file, read out the current action versions, and use those versions when
242+
re-generating the file. This lets mono_repo manage the overall structure of the
243+
file, while allowing Dependabot to independently move various action versions
244+
forward as necessary.
245+
231246
[Dart packages]: https://dart.dev/guides/libraries/create-library-packages
232247
[setup your PATH]: https://dart.dev/tools/pub/cmd/pub-global#running-a-script-from-your-path

‎mono_repo/lib/src/commands/github/action_info.dart

+15-9
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,43 @@
1+
import '../../root_config.dart';
2+
import 'action_versions.dart';
13
import 'job.dart';
24
import 'step.dart';
35

46
enum ActionInfo implements Comparable<ActionInfo> {
57
cache(
68
name: 'Cache Pub hosted dependencies',
79
repo: 'actions/cache',
8-
version: '88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8', // v3.3.1
10+
version: actionsCacheVersion,
911
),
1012
checkout(
1113
name: 'Checkout repository',
1214
repo: 'actions/checkout',
13-
version: '8e5e7e5ab8b370d6c329ec480221332ada57f0ab', // v3.5.2
15+
version: actionsCheckoutVersion,
1416
),
1517
setupDart(
1618
name: 'Setup Dart SDK',
1719
repo: 'dart-lang/setup-dart',
18-
version: 'd6a63dab3335f427404425de0fbfed4686d93c4f', // v1.5.0
20+
version: dartLangSetupDartVersion,
1921
),
2022
setupFlutter(
2123
name: 'Setup Flutter SDK',
2224
repo: 'subosito/flutter-action',
23-
version: '48cafc24713cca54bbe03cdc3a423187d413aafa', // v2.10.0
25+
version: subositoFlutterActionVersion,
2426
),
2527

2628
/// See https://github.com/marketplace/actions/coveralls-github-action
2729
coveralls(
2830
name: 'Upload coverage to Coveralls',
2931
repo: 'coverallsapp/github-action',
30-
version: 'master',
32+
version: coverallsappGithubActionVersion,
3133
completionJobFactory: _coverageCompletionJob,
3234
),
3335

3436
/// See https://github.com/marketplace/actions/codecov
3537
codecov(
3638
name: 'Upload coverage to codecov.io',
3739
repo: 'codecov/codecov-action',
38-
version: 'main',
40+
version: codecovCodecovActionVersion,
3941
);
4042

4143
const ActionInfo({
@@ -48,16 +50,19 @@ enum ActionInfo implements Comparable<ActionInfo> {
4850
final String repo;
4951
final String version;
5052
final String name;
51-
final Job Function()? completionJobFactory;
53+
final Job Function(RootConfig rootConfig)? completionJobFactory;
5254

5355
Step usage({
5456
String? name,
5557
String? id,
5658
Map<String, dynamic>? withContent,
59+
Map<String, String>? versionOverrides,
5760
}) {
5861
name ??= this.name;
62+
final useVersion =
63+
(versionOverrides == null ? null : versionOverrides[repo]) ?? version;
5964
final step = Step.uses(
60-
uses: '$repo@$version',
65+
uses: '$repo@$useVersion',
6166
id: id,
6267
name: name,
6368
withContent: withContent,
@@ -71,7 +76,7 @@ enum ActionInfo implements Comparable<ActionInfo> {
7176
int compareTo(ActionInfo other) => index.compareTo(other.index);
7277
}
7378

74-
Job _coverageCompletionJob() => Job(
79+
Job _coverageCompletionJob(RootConfig rootConfig) => Job(
7580
name: 'Mark Coveralls job finished',
7681
runsOn: 'ubuntu-latest',
7782
steps: [
@@ -82,6 +87,7 @@ Job _coverageCompletionJob() => Job(
8287
'github-token': r'${{ secrets.GITHUB_TOKEN }}',
8388
'parallel-finished': true
8489
},
90+
versionOverrides: rootConfig.existingActionVersions,
8591
)
8692
],
8793
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Copyright (c) 2023, 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+
/// This file is generated, and should not be modified by hand.
6+
///
7+
/// To regenerate it, run the `tool/generate_action_versions.dart` script.
8+
9+
const actionsCacheVersion = '88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8';
10+
const dartLangSetupDartVersion = 'd6a63dab3335f427404425de0fbfed4686d93c4f';
11+
const actionsCheckoutVersion = '8e5e7e5ab8b370d6c329ec480221332ada57f0ab';
12+
const subositoFlutterActionVersion = '48cafc24713cca54bbe03cdc3a423187d413aafa';
13+
const coverallsappGithubActionVersion = 'master';
14+
const codecovCodecovActionVersion = 'main';

‎mono_repo/lib/src/commands/github/generate.dart

+5
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ import '../../root_config.dart';
1212
import '../../user_exception.dart';
1313
import 'github_yaml.dart';
1414

15+
const dependabotFileNames = [
16+
'.github/dependabot.yaml',
17+
'.github/dependabot.yml',
18+
];
19+
1520
void generateGitHubActions(
1621
RootConfig rootConfig, {
1722
bool validateOnly = false,

‎mono_repo/lib/src/commands/github/github_yaml.dart

+25-10
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ Map<String, String> generateGitHubYml(RootConfig rootConfig) {
122122
Map.fromEntries(allJobs.map((e) => MapEntry(e.id, e.value)));
123123

124124
for (var completion in completionMap.entries) {
125-
final job = completion.key.completionJobFactory!()
125+
final job = completion.key.completionJobFactory!(rootConfig)
126126
..needs = completion.value.toList();
127127

128128
jobList['job_${jobList.length + 1}'] = job;
@@ -204,7 +204,10 @@ Iterable<_MapEntryWithStage> _listJobs(
204204

205205
for (var job in jobs) {
206206
if (job is _SelfValidateJob) {
207-
yield jobEntry(_selfValidateJob(rootConfig.monoConfig), job.stageName);
207+
yield jobEntry(
208+
_selfValidateJob(rootConfig.monoConfig, rootConfig),
209+
job.stageName,
210+
);
208211
continue;
209212
}
210213

@@ -340,6 +343,7 @@ extension on CIJobEntry {
340343
job.flavor,
341344
job.sdk,
342345
commandEntries,
346+
rootConfig,
343347
config: rootConfig.monoConfig,
344348
additionalCacheKeys: {
345349
'packages': packages.join('-'),
@@ -382,7 +386,8 @@ Job _githubJob(
382386
String runsOn,
383387
PackageFlavor packageFlavor,
384388
String sdkVersion,
385-
List<_CommandEntryBase> runCommands, {
389+
List<_CommandEntryBase> runCommands,
390+
RootConfig rootConfig, {
386391
required BasicConfiguration config,
387392
Map<String, String>? additionalCacheKeys,
388393
}) =>
@@ -393,17 +398,20 @@ Job _githubJob(
393398
if (!runsOn.startsWith('windows'))
394399
_cacheEntries(
395400
runsOn,
401+
rootConfig: rootConfig,
396402
additionalCacheKeys: {
397403
'sdk': sdkVersion,
398404
if (additionalCacheKeys != null) ...additionalCacheKeys,
399405
},
400406
),
401-
packageFlavor.setupStep(sdkVersion),
407+
packageFlavor.setupStep(sdkVersion, rootConfig),
402408
..._beforeSteps(runCommands.whereType<_CommandEntry>()),
403409
ActionInfo.checkout.usage(
404410
id: 'checkout',
411+
versionOverrides: rootConfig.existingActionVersions,
405412
),
406-
for (var command in runCommands) ...command.runContent(config),
413+
for (var command in runCommands)
414+
...command.runContent(config, rootConfig),
407415
],
408416
);
409417

@@ -424,7 +432,7 @@ class _CommandEntryBase {
424432

425433
_CommandEntryBase(this.name, this.run);
426434

427-
Iterable<Step> runContent(BasicConfiguration config) =>
435+
Iterable<Step> runContent(BasicConfiguration config, RootConfig rootConfig) =>
428436
[Step.run(name: name, run: run)];
429437
}
430438

@@ -444,15 +452,16 @@ class _CommandEntry extends _CommandEntryBase {
444452
});
445453

446454
@override
447-
Iterable<Step> runContent(BasicConfiguration config) => [
455+
Iterable<Step> runContent(BasicConfiguration config, RootConfig rootConfig) =>
456+
[
448457
Step.run(
449458
id: id,
450459
name: name,
451460
ifContent: ifCondition,
452461
workingDirectory: workingDirectory,
453462
run: run,
454463
),
455-
...?type?.afterEachSteps(workingDirectory, config),
464+
...?type?.afterEachSteps(workingDirectory, config, rootConfig),
456465
];
457466
}
458467

@@ -464,6 +473,7 @@ class _CommandEntry extends _CommandEntryBase {
464473
/// store and retrieve the cache.
465474
Step _cacheEntries(
466475
String runsOn, {
476+
required RootConfig rootConfig,
467477
Map<String, String>? additionalCacheKeys,
468478
}) {
469479
final cacheKeyParts = [
@@ -490,6 +500,7 @@ Step _cacheEntries(
490500
'key': restoreKeys.first,
491501
'restore-keys': restoreKeys.skip(1).join('\n'),
492502
},
503+
versionOverrides: rootConfig.existingActionVersions,
493504
);
494505
}
495506

@@ -500,7 +511,8 @@ String _maxLength(String input) {
500511
return input.substring(0, 512 - hash.length) + hash;
501512
}
502513

503-
Job _selfValidateJob(BasicConfiguration config) => _githubJob(
514+
Job _selfValidateJob(BasicConfiguration config, RootConfig rootConfig) =>
515+
_githubJob(
504516
selfValidateJobName,
505517
_ubuntuLatest,
506518
PackageFlavor.dart,
@@ -509,6 +521,7 @@ Job _selfValidateJob(BasicConfiguration config) => _githubJob(
509521
for (var command in selfValidateCommands)
510522
_CommandEntryBase(selfValidateJobName, command),
511523
],
524+
rootConfig,
512525
config: config,
513526
);
514527

@@ -537,16 +550,18 @@ class _MapEntryWithStage {
537550
}
538551

539552
extension on PackageFlavor {
540-
Step setupStep(String sdkVersion) {
553+
Step setupStep(String sdkVersion, RootConfig rootConfig) {
541554
switch (this) {
542555
case PackageFlavor.dart:
543556
return ActionInfo.setupDart.usage(
544557
withContent: {'sdk': sdkVersion},
558+
versionOverrides: rootConfig.existingActionVersions,
545559
);
546560

547561
case PackageFlavor.flutter:
548562
return ActionInfo.setupFlutter.usage(
549563
withContent: {'channel': sdkVersion},
564+
versionOverrides: rootConfig.existingActionVersions,
550565
);
551566
}
552567
}

‎mono_repo/lib/src/root_config.dart

+64-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@ import 'dart:io';
77

88
import 'package:path/path.dart' as p;
99
import 'package:pubspec_parse/pubspec_parse.dart';
10+
import 'package:yaml/yaml.dart';
1011

12+
import 'commands/github/generate.dart';
13+
import 'commands/github/github_yaml.dart';
1114
import 'mono_config.dart';
1215
import 'package_config.dart';
1316
import 'user_exception.dart';
@@ -60,6 +63,7 @@ class RootConfig extends ListBase<PackageConfig> {
6063
final String rootDirectory;
6164
final MonoConfig monoConfig;
6265
final List<PackageConfig> _configs;
66+
final Map<String, String>? existingActionVersions;
6367

6468
factory RootConfig({String? rootDirectory, bool recursive = true}) {
6569
rootDirectory ??= p.current;
@@ -94,14 +98,35 @@ class RootConfig extends ListBase<PackageConfig> {
9498
);
9599
}
96100

101+
// If a dependabot configuration file exists, assume the action versions in
102+
// the generated workflow file are maintained by dependabot; parse and use
103+
// those versions.
104+
Map<String, String>? existingActionVersions;
105+
final hasDependabot = dependabotFileNames
106+
.map((name) => File(p.join(rootDirectory!, name)))
107+
.any((file) => file.existsSync());
108+
final githubWorkflowFile =
109+
File(p.join(rootDirectory, defaultGitHubWorkflowFilePath));
110+
if (hasDependabot && githubWorkflowFile.existsSync()) {
111+
existingActionVersions = parseActionVersions(
112+
githubWorkflowFile.readAsStringSync(),
113+
);
114+
}
115+
97116
return RootConfig._(
98117
rootDirectory,
99118
MonoConfig.fromRepo(rootDirectory: rootDirectory),
100119
configs,
120+
existingActionVersions,
101121
);
102122
}
103123

104-
RootConfig._(this.rootDirectory, this.monoConfig, this._configs);
124+
RootConfig._(
125+
this.rootDirectory,
126+
this.monoConfig,
127+
this._configs,
128+
this.existingActionVersions,
129+
);
105130

106131
@override
107132
int get length => _configs.length;
@@ -116,4 +141,42 @@ class RootConfig extends ListBase<PackageConfig> {
116141
@override
117142
void operator []=(int index, PackageConfig pkg) =>
118143
throw UnsupportedError('This List is read-only.');
144+
145+
/// Parse any github action versions from a workflow file.
146+
///
147+
/// This returns a map of <action name> to <action version>.
148+
static Map<String, String> parseActionVersions(String yamlText) {
149+
// "dart-lang/setup-dart@6a218f2413a3e78e9087f638a238f6b40893203d"
150+
final usageRegex = RegExp(r'([\w\.-]+)\/([\w\.-]+)@([\w\.]+)');
151+
152+
final yaml = loadYaml(yamlText);
153+
final result = <String, String>{};
154+
155+
void collect(dynamic yaml) {
156+
if (yaml is List) {
157+
for (var item in yaml) {
158+
collect(item);
159+
}
160+
} else if (yaml is Map) {
161+
const usesKey = 'uses';
162+
163+
if (yaml.containsKey(usesKey)) {
164+
// dart-lang/setup-dart@6a218f2413a3e78e9087f638a238f6b40893203d
165+
final usage = yaml[usesKey] as String;
166+
final match = usageRegex.firstMatch(usage);
167+
if (match != null) {
168+
result['${match.group(1)}/${match.group(2)}'] = match.group(3)!;
169+
}
170+
}
171+
172+
for (var item in yaml.entries) {
173+
collect(item.value);
174+
}
175+
}
176+
}
177+
178+
collect(yaml);
179+
180+
return result;
181+
}
119182
}

‎mono_repo/lib/src/task_type.dart

+5
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import 'commands/github/action_info.dart';
77
import 'commands/github/step.dart';
88
import 'coverage_processor.dart';
99
import 'package_flavor.dart';
10+
import 'root_config.dart';
1011

1112
abstract class TaskType implements Comparable<TaskType> {
1213
static const command = _CommandTask();
@@ -40,6 +41,7 @@ abstract class TaskType implements Comparable<TaskType> {
4041
Iterable<Step> afterEachSteps(
4142
String packageDirectory,
4243
BasicConfiguration config,
44+
RootConfig rootConfig,
4345
) =>
4446
const Iterable.empty();
4547

@@ -148,6 +150,7 @@ class _TestWithCoverageTask extends TaskType {
148150
Iterable<Step> afterEachSteps(
149151
String packageDirectory,
150152
BasicConfiguration config,
153+
RootConfig rootConfig,
151154
) {
152155
final countString = (_count++).toString().padLeft(2, '0');
153156
return [
@@ -161,6 +164,7 @@ class _TestWithCoverageTask extends TaskType {
161164
'flag-name': 'coverage_$countString',
162165
'parallel': true,
163166
},
167+
versionOverrides: rootConfig.existingActionVersions,
164168
),
165169
if (config.coverageProcessors.contains(CoverageProcessor.codecov))
166170
ActionInfo.codecov.usage(
@@ -169,6 +173,7 @@ class _TestWithCoverageTask extends TaskType {
169173
'fail_ci_if_error': true,
170174
'name': 'coverage_$countString',
171175
},
176+
versionOverrides: rootConfig.existingActionVersions,
172177
),
173178
];
174179
}

‎mono_repo/lib/src/version.dart

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎mono_repo/pubspec.yaml

+4-3
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@ name: mono_repo
22
description: >-
33
CLI tools to make it easier to manage a single source repository containing
44
multiple Dart packages.
5-
version: 6.5.6
5+
version: 6.5.7
66
repository: https://github.com/google/mono_repo.dart
7+
78
topics:
8-
- tool
9-
- repository-management
9+
- tool
10+
- repository-management
1011

1112
environment:
1213
sdk: '>=2.18.0 <3.0.0'
+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Copyright (c) 2023, 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+
// On windows this test fails for unknown reasons, possibly there are carriage
6+
// returns being introduced during formatting.
7+
@TestOn('linux')
8+
import 'dart:io';
9+
10+
import 'package:test/test.dart';
11+
12+
void main() {
13+
test('action versions are up to date', () {
14+
final result = Process.runSync(
15+
Platform.executable,
16+
['tool/generate_action_versions.dart', '--validate'],
17+
);
18+
expect(result.exitCode, 0, reason: result.stdout as String);
19+
});
20+
}

‎mono_repo/test/generate_test.dart

+53-32
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import 'dart:async';
66
import 'dart:convert';
7+
import 'dart:io';
78

89
import 'package:mono_repo/mono_repo.dart';
910
import 'package:mono_repo/src/ci_test_script.dart';
@@ -284,12 +285,12 @@ package:pkg_b''',
284285

285286
group('--validate', () {
286287
setUp(() async {
287-
await d.dir('sub_pkg', [
288-
d.file(monoPkgFileName, testConfig2),
289-
d.file('pubspec.yaml', '''
290-
name: pkg_name
291-
''')
292-
]).create();
288+
await populateConfig(
289+
r'''
290+
github:
291+
dependabot: {}
292+
''',
293+
);
293294
});
294295

295296
test('throws if there is no generated config', () async {
@@ -315,15 +316,32 @@ name: pkg_name
315316

316317
test("doesn't throw if the previous config is up to date", () async {
317318
testGenerateConfig(
318-
printMatcher: _subPkgStandardOutput,
319+
printMatcher: _subPkgStandardOutput(withDependabot: true),
319320
);
320321

321322
// Just check that this doesn't throw.
322323
testGenerateConfig(
323-
printMatcher: '''
324-
package:sub_pkg
325-
Wrote `${p.join(d.sandbox, defaultGitHubWorkflowFilePath)}`.
326-
Wrote `${p.join(d.sandbox, ciScriptPath)}`.''',
324+
printMatcher: 'package:sub_pkg',
325+
validateOnly: true,
326+
);
327+
});
328+
329+
test("doesn't throw if the previous config has different action versions",
330+
() async {
331+
testGenerateConfig(
332+
printMatcher: _subPkgStandardOutput(withDependabot: true),
333+
);
334+
final generatedFile = File(d.path(defaultGitHubWorkflowFilePath));
335+
final contents = generatedFile.readAsStringSync();
336+
generatedFile.writeAsStringSync(
337+
contents.replaceAll(
338+
'dart-lang/setup-dart@',
339+
'dart-lang/setup-dart@Foo',
340+
),
341+
);
342+
testGenerateConfig(
343+
printMatcher: 'package:sub_pkg',
344+
validateOnly: true,
327345
);
328346
});
329347
});
@@ -337,7 +355,7 @@ name: pkg_name
337355
]).create();
338356

339357
testGenerateConfig(
340-
printMatcher: _subPkgStandardOutput,
358+
printMatcher: _subPkgStandardOutput(),
341359
);
342360
await d.file(ciScriptPath, ciShellOutput).validate();
343361
});
@@ -356,7 +374,7 @@ environment:
356374
printMatcher: '''
357375
package:sub_pkg
358376
There are jobs defined that are not compatible with the package SDK constraint (>=2.1.0 <3.0.0): `1.23.0`.
359-
$_writeScriptOutput''',
377+
${_writeScriptOutput(false)}''',
360378
);
361379

362380
await d.file(ciScriptPath, ciShellOutput).validate();
@@ -393,7 +411,7 @@ name: pkg_a
393411
printMatcher: '''
394412
${Iterable.generate(count, (i) => 'package:${pkgName(i)}').join('\n')}
395413
package:sub_pkg
396-
$_writeScriptOutput''',
414+
${_writeScriptOutput(false)}''',
397415
);
398416

399417
validateSandbox(
@@ -446,7 +464,7 @@ name: pkg_b
446464
printMatcher: '''
447465
package:pkg_a
448466
package:pkg_b
449-
$_writeScriptOutput''',
467+
${_writeScriptOutput(false)}''',
450468
);
451469

452470
validateSandbox(
@@ -517,7 +535,7 @@ name: pkg_b
517535
printMatcher: '''
518536
package:pkg_a
519537
package:pkg_b
520-
$_writeScriptOutput''',
538+
${_writeScriptOutput(false)}''',
521539
);
522540

523541
validateSandbox(
@@ -613,7 +631,7 @@ name: pkg_a
613631
package:pkg_a
614632
`dart` values (stable) are not used and can be removed.
615633
`os` values (unneeded) are not used and can be removed.
616-
$_writeScriptOutput''',
634+
${_writeScriptOutput(false)}''',
617635
);
618636

619637
validateSandbox(
@@ -644,7 +662,7 @@ name: pkg_a
644662
testGenerateConfig(
645663
printMatcher: '''
646664
package:pkg_a
647-
$_writeScriptOutput''',
665+
${_writeScriptOutput(false)}''',
648666
);
649667

650668
validateSandbox(
@@ -782,7 +800,7 @@ $lines
782800
await d.nothing(ciScriptPath).validate();
783801

784802
testGenerateConfig(
785-
printMatcher: _subPkgStandardOutput,
803+
printMatcher: _subPkgStandardOutput(),
786804
);
787805

788806
if (expectedGithubContent != null) {
@@ -920,7 +938,7 @@ dependencies:
920938
package:pkg_a
921939
package:pkg_b
922940
package:pkg_c
923-
$_writeScriptOutput''',
941+
${_writeScriptOutput(false)}''',
924942
);
925943

926944
validateSandbox(
@@ -961,7 +979,7 @@ line 1, column 13 of mono_repo.yaml: Unsupported value for "pub_action". Value m
961979
await populateConfig(monoConfigContent);
962980

963981
testGenerateConfig(
964-
printMatcher: _subPkgStandardOutput,
982+
printMatcher: _subPkgStandardOutput(),
965983
);
966984

967985
// TODO: validate GitHub case
@@ -974,7 +992,7 @@ line 1, column 13 of mono_repo.yaml: Unsupported value for "pub_action". Value m
974992
await populateConfig(monoConfigContent);
975993

976994
testGenerateConfig(
977-
printMatcher: _subPkgStandardOutput,
995+
printMatcher: _subPkgStandardOutput(),
978996
);
979997

980998
// TODO: validate GitHub case
@@ -1009,7 +1027,7 @@ line 1, column 14 of mono_repo.yaml: Unsupported value for "pretty_ansi". Value
10091027
await populateConfig(toYaml({'pretty_ansi': false}));
10101028

10111029
testGenerateConfig(
1012-
printMatcher: _subPkgStandardOutput,
1030+
printMatcher: _subPkgStandardOutput(),
10131031
);
10141032

10151033
await d
@@ -1126,7 +1144,7 @@ line 1, column 16 of mono_repo.yaml: Unsupported value for "self_validate". Valu
11261144
await populateConfig(monoConfigContent);
11271145

11281146
testGenerateConfig(
1129-
printMatcher: _subPkgStandardOutput,
1147+
printMatcher: _subPkgStandardOutput(),
11301148
);
11311149

11321150
validateSandbox(
@@ -1143,7 +1161,7 @@ line 1, column 16 of mono_repo.yaml: Unsupported value for "self_validate". Valu
11431161
await populateConfig(monoConfigContent);
11441162

11451163
testGenerateConfig(
1146-
printMatcher: _subPkgStandardOutput,
1164+
printMatcher: _subPkgStandardOutput(),
11471165
);
11481166

11491167
validateSandbox(
@@ -1210,7 +1228,7 @@ environment:
12101228
testGenerateConfig(
12111229
printMatcher: '''
12121230
package:pkg_a
1213-
$_writeScriptOutput''',
1231+
${_writeScriptOutput(false)}''',
12141232
);
12151233

12161234
validateSandbox(
@@ -1388,13 +1406,16 @@ github:
13881406
});
13891407
}
13901408

1391-
String get _subPkgStandardOutput => '''
1409+
String _subPkgStandardOutput({bool withDependabot = false}) => '''
13921410
package:sub_pkg
1393-
$_writeScriptOutput''';
1394-
1395-
String get _writeScriptOutput => '''
1396-
Wrote `${p.join(d.sandbox, defaultGitHubWorkflowFilePath)}`.
1397-
$ciScriptPathMessage''';
1411+
${_writeScriptOutput(withDependabot)}''';
1412+
1413+
String _writeScriptOutput(bool withDependabot) => [
1414+
'Wrote `${p.join(d.sandbox, defaultGitHubWorkflowFilePath)}`.',
1415+
if (withDependabot)
1416+
'Wrote `${p.join(d.sandbox, '.github/dependabot.yml')}`.',
1417+
ciScriptPathMessage
1418+
].join('\n');
13981419

13991420
Future<void> _testBadConfig(
14001421
Object monoRepoYaml,

‎mono_repo/test/root_config_test.dart

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import 'dart:io';
2+
3+
import 'package:mono_repo/src/commands/github/github_yaml.dart';
4+
import 'package:mono_repo/src/root_config.dart';
5+
import 'package:path/path.dart' as path;
6+
import 'package:test/test.dart';
7+
8+
void main() {
9+
group('RootConfig', () {
10+
test('parseActionVersions', () {
11+
final file = File(path.join('..', defaultGitHubWorkflowFilePath));
12+
final parsedVersions =
13+
RootConfig.parseActionVersions(file.readAsStringSync());
14+
15+
expect(parsedVersions, isNotEmpty);
16+
17+
final keys = parsedVersions.keys.toList();
18+
expect(keys, contains('actions/checkout'));
19+
expect(keys, contains('dart-lang/setup-dart'));
20+
});
21+
});
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
// Copyright (c) 2023, 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:mono_repo/src/root_config.dart';
9+
10+
// Should be ran from the `mono_repo` package directory.
11+
void main(List<String> args) {
12+
final parsedArgs = argParser.parse(args);
13+
final validateOnly = parsedArgs['validate'] as bool;
14+
final versionsFile = File('lib/src/commands/github/action_versions.dart');
15+
if (!versionsFile.existsSync()) {
16+
print('Unable to find existing versions file at `${versionsFile.path}`, '
17+
'make sure you are running from the `mono_repo` package directory');
18+
exit(1);
19+
}
20+
final previousContent = versionsFile.readAsStringSync();
21+
final workflowFile = File('../.github/workflows/dart.yml');
22+
final versions =
23+
RootConfig.parseActionVersions(workflowFile.readAsStringSync());
24+
final newContentBuffer = StringBuffer('''
25+
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
26+
// for details. All rights reserved. Use of this source code is governed by a
27+
// BSD-style license that can be found in the LICENSE file.
28+
29+
/// This file is generated, and should not be modified by hand.
30+
///
31+
/// To regenerate it, run the `tool/generate_action_versions.dart` script.
32+
33+
''');
34+
for (var entry in versions.entries) {
35+
newContentBuffer
36+
.writeln("const ${entry.key.toVariableName} = '${entry.value}';");
37+
}
38+
final tmpDir = Directory.systemTemp.createTempSync('gen_action_versions');
39+
final tmpFile =
40+
File.fromUri(tmpDir.uri.resolve('generate_action_versions.dart'))
41+
..writeAsStringSync(newContentBuffer.toString());
42+
final fmtResult =
43+
Process.runSync(Platform.resolvedExecutable, ['format', tmpFile.path]);
44+
if (fmtResult.exitCode != 0) {
45+
stdout
46+
..writeln('Error: Failed to run dartfmt')
47+
..write(fmtResult.stdout);
48+
stderr.write(fmtResult.stderr);
49+
exitCode = 1;
50+
} else {
51+
final newContent = tmpFile.readAsStringSync();
52+
53+
if (previousContent == newContent) {
54+
print('No change');
55+
} else {
56+
stdout.write('''
57+
Content changed!
58+
59+
Previous:
60+
```
61+
$previousContent
62+
```
63+
64+
New:
65+
```
66+
$newContent
67+
```
68+
''');
69+
if (validateOnly) {
70+
exitCode = 1;
71+
} else {
72+
tmpFile.renameSync(versionsFile.path);
73+
}
74+
}
75+
}
76+
tmpDir.deleteSync(recursive: true);
77+
}
78+
79+
final argParser = ArgParser()..addFlag('validate');
80+
81+
extension _ToVariableName on String {
82+
String get toVariableName {
83+
final buffer = StringBuffer();
84+
var capitalizeNext = false;
85+
for (var i = 0; i < length; i++) {
86+
final char = this[i];
87+
switch (char) {
88+
case '-':
89+
case '_':
90+
case '/':
91+
capitalizeNext = true;
92+
continue;
93+
default:
94+
if (capitalizeNext) {
95+
buffer.write(char.toUpperCase());
96+
capitalizeNext = false;
97+
} else {
98+
buffer.write(char);
99+
}
100+
}
101+
}
102+
buffer.write('Version');
103+
return buffer.toString();
104+
}
105+
}

‎tool/ci.sh

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)
Please sign in to comment.