Skip to content

Commit 0ee5d3c

Browse files
committed
Prevent upgrades to prerelease when constrained to stable versions
1 parent eb0952e commit 0ee5d3c

File tree

3 files changed

+59
-3
lines changed

3 files changed

+59
-3
lines changed

lib/src/solver/package_lister.dart

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,10 +162,16 @@ class PackageLister {
162162
/// Returns the best version of this package that matches [constraint]
163163
/// according to the solver's prioritization scheme, or `null` if no versions
164164
/// match.
165+
/// If [allowPrereleases] is false, this will only consider non-prerelease
166+
/// versions unless there are no non-prerelease versions that match
167+
/// [constraint].
165168
///
166169
/// Throws a [PackageNotFoundException] if this lister's package doesn't
167170
/// exist.
168-
Future<PackageId?> bestVersion(VersionConstraint constraint) async {
171+
Future<PackageId?> bestVersion(
172+
VersionConstraint constraint, {
173+
bool allowPrereleases = true,
174+
}) async {
169175
final locked = _locked;
170176
if (locked != null && constraint.allows(locked.version)) return locked;
171177

@@ -192,13 +198,14 @@ class PackageLister {
192198
if (isPastLimit(id.version)) break;
193199

194200
if (!constraint.allows(id.version)) continue;
201+
if (!allowPrereleases && id.version.isPreRelease) continue;
195202
if (!id.version.isPreRelease) {
196203
return id;
197204
}
198205
bestPrerelease ??= id;
199206
}
200207

201-
return bestPrerelease;
208+
return allowPrereleases ? bestPrerelease : null;
202209
}
203210

204211
/// Returns incompatibilities that encapsulate [id]'s dependencies, or that

lib/src/solver/version_solver.dart

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -396,9 +396,44 @@ class VersionSolver {
396396
return null; // when unsatisfied.isEmpty
397397
}
398398

399+
// Prereleases are allowed only if the dependency is transitive, or if
400+
// the constraint explicitly allows prereleases.
401+
bool shouldAllowPrereleases(String packageName) {
402+
final workspaces = [_root, ..._root.workspaceChildren];
403+
bool constraintContainsPrerelease(VersionConstraint? constraint) {
404+
if (constraint is Version) {
405+
return constraint.isPreRelease;
406+
}
407+
if (constraint is VersionRange) {
408+
return (constraint.min != null && constraint.min!.isPreRelease) ||
409+
(constraint.max != null && constraint.max!.isPreRelease) ||
410+
constraint.isAny;
411+
}
412+
return false;
413+
}
414+
415+
var isDirectOrDev = false;
416+
for (final workspace in workspaces) {
417+
final directDep = workspace.dependencies[packageName];
418+
if (directDep != null &&
419+
constraintContainsPrerelease(directDep.constraint)) {
420+
return true;
421+
}
422+
final devDep = workspace.devDependencies[packageName];
423+
if (devDep != null && constraintContainsPrerelease(devDep.constraint)) {
424+
return true;
425+
}
426+
isDirectOrDev = isDirectOrDev || directDep != null || devDep != null;
427+
}
428+
return !isDirectOrDev;
429+
}
430+
399431
PackageId? version;
400432
try {
401-
version = await _packageLister(package).bestVersion(package.constraint);
433+
final allowPrereleases = shouldAllowPrereleases(package.name);
434+
version = await _packageLister(
435+
package,
436+
).bestVersion(package.constraint, allowPrereleases: allowPrereleases);
402437
} on PackageNotFoundException catch (error) {
403438
_addIncompatibility(
404439
Incompatibility([

test/version_solver_test.dart

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1666,6 +1666,20 @@ void prerelease() {
16661666
await d.appDir(dependencies: {'a': '^1.0.0'}).create();
16671667
await expectResolves(tries: 2);
16681668
});
1669+
1670+
// This is a regression test for #4659.
1671+
test('not upgrading to prerelease when constrained to stable', () async {
1672+
await servePackages()
1673+
..serve('a', '1.0.0', deps: {'c': '^1.0.0'})
1674+
..serve('b', '1.0.0', deps: {'c': '^1.0.0'})
1675+
..serve('c', '1.0.0')
1676+
..serve('a', '2.0.0', deps: {'c': '^2.0.0'})
1677+
..serve('b', '2.0.0-dev', deps: {'c': '^2.0.0'})
1678+
..serve('c', '2.0.0');
1679+
1680+
await d.appDir(dependencies: {'a': '^1.0.0', 'b': '^1.0.0'}).create();
1681+
await expectResolves(result: {'a': '1.0.0', 'b': '1.0.0', 'c': '1.0.0'});
1682+
});
16691683
}
16701684

16711685
void override() {

0 commit comments

Comments
 (0)