| // Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file | |
| // for details. 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:io'; | |
| import 'package:args/args.dart'; | |
| const _packageOption = 'package'; | |
| const _versionOption = 'version'; | |
| const _resetFlag = 'reset'; | |
| const _skipStableCheckFlag = 'skipStableCheck'; | |
| final _repoRootDir = File.fromUri(Platform.script).parent.parent.path; | |
| /// Note: Must be run from the /tool directory. | |
| /// | |
| /// To prepare WebDev for release: | |
| /// `dart run release.dart -p webdev` | |
| /// | |
| /// To reset WebDev after a release: | |
| /// `dart run release.dart --reset -p webdev -v [[wip version]]` | |
| void main(List<String> arguments) async { | |
| final parser = ArgParser() | |
| ..addOption( | |
| _packageOption, | |
| abbr: 'p', | |
| allowed: ['webdev'], | |
| defaultsTo: 'webdev', | |
| ) | |
| ..addOption(_versionOption, abbr: 'v') | |
| ..addFlag(_resetFlag, abbr: 'r') | |
| ..addFlag(_skipStableCheckFlag, abbr: 's'); | |
| final argResults = parser.parse(arguments); | |
| final package = argResults[_packageOption] as String?; | |
| if (package == null) { | |
| _logWarning('Please specify package with -p webdev or --package=webdev'); | |
| return; | |
| } | |
| final isReset = argResults[_resetFlag] as bool?; | |
| final newVersion = argResults[_versionOption] as String?; | |
| final skipStableCheck = argResults[_skipStableCheckFlag] as bool?; | |
| int exitCode; | |
| if (isReset == true) { | |
| exitCode = await runReset(package: package, newVersion: newVersion); | |
| } else { | |
| exitCode = await runRelease( | |
| package: package, | |
| newVersion: newVersion, | |
| skipStableCheck: skipStableCheck, | |
| ); | |
| } | |
| if (exitCode != 0) { | |
| _logWarning('Run terminated unexpectedly with exit code: $exitCode'); | |
| } | |
| } | |
| Future<int> runReset({required String package, String? newVersion}) { | |
| // Check that a new wip version has been provided. | |
| final currentVersion = _readVersionFile(package); | |
| if (newVersion == null || !newVersion.contains('wip')) { | |
| _logInfo(''' | |
| Please provide the next wip version for $package, e.g. -v 3.0.1-wip | |
| Current version is $currentVersion. | |
| '''); | |
| return Future.value(1); | |
| } | |
| // Reset the dependency overrides for the package: | |
| _updateOverrides(package, includeOverrides: true); | |
| // Update the version strings in CHANGELOG and pubspec.yaml. | |
| _updateVersionStrings( | |
| package, | |
| currentVersion: currentVersion, | |
| nextVersion: newVersion, | |
| isReset: true, | |
| ); | |
| // Build the package. | |
| final exitCode = _buildPackage(package); | |
| return exitCode; | |
| } | |
| Future<int> runRelease({ | |
| required String package, | |
| String? newVersion, | |
| bool? skipStableCheck, | |
| }) async { | |
| // Check that we are on a stable version of Dart. | |
| if (skipStableCheck != true) { | |
| final checkVersionProcess = await Process.run('dart', ['--version']); | |
| final versionInfo = checkVersionProcess.stdout as String; | |
| if (!versionInfo.contains('stable')) { | |
| _logWarning(''' | |
| Expected to be on stable version of Dart, instead on: | |
| $versionInfo | |
| To skip this check, re-run with --skipStableCheck | |
| '''); | |
| return checkVersionProcess.exitCode; | |
| } | |
| } | |
| // Update the pinned version of DWDS for webdev releases. | |
| if (package == 'webdev') { | |
| final newVersion = await _updateDwdsPin('webdev'); | |
| _logInfo('Add pinned DWDS info to CHANGELOG.'); | |
| final changelog = File('$_repoRootDir/webdev/CHANGELOG.md'); | |
| _addNewLine( | |
| changelog, | |
| newLine: '- Update `dwds` constraint to `${newVersion ?? 'TODO'}`.', | |
| insertAt: 2, | |
| ); | |
| } | |
| // Remove any dependency overrides for the package: | |
| _logInfo('Removing dependency overrides for $package.'); | |
| _updateOverrides(package, includeOverrides: false); | |
| // Run dart pub upgrade. | |
| for (final packagePath in [ | |
| '$_repoRootDir/webdev', | |
| '$_repoRootDir/frontend_server_client', | |
| ]) { | |
| _logInfo('Upgrading pub packages for $packagePath'); | |
| final pubUpgradeProcess = await Process.run('dart', [ | |
| 'pub', | |
| 'upgrade', | |
| ], workingDirectory: packagePath); | |
| final upgradeErrors = pubUpgradeProcess.stderr as String; | |
| if (upgradeErrors.isNotEmpty) { | |
| _logWarning(upgradeErrors); | |
| return pubUpgradeProcess.exitCode; | |
| } | |
| } | |
| // Update the version strings in CHANGELOG and pubspec.yaml. | |
| final currentVersion = _readVersionFile(package); | |
| final nextVersion = newVersion ?? _removeWip(currentVersion); | |
| _updateVersionStrings( | |
| package, | |
| currentVersion: currentVersion, | |
| nextVersion: nextVersion, | |
| ); | |
| // Build the package. | |
| final exitCode = _buildPackage(package); | |
| return exitCode; | |
| } | |
| Future<int> _buildPackage(String package) async { | |
| _logInfo('Building $package'); | |
| final buildProcess = await Process.run('dart', [ | |
| 'run', | |
| 'build_runner', | |
| 'build', | |
| ], workingDirectory: '$_repoRootDir/$package'); | |
| final buildErrors = buildProcess.stderr as String; | |
| if (buildErrors.isNotEmpty) { | |
| _logWarning(buildErrors); | |
| } | |
| return buildProcess.exitCode; | |
| } | |
| void _updateOverrides(String package, {required bool includeOverrides}) { | |
| final overridesFilePath = '$_repoRootDir/$package/pubspec_overrides.yaml'; | |
| final noOverridesFilePath = | |
| '$_repoRootDir/$package/ignore_pubspec_overrides.yaml'; | |
| if (includeOverrides) { | |
| _renameFile(currentName: noOverridesFilePath, newName: overridesFilePath); | |
| } else { | |
| _renameFile(currentName: overridesFilePath, newName: noOverridesFilePath); | |
| } | |
| } | |
| void _renameFile({required String currentName, required String newName}) { | |
| final currentFile = File(currentName); | |
| if (!currentFile.existsSync()) { | |
| _logInfo('Skip renaming $currentName to $newName, file does not exist.'); | |
| return; | |
| } | |
| currentFile.rename(newName); | |
| } | |
| void _updateVersionStrings( | |
| String package, { | |
| required String nextVersion, | |
| required String currentVersion, | |
| bool isReset = false, | |
| }) { | |
| _logInfo('Updating $package from $currentVersion to $nextVersion'); | |
| final pubspec = File('$_repoRootDir/$package/pubspec.yaml'); | |
| final changelog = File('$_repoRootDir/$package/CHANGELOG.md'); | |
| if (isReset) { | |
| _addNewLine(changelog, newLine: '## $nextVersion'); | |
| _replaceInFile(pubspec, query: currentVersion, replaceWith: nextVersion); | |
| } else { | |
| for (final file in [pubspec, changelog]) { | |
| _replaceInFile(file, query: currentVersion, replaceWith: nextVersion); | |
| } | |
| } | |
| } | |
| void _addNewLine(File file, {required String newLine, int insertAt = 0}) { | |
| final currentLines = file.readAsLinesSync(); | |
| final linesBefore = currentLines.sublist(0, insertAt); | |
| final linesAfter = currentLines.sublist(insertAt); | |
| final newLines = [...linesBefore, newLine, '', ...linesAfter]; | |
| final content = newLines.joinWithNewLine(); | |
| return file.writeAsStringSync(content); | |
| } | |
| bool _replaceInFile( | |
| File file, { | |
| required String query, | |
| required String replaceWith, | |
| }) { | |
| final newLines = <String>[]; | |
| var replaced = false; | |
| for (final line in file.readAsLinesSync()) { | |
| if (line.contains(query)) { | |
| newLines.add(line.replaceAll(query, replaceWith)); | |
| replaced = true; | |
| } else { | |
| newLines.add(line); | |
| } | |
| } | |
| final content = newLines.joinWithNewLine(); | |
| file.writeAsStringSync(content); | |
| return replaced; | |
| } | |
| String _readVersionFile(String package) { | |
| final versionFile = File('$_repoRootDir/$package/lib/src/version.dart'); | |
| final lines = versionFile.readAsLinesSync(); | |
| for (final line in lines) { | |
| if (line.startsWith('const packageVersion =')) { | |
| final version = line | |
| .split('=') | |
| .last | |
| .split('') | |
| .where((char) => char != ';' && char != "'" && char != '"') | |
| .join(''); | |
| return version.trim(); | |
| } | |
| } | |
| throw Exception('Could not read version in $package/lib/src/version.dart'); | |
| } | |
| String _removeWip(String wipVersion) { | |
| if (!wipVersion.contains('wip')) { | |
| throw Exception('$wipVersion is not a wip version.'); | |
| } | |
| return wipVersion.split('-wip').first; | |
| } | |
| /// Returns the new pinned DWDS version on success. | |
| Future<String?> _updateDwdsPin(String package) async { | |
| final pubOutdatedProcess = await Process.run('dart', [ | |
| 'pub', | |
| 'outdated', | |
| '--no-dependency-overrides', | |
| ], workingDirectory: '$_repoRootDir/$package'); | |
| final lines = pubOutdatedProcess.stdout.split('\n') as List<String>; | |
| String? nextDwdsVersion; | |
| String? currentDwdsVersion; | |
| for (final line in lines) { | |
| if (line.trim().startsWith('dwds')) { | |
| final segments = line | |
| .trim() | |
| .split(' ') | |
| .where((segment) => segment != ' '); | |
| nextDwdsVersion = segments.last; | |
| currentDwdsVersion = segments | |
| .lastWhere((segment) => segment.startsWith('*')) | |
| .substring(1); | |
| break; | |
| } | |
| } | |
| final next = nextDwdsVersion ?? ''; | |
| final current = currentDwdsVersion ?? ''; | |
| if (next.isNotEmpty && current.isNotEmpty) { | |
| _logInfo('Changing DWDS pin from $current to $next'); | |
| _replaceInFile( | |
| File('$_repoRootDir/$package/pubspec.yaml'), | |
| query: current, | |
| replaceWith: next, | |
| ); | |
| return nextDwdsVersion; | |
| } | |
| _logWarning('Unable to determine DWDS version to pin.'); | |
| return null; | |
| } | |
| void _logInfo(String message) { | |
| stdout.writeln(message); | |
| } | |
| void _logWarning(String warning) { | |
| stderr.writeln(warning); | |
| } | |
| extension JoinExtension on List<String> { | |
| String joinWithNewLine() { | |
| return '${join('\n')}\n'; | |
| } | |
| } |