fix: shorebird fails when packages use hooks (#3408)

This commit is contained in:
Eric Seidel
2025-11-26 10:22:20 -08:00
committed by GitHub
parent 61f8df54e8
commit 7a3726da23
2 changed files with 75 additions and 3 deletions
@@ -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<bool> _linkerUsesGenSnapshot() async {
final version = await _getVersion();
final version = await getVersion();
return version >= Version(0, 0, 1);
}
Future<Version> _getVersion() async {
@visibleForTesting
Future<Version> 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;
}
@@ -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 <command>" 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));
});
});
});
}