Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/Cli/dotnet/Commands/CliCommandStrings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -1733,6 +1733,9 @@ The default is to publish a framework-dependent application.</value>
<data name="RestoreSuccessful" xml:space="preserve">
<value>Tool '{0}' (version '{1}') was restored. Available commands: {2}</value>
</data>
<data name="RestoreNewVersionAvailable" xml:space="preserve">
<value>A newer version of tool '{0}' is available (version '{1}'). Consider updating your manifest file.</value>
</data>
<data name="RollbackDefinitionContainsExtraneousManifestIds" xml:space="preserve">
<value>Invalid rollback definition. The manifest IDs in rollback definition {0} do not match installed manifest IDs {1}.</value>
</data>
Expand Down
44 changes: 43 additions & 1 deletion src/Cli/dotnet/Commands/Tool/Restore/ToolPackageRestorer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ public ToolRestoreResult InstallPackage(
toolPackage.Command.Name));
}

// Check for newer versions and prepare warning message
string warning = CheckForNewerVersion(package, configFile);

return ToolRestoreResult.Success(
saveToCache:
(new RestoredCommandIdentifier(
Expand All @@ -96,7 +99,8 @@ public ToolRestoreResult InstallPackage(
CliCommandStrings.RestoreSuccessful,
package.PackageId,
package.Version.ToNormalizedString(),
string.Join(" ", package.CommandNames)));
string.Join(" ", package.CommandNames)),
warning: warning);
}
catch (ToolPackageException e)
{
Expand Down Expand Up @@ -129,6 +133,44 @@ public bool PackageHasBeenRestored(
&& _fileSystem.File.Exists(toolCommand.Executable.Value);
}

private string CheckForNewerVersion(ToolManifestPackage package, FilePath? configFile)
{
try
{
// Use wildcard version range to get the latest version
var latestVersionRange = VersionRange.Parse("*");

var (latestVersion, _) = _toolPackageDownloader.GetNuGetVersion(
new PackageLocation(
nugetConfig: configFile,
additionalFeeds: _additionalSources,
sourceFeedOverrides: _overrideSources,
rootConfigDirectory: package.FirstEffectDirectory),
package.PackageId,
_verbosity,
latestVersionRange,
_restoreActionConfig);

// Compare versions - only warn if there's a newer stable version or if the manifest uses prerelease
if (latestVersion != null && latestVersion > package.Version)
{
// If the current version is prerelease, show warning for any newer version
// If the current version is stable, only show warning for newer stable versions
if (package.Version.IsPrerelease || !latestVersion.IsPrerelease)
{
return string.Format(CliCommandStrings.RestoreNewVersionAvailable, package.PackageId, latestVersion.ToNormalizedString());
}
}
}
catch
{
// If we can't check for newer versions, don't show a warning
// This could happen due to network issues, package source problems, etc.
}

return string.Empty;
}

private static string JoinBySpaceWithQuote(IEnumerable<object> objects)
{
return string.Join(" ", objects.Select(o => $"\"{o.ToString()}\""));
Expand Down
31 changes: 25 additions & 6 deletions src/Cli/dotnet/Commands/Tool/Restore/ToolRestoreCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,14 @@ private int PrintConclusionAndReturn(ToolRestoreResult[] toolRestoreResults)
{
_reporter.WriteLine();
_reporter.WriteLine(string.Join(Environment.NewLine, successMessage));


// Display warnings for successful restorations even in partial failure case
var warnings = toolRestoreResults.Where(r => r.IsSuccess && !string.IsNullOrEmpty(r.Warning)).Select(r => r.Warning);
if (warnings.Any())
{
_reporter.WriteLine();
_reporter.WriteLine(string.Join(Environment.NewLine, warnings).Yellow());
}
}

_errorReporter.WriteLine(Environment.NewLine +
Expand All @@ -156,6 +163,15 @@ private int PrintConclusionAndReturn(ToolRestoreResult[] toolRestoreResults)
{
_reporter.WriteLine(string.Join(Environment.NewLine,
toolRestoreResults.Where(r => r.IsSuccess).Select(r => r.Message)));

// Display warnings for newer versions available
var warnings = toolRestoreResults.Where(r => r.IsSuccess && !string.IsNullOrEmpty(r.Warning)).Select(r => r.Warning);
if (warnings.Any())
{
_reporter.WriteLine();
_reporter.WriteLine(string.Join(Environment.NewLine, warnings).Yellow());
}

_reporter.WriteLine();
_reporter.WriteLine(CliCommandStrings.LocalToolsRestoreWasSuccessful.Green());

Expand Down Expand Up @@ -205,10 +221,11 @@ public struct ToolRestoreResult
public (RestoredCommandIdentifier restoredCommandIdentifier, ToolCommand toolCommand)? SaveToCache { get; }
public bool IsSuccess { get; }
public string Message { get; }
public string Warning { get; }

private ToolRestoreResult(
(RestoredCommandIdentifier, ToolCommand)? saveToCache,
bool isSuccess, string message)
bool isSuccess, string message, string warning = null)
{
if (string.IsNullOrWhiteSpace(message))
{
Expand All @@ -218,18 +235,20 @@ private ToolRestoreResult(
SaveToCache = saveToCache;
IsSuccess = isSuccess;
Message = message;
Warning = warning;
}

public static ToolRestoreResult Success(
(RestoredCommandIdentifier, ToolCommand)? saveToCache,
string message)
string message,
string warning = null)
{
return new ToolRestoreResult(saveToCache, true, message);
return new ToolRestoreResult(saveToCache, true, message, warning);
}

public static ToolRestoreResult Failure(string message)
{
return new ToolRestoreResult(null, false, message);
return new ToolRestoreResult(null, false, message, null);
}

public static ToolRestoreResult Failure(
Expand All @@ -238,7 +257,7 @@ public static ToolRestoreResult Failure(
{
return new ToolRestoreResult(null, false,
string.Format(CliCommandStrings.PackageFailedToRestore,
packageId.ToString(), toolPackageException.ToString()));
packageId.ToString(), toolPackageException.ToString()), null);
}
}
}
5 changes: 5 additions & 0 deletions src/Cli/dotnet/Commands/xlf/CliCommandStrings.cs.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Cli/dotnet/Commands/xlf/CliCommandStrings.de.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Cli/dotnet/Commands/xlf/CliCommandStrings.es.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Cli/dotnet/Commands/xlf/CliCommandStrings.fr.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Cli/dotnet/Commands/xlf/CliCommandStrings.it.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Cli/dotnet/Commands/xlf/CliCommandStrings.ja.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Cli/dotnet/Commands/xlf/CliCommandStrings.ko.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Cli/dotnet/Commands/xlf/CliCommandStrings.pl.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Cli/dotnet/Commands/xlf/CliCommandStrings.pt-BR.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Cli/dotnet/Commands/xlf/CliCommandStrings.ru.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Cli/dotnet/Commands/xlf/CliCommandStrings.tr.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Cli/dotnet/Commands/xlf/CliCommandStrings.zh-Hans.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Cli/dotnet/Commands/xlf/CliCommandStrings.zh-Hant.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -532,6 +532,61 @@ public void WhenRunWithoutManifestFileItShouldPrintSpecificRestoreErrorMessage()
l.Contains(AnsiExtensions.Yellow(CliCommandStrings.NoToolsWereRestored)));
}

