Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
3 changes: 3 additions & 0 deletions packages/go_router/ci_config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
release:
# TODO(chunhtai): Opt in when ready.
batch: false
3 changes: 3 additions & 0 deletions packages/go_router_builder/ci_config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
release:
# TODO(chunhtai): Opt in when ready.
batch: false
3 changes: 3 additions & 0 deletions script/tool/lib/src/common/repository_package.dart
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ class RepositoryPackage {
/// The package's top-level README.
File get authorsFile => directory.childFile('AUTHORS');

/// The package's top-level ci_config.yaml.
File get ciConfigFile => directory.childFile('ci_config.yaml');

/// The lib directory containing the package's code.
Directory get libDirectory => directory.childDirectory('lib');

Expand Down
74 changes: 74 additions & 0 deletions script/tool/lib/src/repo_package_info_check_command.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// found in the LICENSE file.

import 'package:file/file.dart';
import 'package:yaml/yaml.dart';

import 'common/core.dart';
import 'common/output_utils.dart';
Expand All @@ -12,6 +13,12 @@ import 'common/repository_package.dart';
const int _exitBadTableEntry = 3;
const int _exitUnknownPackageEntry = 4;

const Map<String, Object?> _validCiConfigSyntax = <String, Object?>{
'release': <String, Object?>{
'batch': <bool>{true, false}
},
};

/// A command to verify repository-level metadata about packages, such as
/// repo README and CODEOWNERS entries.
class RepoPackageInfoCheckCommand extends PackageLoopingCommand {
Expand Down Expand Up @@ -108,6 +115,12 @@ class RepoPackageInfoCheckCommand extends PackageLoopingCommand {
errors.add('Missing CODEOWNERS entry');
}

// The content of ci_config.yaml must be valid if there is one.
if (package.ciConfigFile.existsSync()) {
errors.addAll(
_validateCiConfig(package.ciConfigFile, mainPackage: package));
}

// Any published package should be in the README table.
// For federated plugins, only the app-facing package is listed.
if (package.isPublishable() &&
Expand Down Expand Up @@ -190,6 +203,67 @@ class RepoPackageInfoCheckCommand extends PackageLoopingCommand {
: PackageResult.fail(errors);
}

List<String> _validateCiConfig(File ciConfig,
{required RepositoryPackage mainPackage}) {
print('${indentation}Checking '
'${getRelativePosixPath(ciConfig, from: mainPackage.directory)}...');
final YamlMap config;
try {
final Object? yaml = loadYaml(ciConfig.readAsStringSync());
if (yaml is! YamlMap) {
printError('${indentation}The ci_config.yaml file must be a map.');
return <String>['Root of config is not a map.'];
}
config = yaml;
} on YamlException catch (e) {
printError(
'${indentation}Invalid YAML in ${getRelativePosixPath(ciConfig, from: mainPackage.directory)}:');
printError(e.toString());
return <String>['Invalid YAML'];
}

return _checkCiConfigEntries(config, syntax: _validCiConfigSyntax);
}

List<String> _checkCiConfigEntries(YamlMap config,
{required Map<String, Object?> syntax, String configPrefix = ''}) {
final List<String> errors = <String>[];
for (final MapEntry<Object?, Object?> entry in config.entries) {
if (!syntax.containsKey(entry.key)) {
printError(
'${indentation}Unknown key `${entry.key}` in config${_formatConfigPrefix(configPrefix)}, the possible keys are ${syntax.keys.toList()}');
errors.add(
'Unknown key `${entry.key}` in config${_formatConfigPrefix(configPrefix)}');
} else {
final Object syntaxValue = syntax[entry.key]!;
configPrefix = configPrefix.isEmpty
? entry.key! as String
: '$configPrefix.${entry.key}';
if (syntaxValue is Set) {
if (!syntaxValue.contains(entry.value)) {
printError(
'${indentation}Invalid value `${entry.value}` for key${_formatConfigPrefix(configPrefix)}, the possible values are ${syntaxValue.toList()}');
errors.add(
'Invalid value `${entry.value}` for key${_formatConfigPrefix(configPrefix)}');
}
} else if (entry.value is! YamlMap) {
printError(
'${indentation}Invalid value `${entry.value}` for key${_formatConfigPrefix(configPrefix)}, the value must be a map');
errors.add(
'Invalid value `${entry.value}` for key${_formatConfigPrefix(configPrefix)}');
} else {
errors.addAll(_checkCiConfigEntries(entry.value! as YamlMap,
syntax: syntaxValue as Map<String, Object?>,
configPrefix: configPrefix));
}
}
}
return errors;
}

String _formatConfigPrefix(String configPrefix) =>
configPrefix.isEmpty ? '' : ' `$configPrefix`';

String _prTagForPackage(String packageName) => 'p: $packageName';

String _issueTagForPackage(String packageName) {
Expand Down
109 changes: 109 additions & 0 deletions script/tool/test/repo_package_info_check_command_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -408,4 +408,113 @@ ${readmeTableEntry(packageName)}
' Missing CODEOWNERS entry')
]));
});

group('ci_config check', () {
test('control test', () async {
final RepositoryPackage package =
createFakePackage('a_package', packagesDir);

root.childFile('README.md').writeAsStringSync('''
${readmeTableHeader()}
${readmeTableEntry('a_package')}
''');
writeCodeOwners(<RepositoryPackage>[package]);

package.ciConfigFile.writeAsStringSync('''
release:
batch: false
''');

final List<String> output =
await runCapturingPrint(runner, <String>['repo-package-info-check']);

expect(
output,
containsAll(<Matcher>[
contains(' Checking ci_config.yaml...'),
contains('No issues found!'),
]),
);
});

test('missing ci_config file is ok', () async {
final RepositoryPackage package =
createFakePackage('a_package', packagesDir);

root.childFile('README.md').writeAsStringSync('''
${readmeTableHeader()}
${readmeTableEntry('a_package')}
''');
writeCodeOwners(<RepositoryPackage>[package]);

final List<String> output =
await runCapturingPrint(runner, <String>['repo-package-info-check']);

expect(
output,
containsAllInOrder(<Matcher>[
contains('No issues found!'),
]),
);
});

test('fails for unknown key', () async {
final RepositoryPackage package =
createFakePackage('a_package', packagesDir);

root.childFile('README.md').writeAsStringSync('''
${readmeTableHeader()}
${readmeTableEntry('a_package')}
''');
writeCodeOwners(<RepositoryPackage>[package]);
package.ciConfigFile.writeAsStringSync('''
something: true
''');

Error? commandError;
final List<String> output = await runCapturingPrint(
runner, <String>['repo-package-info-check'], errorHandler: (Error e) {
commandError = e;
});

expect(commandError, isA<ToolExit>());
expect(
output,
containsAllInOrder(<Matcher>[
contains('Unknown key `something` in config, the possible keys are'),
]),
);
});

test('fails for invalid value type for batch property in release',
() async {
final RepositoryPackage package =
createFakePackage('a_package', packagesDir);

root.childFile('README.md').writeAsStringSync('''
${readmeTableHeader()}
${readmeTableEntry('a_package')}
''');
writeCodeOwners(<RepositoryPackage>[package]);
package.ciConfigFile.writeAsStringSync('''
release:
batch: 1
''');

Error? commandError;
final List<String> output = await runCapturingPrint(
runner, <String>['repo-package-info-check'], errorHandler: (Error e) {
commandError = e;
});

expect(commandError, isA<ToolExit>());
expect(
output,
containsAllInOrder(<Matcher>[
contains(
'Invalid value `1` for key `release.batch`, the possible values are [true, false]'),
]),
);
});
});
}