diff --git a/mono_repo/README.md b/mono_repo/README.md index e30ff2ce..abe84318 100644 --- a/mono_repo/README.md +++ b/mono_repo/README.md @@ -34,6 +34,7 @@ Global options: Available commands: check Check the state of the repository. generate Generates the CI configuration for child packages. + init Scaffold a new mono repo. presubmit Run the CI presubmits locally. pub Run `pub get` or `pub upgrade` against all packages. travis (Deprecated, use `generate`) Configure Travis-CI for child packages. diff --git a/mono_repo/lib/src/commands/init.dart b/mono_repo/lib/src/commands/init.dart new file mode 100644 index 00000000..9f9f5651 --- /dev/null +++ b/mono_repo/lib/src/commands/init.dart @@ -0,0 +1,105 @@ +// Copyright (c) 2020, 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/command_runner.dart'; +import 'package:io/ansi.dart'; +import 'package:path/path.dart' as p; + +const _pubspecFileName = 'pubspec.yaml'; +const _pkgCfgFileName = 'mono_pkg.yaml'; +const _repoCfgFileName = 'mono_repo.yaml'; +const _recursiveScanFlag = 'recursive'; + +const _repoCfgContents = r''' +# See with https://github.com/dart-lang/mono_repo for details on this file +self_validate: analyze_and_format + +github: + # Setting just `cron` keeps the defaults for `push` and `pull_request` + cron: '0 0 * * 0' + on_completion: + - name: "Notify failure" + runs-on: ubuntu-latest + # Run only if other jobs have failed and this is a push or scheduled build. + if: (github.event_name == 'push' || github.event_name == 'schedule') && failure() + steps: + - run: > + curl -H "Content-Type: application/json" -X POST -d \ + "{'text':'Build failed! ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}'}" \ + "${CHAT_WEBHOOK_URL}" + env: + CHAT_WEBHOOK_URL: ${{ secrets.BUILD_AND_TEST_TEAM_CHAT_WEBHOOK_URL }} + +merge_stages: +- analyze_and_format +'''; + +const _pkgCfgContents = r''' +dart: +- dev +- stable + +os: +- linux + +stages: +- analyze_and_format: + - dartanalyzer: --fatal-infos --fatal-warnings . + - dartfmt: sdk +- unit_test: + - test: +'''; + +class InitCommand extends Command { + @override + String get name => 'init'; + + @override + String get description => 'Scaffold a new mono repo.'; + + @override + void run() => scaffold(p.current, globalResults[_recursiveScanFlag] as bool); +} + +void scaffold(String rootDir, bool recursive) { + configureDirectory(rootDir, recursive: recursive); +} + +void configureDirectory(String rootDir, + {String currentDir, bool recursive = false}) { + currentDir ??= rootDir; + + if (currentDir == rootDir) { + final repoCfgPath = p.join(rootDir, _repoCfgFileName); + if (!File(repoCfgPath).existsSync()) { + File(repoCfgPath).writeAsStringSync(_repoCfgContents); + print(styleBold.wrap('Added $_repoCfgFileName to $rootDir')); + } else { + print(yellow.wrap('$_repoCfgFileName already present in $rootDir.')); + } + } else { + final pkgCfgPath = p.join(currentDir, _pkgCfgFileName); + final pubspecPath = p.join(currentDir, _pubspecFileName); + + if (File(pubspecPath).existsSync()) { + if (!File(pkgCfgPath).existsSync()) { + File(pkgCfgPath).writeAsStringSync(_pkgCfgContents); + print(styleBold.wrap('Added $_pkgCfgFileName to $currentDir')); + } else { + print(yellow.wrap('$_pkgCfgFileName already present in $currentDir')); + } + } + } + + final subdirs = + Directory(currentDir).listSync().whereType().toList(); + for (var subdir in subdirs) { + if (recursive || currentDir == rootDir) { + configureDirectory(rootDir, + currentDir: subdir.path, recursive: recursive); + } + } +} diff --git a/mono_repo/lib/src/runner.dart b/mono_repo/lib/src/runner.dart index 105bcf78..1800f0d9 100644 --- a/mono_repo/lib/src/runner.dart +++ b/mono_repo/lib/src/runner.dart @@ -9,6 +9,7 @@ import 'package:args/command_runner.dart'; import 'commands/check.dart'; import 'commands/generate.dart'; +import 'commands/init.dart'; import 'commands/mono_repo_command.dart'; import 'commands/presubmit.dart'; import 'commands/pub.dart'; @@ -19,6 +20,7 @@ final commands = List>.unmodifiable( [ CheckCommand(), GenerateCommand(), + InitCommand(), PresubmitCommand(), PubCommand(), TravisCommand(), diff --git a/mono_repo/test/init_command_test.dart b/mono_repo/test/init_command_test.dart new file mode 100644 index 00000000..b55a8782 --- /dev/null +++ b/mono_repo/test/init_command_test.dart @@ -0,0 +1,32 @@ +import 'dart:io'; + +import 'package:path/path.dart' as p; +import 'package:test/test.dart'; +import 'package:test_descriptor/test_descriptor.dart' as d; + +import 'package:mono_repo/src/commands/init.dart'; + +void main() { + const repoCfgFileName = 'mono_repo.yaml'; + const pkgCfgFileName = 'mono_pkg.yaml'; + + test('scaffold a new mono repo', () async { + await d.dir('top_level', [ + d.dir('package1', [d.file('pubspec.yaml')]), + d.dir('package2', [d.file('pubspec.yaml')]) + ]).create(); + final rootDirectory = '${d.sandbox}/top_level'; + final package1Directory = '${d.sandbox}/top_level/package1'; + final package2Directory = '${d.sandbox}/top_level/package2'; + + final repoCfg = p.join(rootDirectory, repoCfgFileName); + final pkg1Cfg = p.join(package1Directory, pkgCfgFileName); + final pkg2Cfg = p.join(package2Directory, pkgCfgFileName); + scaffold(rootDirectory, false); + expect( + File(repoCfg).existsSync() && + File(pkg1Cfg).existsSync() && + File(pkg2Cfg).existsSync(), + equals(true)); + }); +} diff --git a/mono_repo/test/mono_repo_test.dart b/mono_repo/test/mono_repo_test.dart index 3574d8d1..60f4137b 100644 --- a/mono_repo/test/mono_repo_test.dart +++ b/mono_repo/test/mono_repo_test.dart @@ -43,6 +43,7 @@ Global options: Available commands: check Check the state of the repository. generate Generates the CI configuration for child packages. + init Scaffold a new mono repo. presubmit Run the CI presubmits locally. pub Run `pub get` or `pub upgrade` against all packages. travis (Deprecated, use `generate`) Configure Travis-CI for child packages.