import 'dart:io'; void main() async { final rootDir = Directory.current; final packagesDir = Directory('${rootDir.path}/packages'); final readmeFile = File('${rootDir.path}/README.md'); final gitmodulesFile = File('${rootDir.path}/.gitmodules'); final submodules = {}; if (gitmodulesFile.existsSync()) { final lines = await gitmodulesFile.readAsLines(); String? currentPath; String? currentUrl; for (final line in lines) { final trimmed = line.trim(); if (trimmed.startsWith('path = ')) { currentPath = trimmed.substring(7).trim(); } else if (trimmed.startsWith('url = ')) { currentUrl = trimmed.substring(6).trim(); } if (currentPath != null && currentUrl != null) { submodules[currentPath] = currentUrl; currentPath = null; currentUrl = null; } } } if (!packagesDir.existsSync()) { print('Error: packages directory not found at ${packagesDir.path}'); return; } final packages = <_Package>[]; await for (final entity in packagesDir.list(recursive: true)) { if (entity is File && entity.path.endsWith('pubspec.yaml')) { // Skip if likely in a build or hidden folder (simple heuristic) if (entity.path.contains('/.') || entity.path.contains('/build/')) { continue; } final pubspecContent = await entity.readAsString(); final name = _getValue(pubspecContent, 'name'); final description = _getValue(pubspecContent, 'description'); final version = _getValue(pubspecContent, 'version'); final publishTo = _getValue(pubspecContent, 'publish_to'); if (name != null && version != null && publishTo != 'none') { // Calculate relative path from root var relativePath = entity.parent.path.replaceFirst(rootDir.path, '.'); if (relativePath.startsWith('./')) { relativePath = relativePath.substring(2); } // Check for submodule match String? linkPath; for (final entry in submodules.entries) { final subPath = entry.key; final subUrl = entry.value; if (relativePath.startsWith(subPath)) { // It is inside this submodule // Verify if it is exact match or subdir if (relativePath == subPath || relativePath.startsWith('$subPath/')) { final remainder = relativePath.substring(subPath.length); // Remove leading slash if present final cleanRemainder = remainder.startsWith('/') ? remainder.substring(1) : remainder; // Construct URL // Assuming standard GitHub structure: url/tree/main/path // If cleanRemainder is empty, we link to root if (cleanRemainder.isEmpty) { linkPath = subUrl; } else { linkPath = '$subUrl/tree/main/$cleanRemainder'; } break; } } } // Fallback to local relative path if not in submodule (add ./ back for consistency if desired, or keep as relative path string) if (linkPath == null) { linkPath = './$relativePath'; } packages.add(_Package( name: name, description: description ?? '', path: linkPath, )); } } } packages.sort((a, b) => a.name.compareTo(b.name)); final sb = StringBuffer(); sb.writeln('# packages.dart'); sb.writeln(); sb.writeln('A collection of Flutter packages maintained by @rodydavis.'); sb.writeln(); sb.writeln('## Packages'); sb.writeln(); sb.writeln('| Package | Description | Version |'); sb.writeln('| :--- | :--- | :--- |'); for (final pkg in packages) { sb.writeln( '| [${pkg.name}](${pkg.path}) | ${pkg.description} | [![Pub](https://img.shields.io/pub/v/${pkg.name}.svg?style=popout)](https://pub.dartlang.org/packages/${pkg.name}) |'); } await readmeFile.writeAsString(sb.toString()); print('README.md updated with ${packages.length} packages.'); } String? _getValue(String content, String key) { final regex = RegExp('^$key:\\s*(.*)\$', multiLine: true); final match = regex.firstMatch(content); var value = match?.group(1)?.trim(); if (value != null) { if (value.startsWith("'") && value.endsWith("'")) { value = value.substring(1, value.length - 1); } else if (value.startsWith('"') && value.endsWith('"')) { value = value.substring(1, value.length - 1); } } return value; } class _Package { final String name; final String description; final String path; _Package({required this.name, required this.description, required this.path}); }