[Fact]
public void WhenNewerVersionIsAvailableItShowsWarning()
{
var newerPackageVersion = NuGetVersion.Parse("2.1.4");
var manifestPackage = new ToolManifestPackage(
_packageIdA,
_packageVersionA,
new[] { _toolCommandNameA },
new DirectoryPath(_temporaryDirectory),
rollForward: false);

// Create a mock feed with both the current version and a newer version
var feeds = new List<MockFeed>
{
new MockFeed
{
Type = MockFeedType.FeedFromGlobalNugetConfig,
Packages = new List<MockFeedPackage>
{
new MockFeedPackage
{
PackageId = _packageIdA.ToString(),
Version = _packageVersionA.ToNormalizedString(),
ToolCommandName = _toolCommandNameA.Value,
},
new MockFeedPackage
{
PackageId = _packageIdA.ToString(),
Version = newerPackageVersion.ToNormalizedString(),
ToolCommandName = _toolCommandNameA.Value,
}
}
}
};

var toolPackageDownloaderMockWithFeeds = new ToolPackageDownloaderMock(
_toolPackageStore, _fileSystem, feeds: feeds);

IToolManifestFinder fakeManifestFinder =
new MockManifestFinder(new[] { manifestPackage });

ToolRestoreCommand toolRestoreCommand = new(_parseResult,
toolPackageDownloaderMockWithFeeds,
fakeManifestFinder,
_localToolsResolverCache,
_fileSystem,
_reporter
);

toolRestoreCommand.Execute().Should().Be(0);

_reporter.Lines.Should().Contain(l =>
l.Contains(string.Format(CliCommandStrings.RestoreNewVersionAvailable, _packageIdA, newerPackageVersion.ToNormalizedString())));
}

private class MockManifestFinder : IToolManifestFinder
{
private readonly IReadOnlyCollection<ToolManifestPackage> _toReturn;
Expand Down