Skip to content

Commit

Permalink
Add a command to generate release notes on the flutter website (flutt…
Browse files Browse the repository at this point in the history
  • Loading branch information
kenzieschmoll authored May 2, 2024
1 parent 7ead334 commit 0dfe500
Show file tree
Hide file tree
Showing 4 changed files with 262 additions and 25 deletions.
29 changes: 13 additions & 16 deletions packages/devtools_app/release_notes/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,22 +46,19 @@ viewer can be used efficiently.
Release notes for DevTools are hosted on the Flutter website.
They are indexed at https://docs.flutter.dev/tools/devtools/release-notes.

To add release notes for the latest release, create a PR with the appropriate
changes for your release:

1. Copy the markdown from [NEXT_RELEASE_NOTES.md](NEXT_RELEASE_NOTES.md) over
to the Flutter website. This file contains the running release notes for
the current DevTools version.
- See this [PR](https://github.com/flutter/website/pull/10113) for
an example of how to add these notes to the Flutter website.
2. Copy any images from the `images/` directory over to the Flutter website.
- Make sure to copy all images over to the proper website directory:
- `.../tools/devtools/release-notes/images-<VERSION>/`
- Make sure to update all image links in the markdown with the `site_url` tag:
- `/tools/devtools/release-notes/images-<VERSION>/<IMAGE_FILE>`
3. Once you are satisfied with the release notes,
create a new branch directly on the `flutter/website` repo and open a PR,
and then proceed to the testing steps below.
### Prerequisite

Before continuing, ensure you have your local environment set up for
[contributing](https://github.com/flutter/website) to the `flutter/website` repo.

### Creating the release notes PR

Draft release notes on a local `flutter/website` branch using the following command:
```console
devtools_tool release-notes -w /Users/me/absolute/path/to/flutter/website
```

Clean up the drafted notes on your local `flutter/website` branch and open a PR.

### Testing the release notes in DevTools

Expand Down
18 changes: 9 additions & 9 deletions tool/RELEASE_INSTRUCTIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,15 @@ just released into the Dart SDK (the hash you updated the DEPS file with):
version for the tag is automatically determined from `packages/devtools/pubspec.yaml`
so there is no need to manually enter the version.
### Verify and Submit the release notes
1. Follow the instructions outlined in the release notes
[README.md](https://github.com/flutter/devtools/blob/master/packages/devtools_app/release_notes/README.md)
to add DevTools release notes to Flutter website and test them in DevTools.
2. Once release notes are submitted to the Flutter website, send an announcement to
[g/flutter-internal-announce](http://g/flutter-internal-announce) with a link to
the new release notes.
### Prepare DevTools for the next beta release
1. Update the DevTools version for the next release:
```shell
Expand All @@ -207,15 +216,6 @@ just released into the Dart SDK (the hash you updated the DEPS file with):
- Go to https://github.com/flutter/devtools/pulls to see the pull request that
ends up being created
### Verify and Submit the release notes
1. Follow the instructions outlined in the release notes
[README.md](https://github.com/flutter/devtools/blob/master/packages/devtools_app/release_notes/README.md)
to add DevTools release notes to Flutter website and test them in DevTools.
2. Once release notes are submitted to the Flutter website, send an announcement to
[g/flutter-internal-announce](http://g/flutter-internal-announce) with a link to
the new release notes.
## Dev release into the Dart SDK master branch
To publish a dev release follow just section [Update the DevTools hash in the Dart SDK](#update-the-devtools-hash-in-the-dart-sdk)
Expand Down
238 changes: 238 additions & 0 deletions tool/lib/commands/release_notes_helper.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
// Copyright 2024 The Chromium Authors. 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:cli_util/cli_logging.dart';
import 'package:devtools_tool/model.dart';
import 'package:devtools_tool/utils.dart';
import 'package:io/io.dart';
import 'package:path/path.dart' as p;

class ReleaseNotesCommand extends Command {
ReleaseNotesCommand() {
argParser.addOption(
_websiteRepoPath,
abbr: 'w',
help: 'The absolute path to the flutter/website repo clone on disk.',
);
}

static const _websiteRepoPath = 'website-repo';

@override
String get description =>
'Creates a PR on the flutter/website repo with the current release notes.';

@override
String get name => 'release-notes';

@override
FutureOr? run() async {
final log = Logger.standard();
final processManager = ProcessManager();

final devToolsReleaseNotesDirectory = Directory(
p.join(
DevToolsRepo.getInstance().devtoolsAppDirectoryPath,
'release_notes',
),
);
final devToolsReleaseNotes = _DevToolsReleaseNotes.fromFile(
File(p.join(devToolsReleaseNotesDirectory.path, 'NEXT_RELEASE_NOTES.md')),
);
final releaseNotesVersion = devToolsReleaseNotes.version;
log.stdout(
'Drafting release notes for DevTools version $releaseNotesVersion...',
);

// Create a local branch on the flutter/website repo.
final websiteRepoPath = argResults![_websiteRepoPath] as String;
try {
await processManager.runAll(
commands: [
CliCommand.git(['stash']),
CliCommand.git(['checkout', 'main']),
CliCommand.git(['pull']),
CliCommand.git(['submodule', 'update', '--init', '--recursive']),
CliCommand.git(
['checkout', '-b', 'devtools-release-notes-$releaseNotesVersion'],
),
],
workingDirectory: websiteRepoPath,
);
} catch (e) {
log.stderr(
'Something went wrong while trying to prepare a branch on the '
'flutter/website repo. Please make sure your flutter/website clone '
'is set up as specified by the contributing instructions: '
'https://github.com/flutter/website?tab=readme-ov-file#contributing.'
'\n\n$e',
);
return;
}

final websiteReleaseNotesDir = Directory(
p.join(
websiteRepoPath,
'src',
'content',
'tools',
'devtools',
'release-notes',
),
);
if (!websiteReleaseNotesDir.existsSync()) {
throw FileSystemException(
'Website release notes directory does not exist.',
websiteReleaseNotesDir.path,
);
}

// Write the 'release-notes-<x.y.z>.md' file.
File(
p.join(
websiteReleaseNotesDir.path,
'release-notes-$releaseNotesVersion.md',
),
)
..createSync()
..writeAsStringSync(
'''---
short-title: $releaseNotesVersion release notes
description: Release notes for Dart and Flutter DevTools version $releaseNotesVersion.
toc: false
---
{% include ./release-notes-$releaseNotesVersion-src.md %}
''',
flush: true,
);

// Create the 'release-notes-<x.y.z>-src.md' file.
final releaseNotesSrcMd = File(
p.join(
websiteReleaseNotesDir.path,
'release-notes-$releaseNotesVersion-src.md',
),
)..createSync();

final srcLines = devToolsReleaseNotes.srcLines;

// Copy release notes images and fix image reference paths.
if (devToolsReleaseNotes.imageLineIndices.isNotEmpty) {
// This set of release notes contains images. Perform the line
// transformations and copy the image files.
final websiteImagesDirName = 'images-$releaseNotesVersion';
final devtoolsImagesDir =
Directory(p.join(devToolsReleaseNotesDirectory.path, 'images'));
final websiteImagesDir = Directory(
p.join(websiteReleaseNotesDir.path, websiteImagesDirName),
)..createSync();
await copyPath(devtoolsImagesDir.path, websiteImagesDir.path);

// Remove the .gitkeep file that was copied over.
File(p.join(websiteImagesDir.path, '.gitkeep')).deleteSync();

for (final index in devToolsReleaseNotes.imageLineIndices) {
final line = srcLines[index];
final transformed = line.replaceFirst(
_DevToolsReleaseNotes._imagePathMarker,
'/tools/devtools/release-notes/$websiteImagesDirName',
);
srcLines[index] = transformed;
}
}

// Write the 'release-notes-<x.y.z>-src.md' file, including any updates for
// image paths.
releaseNotesSrcMd.writeAsStringSync(
srcLines.joinWithNewLine(),
flush: true,
);

// Write the 'devtools_releases.yml' file.
final releasesYml =
File(p.join(websiteRepoPath, 'src', '_data', 'devtools_releases.yml'));
if (!releasesYml.existsSync()) {
throw FileSystemException(
'The devtools_releases.yml file does not exist.',
releasesYml.path,
);
}
final releasesYmlContent =
releasesYml.readAsStringSync().replaceFirst('releases:', '''releases:
- '$releaseNotesVersion\'''');
releasesYml.writeAsStringSync(releasesYmlContent, flush: true);

log.stdout(
'Release notes successfully drafted in a local flutter/website branch. '
'Please clean them up by deleting empty sections and fixing any grammar '
'mistakes or typos.\n\nCreate a PR on the flutter/website repo when you are '
'finished.',
);
}
}

class _DevToolsReleaseNotes {
_DevToolsReleaseNotes._({
required this.file,
required this.version,
required this.srcLines,
required this.imageLineIndices,
});

factory _DevToolsReleaseNotes.fromFile(File file) {
if (!file.existsSync()) {
throw FileSystemException(
'NEXT_RELEASE_NOTES.md file does not exist.',
file.path,
);
}

final rawLines = file.readAsLinesSync();

late String version;
late int titleLineIndex;
final versionRegExp = RegExp(r"\d+\.\d+\.\d+");
for (int i = 0; i < rawLines.length; i++) {
final line = rawLines[i];
final matches = versionRegExp.allMatches(line);
if (matches.isEmpty) continue;
// This match should be from the line "# DevTools <x.y.z> release notes".
version = matches.first.group(0)!;
// This is the markdown title where the release notes src begins.
titleLineIndex = i;
break;
}

// TODO(kenz): one nice polish task could be to remove sections that are
// empty (i.e. sections that have the line
// "TODO: Remove this section if there are not any general updates.").
final srcLines = rawLines.sublist(titleLineIndex);
final imageLineIndices = <int>{};
for (int i = 0; i < srcLines.length; i++) {
final line = srcLines[i];
if (line.contains(_imagePathMarker)) {
imageLineIndices.add(i);
}
}

return _DevToolsReleaseNotes._(
file: file,
version: version,
srcLines: srcLines,
imageLineIndices: imageLineIndices,
);
}

final File file;
final String version;
final List<String> srcLines;
final Set<int> imageLineIndices;

static const _imagePathMarker = './images/';
}
2 changes: 2 additions & 0 deletions tool/lib/devtools_command_runner.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import 'package:args/command_runner.dart';
import 'package:devtools_tool/commands/build.dart';
import 'package:devtools_tool/commands/fix_goldens.dart';
import 'package:devtools_tool/commands/generate_code.dart';
import 'package:devtools_tool/commands/release_notes_helper.dart';
import 'package:devtools_tool/commands/serve.dart';
import 'package:devtools_tool/commands/sync.dart';
import 'package:devtools_tool/commands/tag_version.dart';
Expand Down Expand Up @@ -35,6 +36,7 @@ class DevToolsCommandRunner extends CommandRunner {
addCommand(ListCommand());
addCommand(PubGetCommand());
addCommand(ReleaseHelperCommand());
addCommand(ReleaseNotesCommand());
addCommand(RepoCheckCommand());
addCommand(RollbackCommand());
addCommand(ServeCommand());
Expand Down

0 comments on commit 0dfe500

Please sign in to comment.