From bb458c23f6a2ee32b5f741b6c1d68cf88b8f48df Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Wed, 8 Jan 2025 15:18:05 -0800 Subject: [PATCH 1/6] [puppy] Introduce a package for misc Dart CLI tools First tool: for_all_package_dirs to recursively run a command in every directory that contains pubspec.yaml --- .github/workflows/puppy.yml | 41 ++++++++++++++++++++ README.md | 1 + pkgs/puppy/README.md | 11 ++++++ pkgs/puppy/analysis_options.yaml | 1 + pkgs/puppy/bin/for_all_package_dirs.dart | 49 ++++++++++++++++++++++++ pkgs/puppy/pubspec.yaml | 14 +++++++ 6 files changed, 117 insertions(+) create mode 100644 .github/workflows/puppy.yml create mode 100644 pkgs/puppy/README.md create mode 100644 pkgs/puppy/analysis_options.yaml create mode 100755 pkgs/puppy/bin/for_all_package_dirs.dart create mode 100644 pkgs/puppy/pubspec.yaml diff --git a/.github/workflows/puppy.yml b/.github/workflows/puppy.yml new file mode 100644 index 00000000..a96eb8d1 --- /dev/null +++ b/.github/workflows/puppy.yml @@ -0,0 +1,41 @@ +name: package:puppy + +permissions: read-all + +on: + pull_request: + branches: [ main ] + paths: + - '.github/workflows/puppy.yml' + - 'pkgs/puppy/**' + push: + branches: [ main ] + paths: + - '.github/workflows/puppy.yml' + - 'pkgs/puppy/**' + schedule: + - cron: '0 0 * * 0' # weekly + +defaults: + run: + working-directory: pkgs/puppy + +jobs: + build: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + sdk: [3.6, dev] + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94 + with: + sdk: ${{ matrix.sdk }} + + - run: dart pub get + - run: dart analyze --fatal-infos + - run: dart format --output=none --set-exit-if-changed . + if: ${{ matrix.sdk == 'dev' }} + # TODO: enable when there are tests! + #- run: dart test diff --git a/README.md b/README.md index aa47d0ef..9b89701c 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ This repository is home to general Dart Ecosystem tools and packages. | [corpus](pkgs/corpus/) | A tool to calculate the API usage for a package. | | | [dart_flutter_team_lints](pkgs/dart_flutter_team_lints/) | An analysis rule set used by the Dart and Flutter teams. | [![pub package](https://img.shields.io/pub/v/dart_flutter_team_lints.svg)](https://pub.dev/packages/dart_flutter_team_lints) | | [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) | +| [puppy](pkgs/puppy/) | A grab bag of CLI tools for managing Dart code. | | | [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 moving existing packages into monorepos. | | diff --git a/pkgs/puppy/README.md b/pkgs/puppy/README.md new file mode 100644 index 00000000..878276f3 --- /dev/null +++ b/pkgs/puppy/README.md @@ -0,0 +1,11 @@ +Activate locally using: + +```console +cd +dart pub global activate --source=path . +``` + +### Tools + +- `for_all_package_dirs`: runs a command in every directory containing + `pubspec.yaml`. diff --git a/pkgs/puppy/analysis_options.yaml b/pkgs/puppy/analysis_options.yaml new file mode 100644 index 00000000..d978f811 --- /dev/null +++ b/pkgs/puppy/analysis_options.yaml @@ -0,0 +1 @@ +include: package:dart_flutter_team_lints/analysis_options.yaml diff --git a/pkgs/puppy/bin/for_all_package_dirs.dart b/pkgs/puppy/bin/for_all_package_dirs.dart new file mode 100755 index 00000000..e818ce3e --- /dev/null +++ b/pkgs/puppy/bin/for_all_package_dirs.dart @@ -0,0 +1,49 @@ +// Copyright (c) 2025, 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:io/ansi.dart'; +import 'package:io/io.dart'; + +Future main(List args) async { + if (args.isEmpty) { + print('Need something to execute!'); + exitCode = ExitCode.usage.code; + return; + } + + final exe = args.first; + final extraArgs = args.skip(1).toList(); + + final exits = {}; + + Future inspectDirectory(Directory dir) async { + final pubspecs = dir + .listSync() + .whereType() + .where((element) => element.uri.pathSegments.last == 'pubspec.yaml') + .toList(); + + if (pubspecs.isNotEmpty) { + print(green.wrap(dir.path)); + final proc = await Process.start( + exe, + extraArgs, + mode: ProcessStartMode.inheritStdio, + workingDirectory: dir.path, + ); + + // TODO(kevmoo): display a summary of results on completion + exits[dir.path] = await proc.exitCode; + } + + for (var subDir in dir.listSync().whereType().where((element) => + !element.uri.pathSegments.any((element) => element.startsWith('.')))) { + await inspectDirectory(subDir); + } + } + + await inspectDirectory(Directory.current); +} diff --git a/pkgs/puppy/pubspec.yaml b/pkgs/puppy/pubspec.yaml new file mode 100644 index 00000000..6d5539d4 --- /dev/null +++ b/pkgs/puppy/pubspec.yaml @@ -0,0 +1,14 @@ +name: puppy +publish_to: none + +environment: + sdk: ^3.6.0 + +dependencies: + io: ^1.0.5 + +dev_dependencies: + dart_flutter_team_lints: ^3.0.0 + +executables: + for_all_package_dirs: From e42ed2de4629c04609679727f99e2c3fea8cbc64 Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Wed, 8 Jan 2025 15:41:45 -0800 Subject: [PATCH 2/6] update labels --- .github/labeler.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/labeler.yml b/.github/labeler.yml index f4837675..7ad166da 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -24,6 +24,10 @@ - changed-files: - any-glob-to-any-file: 'pkgs/repo_manage/**' +'package:puppy': + - changed-files: + - any-glob-to-any-file: 'pkgs/puppy/**' + 'package:sdk_triage_bot': - changed-files: - any-glob-to-any-file: 'pkgs/sdk_triage_bot/**' From 030feb04842a41d057314bbc8224cb39af6717c8 Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Fri, 10 Jan 2025 13:12:18 -0800 Subject: [PATCH 3/6] review notes --- pkgs/puppy/README.md | 4 +- pkgs/puppy/bin/for_all_package_dirs.dart | 49 -------------- pkgs/puppy/bin/puppy.dart | 10 +++ pkgs/puppy/lib/src/map_command.dart | 86 ++++++++++++++++++++++++ pkgs/puppy/lib/src/map_command.g.dart | 34 ++++++++++ pkgs/puppy/pubspec.yaml | 6 +- 6 files changed, 137 insertions(+), 52 deletions(-) delete mode 100755 pkgs/puppy/bin/for_all_package_dirs.dart create mode 100755 pkgs/puppy/bin/puppy.dart create mode 100644 pkgs/puppy/lib/src/map_command.dart create mode 100644 pkgs/puppy/lib/src/map_command.g.dart diff --git a/pkgs/puppy/README.md b/pkgs/puppy/README.md index 878276f3..b4305165 100644 --- a/pkgs/puppy/README.md +++ b/pkgs/puppy/README.md @@ -5,7 +5,7 @@ cd dart pub global activate --source=path . ``` -### Tools +### Commands -- `for_all_package_dirs`: runs a command in every directory containing +- `run`: runs a command in every directory containing `pubspec.yaml`. diff --git a/pkgs/puppy/bin/for_all_package_dirs.dart b/pkgs/puppy/bin/for_all_package_dirs.dart deleted file mode 100755 index e818ce3e..00000000 --- a/pkgs/puppy/bin/for_all_package_dirs.dart +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) 2025, 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:io/ansi.dart'; -import 'package:io/io.dart'; - -Future main(List args) async { - if (args.isEmpty) { - print('Need something to execute!'); - exitCode = ExitCode.usage.code; - return; - } - - final exe = args.first; - final extraArgs = args.skip(1).toList(); - - final exits = {}; - - Future inspectDirectory(Directory dir) async { - final pubspecs = dir - .listSync() - .whereType() - .where((element) => element.uri.pathSegments.last == 'pubspec.yaml') - .toList(); - - if (pubspecs.isNotEmpty) { - print(green.wrap(dir.path)); - final proc = await Process.start( - exe, - extraArgs, - mode: ProcessStartMode.inheritStdio, - workingDirectory: dir.path, - ); - - // TODO(kevmoo): display a summary of results on completion - exits[dir.path] = await proc.exitCode; - } - - for (var subDir in dir.listSync().whereType().where((element) => - !element.uri.pathSegments.any((element) => element.startsWith('.')))) { - await inspectDirectory(subDir); - } - } - - await inspectDirectory(Directory.current); -} diff --git a/pkgs/puppy/bin/puppy.dart b/pkgs/puppy/bin/puppy.dart new file mode 100755 index 00000000..100da2b2 --- /dev/null +++ b/pkgs/puppy/bin/puppy.dart @@ -0,0 +1,10 @@ +import 'package:args/command_runner.dart'; +import 'package:puppy/src/map_command.dart'; + +Future main(List args) async { + var runner = CommandRunner( + 'dgit', 'A dart implementation of distributed version control.') + ..addCommand(MapCommand()); + + await runner.run(args); +} diff --git a/pkgs/puppy/lib/src/map_command.dart b/pkgs/puppy/lib/src/map_command.dart new file mode 100644 index 00000000..4c74fdb4 --- /dev/null +++ b/pkgs/puppy/lib/src/map_command.dart @@ -0,0 +1,86 @@ +// Copyright (c) 2025, 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:async'; +import 'dart:io'; + +import 'package:args/command_runner.dart'; +import 'package:build_cli_annotations/build_cli_annotations.dart'; +import 'package:io/ansi.dart'; + +part 'map_command.g.dart'; + +class MapCommand extends _$MapArgsCommand { + @override + String get description => + 'Run the provided command in each subdirectory containing ' + '`pubspec.yaml`.'; + + @override + String get name => 'map'; + + @override + Future? run() async { + await _doMap(_options); + } +} + +@CliOptions(createCommand: true) +class MapArgs { + @CliOption(abbr: 'd', help: 'Keep looking for "nested" pubspec files.') + final bool deep; + + final List rest; + + MapArgs({ + this.deep = false, + required this.rest, + }) { + if (rest.isEmpty) { + throw UsageException( + 'Missing command to invoke!', + 'puppy map [--deep] ', + ); + } + } +} + +Future _doMap(MapArgs args) async { + final exe = args.rest.first; + final extraArgs = args.rest.skip(1).toList(); + + final exits = {}; + + Future inspectDirectory(Directory dir, {required bool deep}) async { + final pubspecs = dir + .listSync() + .whereType() + .where((element) => element.uri.pathSegments.last == 'pubspec.yaml') + .toList(); + + final pubspecHere = pubspecs.isNotEmpty; + if (pubspecHere) { + print(green.wrap(dir.path)); + final proc = await Process.start( + exe, + extraArgs, + mode: ProcessStartMode.inheritStdio, + workingDirectory: dir.path, + ); + + // TODO(kevmoo): display a summary of results on completion + exits[dir.path] = await proc.exitCode; + } + + if (!pubspecHere || deep) { + for (var subDir in dir.listSync().whereType().where( + (element) => !element.uri.pathSegments + .any((element) => element.startsWith('.')))) { + await inspectDirectory(subDir, deep: deep); + } + } + } + + await inspectDirectory(Directory.current, deep: args.deep); +} diff --git a/pkgs/puppy/lib/src/map_command.g.dart b/pkgs/puppy/lib/src/map_command.g.dart new file mode 100644 index 00000000..a7d44e21 --- /dev/null +++ b/pkgs/puppy/lib/src/map_command.g.dart @@ -0,0 +1,34 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'map_command.dart'; + +// ************************************************************************** +// CliGenerator +// ************************************************************************** + +MapArgs _$parseMapArgsResult(ArgResults result) => MapArgs( + deep: result['deep'] as bool, + rest: result.rest, + ); + +ArgParser _$populateMapArgsParser(ArgParser parser) => parser + ..addFlag( + 'deep', + abbr: 'd', + help: 'Keep looking for "nested" pubspec files.', + ); + +final _$parserForMapArgs = _$populateMapArgsParser(ArgParser()); + +MapArgs parseMapArgs(List args) { + final result = _$parserForMapArgs.parse(args); + return _$parseMapArgsResult(result); +} + +abstract class _$MapArgsCommand extends Command { + _$MapArgsCommand() { + _$populateMapArgsParser(argParser); + } + + late final _options = _$parseMapArgsResult(argResults!); +} diff --git a/pkgs/puppy/pubspec.yaml b/pkgs/puppy/pubspec.yaml index 6d5539d4..26a6d9db 100644 --- a/pkgs/puppy/pubspec.yaml +++ b/pkgs/puppy/pubspec.yaml @@ -5,10 +5,14 @@ environment: sdk: ^3.6.0 dependencies: + args: ^2.6.0 + build_cli_annotations: ^2.1.0 io: ^1.0.5 dev_dependencies: + build_cli: ^2.2.4 + build_runner: ^2.4.14 dart_flutter_team_lints: ^3.0.0 executables: - for_all_package_dirs: + puppy: From 9e00209b92fda76abfacd006ddac2cea9c2a2dcc Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Fri, 10 Jan 2025 13:14:16 -0800 Subject: [PATCH 4/6] fix licence things --- pkgs/puppy/bin/puppy.dart | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkgs/puppy/bin/puppy.dart b/pkgs/puppy/bin/puppy.dart index 100da2b2..450ac0ca 100755 --- a/pkgs/puppy/bin/puppy.dart +++ b/pkgs/puppy/bin/puppy.dart @@ -1,3 +1,7 @@ +// Copyright (c) 2025, 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 'package:args/command_runner.dart'; import 'package:puppy/src/map_command.dart'; From 1a2998eb9439fde3a4677a48b6d18acac3b5276c Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Fri, 10 Jan 2025 13:28:28 -0800 Subject: [PATCH 5/6] fix nits --- pkgs/puppy/bin/puppy.dart | 4 ++-- pkgs/puppy/lib/src/constants.dart | 1 + pkgs/puppy/lib/src/map_command.dart | 4 +++- 3 files changed, 6 insertions(+), 3 deletions(-) create mode 100644 pkgs/puppy/lib/src/constants.dart diff --git a/pkgs/puppy/bin/puppy.dart b/pkgs/puppy/bin/puppy.dart index 450ac0ca..50c87809 100755 --- a/pkgs/puppy/bin/puppy.dart +++ b/pkgs/puppy/bin/puppy.dart @@ -3,11 +3,11 @@ // BSD-style license that can be found in the LICENSE file. import 'package:args/command_runner.dart'; +import 'package:puppy/src/constants.dart'; import 'package:puppy/src/map_command.dart'; Future main(List args) async { - var runner = CommandRunner( - 'dgit', 'A dart implementation of distributed version control.') + var runner = CommandRunner(cmdName, 'Dart repository management tools.') ..addCommand(MapCommand()); await runner.run(args); diff --git a/pkgs/puppy/lib/src/constants.dart b/pkgs/puppy/lib/src/constants.dart new file mode 100644 index 00000000..c1a32dd9 --- /dev/null +++ b/pkgs/puppy/lib/src/constants.dart @@ -0,0 +1 @@ +const cmdName = 'puppy'; diff --git a/pkgs/puppy/lib/src/map_command.dart b/pkgs/puppy/lib/src/map_command.dart index 4c74fdb4..5713a78e 100644 --- a/pkgs/puppy/lib/src/map_command.dart +++ b/pkgs/puppy/lib/src/map_command.dart @@ -9,6 +9,8 @@ import 'package:args/command_runner.dart'; import 'package:build_cli_annotations/build_cli_annotations.dart'; import 'package:io/ansi.dart'; +import 'constants.dart'; + part 'map_command.g.dart'; class MapCommand extends _$MapArgsCommand { @@ -40,7 +42,7 @@ class MapArgs { if (rest.isEmpty) { throw UsageException( 'Missing command to invoke!', - 'puppy map [--deep] ', + '$cmdName map [--deep] ', ); } } From 0f221b2d5daab7479ed7fd44d3b3f05ff7781b5a Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Fri, 10 Jan 2025 13:29:25 -0800 Subject: [PATCH 6/6] add license fun --- pkgs/puppy/lib/src/constants.dart | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkgs/puppy/lib/src/constants.dart b/pkgs/puppy/lib/src/constants.dart index c1a32dd9..c8361ee0 100644 --- a/pkgs/puppy/lib/src/constants.dart +++ b/pkgs/puppy/lib/src/constants.dart @@ -1 +1,5 @@ +// Copyright (c) 2025, 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. + const cmdName = 'puppy';