/// Runs the Check Docs workspace validation tool. library; import 'dart:io'; /// Generates Dart docs for each package with public libraries and fails on warnings. Future main(List arguments) async { final missingFileDocs = await _trackedDartFilesMissingFileDocs(); if (missingFileDocs.isNotEmpty) { stderr.writeln('Dart files missing file-level Dartdoc library headers:'); for (final path in missingFileDocs) { stderr.writeln('- $path'); } } final outputRoot = Directory(_join(_join('doc', 'api'), 'packages')); if (await outputRoot.exists()) { await outputRoot.delete(recursive: true); } await outputRoot.create(recursive: true); final packages = Directory('packages') .listSync() .whereType() .where((directory) { return File(_join(directory.path, 'pubspec.yaml')).existsSync() && Directory(_join(directory.path, 'lib')).existsSync(); }).toList(growable: false) ..sort((left, right) => left.path.compareTo(right.path)); var failed = missingFileDocs.isNotEmpty; for (final package in packages) { final outputPath = _join(outputRoot.absolute.path, _basename(package.path)); stdout.writeln('> dart doc --output $outputPath (${package.path})'); final result = await Process.run( 'dart', ['doc', '--output', outputPath], workingDirectory: package.path, runInShell: Platform.isWindows, ); final output = '${result.stdout}${result.stderr}'; stdout.write(output); if (result.exitCode != 0 || _hasWarnings(output)) { failed = true; } } if (failed) { stderr.writeln('Dart documentation gate failed.'); exitCode = 1; } } Future> _trackedDartFilesMissingFileDocs() async { final result = await Process.run( 'git', ['ls-files', '*.dart'], runInShell: Platform.isWindows, ); if (result.exitCode != 0) { throw ProcessException( 'git', ['ls-files', '*.dart'], 'Unable to list tracked Dart files.', result.exitCode, ); } final paths = (result.stdout as String) .split('\n') .where((path) => path.trim().isNotEmpty) .toList(growable: false) ..sort(); final missing = []; for (final path in paths) { final file = File(path); final contents = await file.readAsString(); if (_isGeneratedPart(path, contents)) { continue; } if (!_hasFileLevelLibraryDocs(contents)) { missing.add(path); } } return missing; } bool _isGeneratedPart(String path, String contents) { return path.endsWith('.g.dart') || contents.startsWith("part of '"); } bool _hasFileLevelLibraryDocs(String contents) { return RegExp(r'^\s*///[\s\S]*?\nlibrary;', multiLine: false) .hasMatch(contents); } bool _hasWarnings(String output) { return output.contains(' warning:') || RegExp(r'Found [1-9][0-9]* warnings').hasMatch(output); } String _join(String first, String second) { final separator = Platform.pathSeparator; return '$first$separator$second'; } String _basename(String path) { final normalized = path.replaceAll('\\', '/'); return normalized.substring(normalized.lastIndexOf('/') + 1); }