From 8ec486b836fef781b36fbc14d533bfe098e38031 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Wed, 14 Jun 2023 10:46:22 -0400 Subject: [PATCH 1/2] [tool] Support code excerpts for any .md file Updates `update-excerpts` to support any top-level .md file (other than CHANGELOG.md), rather than just README.md. This is useful for supplemental content, such as migration guides linked from the main README file. Also makes some small improvements to the error messaging: - The list of incorrect files is now relative to, and restricted to, the package. This makes the error message simpler, and ensures that changed files in other packages don't get listed. - Adds a link to the relevant wiki docs, since this has been a source of confusion for newer contributors. --- .../tool/lib/src/update_excerpts_command.dart | 43 +++++++++----- .../test/update_excerpts_command_test.dart | 57 +++++++++++++++++-- 2 files changed, 80 insertions(+), 20 deletions(-) diff --git a/script/tool/lib/src/update_excerpts_command.dart b/script/tool/lib/src/update_excerpts_command.dart index 8583e4993c7..023c6c1e587 100644 --- a/script/tool/lib/src/update_excerpts_command.dart +++ b/script/tool/lib/src/update_excerpts_command.dart @@ -15,7 +15,7 @@ import 'common/package_looping_command.dart'; import 'common/process_runner.dart'; import 'common/repository_package.dart'; -/// A command to update README code excerpts from code files. +/// A command to update .md code excerpts from code files. class UpdateExcerptsCommand extends PackageLoopingCommand { /// Creates a excerpt updater command instance. UpdateExcerptsCommand( @@ -51,7 +51,7 @@ class UpdateExcerptsCommand extends PackageLoopingCommand { final String name = 'update-excerpts'; @override - final String description = 'Updates code excerpts in README.md files, based ' + final String description = 'Updates code excerpts in .md files, based ' 'on code from code files, via code-excerpt'; @override @@ -105,13 +105,16 @@ class UpdateExcerptsCommand extends PackageLoopingCommand { } if (getBoolArg(_failOnChangeFlag)) { - final String? stateError = await _validateRepositoryState(); + final String? stateError = await _validateRepositoryState(package); if (stateError != null) { - printError('README.md is out of sync with its source excerpts.\n\n' - 'If you edited code in README.md directly, you should instead edit ' - 'the example source files. If you edited source files, run the ' - 'repository tooling\'s "$name" command on this package, and update ' - 'your PR with the resulting changes.'); + printError('One or more .md files are out of sync with their source ' + 'excerpts.\n\n' + 'If you edited code in a .md file directly, you should instead ' + 'edit the example source files. If you edited source files, run ' + 'the repository tooling\'s "$name" command on this package, and ' + 'update your PR with the resulting changes.\n\n' + 'For more information, see ' + 'https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#readme-code'); return PackageResult.fail([stateError]); } } @@ -138,14 +141,24 @@ class UpdateExcerptsCommand extends PackageLoopingCommand { return exitCode == 0; } - /// Runs the injection step to update [targetPackage]'s README with the latest - /// excerpts from [example], returning true on success. + /// Runs the injection step to update [targetPackage]'s top-level md files + /// with the latest excerpts from [example], returning true on success. Future _injectSnippets( RepositoryPackage example, { required RepositoryPackage targetPackage, }) async { - final String relativeReadmePath = - getRelativePosixPath(targetPackage.readmeFile, from: example.directory); + final List relativeMdPaths = targetPackage.directory + .listSync() + .whereType() + .where((File f) => + f.basename.toLowerCase().endsWith('.md') && + // Exclude CHANGELOG since it should never have excerpts. + f.basename != 'CHANGELOG.md') + .map((File f) => getRelativePosixPath(f, from: example.directory)) + .toList(); + if (relativeMdPaths.isEmpty) { + return true; + } final int exitCode = await processRunner.runAndStream( 'dart', [ @@ -154,7 +167,7 @@ class UpdateExcerptsCommand extends PackageLoopingCommand { '--write-in-place', '--yaml', '--no-escape-ng-interpolation', - relativeReadmePath, + ...relativeMdPaths, ], workingDir: example.directory); return exitCode == 0; @@ -212,11 +225,11 @@ class UpdateExcerptsCommand extends PackageLoopingCommand { /// Checks the git state, returning an error string if any .md files have /// changed. - Future _validateRepositoryState() async { + Future _validateRepositoryState(RepositoryPackage package) async { final io.ProcessResult checkFiles = await processRunner.run( 'git', ['ls-files', '--modified'], - workingDir: packagesDir, + workingDir: package.directory, logOnError: true, ); if (checkFiles.exitCode != 0) { diff --git a/script/tool/test/update_excerpts_command_test.dart b/script/tool/test/update_excerpts_command_test.dart index 7bb0297de13..09862b3b321 100644 --- a/script/tool/test/update_excerpts_command_test.dart +++ b/script/tool/test/update_excerpts_command_test.dart @@ -111,7 +111,7 @@ void main() { test('updates example readme when config is present', () async { final RepositoryPackage package = createFakePlugin('a_package', packagesDir, - extraFiles: [kReadmeExcerptConfigPath]); + extraFiles: [kReadmeExcerptConfigPath, 'example/README.md']); final Directory example = getExampleDir(package); final List output = @@ -153,6 +153,52 @@ void main() { ])); }); + test('includes all top-level .md files', () async { + const String otherMdFileName = 'another_file.md'; + final RepositoryPackage package = createFakePlugin('a_package', packagesDir, + extraFiles: [kReadmeExcerptConfigPath, otherMdFileName]); + final Directory example = getExampleDir(package); + + final List output = + await runCapturingPrint(runner, ['update-excerpts']); + + expect( + processRunner.recordedCalls, + containsAll([ + ProcessCall( + 'dart', + const [ + 'run', + 'build_runner', + 'build', + '--config', + 'excerpt', + '--output', + 'excerpts', + '--delete-conflicting-outputs', + ], + example.path), + ProcessCall( + 'dart', + const [ + 'run', + 'code_excerpt_updater', + '--write-in-place', + '--yaml', + '--no-escape-ng-interpolation', + '../README.md', + '../$otherMdFileName', + ], + example.path), + ])); + + expect( + output, + containsAllInOrder([ + contains('Ran for 1 package(s)'), + ])); + }); + test('skips when no config is present', () async { createFakePlugin('a_package', packagesDir); @@ -277,7 +323,7 @@ void main() { test('fails if example injection fails', () async { createFakePlugin('a_package', packagesDir, - extraFiles: [kReadmeExcerptConfigPath]); + extraFiles: [kReadmeExcerptConfigPath, 'example/README.md']); processRunner.mockProcessesForExecutable['dart'] = [ FakeProcessInfo(MockProcess(), ['pub', 'get']), @@ -307,7 +353,7 @@ void main() { createFakePlugin('a_plugin', packagesDir, extraFiles: [kReadmeExcerptConfigPath]); - const String changedFilePath = 'packages/a_plugin/README.md'; + const String changedFilePath = 'README.md'; processRunner.mockProcessesForExecutable['git'] = [ FakeProcessInfo(MockProcess(stdout: changedFilePath)), ]; @@ -323,9 +369,10 @@ void main() { expect( output, containsAllInOrder([ - contains('README.md is out of sync with its source excerpts'), + contains( + 'One or more .md files are out of sync with their source excerpts'), contains('Snippets are out of sync in the following files: ' - 'packages/a_plugin/README.md'), + '$changedFilePath'), ])); }); From 9c7d30ca7f4829d11b13a9d71ba11970a35f64f4 Mon Sep 17 00:00:00 2001 From: stuartmorgan Date: Thu, 15 Jun 2023 08:59:42 -0400 Subject: [PATCH 2/2] Adjust comment --- script/tool/lib/src/update_excerpts_command.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/tool/lib/src/update_excerpts_command.dart b/script/tool/lib/src/update_excerpts_command.dart index 023c6c1e587..d50082377f8 100644 --- a/script/tool/lib/src/update_excerpts_command.dart +++ b/script/tool/lib/src/update_excerpts_command.dart @@ -141,7 +141,7 @@ class UpdateExcerptsCommand extends PackageLoopingCommand { return exitCode == 0; } - /// Runs the injection step to update [targetPackage]'s top-level md files + /// Runs the injection step to update [targetPackage]'s top-level .md files /// with the latest excerpts from [example], returning true on success. Future _injectSnippets( RepositoryPackage example, {