Skip to content

Commit 61a448b

Browse files
committed
Enable --interactive by default for sessions that are local.
This change unifies the `--interactive` infrastructure for all `dotnet` commands. With that common infra, it also enables interactivity for non-CI scenarios by default. We reuse CI detection from telemetry and from output redirection to determine whether to enable interactivity.
1 parent 6281db0 commit 61a448b

File tree

10 files changed

+83
-63
lines changed

10 files changed

+83
-63
lines changed

src/Cli/dotnet/CommonOptions.cs

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -116,12 +116,24 @@ public static CliArgument<string> DefaultToCurrentDirectory(this CliArgument<str
116116
Arity = ArgumentArity.Zero
117117
}.ForwardAs("-restore:false");
118118

119-
public static CliOption<bool> InteractiveMsBuildForwardOption =
120-
new ForwardedOption<bool>("--interactive")
121-
{
122-
Description = CommonLocalizableStrings.CommandInteractiveOptionDescription,
123-
Arity = ArgumentArity.Zero
124-
}.ForwardAs("-property:NuGetInteractive=true");
119+
private static bool IsCIEnvironmentOrRedirected() =>
120+
new Telemetry.CIEnvironmentDetectorForTelemetry().IsCIEnvironment() || Console.IsOutputRedirected;
121+
122+
/// <summary>
123+
/// A 'template' for interactive usage across the whole dotnet CLI. Use this as a base and then specialize it for your use cases.
124+
/// Despite being a 'forwarded option' there is no default forwarding configured, so if you want forwarding you can add it on a per-command basis.
125+
/// </summary>
126+
/// <remarks>If not set by a user, this will default to true if the user is not in a CI environment as detected by <see cref="Telemetry.CIEnvironmentDetectorForTelemetry.IsCIEnvironment"/>.</remarks>
127+
public static ForwardedOption<bool> InteractiveOption() =>
128+
new("--interactive")
129+
{
130+
Description = CommonLocalizableStrings.CommandInteractiveOptionDescription,
131+
Arity = ArgumentArity.Zero,
132+
// this default is called when no tokens/options are passed on the CLI args
133+
DefaultValueFactory = (ar) => IsCIEnvironmentOrRedirected()
134+
};
135+
136+
public static CliOption<bool> InteractiveMsBuildForwardOption = InteractiveOption().ForwardAsSingle(interactive => $"-property:NuGetInteractive={interactive}");
125137

126138
public static CliOption<bool> DisableBuildServersOption =
127139
new ForwardedOption<bool>("--disable-build-servers")

src/Cli/dotnet/OptionForwardingExtensions.cs

Lines changed: 56 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,19 @@
55
using System.CommandLine.Parsing;
66
using System.CommandLine.StaticCompletions;
77

