Skip to content

Commit

Permalink
feat: output URLs to prefilled GitHub release forms when executing `m…
Browse files Browse the repository at this point in the history
…elos version` (#406)

Co-authored-by: Gabriel Terwesten <[email protected]>
  • Loading branch information
Almighty-Alpaca and blaugold authored Oct 17, 2022
1 parent 2a8de82 commit 9c22cfb
Show file tree
Hide file tree
Showing 10 changed files with 192 additions and 15 deletions.
16 changes: 16 additions & 0 deletions docs/commands/version.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,22 @@ melos version -t

Use `--no-git-tag-version` to disable.

## --[no-]release-url (-r)

Generate and print a link to the prefilled release creation page for each package after versioning.
Defaults to `false`.

```bash
melos version --release-url
melos version -r
```

Use `--no-release-url` to disable.

The default for this option can be configured in
[command/version/releaseUrl](/configuration/overview#commandversionreleaseUrl) in your `melos.yaml`
file.

## --[no-]dependent-constraints

Update dependency version constraints of packages in this workspace that depend on any of the packages that will be updated with this versioning run. (defaults to on)
Expand Down
9 changes: 7 additions & 2 deletions docs/configuration/overview.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ A template for the commit message, that is generated by `melos version`.
Templates must use mustache syntax and have the following variables available:

- `new_package_versions`: A list of the versioned packages and their new
versions.
versions.

The default is:

Expand Down Expand Up @@ -230,7 +230,7 @@ command:
Whether to add links to commits in the CHANGELOG.md that is generated by
`melos version`.

Enabling this option, requires [`repository`](#repository) to be specified.
Enabling this option, requires [`repository`](/configuration/overview#repository) to be specified.

```yaml
command:
Expand Down Expand Up @@ -264,4 +264,9 @@ command:
updateGitTagRefs: true
```

### `command/version/releaseUrl`

Whether to generate and print a link to the prefilled release creation page for each package after
versioning. Defaults to `false`.

[glob]: https://docs.python.org/3/library/glob.html
27 changes: 23 additions & 4 deletions docs/guides/automated-releases.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -170,10 +170,10 @@ To publish packages on [pub.dev](https://pub.dev) you must:

> Internally, Melos uses `pub publish` to publish the packages.
Once you have [versioned](#versioning) your packages, run the publish command to
check everything is good to go:
Once you have [versioned](/guides/automated-releases#versioning) your packages,
run the publish command to check everything is good to go:

```dart
```sh
melos publish
```

Expand All @@ -182,10 +182,29 @@ By default, a dry-run is performed (nothing will be published).
Once satisfied with your pending releases, release them to
[pub.dev](https://pub.dev):

```dart
```sh
melos publish --no-dry-run
```

## Releases

Melos can generate a link to the prefilled release creation page.

To enable this once use one of the following arguments to `melos version`:

```sh
melos version --release-url
melos version -r
```

To enable this feature permanently, add the following to your `melos.yaml`:

```yaml
command:
version:
releaseUrl: true
```
## Git Hosted Packages
If your packages are private and you don't publish them to
Expand Down
9 changes: 9 additions & 0 deletions packages/melos/lib/src/command_runner/version.dart
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,12 @@ class VersionCommand extends MelosCommand {
'and tag the release. Pass --no-git-tag-version to disable the '
'behaviour.',
);
argParser.addFlag(
'release-url',
abbr: 'r',
help: 'Generate and print a link to the prefilled release creation page '
'for each package after versioning',
);
argParser.addOption(
'message',
abbr: 'm',
Expand Down Expand Up @@ -162,6 +168,7 @@ class VersionCommand extends MelosCommand {
final updateDependentsConstraints =
argResults!['dependent-constraints'] as bool;
final tag = argResults!['git-tag-version'] as bool;
final releaseUrl = argResults!.optional('release-url') as bool?;
final changelog = argResults!['changelog'] as bool;
final commitMessage =
(argResults!['message'] as String?)?.replaceAll(r'\n', '\n');
Expand Down Expand Up @@ -190,6 +197,7 @@ class VersionCommand extends MelosCommand {
manualVersions: {packageName: versionChange},
force: force,
gitTag: tag,
releaseUrl: releaseUrl,
updateChangelog: changelog,
updateDependentsConstraints: updateDependentsConstraints,
updateDependentsVersions: false,
Expand Down Expand Up @@ -231,6 +239,7 @@ class VersionCommand extends MelosCommand {
filter: parsePackageFilter(config.path),
force: force,
gitTag: tag,
releaseUrl: releaseUrl,
updateChangelog: changelog,
updateDependentsConstraints: updateDependentsConstraints,
updateDependentsVersions: updateDependentsVersions,
Expand Down
1 change: 1 addition & 0 deletions packages/melos/lib/src/commands/runner.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import '../command_runner/version.dart';
import '../common/exception.dart';
import '../common/git.dart';
import '../common/git_commit.dart';
import '../common/git_repository.dart';
import '../common/glob.dart';
import '../common/intellij_project.dart';
import '../common/io.dart';
Expand Down
31 changes: 31 additions & 0 deletions packages/melos/lib/src/commands/version.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ mixin _VersionMixin on _RunMixin {
bool updateDependentsConstraints = true,
bool updateDependentsVersions = true,
bool gitTag = true,
bool? releaseUrl,
String? message,
bool force = false,
// all
Expand Down Expand Up @@ -342,6 +343,36 @@ mixin _VersionMixin on _RunMixin {
'Ensure you commit and push your changes (if applicable).',
);
}

// TODO Support for automatically creating a release,
// e.g. when GITHUB_TOKEN is present in CI or using `gh release create`
// from GitHub CLI.

if (releaseUrl ?? config.commands.version.releaseUrl) {
final repository = workspace.config.repository;

if (repository == null) {
logger.warning(
'No repository configured in melos.yaml to generate a '
'release for.',
);
} else if (repository is! SupportsManualRelease) {
logger.warning('Repository does not support releases urls');
} else {
final pendingPackageReleases = pendingPackageUpdates.map((update) {
return link(
repository.releaseUrlForUpdate(update),
update.package.name,
);
}).join(ansiStylesDisabled ? '\n' : ', ');

logger.success(
'Make sure you create a release for each new package version:'
'${ansiStylesDisabled ? '\n' : ' '}'
'${AnsiStyles.bgBlack.gray(pendingPackageReleases)}',
);
}
}
}

Future<void> _setPubspecVersionForPackage(
Expand Down
9 changes: 9 additions & 0 deletions packages/melos/lib/src/common/git.dart
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,15 @@ String gitTagForPackageVersion(
return '$packageName-$prefix$packageVersion';
}

/// Generate a git release title for the specified package name and version.
String gitReleaseTitleForPackageVersion(
String packageName,
String packageVersion, {
String prefix = 'v',
}) {
return '$packageName $prefix$packageVersion';
}

/// Execute a `git` CLI command with arguments.
Future<ProcessResult> gitExecuteCommand({
required List<String> arguments,
Expand Down
51 changes: 50 additions & 1 deletion packages/melos/lib/src/common/git_repository.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@

import 'package:meta/meta.dart';

import 'git.dart';
import 'pending_package_update.dart';

/// A hosted git repository.
@immutable
abstract class HostedGitRepository {
Expand All @@ -35,9 +38,38 @@ abstract class HostedGitRepository {
Uri issueUrl(String id);
}

mixin SupportsManualRelease on HostedGitRepository {
/// Returns the URL to the prefilled release creation page for the release
/// of a package.
Uri releaseUrlForUpdate(MelosPendingPackageUpdate pendingPackageUpdate) {
final packageName = pendingPackageUpdate.package.name;
final packageVersion = pendingPackageUpdate.nextVersion.toString();

final tag = gitTagForPackageVersion(packageName, packageVersion);
final title = gitReleaseTitleForPackageVersion(packageName, packageVersion);
final body = pendingPackageUpdate.changelog.markdown;
final isPreRelease = pendingPackageUpdate.nextVersion.isPreRelease;

return releaseUrl(
tag: tag,
title: title,
body: body,
isPreRelease: isPreRelease,
);
}

/// Returns a URL to the release creation page with the given parameters.
Uri releaseUrl({
String? tag,
String? title,
String? body,
bool? isPreRelease,
});
}

/// A git repository, hosted by GitHub.
@immutable
class GitHubRepository extends HostedGitRepository {
class GitHubRepository extends HostedGitRepository with SupportsManualRelease {
const GitHubRepository({
required this.owner,
required this.name,
Expand Down Expand Up @@ -72,6 +104,23 @@ class GitHubRepository extends HostedGitRepository {
@override
Uri issueUrl(String id) => url.resolve('issues/$id');

@override
Uri releaseUrl({
String? tag,
String? title,
String? body,
bool? isPreRelease,
}) {
return url.resolve('releases/new').replace(
queryParameters: <String, String>{
if (tag != null) 'tag': tag,
if (title != null) 'title': title,
if (body != null) 'body': body,
if (isPreRelease != null) 'prerelease': '$isPreRelease',
},
);
}

@override
String toString() {
return '''
Expand Down
20 changes: 19 additions & 1 deletion packages/melos/lib/src/common/utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import 'dart:io';
import 'dart:isolate';

import 'package:ansi_styles/ansi_styles.dart';
import 'package:args/args.dart';
import 'package:graphs/graphs.dart';
import 'package:path/path.dart' as p;
import 'package:prompts/prompts.dart' as prompts;
Expand Down Expand Up @@ -255,7 +256,7 @@ bool get isCI {
String printablePath(String path) {
return p.posix
.prettyUri(p.posix.normalize(path))
.replaceAll(RegExp(r'[\/\\]+'), '/');
.replaceAll(RegExp(r'[/\\]+'), '/');
}

String get pathEnvVarSeparator => currentPlatform.isWindows ? ';' : ':';
Expand Down Expand Up @@ -350,6 +351,19 @@ String listAsPaddedTable(List<List<String>> table, {int paddingSize = 1}) {
return output.join('\n');
}

/// Generate a link for display in a terminal.
///
/// Similar to `<a href="$url">$text</a>` in HTML.
/// If ANSI escape codes are not supported, the link will be displayed as plain
/// text.
String link(Uri url, String text) {
if (ansiStylesDisabled) {
return '$text $url';
} else {
return '\x1B]8;;$url\x07$text\x1B]8;;\x07';
}
}

/// Simple check to see if the [Directory] qualifies as a plugin repository.
bool isWorkspaceDirectory(String directory) =>
fileExists(melosYamlPathForDirectory(directory));
Expand Down Expand Up @@ -613,3 +627,7 @@ extension Utf8StreamUtils on Stream<List<int>> {
// Encodes a [value] as a JSON string with indentation.
String prettyEncodeJson(Object? value) =>
const JsonEncoder.withIndent(' ').convert(value);

extension OptionalArgResults on ArgResults {
dynamic optional(String name) => wasParsed(name) ? this[name] : null;
}
Loading

0 comments on commit 9c22cfb

Please sign in to comment.