@@ -569,14 +569,17 @@ public async Task<int> RunAsync(FileInfo projectFile, bool watch, bool noBuild,
569569 using var activity = telemetry . StartDiagnosticActivity ( kind : ActivityKind . Client ) ;
570570
571571 // NOTE: The change to @ over :: for template version separator (now enforced in .NET 10.0 SDK).
572- List < string > cliArgs = [ "new" , "install" , $ "{ packageName } @{ version } "] ;
572+ var workingDirectory = nugetConfigFile ? . Directory ?? executionContext . WorkingDirectory ;
573+ var localPackagePath = ResolveLocalTemplatePackagePath ( packageName , version , nugetSource , workingDirectory ) ;
574+
575+ List < string > cliArgs = [ "new" , "install" , localPackagePath ? . FullName ?? $ "{ packageName } @{ version } "] ;
573576
574577 if ( force )
575578 {
576579 cliArgs . Add ( "--force" ) ;
577580 }
578581
579- if ( nugetSource is not null )
582+ if ( localPackagePath is null && nugetSource is not null )
580583 {
581584 cliArgs . Add ( "--nuget-source" ) ;
582585 cliArgs . Add ( nugetSource ) ;
@@ -602,7 +605,6 @@ public async Task<int> RunAsync(FileInfo projectFile, bool watch, bool noBuild,
602605 // folder as the working directory for the command. If we are using an implicit channel
603606 // then we just use the current execution context for the CLI and inherit whatever
604607 // NuGet.configs that may or may not be laying around.
605- var workingDirectory = nugetConfigFile ? . Directory ?? executionContext . WorkingDirectory ;
606608
607609 var exitCode = await ExecuteAsync (
608610 args : [ .. cliArgs ] ,
@@ -634,6 +636,11 @@ public async Task<int> RunAsync(FileInfo projectFile, bool watch, bool noBuild,
634636 }
635637 else
636638 {
639+ if ( localPackagePath is not null )
640+ {
641+ return ( exitCode , version ) ;
642+ }
643+
637644 if ( stdout is null )
638645 {
639646 logger . LogError ( "Failed to read stdout from the process. This should never happen." ) ;
@@ -659,6 +666,45 @@ public async Task<int> RunAsync(FileInfo projectFile, bool watch, bool noBuild,
659666 }
660667 }
661668
669+ private static FileInfo ? ResolveLocalTemplatePackagePath ( string packageName , string version , string ? nugetSource , DirectoryInfo workingDirectory )
670+ {
671+ if ( string . IsNullOrWhiteSpace ( nugetSource ) )
672+ {
673+ return null ;
674+ }
675+
676+ string sourcePath ;
677+ if ( Uri . TryCreate ( nugetSource , UriKind . Absolute , out var uri ) )
678+ {
679+ if ( ! uri . IsFile )
680+ {
681+ return null ;
682+ }
683+
684+ sourcePath = uri . LocalPath ;
685+ }
686+ else
687+ {
688+ sourcePath = Path . GetFullPath ( nugetSource , workingDirectory . FullName ) ;
689+ }
690+
691+ if ( File . Exists ( sourcePath ) && string . Equals ( Path . GetExtension ( sourcePath ) , ".nupkg" , StringComparison . OrdinalIgnoreCase ) )
692+ {
693+ return new FileInfo ( sourcePath ) ;
694+ }
695+
696+ if ( ! Directory . Exists ( sourcePath ) )
697+ {
698+ return null ;
699+ }
700+
701+ var expectedFileName = $ "{ packageName } .{ version } .nupkg";
702+ var packagePath = Directory . EnumerateFiles ( sourcePath , "*.nupkg" , SearchOption . TopDirectoryOnly )
703+ . FirstOrDefault ( path => string . Equals ( Path . GetFileName ( path ) , expectedFileName , StringComparison . OrdinalIgnoreCase ) ) ;
704+
705+ return packagePath is null ? null : new FileInfo ( packagePath ) ;
706+ }
707+
662708 internal static bool TryParsePackageVersionFromStdout ( string stdout , [ NotNullWhen ( true ) ] out string ? version )
663709 {
664710 var lines = stdout . Split ( Environment . NewLine ) ;
0 commit comments