8+
#nullable enable
9+
810
namespace Microsoft.DotNet.Cli
911
{
1012
public static class OptionForwardingExtensions
1113
{
12-
public static ForwardedOption<T> Forward<T>(this ForwardedOption<T> option) => option.SetForwardingFunction((T o) => new string[] { option.Name });
14+
public static ForwardedOption<T> Forward<T>(this ForwardedOption<T> option) => option.SetForwardingFunction((T? o) => [option.Name]);
15+
16+
public static ForwardedOption<T> ForwardAs<T>(this ForwardedOption<T> option, string value) => option.SetForwardingFunction((T? o) => [value]);
1317

14-
public static ForwardedOption<T> ForwardAs<T>(this ForwardedOption<T> option, string value) => option.SetForwardingFunction((T o) => new string[] { value });
18+
public static ForwardedOption<T> ForwardAsSingle<T>(this ForwardedOption<T> option, Func<T?, string> format) => option.SetForwardingFunction(format);
1519

16-
public static ForwardedOption<T> ForwardAsSingle<T>(this ForwardedOption<T> option, Func<T, string> format) => option.SetForwardingFunction(format);
20+
public static ForwardedOption<bool> ForwardIfEnabled(this ForwardedOption<bool> option, string value) => option.SetForwardingFunction((bool o) => o == true ? [value] : []);
1721

1822
/// <summary>
1923
/// Set up an option to be forwaded as an output path to MSBuild
@@ -24,47 +28,60 @@ public static class OptionForwardingExtensions
2428
/// <returns>The option</returns>
2529
public static ForwardedOption<string> ForwardAsOutputPath(this ForwardedOption<string> option, string outputPropertyName, bool surroundWithDoubleQuotes = false)
2630
{
27-
return option.SetForwardingFunction((string o) =>
31+
return option.SetForwardingFunction((string? o) =>
2832
{
33+
if (o is null)
34+
{
35+
return [];
36+
}
2937
string argVal = CommandDirectoryContext.GetFullPath(o);
3038
if (surroundWithDoubleQuotes)
3139
{
3240
// Not sure if this is necessary, but this is what "dotnet test" previously did and so we are
3341
// preserving the behavior here after refactoring
3442
argVal = TestCommandParser.SurroundWithDoubleQuotes(argVal);
3543
}
36-
return new string[]
37-
{
44+
return [
3845
$"-property:{outputPropertyName}={argVal}",
3946
"-property:_CommandLineDefinedOutputPath=true"
40-
};
47+
];
4148
});
4249
}
4350

4451
public static ForwardedOption<string[]> ForwardAsProperty(this ForwardedOption<string[]> option) => option
4552
.SetForwardingFunction((optionVals) =>
46-
optionVals
53+
(optionVals ?? [])
4754
.SelectMany(Utils.MSBuildPropertyParser.ParseProperties)
4855
.Select(keyValue => $"{option.Name}:{keyValue.key}={keyValue.value}")
4956
);
5057

51-
public static CliOption<T> ForwardAsMany<T>(this ForwardedOption<T> option, Func<T, IEnumerable<string>> format) => option.SetForwardingFunction(format);
58+
public static CliOption<T> ForwardAsMany<T>(this ForwardedOption<T> option, Func<T?, IEnumerable<string>> format) => option.SetForwardingFunction(format);
5259

5360
public static CliOption<IEnumerable<string>> ForwardAsManyArgumentsEachPrefixedByOption(this ForwardedOption<IEnumerable<string>> option, string alias) => option.ForwardAsMany(o => ForwardedArguments(alias, o));
5461

5562
public static IEnumerable<string> OptionValuesToBeForwarded(this ParseResult parseResult, CliCommand command) =>
5663
command.Options
5764
.OfType<IForwardedOption>()
58-
.SelectMany(o => o.GetForwardingFunction()(parseResult)) ?? Array.Empty<string>();
65+
.Select(o => o.GetForwardingFunction())
66+
.SelectMany(f => f is not null ? f(parseResult) : Array.Empty<string>());
5967

6068

61-
public static IEnumerable<string> ForwardedOptionValues<T>(this ParseResult parseResult, CliCommand command, string alias) =>
62-
command.Options?
69+
public static IEnumerable<string> ForwardedOptionValues<T>(this ParseResult parseResult, CliCommand command, string alias)
70+
{
71+
var func = command.Options?
6372
.Where(o => o.Name.Equals(alias) || o.Aliases.Contains(alias))?
6473
.OfType<IForwardedOption>()?
6574
.FirstOrDefault()?
66-
.GetForwardingFunction()(parseResult)
67-
?? Array.Empty<string>();
75+
.GetForwardingFunction();
76+
if (func is not null)
77+
{
78+
return func(parseResult) ?? [];
79+
}
80+
else
81+
{
82+
return [];
83+
}
84+
}
6885

6986
public static CliOption<T> AllowSingleArgPerToken<T>(this CliOption<T> option)
7087
{
@@ -94,9 +111,9 @@ public static CliOption<T> WithHelpDescription<T>(this CliOption<T> option, CliC
94111
return option;
95112
}
96113

97-
private static IEnumerable<string> ForwardedArguments(string alias, IEnumerable<string> arguments)
114+
private static IEnumerable<string> ForwardedArguments(string alias, IEnumerable<string>? arguments)
98115
{
99-
foreach (string arg in arguments)
116+
foreach (string arg in arguments ?? [])
100117
{
101118
yield return alias;
102119
yield return arg;
@@ -113,34 +130,48 @@ public class ForwardedOption<T> : CliOption<T>, IForwardedOption
113130
{
114131
private Func<ParseResult, IEnumerable<string>> ForwardingFunction;
115132

116-
public ForwardedOption(string name, params string[] aliases) : base(name, aliases) { }
133+
public ForwardedOption(string name, params string[] aliases) : base(name, aliases)
134+
{
135+
ForwardingFunction = _ => [];
136+
}
117137

118-
public ForwardedOption(string name, Func<ArgumentResult, T> parseArgument, string description = null)
138+
public ForwardedOption(string name, Func<ArgumentResult, T> parseArgument, string? description = null)
119139
: base(name)
120140
{
121141
CustomParser = parseArgument;
122142
Description = description;
143+
ForwardingFunction = _ => [];
123144
}
124145

125-
public ForwardedOption<T> SetForwardingFunction(Func<T, IEnumerable<string>> func)
146+
public ForwardedOption<T> SetForwardingFunction(Func<T?, IEnumerable<string>> func)
126147
{
127148
ForwardingFunction = GetForwardingFunction(func);
128149
return this;
129150
}
130151

131-
public ForwardedOption<T> SetForwardingFunction(Func<T, string> format)
152+
public ForwardedOption<T> SetForwardingFunction(Func<T?, string> format)
132153
{
133-
ForwardingFunction = GetForwardingFunction((o) => new string[] { format(o) });
154+
ForwardingFunction = GetForwardingFunction((o) => [format(o)]);
134155
return this;
135156
}
136157

137-
public ForwardedOption<T> SetForwardingFunction(Func<T, ParseResult, IEnumerable<string>> func)
158+
public ForwardedOption<T> SetForwardingFunction(Func<T?, ParseResult, IEnumerable<string>> func)
138159
{
139-
ForwardingFunction = (ParseResult parseResult) => parseResult.GetResult(this) is not null ? func(parseResult.GetValue<T>(this), parseResult) : Array.Empty<string>();
160+
ForwardingFunction = (ParseResult parseResult) =>
161+
{
162+
if (parseResult.GetResult(this) is OptionResult argresult && argresult.GetValue<T>(this) is T validValue)
163+
{
164+
return func(validValue, parseResult) ?? [];
165+
}
166+
else
167+
{
168+
return [];
169+
}
170+
};
140171
return this;
141172
}
142173

143-
public Func<ParseResult, IEnumerable<string>> GetForwardingFunction(Func<T, IEnumerable<string>> func)
174+
public Func<ParseResult, IEnumerable<string>> GetForwardingFunction(Func<T?, IEnumerable<string>> func)
144175
{
145176
return (ParseResult parseResult) => parseResult.GetResult(this) is not null ? func(parseResult.GetValue<T>(this)) : Array.Empty<string>();
146177
}
@@ -153,7 +184,7 @@ public Func<ParseResult, IEnumerable<string>> GetForwardingFunction()
153184

154185
public class DynamicForwardedOption<T> : ForwardedOption<T>, IDynamicOption
155186
{
156-
public DynamicForwardedOption(string name, Func<ArgumentResult, T> parseArgument, string description = null)
187+
public DynamicForwardedOption(string name, Func<ArgumentResult, T> parseArgument, string? description = null)
157188
: base(name, parseArgument, description)
158189
{
159190
}

src/Cli/dotnet/commands/dotnet-nuget/NuGetCommandParser.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ private static CliCommand GetDeleteCommand()
6464
{
6565
Arity = ArgumentArity.Zero
6666
});
67-
deleteCommand.Options.Add(new CliOption<bool>("--interactive"));
67+
deleteCommand.Options.Add(CommonOptions.InteractiveOption());
6868

6969
deleteCommand.SetAction(NuGetCommand.Run);
7070

@@ -125,7 +125,7 @@ private static CliCommand GetPushCommand()
125125
{
126126
Arity = ArgumentArity.Zero
127127
});
128-
pushCommand.Options.Add(new CliOption<bool>("--interactive"));
128+
pushCommand.Options.Add(CommonOptions.InteractiveOption());
129129
pushCommand.Options.Add(new CliOption<bool>("--skip-duplicate")
130130
{
131131
Arity = ArgumentArity.Zero

src/Cli/dotnet/commands/dotnet-package/add/PackageAddCommandParser.cs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,7 @@ internal static class PackageAddCommandParser
7070
HelpName = LocalizableStrings.CmdPackageDirectory
7171
}.ForwardAsSingle(o => $"--package-directory {o}");
7272

73-
public static readonly CliOption<bool> InteractiveOption = new ForwardedOption<bool>("--interactive")
74-
{
75-
Description = CommonLocalizableStrings.CommandInteractiveOptionDescription,
76-
Arity = ArgumentArity.Zero
77-
}.ForwardAs("--interactive");
73+
public static readonly CliOption<bool> InteractiveOption = CommonOptions.InteractiveOption().ForwardIfEnabled("--interactive");
7874

7975
public static readonly CliOption<bool> PrereleaseOption = new ForwardedOption<bool>("--prerelease")
8076
{

src/Cli/dotnet/commands/dotnet-package/list/PackageListCommandParser.cs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -72,11 +72,7 @@ internal static class PackageListCommandParser
7272
}.ForwardAsManyArgumentsEachPrefixedByOption("--source")
7373
.AllowSingleArgPerToken();
7474

75-
public static readonly CliOption InteractiveOption = new ForwardedOption<bool>("--interactive")
76-
{
77-
Description = CommonLocalizableStrings.CommandInteractiveOptionDescription,
78-
Arity = ArgumentArity.Zero
79-
}.ForwardAs("--interactive");
75+
public static readonly CliOption InteractiveOption = CommonOptions.InteractiveOption().ForwardIfEnabled("--interactive");
8076

8177
public static readonly CliOption VerbosityOption = new ForwardedOption<VerbosityOptions>("--verbosity", "-v")
8278
{

src/Cli/dotnet/commands/dotnet-package/remove/PackageRemoveCommandParser.cs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,7 @@ internal static class PackageRemoveCommandParser
1515
Arity = ArgumentArity.OneOrMore,
1616
};
1717

18-
public static readonly CliOption<bool> InteractiveOption = new ForwardedOption<bool>("--interactive")
19-
{
20-
Description = CommonLocalizableStrings.CommandInteractiveOptionDescription,
21-
Arity = ArgumentArity.Zero
22-
}.ForwardAs("--interactive");
18+
public static readonly CliOption<bool> InteractiveOption = CommonOptions.InteractiveOption().ForwardIfEnabled("--interactive");
2319

2420
private static readonly CliCommand Command = ConstructCommand();
2521

src/Cli/dotnet/commands/dotnet-package/search/PackageSearchCommandParser.cs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,7 @@ internal static class PackageSearchCommandParser
4040
Arity = ArgumentArity.Zero
4141
}.ForwardAs("--exact-match");
4242

43-
public static readonly CliOption<bool> Interactive = new ForwardedOption<bool>("--interactive")
44-
{
45-
Description = LocalizableStrings.InteractiveDescription,
46-
Arity = ArgumentArity.Zero
47-
}.ForwardAs("--interactive");
43+
public static readonly CliOption<bool> Interactive = CommonOptions.InteractiveOption().ForwardIfEnabled("--interactive");
4844

4945
public static readonly CliOption<bool> Prerelease = new ForwardedOption<bool>("--prerelease")
5046
{

src/Cli/dotnet/commands/dotnet-reference/dotnet-reference-add/ReferenceAddCommandParser.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ internal static class ReferenceAddCommandParser
3030

3131
}.AddCompletions(Complete.TargetFrameworksFromProjectFile);
3232

33-
public static readonly CliOption<bool> InteractiveOption = CommonOptions.InteractiveMsBuildForwardOption;
33+
public static readonly CliOption<bool> InteractiveOption = CommonOptions.InteractiveOption();
3434

3535
private static readonly CliCommand Command = ConstructCommand();
3636

src/Cli/dotnet/commands/dotnet-tool/ToolCommandRestorePassThroughOptions.cs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,6 @@ internal static class ToolCommandRestorePassThroughOptions
3535
Arity = ArgumentArity.Zero
3636
}.ForwardAs("--ignore-failed-sources");
3737

38-
public static CliOption<bool> InteractiveRestoreOption = new ForwardedOption<bool>("--interactive")
39-
{
40-
Description = CommonLocalizableStrings.CommandInteractiveOptionDescription,
41-
Arity = ArgumentArity.Zero
42-
}.ForwardAs(Constants.RestoreInteractiveOption);
38+
public static CliOption<bool> InteractiveRestoreOption = CommonOptions.InteractiveOption().ForwardIfEnabled(Constants.RestoreInteractiveOption);
4339
}
4440
}

src/Cli/dotnet/commands/dotnet-workload/WorkloadCommandNuGetRestoreActionConfigOptions.cs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,7 @@ internal static class WorkloadCommandNuGetRestoreActionConfigOptions
3535
Arity = ArgumentArity.Zero
3636
};
3737

38-
public static CliOption<bool> InteractiveRestoreOption = new ForwardedOption<bool>("--interactive")
39-
{
40-
Description = CommonLocalizableStrings.CommandInteractiveOptionDescription,
41-
};
38+
public static CliOption<bool> InteractiveRestoreOption = CommonOptions.InteractiveOption();
4239

4340
public static CliOption<bool> HiddenDisableParallelOption = new ForwardedOption<bool>("--disable-parallel")
4441
{

0 commit comments

Comments
 (0)