Skip to content

Commit

Permalink
Merge pull request #5 from phntmxyz/modifications
Browse files Browse the repository at this point in the history
  • Loading branch information
passsy authored Nov 30, 2023
2 parents b68e1c2 + 8f19532 commit 711726e
Show file tree
Hide file tree
Showing 6 changed files with 211 additions and 24 deletions.
5 changes: 4 additions & 1 deletion .github/workflows/unit_tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,7 @@ jobs:
- name: Install dependencies
run: dart pub get
- name: Run tests
run: dart test
run: |
git config --global user.email "[email protected]"
git config --global user.name "Dash"
dart test
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Changelog

## 1.2.0
- Add modification API `BumpVersionCommand.addModification(...)` #5

## 1.1.1
- Add topics to `pubspec.yaml`

Expand Down
24 changes: 20 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@ A plugin for [sidekick CLIs](https://pub.dev/packages/sidekick).

## Description

Bumps the version of a package.

Take a look at the available [sidekick plugins on pub.dev](https://pub.dev/packages?q=dependency%3Asidekick_core).

- Bumps the version of a package (major, minor, patch)
- Optionally commits the version bump
- modification API to adjust other files (e.g. `CHANGELOG.md`)

## Installation

Expand Down Expand Up @@ -40,6 +39,23 @@ E.g. current version is `1.2.3`, `--minor` is selected -> updates to `1.3.0`.

If `--commit` is given, the version bump is automatically committed.

## Modification API

```dart
runner..addCommand(BumpVersionCommand()..addModification(bumpChangelog));
```

```dart
Future<void> bumpChangelog(DartPackage package, Version oldVersion, Version newVersion) async {
final readme = package.root.file('README.md');
final content = readme.readAsStringSync();
final versionRegex = RegExp(r'my_package: \^(.+)');
final update = content.replaceFirst(versionRegex, 'my_package: ^${newVersion.canonicalizedVersion}');
readme.writeAsStringSync(update);
}
```

## License

```
Expand Down
99 changes: 90 additions & 9 deletions lib/src/bump_version_command.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'dart:async';

import 'package:phntmxyz_bump_version_sidekick_plugin/phntmxyz_bump_version_sidekick_plugin.dart';
import 'package:phntmxyz_bump_version_sidekick_plugin/src/git_file_committer.dart';
import 'package:sidekick_core/sidekick_core.dart';

class BumpVersionCommand extends Command {
Expand Down Expand Up @@ -33,6 +34,13 @@ class BumpVersionCommand extends Command {
help:
'Automatically commits the version bump. Precondition, no local changes in pubspec.yaml',
);
addModification(bumpPubspecVersion);
}

final List<FileModification> _modifications = [];

void addModification(FileModification modification) {
_modifications.add(modification);
}

@override
Expand Down Expand Up @@ -63,20 +71,93 @@ class BumpVersionCommand extends Command {
// Bump version
final Version bumpedVersion = currentVersion.bumpVersion(bumpType);

final fileCommitter = GitFileCommitter(pubspecFile);
Future<void> applyModifications() async {
for (final modification in _modifications) {
await modification.call(package, version, bumpedVersion);
}
}

bool bumped = false;
if (commit) {
fileCommitter.captureFileStatus();
final detachedHEAD = 'git symbolic-ref -q HEAD'
.start(progress: Progress.printStdErr(), nothrow: true);
if (detachedHEAD.exitCode != 0) {
throw 'You are in "detached HEAD" state, can not commit version bump';
} else {
await commitFileModifications(
applyModifications,
commitMessage: 'Bump version to $bumpedVersion',
);
bumped = true;
}
}
if (!bumped) {
await applyModifications();
}

// Save to pubspec.yaml
setPubspecVersion(pubspecFile, bumpedVersion);
print(
green('${package.name} version bumped '
'from $currentVersion to $bumpedVersion'),
green(
'${package.name} version bumped '
'from $currentVersion to $bumpedVersion',
),
);
}

if (commit) {
fileCommitter.commit('Bump version to $bumpedVersion');
/// Updates the version in pubspec.yaml
void bumpPubspecVersion(
DartPackage package,
Version oldVersion,
Version newVersion,
) {
setPubspecVersion(package.pubspec, newVersion);
}
}

typedef FileModification = FutureOr<void> Function(
DartPackage package,
Version oldVersion,
Version newVersion,
);

/// Commits only the file changes that have been done in [block]
Future<void> commitFileModifications(
FutureOr<void> Function() block, {
required String commitMessage,
}) async {
final stashName = 'pre-bump-${DateTime.now().toIso8601String()}';

// stash changes
'git stash save --include-untracked "$stashName"'
.start(progress: Progress.printStdErr());

try {
// apply modifications
await block();

// commit
'git add -A'.start(progress: Progress.printStdErr());
'git commit -m "$commitMessage" --no-verify'
.start(progress: Progress.printStdErr());
'git --no-pager log -n1 --oneline'.run;
} catch (e) {
printerr('Detected error, discarding modifications');
// discard all modifications
'git reset --hard'.start(progress: Progress.printStdErr());
rethrow;
} finally {
final stashes = 'git stash list'.start(progress: Progress.capture()).lines;
final stash = stashes.firstOrNullWhere((line) => line.contains(stashName));
if (stash != null) {
final stashId = RegExp(r'stash@{(\d+)}').firstMatch(stash)?.group(1);
// restore changes
'git merge --squash --strategy-option=theirs stash@{$stashId}'
.start(progress: Progress.print());
try {
// apply modifications again to make sure the stash did not overwrite already made changes
await block();
} catch (e) {
// ignore
}
}
}
}
Expand Down
73 changes: 73 additions & 0 deletions test/add_modification_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import 'package:phntmxyz_bump_version_sidekick_plugin/phntmxyz_bump_version_sidekick_plugin.dart';
import 'package:sidekick_core/sidekick_core.dart';
import 'package:sidekick_test/sidekick_test.dart';
import 'package:test/test.dart';

void main() {
test('executes sync modification', () async {
await insideFakeProjectWithSidekick((dir) async {
await dir.file('pubspec.yaml').appendString('\nversion: 1.2.3');
final readme = dir.file('README.md');
readme.writeAsStringSync('''
# Package
```yaml
dependencies:
my_package: ^1.2.3
```
''');
final runner = initializeSidekick(
dartSdkPath: fakeDartSdk().path,
);
runner.addCommand(
BumpVersionCommand()
..addModification((package, oldVersion, newVersion) {
final versionRegex = RegExp(r'my_package: \^(.+)');
final update = readme.readAsStringSync().replaceFirst(
versionRegex,
'my_package: ^${newVersion.canonicalizedVersion}',
);
readme.writeAsStringSync(update);
}),
);
await runner.run(['bump-version', '--minor']);

expect(readme.readAsStringSync(), contains('my_package: ^1.3.0'));
});
});

test('executes async modification', () async {
await insideFakeProjectWithSidekick((dir) async {
await dir.file('pubspec.yaml').appendString('\nversion: 1.2.3');
final readme = dir.file('README.md');
readme.writeAsStringSync('''
# Package
```yaml
dependencies:
my_package: ^1.2.3
```
''');
final runner = initializeSidekick(
dartSdkPath: fakeDartSdk().path,
);
runner.addCommand(
BumpVersionCommand()
..addModification((package, oldVersion, newVersion) async {
// make it really async
await Future.delayed(const Duration(milliseconds: 200));

final versionRegex = RegExp(r'my_package: \^(.+)');
final update = readme.readAsStringSync().replaceFirst(
versionRegex,
'my_package: ^${newVersion.canonicalizedVersion}',
);
readme.writeAsStringSync(update);
}),
);
await runner.run(['bump-version', '--minor']);

expect(readme.readAsStringSync(), contains('my_package: ^1.3.0'));
});
});
}
31 changes: 21 additions & 10 deletions test/bump_version_command_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -79,40 +79,51 @@ void main() {
});

group('commit', () {
test('throws when pubspec has local changes', () async {
test('works when pubspec has local changes', () async {
await insideFakeProjectWithSidekick((dir) async {
'git init -q ${dir.path} '.run;
'git -C ${dir.path} add .'.run;
_gitCommit(dir);
await dir.file('pubspec.yaml').appendString('\nversion: 1.2.3');
_gitCommit(dir);
// local change
await dir.file('pubspec.yaml').appendString('\n\n#comment');
final runner = initializeSidekick(
dartSdkPath: fakeDartSdk().path,
);
runner.addCommand(BumpVersionCommand());
await runner.run(['bump-version', '--major', '--commit']);

final lastCommitMessage = 'git -C ${dir.path} show -s --format=%s'
.start(progress: Progress.capture(), nothrow: true)
.lines
.first;
expect(lastCommitMessage, contains('Bump version to 2.0.0'));
expect(
() => runner.run(['bump-version', '--major', '--commit']),
throwsA(contains('There are local changes')),
dir.file('pubspec.yaml').readAsStringSync(),
contains('#comment'),
);
expect(
dir.file('pubspec.yaml').readAsStringSync(),
contains('version: 2.0.0'),
);
});
});

test('throws when there are staged files', () async {
test('staged files are restored', () async {
await insideFakeProjectWithSidekick((dir) async {
await dir.file('pubspec.yaml').appendString('\nversion: 1.2.3');
'git init -q ${dir.path} '.run;
'git -C ${dir.path} add .'.run;
_gitCommit(dir);
dir.file('foo').writeAsStringSync('foo');
final fooFile = dir.file('foo')..writeAsStringSync('foo');
'git -C ${dir.path} add foo'.run;

final runner = initializeSidekick(
dartSdkPath: fakeDartSdk().path,
);
runner.addCommand(BumpVersionCommand());
expect(
() => runner.run(['bump-version', '--major', '--commit']),
throwsA(contains('There are staged files')),
);
expect(fooFile.existsSync(), isTrue);
expect(fooFile.readAsStringSync(), 'foo');
});
});

Expand Down

0 comments on commit 711726e

Please sign in to comment.