From fa2e8a052817d69bd4dd9602a6a5ea82affd29e3 Mon Sep 17 00:00:00 2001 From: stuartmorgan Date: Thu, 15 Jun 2023 09:57:35 -0400 Subject: [PATCH] [tool] Support code excerpts for any .md file (#4212) 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 8583e4993c..d50082377f 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 7bb0297de1..09862b3b32 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'), ])); });