From 25c57b8874077298ac299f22c74116df959418f8 Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Fri, 10 Jan 2025 15:42:36 -0600 Subject: [PATCH] [puppy] Introduce a package for misc Dart CLI tools (#335) First command: `map` to recursively run a command in every directory that contains pubspec.yaml --- .github/labeler.yml | 4 ++ .github/workflows/puppy.yml | 41 +++++++++++++ README.md | 1 + pkgs/puppy/README.md | 11 ++++ pkgs/puppy/analysis_options.yaml | 1 + pkgs/puppy/bin/puppy.dart | 14 +++++ pkgs/puppy/lib/src/constants.dart | 5 ++ pkgs/puppy/lib/src/map_command.dart | 88 +++++++++++++++++++++++++++ pkgs/puppy/lib/src/map_command.g.dart | 34 +++++++++++ pkgs/puppy/pubspec.yaml | 18 ++++++ 10 files changed, 217 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/puppy.dart create mode 100644 pkgs/puppy/lib/src/constants.dart create mode 100644 pkgs/puppy/lib/src/map_command.dart create mode 100644 pkgs/puppy/lib/src/map_command.g.dart create mode 100644 pkgs/puppy/pubspec.yaml 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/**' 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..b4305165 --- /dev/null +++ b/pkgs/puppy/README.md @@ -0,0 +1,11 @@ +Activate locally using: + +```console +cd +dart pub global activate --source=path . +``` + +### Commands + +- `run`: 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/puppy.dart b/pkgs/puppy/bin/puppy.dart new file mode 100755 index 00000000..50c87809 --- /dev/null +++ b/pkgs/puppy/bin/puppy.dart @@ -0,0 +1,14 @@ +// 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/constants.dart'; +import 'package:puppy/src/map_command.dart'; + +Future main(List args) async { + 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..c8361ee0 --- /dev/null +++ b/pkgs/puppy/lib/src/constants.dart @@ -0,0 +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'; diff --git a/pkgs/puppy/lib/src/map_command.dart b/pkgs/puppy/lib/src/map_command.dart new file mode 100644 index 00000000..5713a78e --- /dev/null +++ b/pkgs/puppy/lib/src/map_command.dart @@ -0,0 +1,88 @@ +// 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'; + +import 'constants.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!', + '$cmdName 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 new file mode 100644 index 00000000..26a6d9db --- /dev/null +++ b/pkgs/puppy/pubspec.yaml @@ -0,0 +1,18 @@ +name: puppy +publish_to: none + +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: + puppy: