Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Develop #5

Merged
merged 16 commits into from
Apr 1, 2022
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
37 changes: 37 additions & 0 deletions .github/workflows/static-analysis.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: Static Analysis
on:
push:
pull_request:

jobs:
static-analysis:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@master
- uses: subosito/flutter-action@v2
- name: Pub get
run: flutter pub get
- name: Format
run: flutter format . --set-exit-if-changed
- name: Analyze
run: flutter analyze
# - name: Embedme
# run: |
# npm install embedme
# npx embedme README.md --verify
# - name: Build runner
# if: ${{ matrix.project == 'fast_rx_test' }}
# run: |
# flutter pub run build_runner build --delete-conflicting-outputs
# git diff --exit-code
- name: Activate fvm
run: |
dart pub global activate fvm
fvm install stable
- name: Test
run: flutter test
- name: Pana
run: |
flutter pub global activate pana
pana --no-warning --exit-code-threshold 20
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 1.13.0
- FVM support
- Config file to allow per-project command exclusions
- Added tests

## 1.12.2
- Updated pub_update_checker

Expand Down
24 changes: 23 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ Run pub commands for all sub projects in the current directory recursively
## Features
- Supports all project-level pub commands
- Determines if a project uses dart or flutter automatically
- FVM support
- Convenience shortcuts for common dart/flutter commands
- Combined exit code for use in CI
- Per-project command exclusions

| Command | Equivalent |
| ---------------------- | ----------------------------------------------------------------------------------- |
Expand All @@ -13,6 +15,8 @@ Run pub commands for all sub projects in the current directory recursively
| `puby test [options]` | `[dart\|flutter] test [options]` |
| `puby clean [options]` | `flutter clean [options]` (only runs in flutter projects) |

For projects configured with FVM, `fvm flutter [options]` is used.

## Use as an executable

### Installation
Expand All @@ -25,4 +29,22 @@ $ dart pub global activate puby
$ puby get
$ puby upgrade --major-versions
...
```
```

## Configuration
Create a `puby.yaml` file in the root of the project you want to configure

### Exclusions
Add command exclusions to prevent them from running in a project

```yaml
exclude:
- test
- pub run build_runner
```
Exclusions match from the start of a command, and the entire exclusion string must be present. Here are some examples:
| Exclusion | Example command excluded |
| ---------------------- | -------------------------------------------- |
| `test` | `[dart\|flutter] test --coverage` |
| `pub run build_runner` | `[dart\|flutter] pub run build_runner build` |
7 changes: 6 additions & 1 deletion analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
include: package:rexios_lints/dart/package.yaml
include: package:rexios_lints/dart/package.yaml

analyzer:
exclude:
# workaround for https://github.com/dart-lang/sdk/issues/42910
- 'test_resources/**'
25 changes: 25 additions & 0 deletions bin/config.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import 'dart:io';

import 'package:yaml/yaml.dart';

class PubyConfig {
final List<String> excludes;

PubyConfig._({
required this.excludes,
});

PubyConfig.empty() : this._(excludes: []);

factory PubyConfig.fromProjectPath(String path) {
final file = File('$path/puby.yaml');
if (!file.existsSync()) {
return PubyConfig.empty();
}

final yaml = loadYaml(file.readAsStringSync());
final excludes = (yaml['exclude'] as List?)?.cast<String>() ?? [];

return PubyConfig._(excludes: excludes);
}
}
60 changes: 50 additions & 10 deletions bin/puby.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import 'package:path/path.dart';
import 'package:pub_update_checker/pub_update_checker.dart';
import 'package:yaml/yaml.dart';

import 'config.dart';

final decoder = Utf8Decoder();
final convenienceCommands = {
'gen': [
Expand Down Expand Up @@ -49,35 +51,41 @@ Usage:
exit(1);
}

final List<String> args;
final List<String> transformedArgs;
final firstArg = arguments.first;
if (convenienceCommands.containsKey(firstArg)) {
args = convenienceCommands[firstArg]! + arguments.sublist(1);
transformedArgs = convenienceCommands[firstArg]! + arguments.sublist(1);
} else {
args = ['pub', ...arguments];
transformedArgs = ['pub', ...arguments];
}
final argString = args.join(' ');

final projects = await findProjects();

int exitCode = 0;
for (final project in projects) {
if (shouldSkipProject(project, projects.length, args)) {
// Fvm is a layer on top of flutter, so don't add the prefix args for these checks
if (explicitExclude(project, transformedArgs) ||
defaultExclude(project, projects.length, transformedArgs)) {
continue;
}

final finalArgs = project.engine.prefixArgs + transformedArgs;

final argString = finalArgs.join(' ');
final pathString = project.path == '.' ? 'current directory' : project.path;
print(
greenPen(
'\nRunning "${project.engine.name} $argString" in $pathString...',
),
);

final process = await Process.start(
project.engine.name,
args,
finalArgs,
workingDirectory: project.path,
runInShell: true,
);

// Piping directly to stdout and stderr can cause unexpected behavior
process.stdout.listen((e) => stdout.write(decoder.convert(e)));
process.stderr.listen((e) => stderr.write(redPen(decoder.convert(e))));
Expand All @@ -97,14 +105,14 @@ Usage:
exit(exitCode);
}

bool shouldSkipProject(Project project, int projectCount, List<String> args) {
bool defaultExclude(Project project, int projectCount, List<String> args) {
final bool skip;
final String? message;
if (project.hidden) {
// Skip hidden folders
message = 'Skipping hidden project: ${project.path}';
skip = true;
} else if (project.engine == Engine.flutter &&
} else if (project.engine.isFlutter &&
project.example &&
args.length >= 2 &&
args[0] == 'pub' &&
Expand All @@ -128,6 +136,17 @@ bool shouldSkipProject(Project project, int projectCount, List<String> args) {
return skip;
}

bool explicitExclude(Project project, List<String> args) {
final argString = args.join(' ');

final skip = project.config.excludes.any(argString.startsWith);
if (skip) {
print(yellowPen('\nSkipping project with exclusion: ${project.path}'));
}

return skip;
}

Future<List<Project>> findProjects() async {
final pubspecEntities =
Directory.current.listSync(recursive: true, followLinks: false).where(
Expand All @@ -145,27 +164,32 @@ Future<List<Project>> findProjects() async {
class Project {
final Engine engine;
final String path;
final PubyConfig config;
final bool example;
final bool hidden;

Project._({
required this.engine,
required this.path,
required this.config,
required this.example,
required this.hidden,
});

static Future<Project> fromPubspecEntity(FileSystemEntity entity) async {
final pubspec = await loadYaml(File(entity.path).readAsStringSync());
final path = relative(entity.parent.path);
final config = PubyConfig.fromProjectPath(path);

final Engine engine;
if (pubspec['dependencies']?['flutter'] != null) {
if (Directory('$path/.fvm').existsSync()) {
engine = Engine.fvm;
} else if (pubspec['dependencies']?['flutter'] != null) {
engine = Engine.flutter;
} else {
engine = Engine.dart;
}

final path = relative(entity.parent.path);
final example = path.split(Platform.pathSeparator).last == 'example';
final hidden = path
.split(Platform.pathSeparator)
Expand All @@ -174,6 +198,7 @@ class Project {
return Project._(
engine: engine,
path: path,
config: config,
example: example,
hidden: hidden,
);
Expand All @@ -183,4 +208,19 @@ class Project {
enum Engine {
dart,
flutter,
fvm,
}

extension on Engine {
bool get isFlutter => this == Engine.flutter || this == Engine.fvm;

List<String> get prefixArgs {
switch (this) {
case Engine.dart:
case Engine.flutter:
return [];
case Engine.fvm:
return ['flutter'];
}
}
}
3 changes: 2 additions & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: puby
description: Run pub commands for all sub projects in the current directory recursively
version: 1.12.2
version: 1.13.0
homepage: https://github.com/Rexios80/puby

environment:
Expand All @@ -13,6 +13,7 @@ dependencies:
pub_update_checker: ^1.2.0

dev_dependencies:
test: ^1.20.2
rexios_lints: ^1.0.0

executables:
Expand Down
37 changes: 37 additions & 0 deletions test/clean_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import 'dart:io';

import 'package:test/test.dart';

import 'test_utils.dart';

void main() {
test('[engine] clean', () async {
final result = await testCommand(['clean']);
final stdout = result.stdout;

expect(result.exitCode, 0);

// dart
// Default exclusion
expectLine(stdout, ['dart_puby_test', 'Skip']);
// Default exclusion
expectLine(
stdout,
['dart_puby_test${Platform.pathSeparator}example', 'Skip'],
);

// flutter
expectLine(stdout, ['flutter_puby_test', 'flutter clean']);
expectLine(stdout, [
'flutter_puby_test${Platform.pathSeparator}example',
'flutter clean',
]);

// fvm
expectLine(stdout, ['fvm_puby_test', 'fvm flutter clean']);
expectLine(stdout, [
'fvm_puby_test${Platform.pathSeparator}example',
'fvm flutter clean',
]);
});
}
48 changes: 48 additions & 0 deletions test/gen_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import 'dart:io';

import 'package:test/test.dart';

import 'test_utils.dart';

void main() {
test('[engine] gen', () async {
final result = await testCommand(['gen']);
final stdout = result.stdout;

// Since these projects have no code generation, the command should fail
expect(result.exitCode, isNot(0));

// dart
expectLine(stdout, [
'dart_puby_test',
'dart pub run build_runner build --delete-conflicting-outputs',
]);
// Explicit exclusion
expectLine(
stdout,
['dart_puby_test${Platform.pathSeparator}example', 'Skip'],
);

// flutter
expectLine(stdout, [
'flutter_puby_test',
'flutter pub run build_runner build --delete-conflicting-outputs',
]);
// Explicit exclusion
expectLine(
stdout,
['flutter_puby_test${Platform.pathSeparator}example', 'Skip'],
);

// fvm
expectLine(stdout, [
'fvm_puby_test',
'fvm flutter pub run build_runner build --delete-conflicting-outputs',
]);
// Explicit exclusion
expectLine(
stdout,
['fvm_puby_test${Platform.pathSeparator}example', 'Skip'],
);
});
}
Loading