From 7a3726da237675f5b504fd88ac64cb9f37163849 Mon Sep 17 00:00:00 2001 From: Eric Seidel Date: Wed, 26 Nov 2025 10:22:20 -0800 Subject: [PATCH] fix: shorebird fails when packages use hooks (#3408) --- .../lib/src/executables/aot_tools.dart | 14 +++- .../test/src/executables/aot_tools_test.dart | 64 +++++++++++++++++++ 2 files changed, 75 insertions(+), 3 deletions(-) diff --git a/packages/shorebird_cli/lib/src/executables/aot_tools.dart b/packages/shorebird_cli/lib/src/executables/aot_tools.dart index a4f27fee..ccdb8113 100644 --- a/packages/shorebird_cli/lib/src/executables/aot_tools.dart +++ b/packages/shorebird_cli/lib/src/executables/aot_tools.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:collection/collection.dart'; import 'package:mason_logger/mason_logger.dart'; +import 'package:meta/meta.dart'; import 'package:path/path.dart' as p; import 'package:pub_semver/pub_semver.dart'; import 'package:scoped_deps/scoped_deps.dart'; @@ -209,11 +210,12 @@ class AotTools { } Future _linkerUsesGenSnapshot() async { - final version = await _getVersion(); + final version = await getVersion(); return version >= Version(0, 0, 1); } - Future _getVersion() async { + @visibleForTesting + Future getVersion() async { // Use 0.0.0 to allow callers to easily compare w/o checking for null. // If callers need to care about null, we can change this function to // return Version?. @@ -222,7 +224,13 @@ class AotTools { if (result.exitCode != ExitCode.success.code) { return noVersion; } - final version = result.stdout.toString().trim(); + final version = result.stdout + .toString() + // Hack to work around https://github.com/dart-lang/sdk/issues/61996 + // Which manifests in 3.38.0, 3.38.1, and 3.38.2 + // It typically ends in a newline, but trim() will remove it. + .replaceAll('Running build hooks...', '') + .trim(); return tryParseVersion(version) ?? noVersion; } diff --git a/packages/shorebird_cli/test/src/executables/aot_tools_test.dart b/packages/shorebird_cli/test/src/executables/aot_tools_test.dart index 867801b2..13169f9f 100644 --- a/packages/shorebird_cli/test/src/executables/aot_tools_test.dart +++ b/packages/shorebird_cli/test/src/executables/aot_tools_test.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:mocktail/mocktail.dart'; import 'package:path/path.dart' as p; +import 'package:pub_semver/pub_semver.dart'; import 'package:scoped_deps/scoped_deps.dart'; import 'package:shorebird_cli/src/cache.dart'; import 'package:shorebird_cli/src/executables/executables.dart'; @@ -1021,5 +1022,68 @@ Run "aot_tools help " for more information about a command. ); }); }); + + group('getVersion', () { + late int exitCode; + late String stdout; + late String stderr; + + setUp(() { + exitCode = 0; + stdout = ''; + stderr = ''; + when( + () => process.start( + any(), + any(), + workingDirectory: any(named: 'workingDirectory'), + ), + ).thenAnswer((_) async { + final mockProcess = MockProcess(); + when(() => mockProcess.exitCode).thenAnswer((_) async => exitCode); + when( + () => mockProcess.stdout, + ).thenAnswer((_) => Stream.value(utf8.encode(stdout))); + when( + () => mockProcess.stderr, + ).thenAnswer((_) => Stream.value(utf8.encode(stderr))); + return mockProcess; + }); + }); + + test( + 'returns parsed version when aot_tools outputs valid version', + () async { + stdout = '1.2.3'; + final result = await runWithOverrides(() => aotTools.getVersion()); + expect(result, Version(1, 2, 3)); + }, + ); + + test('returns 0.0.0 when process exits with non-zero code', () async { + exitCode = 1; + stderr = 'error'; + final result = await runWithOverrides(() => aotTools.getVersion()); + expect(result, Version(0, 0, 0)); + }); + + test('returns 0.0.0 when version string is invalid', () async { + stdout = 'invalid version'; + final result = await runWithOverrides(() => aotTools.getVersion()); + expect(result, Version(0, 0, 0)); + }); + + test('removes build hooks prefix from version output', () async { + stdout = 'Running build hooks...\n1.2.3'; + final result = await runWithOverrides(() => aotTools.getVersion()); + expect(result, Version(1, 2, 3)); + }); + + test('handles version with trailing whitespace', () async { + stdout = '1.2.3 \n'; + final result = await runWithOverrides(() => aotTools.getVersion()); + expect(result, Version(1, 2, 3)); + }); + }); }); }