Create console reporter API
The reporter API breaks down complex formatter in to composable compontents. Adds 'ReporterBuilder' and 'FormatterBuilder' as syntax sugar for creating reporters with complex formatting rules Changes in dotnet-watch - Remove dependency on Microsoft.Extensions.Logging and instead use IReporter for console output - Only use color output when stdout and stderr are not being redirected - Make the default output less noisy Changes in dotnet-user-secrets - Remove dependency on Microsoft.Extensions.Logging to use IReporter Changes in dotnet-sql-cache - Remove dependency on Microsoft.Extensions.Logging to use IReporter - Add --verbose option
This commit is contained in:
parent
e0f85971a3
commit
17da5242e0
|
|
@ -1,4 +1,4 @@
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
# Visual Studio 14
|
# Visual Studio 14
|
||||||
VisualStudioVersion = 14.0.25420.1
|
VisualStudioVersion = 14.0.25420.1
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
|
@ -28,6 +28,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{59E02BDF
|
||||||
EndProject
|
EndProject
|
||||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "NuGetPackager", "tools\NuGetPackager\NuGetPackager.xproj", "{8B781D87-1FC3-4A34-9089-2BDF6B562B85}"
|
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "NuGetPackager", "tools\NuGetPackager\NuGetPackager.xproj", "{8B781D87-1FC3-4A34-9089-2BDF6B562B85}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.Extensions.Tools.Tests", "test\Microsoft.Extensions.Tools.Tests\Microsoft.Extensions.Tools.Tests.xproj", "{A24BF1D1-4326-4455-A528-09F1E20EDC83}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
|
@ -62,6 +64,10 @@ Global
|
||||||
{8B781D87-1FC3-4A34-9089-2BDF6B562B85}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{8B781D87-1FC3-4A34-9089-2BDF6B562B85}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{8B781D87-1FC3-4A34-9089-2BDF6B562B85}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{8B781D87-1FC3-4A34-9089-2BDF6B562B85}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{8B781D87-1FC3-4A34-9089-2BDF6B562B85}.Release|Any CPU.Build.0 = Release|Any CPU
|
{8B781D87-1FC3-4A34-9089-2BDF6B562B85}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{A24BF1D1-4326-4455-A528-09F1E20EDC83}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{A24BF1D1-4326-4455-A528-09F1E20EDC83}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{A24BF1D1-4326-4455-A528-09F1E20EDC83}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{A24BF1D1-4326-4455-A528-09F1E20EDC83}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|
@ -74,5 +80,6 @@ Global
|
||||||
{8A2E6961-6B12-4A8E-8215-3E7301D52EAC} = {F5B382BC-258F-46E1-AC3D-10E5CCD55134}
|
{8A2E6961-6B12-4A8E-8215-3E7301D52EAC} = {F5B382BC-258F-46E1-AC3D-10E5CCD55134}
|
||||||
{53F3B53D-303A-4DAA-9C38-4F55195FA5B9} = {66517987-2A5A-4330-B130-207039378FD4}
|
{53F3B53D-303A-4DAA-9C38-4F55195FA5B9} = {66517987-2A5A-4330-B130-207039378FD4}
|
||||||
{8B781D87-1FC3-4A34-9089-2BDF6B562B85} = {59E02BDF-98DE-4D64-B576-2D0299D5E052}
|
{8B781D87-1FC3-4A34-9089-2BDF6B562B85} = {59E02BDF-98DE-4D64-B576-2D0299D5E052}
|
||||||
|
{A24BF1D1-4326-4455-A528-09F1E20EDC83} = {F5B382BC-258F-46E1-AC3D-10E5CCD55134}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using Microsoft.DotNet.Watcher.Tools;
|
using Microsoft.DotNet.Watcher.Tools;
|
||||||
|
|
@ -17,9 +16,11 @@ namespace Microsoft.DotNet.Watcher
|
||||||
public bool IsQuiet { get; private set; }
|
public bool IsQuiet { get; private set; }
|
||||||
public bool IsVerbose { get; private set; }
|
public bool IsVerbose { get; private set; }
|
||||||
public IList<string> RemainingArguments { get; private set; }
|
public IList<string> RemainingArguments { get; private set; }
|
||||||
|
|
||||||
public static CommandLineOptions Parse(string[] args, IConsole console)
|
public static CommandLineOptions Parse(string[] args, IConsole console)
|
||||||
{
|
{
|
||||||
Ensure.NotNull(args, nameof(args));
|
Ensure.NotNull(args, nameof(args));
|
||||||
|
Ensure.NotNull(console, nameof(console));
|
||||||
|
|
||||||
var app = new CommandLineApplication(throwOnUnexpectedArg: false)
|
var app = new CommandLineApplication(throwOnUnexpectedArg: false)
|
||||||
{
|
{
|
||||||
|
|
@ -60,20 +61,10 @@ Examples:
|
||||||
CommandOptionType.SingleValue); // TODO multiple shouldn't be too hard to support
|
CommandOptionType.SingleValue); // TODO multiple shouldn't be too hard to support
|
||||||
var optQuiet = app.Option("-q|--quiet", "Suppresses all output except warnings and errors",
|
var optQuiet = app.Option("-q|--quiet", "Suppresses all output except warnings and errors",
|
||||||
CommandOptionType.NoValue);
|
CommandOptionType.NoValue);
|
||||||
var optVerbose = app.Option("-v|--verbose", "Show verbose output",
|
var optVerbose = app.VerboseOption();
|
||||||
CommandOptionType.NoValue);
|
|
||||||
app.VersionOptionFromAssemblyAttributes(typeof(Program).GetTypeInfo().Assembly);
|
app.VersionOptionFromAssemblyAttributes(typeof(Program).GetTypeInfo().Assembly);
|
||||||
|
|
||||||
app.OnExecute(() =>
|
|
||||||
{
|
|
||||||
if (app.RemainingArguments.Count == 0)
|
|
||||||
{
|
|
||||||
app.ShowHelp();
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (app.Execute(args) != 0)
|
if (app.Execute(args) != 0)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -81,8 +72,12 @@ Examples:
|
||||||
|
|
||||||
if (optQuiet.HasValue() && optVerbose.HasValue())
|
if (optQuiet.HasValue() && optVerbose.HasValue())
|
||||||
{
|
{
|
||||||
console.Error.WriteLine(Resources.Error_QuietAndVerboseSpecified.Bold().Red());
|
throw new CommandParsingException(app, Resources.Error_QuietAndVerboseSpecified);
|
||||||
return null;
|
}
|
||||||
|
|
||||||
|
if (app.RemainingArguments.Count == 0)
|
||||||
|
{
|
||||||
|
app.ShowHelp();
|
||||||
}
|
}
|
||||||
|
|
||||||
return new CommandLineOptions
|
return new CommandLineOptions
|
||||||
|
|
@ -95,4 +90,4 @@ Examples:
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,64 +0,0 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using Microsoft.Extensions.CommandLineUtils;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
|
|
||||||
namespace Microsoft.DotNet.Watcher
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Logger to print formatted command output.
|
|
||||||
/// </summary>
|
|
||||||
public class CommandOutputLogger : ILogger
|
|
||||||
{
|
|
||||||
private readonly CommandOutputProvider _provider;
|
|
||||||
private readonly AnsiConsole _outConsole;
|
|
||||||
private readonly string _loggerName;
|
|
||||||
|
|
||||||
public CommandOutputLogger(CommandOutputProvider commandOutputProvider, string loggerName, bool useConsoleColor)
|
|
||||||
{
|
|
||||||
_provider = commandOutputProvider;
|
|
||||||
_outConsole = AnsiConsole.GetOutput(useConsoleColor);
|
|
||||||
_loggerName = loggerName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IDisposable BeginScope<TState>(TState state)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsEnabled(LogLevel logLevel)
|
|
||||||
{
|
|
||||||
if (logLevel < _provider.LogLevel)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
|
|
||||||
{
|
|
||||||
if (IsEnabled(logLevel))
|
|
||||||
{
|
|
||||||
_outConsole.WriteLine($"[{_loggerName}] {Caption(logLevel)}: {formatter(state, exception)}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private string Caption(LogLevel logLevel)
|
|
||||||
{
|
|
||||||
switch (logLevel)
|
|
||||||
{
|
|
||||||
case LogLevel.Trace: return "\x1b[35mtrce\x1b[39m";
|
|
||||||
case LogLevel.Debug: return "\x1b[35mdbug\x1b[39m";
|
|
||||||
case LogLevel.Information: return "\x1b[32minfo\x1b[39m";
|
|
||||||
case LogLevel.Warning: return "\x1b[33mwarn\x1b[39m";
|
|
||||||
case LogLevel.Error: return "\x1b[31mfail\x1b[39m";
|
|
||||||
case LogLevel.Critical: return "\x1b[31mcrit\x1b[39m";
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Exception("Unknown LogLevel");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
|
||||||
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
|
|
||||||
namespace Microsoft.DotNet.Watcher
|
|
||||||
{
|
|
||||||
public class CommandOutputProvider : ILoggerProvider
|
|
||||||
{
|
|
||||||
private readonly bool _isWindows;
|
|
||||||
|
|
||||||
public CommandOutputProvider()
|
|
||||||
{
|
|
||||||
_isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ILogger CreateLogger(string name)
|
|
||||||
{
|
|
||||||
return new CommandOutputLogger(this, name, useConsoleColor: _isWindows);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public LogLevel LogLevel { get; set; } = LogLevel.Information;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,33 +1,35 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.DotNet.Watcher.Internal;
|
using Microsoft.DotNet.Watcher.Internal;
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Microsoft.Extensions.Tools.Internal;
|
using Microsoft.Extensions.Tools.Internal;
|
||||||
|
|
||||||
namespace Microsoft.DotNet.Watcher
|
namespace Microsoft.DotNet.Watcher
|
||||||
{
|
{
|
||||||
public class DotNetWatcher
|
public class DotNetWatcher
|
||||||
{
|
{
|
||||||
private readonly ILogger _logger;
|
private readonly IReporter _reporter;
|
||||||
private readonly ProcessRunner _processRunner;
|
private readonly ProcessRunner _processRunner;
|
||||||
|
|
||||||
public DotNetWatcher(ILogger logger)
|
public DotNetWatcher(IReporter reporter)
|
||||||
{
|
{
|
||||||
Ensure.NotNull(logger, nameof(logger));
|
Ensure.NotNull(reporter, nameof(reporter));
|
||||||
|
|
||||||
_logger = logger;
|
_reporter = reporter;
|
||||||
_processRunner = new ProcessRunner(logger);
|
_processRunner = new ProcessRunner(reporter);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task WatchAsync(ProcessSpec processSpec, IFileSetFactory fileSetFactory, CancellationToken cancellationToken)
|
public async Task WatchAsync(ProcessSpec processSpec, IFileSetFactory fileSetFactory,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
Ensure.NotNull(processSpec, nameof(processSpec));
|
Ensure.NotNull(processSpec, nameof(processSpec));
|
||||||
|
|
||||||
var cancelledTaskSource = new TaskCompletionSource<object>();
|
var cancelledTaskSource = new TaskCompletionSource<object>();
|
||||||
cancellationToken.Register(state => ((TaskCompletionSource<object>)state).TrySetResult(null), cancelledTaskSource);
|
cancellationToken.Register(state => ((TaskCompletionSource<object>) state).TrySetResult(null),
|
||||||
|
cancelledTaskSource);
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
|
|
@ -46,9 +48,10 @@ namespace Microsoft.DotNet.Watcher
|
||||||
var fileSetTask = fileSetWatcher.GetChangedFileAsync(combinedCancellationSource.Token);
|
var fileSetTask = fileSetWatcher.GetChangedFileAsync(combinedCancellationSource.Token);
|
||||||
var processTask = _processRunner.RunAsync(processSpec, combinedCancellationSource.Token);
|
var processTask = _processRunner.RunAsync(processSpec, combinedCancellationSource.Token);
|
||||||
|
|
||||||
_logger.LogInformation("Running {execName} with the following arguments: {args}",
|
var args = ArgumentEscaper.EscapeAndConcatenate(processSpec.Arguments);
|
||||||
processSpec.ShortDisplayName(),
|
_reporter.Verbose($"Running {processSpec.ShortDisplayName()} with the following arguments: {args}");
|
||||||
ArgumentEscaper.EscapeAndConcatenate(processSpec.Arguments));
|
|
||||||
|
_reporter.Output("Started");
|
||||||
|
|
||||||
var finishedTask = await Task.WhenAny(processTask, fileSetTask, cancelledTaskSource.Task);
|
var finishedTask = await Task.WhenAny(processTask, fileSetTask, cancelledTaskSource.Task);
|
||||||
|
|
||||||
|
|
@ -58,6 +61,15 @@ namespace Microsoft.DotNet.Watcher
|
||||||
|
|
||||||
await Task.WhenAll(processTask, fileSetTask);
|
await Task.WhenAll(processTask, fileSetTask);
|
||||||
|
|
||||||
|
if (processTask.Result == 0)
|
||||||
|
{
|
||||||
|
_reporter.Output("Exited");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_reporter.Error($"Exited with error code {processTask.Result}");
|
||||||
|
}
|
||||||
|
|
||||||
if (finishedTask == cancelledTaskSource.Task || cancellationToken.IsCancellationRequested)
|
if (finishedTask == cancelledTaskSource.Task || cancellationToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
|
|
@ -65,7 +77,7 @@ namespace Microsoft.DotNet.Watcher
|
||||||
|
|
||||||
if (finishedTask == processTask)
|
if (finishedTask == processTask)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Waiting for a file to change before restarting dotnet...");
|
_reporter.Warn("Waiting for a file to change before restarting dotnet...");
|
||||||
|
|
||||||
// Now wait for a file to change before restarting process
|
// Now wait for a file to change before restarting process
|
||||||
await fileSetWatcher.GetChangedFileAsync(cancellationToken);
|
await fileSetWatcher.GetChangedFileAsync(cancellationToken);
|
||||||
|
|
@ -73,10 +85,10 @@ namespace Microsoft.DotNet.Watcher
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(fileSetTask.Result))
|
if (!string.IsNullOrEmpty(fileSetTask.Result))
|
||||||
{
|
{
|
||||||
_logger.LogInformation($"File changed: {fileSetTask.Result}");
|
_reporter.Output($"File changed: {fileSetTask.Result}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -18,6 +18,8 @@ namespace Microsoft.DotNet.Watcher.Internal
|
||||||
|
|
||||||
public bool Contains(string filePath) => _files.Contains(filePath);
|
public bool Contains(string filePath) => _files.Contains(filePath);
|
||||||
|
|
||||||
|
public int Count => _files.Count;
|
||||||
|
|
||||||
public IEnumerator<string> GetEnumerator() => _files.GetEnumerator();
|
public IEnumerator<string> GetEnumerator() => _files.GetEnumerator();
|
||||||
IEnumerator IEnumerable.GetEnumerator() => _files.GetEnumerator();
|
IEnumerator IEnumerable.GetEnumerator() => _files.GetEnumerator();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,48 +7,47 @@ using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Microsoft.Extensions.Tools.Internal;
|
using Microsoft.Extensions.Tools.Internal;
|
||||||
|
|
||||||
namespace Microsoft.DotNet.Watcher.Internal
|
namespace Microsoft.DotNet.Watcher.Internal
|
||||||
{
|
{
|
||||||
public class MsBuildFileSetFactory : IFileSetFactory
|
public class MsBuildFileSetFactory : IFileSetFactory
|
||||||
{
|
{
|
||||||
|
private const string TargetName = "GenerateWatchList";
|
||||||
private const string ProjectExtensionFileExtension = ".dotnetwatch.targets";
|
private const string ProjectExtensionFileExtension = ".dotnetwatch.targets";
|
||||||
private const string WatchTargetsFileName = "DotNetWatchCommon.targets";
|
private const string WatchTargetsFileName = "DotNetWatchCommon.targets";
|
||||||
private readonly ILogger _logger;
|
private readonly IReporter _reporter;
|
||||||
private readonly string _projectFile;
|
private readonly string _projectFile;
|
||||||
private readonly string _watchTargetsDir;
|
private readonly string _watchTargetsDir;
|
||||||
private readonly OutputSink _outputSink;
|
private readonly OutputSink _outputSink;
|
||||||
private readonly ProcessRunner _processRunner;
|
private readonly ProcessRunner _processRunner;
|
||||||
|
|
||||||
public MsBuildFileSetFactory(ILogger logger, string projectFile)
|
public MsBuildFileSetFactory(IReporter reporter, string projectFile)
|
||||||
: this(logger, projectFile, new OutputSink())
|
: this(reporter, projectFile, new OutputSink())
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
// output sink is for testing
|
// output sink is for testing
|
||||||
internal MsBuildFileSetFactory(ILogger logger, string projectFile, OutputSink outputSink)
|
internal MsBuildFileSetFactory(IReporter reporter, string projectFile, OutputSink outputSink)
|
||||||
{
|
{
|
||||||
Ensure.NotNull(logger, nameof(logger));
|
Ensure.NotNull(reporter, nameof(reporter));
|
||||||
Ensure.NotNullOrEmpty(projectFile, nameof(projectFile));
|
Ensure.NotNullOrEmpty(projectFile, nameof(projectFile));
|
||||||
Ensure.NotNull(outputSink, nameof(outputSink));
|
Ensure.NotNull(outputSink, nameof(outputSink));
|
||||||
|
|
||||||
_logger = logger;
|
_reporter = reporter;
|
||||||
_projectFile = projectFile;
|
_projectFile = projectFile;
|
||||||
_watchTargetsDir = FindWatchTargetsDir();
|
_watchTargetsDir = FindWatchTargetsDir();
|
||||||
_outputSink = outputSink;
|
_outputSink = outputSink;
|
||||||
_processRunner = new ProcessRunner(logger);
|
_processRunner = new ProcessRunner(reporter);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal List<string> BuildFlags { get; } = new List<string>
|
internal List<string> BuildFlags { get; } = new List<string>
|
||||||
{
|
{
|
||||||
"/nologo",
|
"/nologo",
|
||||||
"/v:n",
|
"/v:n",
|
||||||
"/t:GenerateWatchList",
|
"/t:" + TargetName,
|
||||||
"/p:DotNetWatchBuild=true", // extensibility point for users
|
"/p:DotNetWatchBuild=true", // extensibility point for users
|
||||||
"/p:DesignTimeBuild=true", // don't do expensive things
|
"/p:DesignTimeBuild=true", // don't do expensive things
|
||||||
};
|
};
|
||||||
|
|
@ -66,10 +65,6 @@ namespace Microsoft.DotNet.Watcher.Internal
|
||||||
{
|
{
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
var stopwatch = new Stopwatch();
|
|
||||||
stopwatch.Start();
|
|
||||||
#endif
|
|
||||||
var capture = _outputSink.StartCapture();
|
var capture = _outputSink.StartCapture();
|
||||||
// TODO adding files doesn't currently work. Need to provide a way to detect new files
|
// TODO adding files doesn't currently work. Need to provide a way to detect new files
|
||||||
// find files
|
// find files
|
||||||
|
|
@ -80,53 +75,56 @@ namespace Microsoft.DotNet.Watcher.Internal
|
||||||
Arguments = new[]
|
Arguments = new[]
|
||||||
{
|
{
|
||||||
"msbuild",
|
"msbuild",
|
||||||
_projectFile,
|
_projectFile,
|
||||||
$"/p:_DotNetWatchTargetsLocation={_watchTargetsDir}", // add our dotnet-watch targets
|
$"/p:_DotNetWatchTargetsLocation={_watchTargetsDir}", // add our dotnet-watch targets
|
||||||
$"/p:_DotNetWatchListFile={watchList}"
|
$"/p:_DotNetWatchListFile={watchList}"
|
||||||
}.Concat(BuildFlags),
|
}.Concat(BuildFlags),
|
||||||
OutputCapture = capture
|
OutputCapture = capture
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_reporter.Verbose($"Running MSBuild target '{TargetName}' on '{_projectFile}'");
|
||||||
|
|
||||||
var exitCode = await _processRunner.RunAsync(processSpec, cancellationToken);
|
var exitCode = await _processRunner.RunAsync(processSpec, cancellationToken);
|
||||||
|
|
||||||
if (exitCode == 0)
|
if (exitCode == 0)
|
||||||
{
|
{
|
||||||
var files = File.ReadAllLines(watchList)
|
var fileset = new FileSet(
|
||||||
|
File.ReadAllLines(watchList)
|
||||||
.Select(l => l?.Trim())
|
.Select(l => l?.Trim())
|
||||||
.Where(l => !string.IsNullOrEmpty(l));
|
.Where(l => !string.IsNullOrEmpty(l)));
|
||||||
|
|
||||||
var fileset = new FileSet(files);
|
|
||||||
|
|
||||||
|
_reporter.Verbose($"Watching {fileset.Count} file(s) for changes");
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
_logger.LogDebug(string.Join(Environment.NewLine, fileset));
|
|
||||||
Debug.Assert(files.All(Path.IsPathRooted), "All files should be rooted paths");
|
foreach (var file in fileset)
|
||||||
stopwatch.Stop();
|
{
|
||||||
_logger.LogDebug("Gathered project information in {time}ms", stopwatch.ElapsedMilliseconds);
|
_reporter.Verbose($" -> {file}");
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.Assert(fileset.All(Path.IsPathRooted), "All files should be rooted paths");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return fileset;
|
return fileset;
|
||||||
}
|
}
|
||||||
|
|
||||||
var sb = new StringBuilder()
|
_reporter.Error($"Error(s) finding watch items project file '{Path.GetFileName(_projectFile)}'");
|
||||||
.Append("Error(s) finding watch items project file '")
|
|
||||||
.Append(Path.GetFileName(_projectFile))
|
_reporter.Output($"MSBuild output from target '{TargetName}'");
|
||||||
.AppendLine("' :");
|
|
||||||
|
|
||||||
foreach (var line in capture.Lines)
|
foreach (var line in capture.Lines)
|
||||||
{
|
{
|
||||||
sb.Append(" [MSBUILD] :").AppendLine(line);
|
_reporter.Output($" [MSBUILD] : {line}");
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.LogError(sb.ToString());
|
_reporter.Warn("Fix the error to continue or press Ctrl+C to exit.");
|
||||||
_logger.LogInformation("Fix the error to continue or press Ctrl+C to exit.");
|
|
||||||
|
|
||||||
var fileSet = new FileSet(new[] { _projectFile });
|
var fileSet = new FileSet(new[] {_projectFile});
|
||||||
|
|
||||||
using (var watcher = new FileSetWatcher(fileSet))
|
using (var watcher = new FileSetWatcher(fileSet))
|
||||||
{
|
{
|
||||||
await watcher.GetChangedFileAsync(cancellationToken);
|
await watcher.GetChangedFileAsync(cancellationToken);
|
||||||
|
|
||||||
_logger.LogInformation($"File changed: {_projectFile}");
|
_reporter.Output($"File changed: {_projectFile}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -143,7 +141,8 @@ namespace Microsoft.DotNet.Watcher.Internal
|
||||||
var projectExtensionsPath = Path.Combine(Path.GetDirectoryName(_projectFile), "obj");
|
var projectExtensionsPath = Path.Combine(Path.GetDirectoryName(_projectFile), "obj");
|
||||||
|
|
||||||
// see https://github.com/Microsoft/msbuild/blob/bf9b21cc7869b96ea2289ff31f6aaa5e1d525a26/src/XMakeTasks/Microsoft.Common.targets#L127
|
// see https://github.com/Microsoft/msbuild/blob/bf9b21cc7869b96ea2289ff31f6aaa5e1d525a26/src/XMakeTasks/Microsoft.Common.targets#L127
|
||||||
var projectExtensionFile = Path.Combine(projectExtensionsPath, Path.GetFileName(_projectFile) + ProjectExtensionFileExtension);
|
var projectExtensionFile = Path.Combine(projectExtensionsPath,
|
||||||
|
Path.GetFileName(_projectFile) + ProjectExtensionFileExtension);
|
||||||
|
|
||||||
if (!File.Exists(projectExtensionFile))
|
if (!File.Exists(projectExtensionFile))
|
||||||
{
|
{
|
||||||
|
|
@ -173,4 +172,4 @@ namespace Microsoft.DotNet.Watcher.Internal
|
||||||
return Path.GetDirectoryName(targetPath);
|
return Path.GetDirectoryName(targetPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -7,20 +7,19 @@ using System.IO;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.Extensions.Internal;
|
using Microsoft.Extensions.Internal;
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Microsoft.Extensions.Tools.Internal;
|
using Microsoft.Extensions.Tools.Internal;
|
||||||
|
|
||||||
namespace Microsoft.DotNet.Watcher.Internal
|
namespace Microsoft.DotNet.Watcher.Internal
|
||||||
{
|
{
|
||||||
public class ProcessRunner
|
public class ProcessRunner
|
||||||
{
|
{
|
||||||
private readonly ILogger _logger;
|
private readonly IReporter _reporter;
|
||||||
|
|
||||||
public ProcessRunner(ILogger logger)
|
public ProcessRunner(IReporter reporter)
|
||||||
{
|
{
|
||||||
Ensure.NotNull(logger, nameof(logger));
|
Ensure.NotNull(reporter, nameof(reporter));
|
||||||
|
|
||||||
_logger = logger;
|
_reporter = reporter;
|
||||||
}
|
}
|
||||||
|
|
||||||
// May not be necessary in the future. See https://github.com/dotnet/corefx/issues/12039
|
// May not be necessary in the future. See https://github.com/dotnet/corefx/issues/12039
|
||||||
|
|
@ -30,12 +29,16 @@ namespace Microsoft.DotNet.Watcher.Internal
|
||||||
|
|
||||||
int exitCode;
|
int exitCode;
|
||||||
|
|
||||||
|
var stopwatch = new Stopwatch();
|
||||||
|
|
||||||
using (var process = CreateProcess(processSpec))
|
using (var process = CreateProcess(processSpec))
|
||||||
using (var processState = new ProcessState(process))
|
using (var processState = new ProcessState(process))
|
||||||
{
|
{
|
||||||
cancellationToken.Register(() => processState.TryKill());
|
cancellationToken.Register(() => processState.TryKill());
|
||||||
|
|
||||||
|
stopwatch.Start();
|
||||||
process.Start();
|
process.Start();
|
||||||
|
_reporter.Verbose($"Started '{processSpec.Executable}' with process id {process.Id}");
|
||||||
|
|
||||||
if (processSpec.IsOutputCaptured)
|
if (processSpec.IsOutputCaptured)
|
||||||
{
|
{
|
||||||
|
|
@ -47,16 +50,12 @@ namespace Microsoft.DotNet.Watcher.Internal
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_logger.LogInformation("{execName} process id: {pid}", processSpec.ShortDisplayName(), process.Id);
|
|
||||||
await processState.Task;
|
await processState.Task;
|
||||||
}
|
}
|
||||||
|
|
||||||
exitCode = process.ExitCode;
|
exitCode = process.ExitCode;
|
||||||
}
|
stopwatch.Stop();
|
||||||
|
_reporter.Verbose($"Process id {process.Id} ran for {stopwatch.ElapsedMilliseconds}ms");
|
||||||
if (!processSpec.IsOutputCaptured)
|
|
||||||
{
|
|
||||||
LogResult(processSpec, exitCode);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return exitCode;
|
return exitCode;
|
||||||
|
|
@ -81,19 +80,6 @@ namespace Microsoft.DotNet.Watcher.Internal
|
||||||
return process;
|
return process;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LogResult(ProcessSpec processSpec, int exitCode)
|
|
||||||
{
|
|
||||||
var processName = processSpec.ShortDisplayName();
|
|
||||||
if (exitCode == 0)
|
|
||||||
{
|
|
||||||
_logger.LogInformation("{execName} exit code: {code}", processName, exitCode);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_logger.LogError("{execName} exit code: {code}", processName, exitCode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async Task ConsumeStreamAsync(StreamReader reader, Action<string> consume)
|
private static async Task ConsumeStreamAsync(StreamReader reader, Action<string> consume)
|
||||||
{
|
{
|
||||||
string line;
|
string line;
|
||||||
|
|
|
||||||
|
|
@ -14,9 +14,7 @@
|
||||||
</packageTypes>
|
</packageTypes>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<group targetFramework=".NETCoreApp1.0">
|
<group targetFramework=".NETCoreApp1.0">
|
||||||
<dependency id="Microsoft.Extensions.Logging" version="$dep_1$" />
|
<dependency id="Microsoft.NETCore.App" version="$dep_1$" />
|
||||||
<dependency id="Microsoft.Extensions.Logging.Console" version="$dep_2$" />
|
|
||||||
<dependency id="Microsoft.NETCore.App" version="$dep_3$" />
|
|
||||||
</group>
|
</group>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</metadata>
|
</metadata>
|
||||||
|
|
|
||||||
|
|
@ -2,20 +2,18 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.DotNet.Watcher.Internal;
|
using Microsoft.DotNet.Watcher.Internal;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.CommandLineUtils;
|
||||||
using Microsoft.Extensions.Tools.Internal;
|
using Microsoft.Extensions.Tools.Internal;
|
||||||
|
|
||||||
namespace Microsoft.DotNet.Watcher
|
namespace Microsoft.DotNet.Watcher
|
||||||
{
|
{
|
||||||
public class Program
|
public class Program
|
||||||
{
|
{
|
||||||
private const string LoggerName = "DotNetWatcher";
|
|
||||||
private readonly IConsole _console;
|
private readonly IConsole _console;
|
||||||
private readonly string _workingDir;
|
private readonly string _workingDir;
|
||||||
|
|
||||||
|
|
@ -39,44 +37,18 @@ namespace Microsoft.DotNet.Watcher
|
||||||
|
|
||||||
public async Task<int> RunAsync(string[] args)
|
public async Task<int> RunAsync(string[] args)
|
||||||
{
|
{
|
||||||
using (CancellationTokenSource ctrlCTokenSource = new CancellationTokenSource())
|
CommandLineOptions options;
|
||||||
|
try
|
||||||
{
|
{
|
||||||
_console.CancelKeyPress += (sender, ev) =>
|
options = CommandLineOptions.Parse(args, _console);
|
||||||
{
|
}
|
||||||
if (!ctrlCTokenSource.IsCancellationRequested)
|
catch (CommandParsingException ex)
|
||||||
{
|
{
|
||||||
_console.Out.WriteLine($"[{LoggerName}] Shutdown requested. Press Ctrl+C again to force exit.");
|
CreateReporter(verbose: true, quiet: false, console: _console)
|
||||||
ev.Cancel = true;
|
.Error(ex.Message);
|
||||||
}
|
return 1;
|
||||||
else
|
|
||||||
{
|
|
||||||
ev.Cancel = false;
|
|
||||||
}
|
|
||||||
ctrlCTokenSource.Cancel();
|
|
||||||
};
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return await MainInternalAsync(args, ctrlCTokenSource.Token);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
if (ex is TaskCanceledException || ex is OperationCanceledException)
|
|
||||||
{
|
|
||||||
// swallow when only exception is the CTRL+C forced an exit
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
_console.Error.WriteLine(ex.ToString());
|
|
||||||
_console.Error.WriteLine($"[{LoggerName}] An unexpected error occurred".Bold().Red());
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<int> MainInternalAsync(string[] args, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var options = CommandLineOptions.Parse(args, _console);
|
|
||||||
if (options == null)
|
if (options == null)
|
||||||
{
|
{
|
||||||
// invalid args syntax
|
// invalid args syntax
|
||||||
|
|
@ -88,58 +60,122 @@ namespace Microsoft.DotNet.Watcher
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
var loggerFactory = new LoggerFactory();
|
var reporter = CreateReporter(options.IsVerbose, options.IsQuiet, _console);
|
||||||
var commandProvider = new CommandOutputProvider
|
|
||||||
{
|
|
||||||
LogLevel = ResolveLogLevel(options)
|
|
||||||
};
|
|
||||||
loggerFactory.AddProvider(commandProvider);
|
|
||||||
var logger = loggerFactory.CreateLogger(LoggerName);
|
|
||||||
|
|
||||||
|
using (CancellationTokenSource ctrlCTokenSource = new CancellationTokenSource())
|
||||||
|
{
|
||||||
|
_console.CancelKeyPress += (sender, ev) =>
|
||||||
|
{
|
||||||
|
if (!ctrlCTokenSource.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
reporter.Output("Shutdown requested. Press Ctrl+C again to force exit.");
|
||||||
|
ev.Cancel = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ev.Cancel = false;
|
||||||
|
}
|
||||||
|
ctrlCTokenSource.Cancel();
|
||||||
|
};
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return await MainInternalAsync(reporter, options.Project, options.RemainingArguments, ctrlCTokenSource.Token);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
if (ex is TaskCanceledException || ex is OperationCanceledException)
|
||||||
|
{
|
||||||
|
// swallow when only exception is the CTRL+C forced an exit
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
reporter.Error(ex.ToString());
|
||||||
|
reporter.Error("An unexpected error occurred");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<int> MainInternalAsync(
|
||||||
|
IReporter reporter,
|
||||||
|
string project,
|
||||||
|
ICollection<string> args,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
// TODO multiple projects should be easy enough to add here
|
// TODO multiple projects should be easy enough to add here
|
||||||
string projectFile;
|
string projectFile;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
projectFile = MsBuildProjectFinder.FindMsBuildProject(_workingDir, options.Project);
|
projectFile = MsBuildProjectFinder.FindMsBuildProject(_workingDir, project);
|
||||||
}
|
}
|
||||||
catch (FileNotFoundException ex)
|
catch (FileNotFoundException ex)
|
||||||
{
|
{
|
||||||
_console.Error.WriteLine(ex.Message.Bold().Red());
|
reporter.Error(ex.Message);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
var fileSetFactory = new MsBuildFileSetFactory(logger, projectFile);
|
var fileSetFactory = new MsBuildFileSetFactory(reporter, projectFile);
|
||||||
|
|
||||||
var processInfo = new ProcessSpec
|
var processInfo = new ProcessSpec
|
||||||
{
|
{
|
||||||
Executable = DotNetMuxer.MuxerPathOrDefault(),
|
Executable = DotNetMuxer.MuxerPathOrDefault(),
|
||||||
WorkingDirectory = Path.GetDirectoryName(projectFile),
|
WorkingDirectory = Path.GetDirectoryName(projectFile),
|
||||||
Arguments = options.RemainingArguments
|
Arguments = args
|
||||||
};
|
};
|
||||||
|
|
||||||
await new DotNetWatcher(logger)
|
await new DotNetWatcher(reporter)
|
||||||
.WatchAsync(processInfo, fileSetFactory, cancellationToken);
|
.WatchAsync(processInfo, fileSetFactory, cancellationToken);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private LogLevel ResolveLogLevel(CommandLineOptions options)
|
private static IReporter CreateReporter(bool verbose, bool quiet, IConsole console)
|
||||||
{
|
{
|
||||||
if (options.IsQuiet)
|
const string prefix = "watch : ";
|
||||||
{
|
var colorPrefix = new ColorFormatter(ConsoleColor.DarkGray).Format(prefix);
|
||||||
return LogLevel.Warning;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool globalVerbose;
|
return new ReporterBuilder()
|
||||||
bool.TryParse(Environment.GetEnvironmentVariable("DOTNET_CLI_CONTEXT_VERBOSE"), out globalVerbose);
|
.WithConsole(console)
|
||||||
|
.Verbose(f =>
|
||||||
|
{
|
||||||
|
if (console.IsOutputRedirected)
|
||||||
|
{
|
||||||
|
f.WithPrefix(prefix);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
f.WithColor(ConsoleColor.DarkGray).WithPrefix(colorPrefix);
|
||||||
|
}
|
||||||
|
|
||||||
if (options.IsVerbose // dotnet watch --verbose
|
f.When(() => verbose || CliContext.IsGlobalVerbose());
|
||||||
|| globalVerbose) // dotnet --verbose watch
|
})
|
||||||
{
|
.Output(f => f
|
||||||
return LogLevel.Debug;
|
.WithPrefix(console.IsOutputRedirected ? prefix : colorPrefix)
|
||||||
}
|
.When(() => !quiet))
|
||||||
|
.Warn(f =>
|
||||||
return LogLevel.Information;
|
{
|
||||||
|
if (console.IsOutputRedirected)
|
||||||
|
{
|
||||||
|
f.WithPrefix(prefix);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
f.WithColor(ConsoleColor.Yellow).WithPrefix(colorPrefix);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.Error(f =>
|
||||||
|
{
|
||||||
|
if (console.IsOutputRedirected)
|
||||||
|
{
|
||||||
|
f.WithPrefix(prefix);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
f.WithColor(ConsoleColor.Red).WithPrefix(colorPrefix);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.Build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -32,8 +32,6 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Extensions.Logging": "1.0.0",
|
|
||||||
"Microsoft.Extensions.Logging.Console": "1.0.0",
|
|
||||||
"Microsoft.Extensions.Process.Sources": {
|
"Microsoft.Extensions.Process.Sources": {
|
||||||
"type": "build",
|
"type": "build",
|
||||||
"version": "1.0.0"
|
"version": "1.0.0"
|
||||||
|
|
|
||||||
|
|
@ -14,11 +14,8 @@
|
||||||
</packageTypes>
|
</packageTypes>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<group targetFramework=".NETCoreApp1.0">
|
<group targetFramework=".NETCoreApp1.0">
|
||||||
<dependency id="Microsoft.Extensions.CommandLineUtils" version="$dep_1$" />
|
<dependency id="Microsoft.NETCore.App" version="$dep_1$" />
|
||||||
<dependency id="Microsoft.Extensions.Logging" version="$dep_2$" />
|
<dependency id="System.Data.SqlClient" version="$dep_2$" />
|
||||||
<dependency id="Microsoft.Extensions.Logging.Console" version="$dep_3$" />
|
|
||||||
<dependency id="Microsoft.NETCore.App" version="$dep_4$" />
|
|
||||||
<dependency id="System.Data.SqlClient" version="$dep_5$" />
|
|
||||||
</group>
|
</group>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</metadata>
|
</metadata>
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ using System.Data;
|
||||||
using System.Data.SqlClient;
|
using System.Data.SqlClient;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using Microsoft.Extensions.CommandLineUtils;
|
using Microsoft.Extensions.CommandLineUtils;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Tools.Internal;
|
||||||
|
|
||||||
namespace Microsoft.Extensions.Caching.SqlConfig.Tools
|
namespace Microsoft.Extensions.Caching.SqlConfig.Tools
|
||||||
{
|
{
|
||||||
|
|
@ -15,54 +15,61 @@ namespace Microsoft.Extensions.Caching.SqlConfig.Tools
|
||||||
private string _connectionString = null;
|
private string _connectionString = null;
|
||||||
private string _schemaName = null;
|
private string _schemaName = null;
|
||||||
private string _tableName = null;
|
private string _tableName = null;
|
||||||
|
private readonly IConsole _console;
|
||||||
|
|
||||||
private readonly ILogger _logger;
|
public Program(IConsole console)
|
||||||
|
|
||||||
public Program()
|
|
||||||
{
|
{
|
||||||
var loggerFactory = new LoggerFactory();
|
Ensure.NotNull(console, nameof(console));
|
||||||
loggerFactory.AddConsole();
|
|
||||||
_logger = loggerFactory.CreateLogger<Program>();
|
_console = console;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int Main(string[] args)
|
public static int Main(string[] args)
|
||||||
{
|
{
|
||||||
return new Program().Run(args);
|
return new Program(PhysicalConsole.Singleton).Run(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int Run(string[] args)
|
public int Run(string[] args)
|
||||||
{
|
{
|
||||||
|
DebugHelper.HandleDebugSwitch(ref args);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var description = "Creates table and indexes in Microsoft SQL Server database " +
|
|
||||||
"to be used for distributed caching";
|
|
||||||
|
|
||||||
var app = new CommandLineApplication
|
var app = new CommandLineApplication
|
||||||
{
|
{
|
||||||
Name = "dotnet sql-cache",
|
Name = "dotnet sql-cache",
|
||||||
FullName = "SQL Server Cache Command Line Tool",
|
FullName = "SQL Server Cache Command Line Tool",
|
||||||
Description = description,
|
Description =
|
||||||
|
"Creates table and indexes in Microsoft SQL Server database to be used for distributed caching",
|
||||||
};
|
};
|
||||||
app.HelpOption("-?|-h|--help");
|
|
||||||
|
app.HelpOption();
|
||||||
app.VersionOptionFromAssemblyAttributes(typeof(Program).GetTypeInfo().Assembly);
|
app.VersionOptionFromAssemblyAttributes(typeof(Program).GetTypeInfo().Assembly);
|
||||||
|
var verbose = app.VerboseOption();
|
||||||
|
|
||||||
app.Command("create", command =>
|
app.Command("create", command =>
|
||||||
{
|
{
|
||||||
command.Description = description;
|
command.Description = app.Description;
|
||||||
|
|
||||||
var connectionStringArg = command.Argument(
|
var connectionStringArg = command.Argument(
|
||||||
"[connectionString]",
|
"[connectionString]", "The connection string to connect to the database.");
|
||||||
"The connection string to connect to the database.");
|
|
||||||
var schemaNameArg = command.Argument("[schemaName]", "Name of the table schema.");
|
var schemaNameArg = command.Argument(
|
||||||
var tableNameArg = command.Argument("[tableName]", "Name of the table to be created.");
|
"[schemaName]", "Name of the table schema.");
|
||||||
command.HelpOption("-?|-h|--help");
|
|
||||||
|
var tableNameArg = command.Argument(
|
||||||
|
"[tableName]", "Name of the table to be created.");
|
||||||
|
|
||||||
|
command.HelpOption();
|
||||||
|
|
||||||
command.OnExecute(() =>
|
command.OnExecute(() =>
|
||||||
{
|
{
|
||||||
|
var reporter = CreateReporter(verbose.HasValue());
|
||||||
if (string.IsNullOrEmpty(connectionStringArg.Value)
|
if (string.IsNullOrEmpty(connectionStringArg.Value)
|
||||||
|| string.IsNullOrEmpty(schemaNameArg.Value)
|
|| string.IsNullOrEmpty(schemaNameArg.Value)
|
||||||
|| string.IsNullOrEmpty(tableNameArg.Value))
|
|| string.IsNullOrEmpty(tableNameArg.Value))
|
||||||
{
|
{
|
||||||
_logger.LogWarning("Invalid input");
|
reporter.Error("Invalid input");
|
||||||
app.ShowHelp();
|
app.ShowHelp();
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
|
|
@ -71,7 +78,7 @@ namespace Microsoft.Extensions.Caching.SqlConfig.Tools
|
||||||
_schemaName = schemaNameArg.Value;
|
_schemaName = schemaNameArg.Value;
|
||||||
_tableName = tableNameArg.Value;
|
_tableName = tableNameArg.Value;
|
||||||
|
|
||||||
return CreateTableAndIndexes();
|
return CreateTableAndIndexes(reporter);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -86,12 +93,41 @@ namespace Microsoft.Extensions.Caching.SqlConfig.Tools
|
||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
_logger.LogCritical("An error occurred. {ErrorMessage}", exception.Message);
|
CreateReporter(verbose: false).Error($"An error occurred. {exception.Message}");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private int CreateTableAndIndexes()
|
private IReporter CreateReporter(bool verbose)
|
||||||
|
{
|
||||||
|
return new ReporterBuilder()
|
||||||
|
.WithConsole(_console)
|
||||||
|
.Verbose(f =>
|
||||||
|
{
|
||||||
|
f.When(() => verbose);
|
||||||
|
if (!_console.IsOutputRedirected)
|
||||||
|
{
|
||||||
|
f.WithColor(ConsoleColor.DarkGray);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.Warn(f =>
|
||||||
|
{
|
||||||
|
if (!_console.IsOutputRedirected)
|
||||||
|
{
|
||||||
|
f.WithColor(ConsoleColor.Yellow);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.Error(f =>
|
||||||
|
{
|
||||||
|
if (!_console.IsErrorRedirected)
|
||||||
|
{
|
||||||
|
f.WithColor(ConsoleColor.Red);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.Build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private int CreateTableAndIndexes(IReporter reporter)
|
||||||
{
|
{
|
||||||
ValidateConnectionString();
|
ValidateConnectionString();
|
||||||
|
|
||||||
|
|
@ -106,7 +142,7 @@ namespace Microsoft.Extensions.Caching.SqlConfig.Tools
|
||||||
{
|
{
|
||||||
if (reader.Read())
|
if (reader.Read())
|
||||||
{
|
{
|
||||||
_logger.LogWarning(
|
reporter.Warn(
|
||||||
$"Table with schema '{_schemaName}' and name '{_tableName}' already exists. " +
|
$"Table with schema '{_schemaName}' and name '{_tableName}' already exists. " +
|
||||||
"Provide a different table name and try again.");
|
"Provide a different table name and try again.");
|
||||||
return 1;
|
return 1;
|
||||||
|
|
@ -118,23 +154,26 @@ namespace Microsoft.Extensions.Caching.SqlConfig.Tools
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
command = new SqlCommand(sqlQueries.CreateTable, connection, transaction);
|
command = new SqlCommand(sqlQueries.CreateTable, connection, transaction);
|
||||||
|
|
||||||
|
reporter.Verbose($"Executing {command.CommandText}");
|
||||||
command.ExecuteNonQuery();
|
command.ExecuteNonQuery();
|
||||||
|
|
||||||
command = new SqlCommand(
|
command = new SqlCommand(
|
||||||
sqlQueries.CreateNonClusteredIndexOnExpirationTime,
|
sqlQueries.CreateNonClusteredIndexOnExpirationTime,
|
||||||
connection,
|
connection,
|
||||||
transaction);
|
transaction);
|
||||||
|
|
||||||
|
reporter.Verbose($"Executing {command.CommandText}");
|
||||||
command.ExecuteNonQuery();
|
command.ExecuteNonQuery();
|
||||||
|
|
||||||
transaction.Commit();
|
transaction.Commit();
|
||||||
|
|
||||||
_logger.LogInformation("Table and index were created successfully.");
|
reporter.Output("Table and index were created successfully.");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(
|
reporter.Error(
|
||||||
"An error occurred while trying to create the table and index. {ErrorMessage}",
|
$"An error occurred while trying to create the table and index. {ex.Message}");
|
||||||
ex.Message);
|
|
||||||
transaction.Rollback();
|
transaction.Rollback();
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
|
|
@ -154,7 +193,7 @@ namespace Microsoft.Extensions.Caching.SqlConfig.Tools
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
throw new ArgumentException(
|
throw new ArgumentException(
|
||||||
$"Invalid Sql server connection string '{_connectionString}'. {ex.Message}", ex);
|
$"Invalid SQL Server connection string '{_connectionString}'. {ex.Message}", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,8 @@
|
||||||
"emitEntryPoint": true,
|
"emitEntryPoint": true,
|
||||||
"warningsAsErrors": true,
|
"warningsAsErrors": true,
|
||||||
"keyFile": "../../tools/Key.snk",
|
"keyFile": "../../tools/Key.snk",
|
||||||
"compile": "../Shared/CommandLineApplicationExtensions.cs"
|
"compile": "../Shared/**/*.cs"
|
||||||
|
|
||||||
},
|
},
|
||||||
"description": "Command line tool to create tables and indexes in a Microsoft SQL Server database for distributed caching.",
|
"description": "Command line tool to create tables and indexes in a Microsoft SQL Server database for distributed caching.",
|
||||||
"packOptions": {
|
"packOptions": {
|
||||||
|
|
@ -20,9 +21,6 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Extensions.CommandLineUtils": "1.0.0",
|
|
||||||
"Microsoft.Extensions.Logging": "1.0.0",
|
|
||||||
"Microsoft.Extensions.Logging.Console": "1.0.0",
|
|
||||||
"Microsoft.NETCore.App": {
|
"Microsoft.NETCore.App": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"type": "platform"
|
"type": "platform"
|
||||||
|
|
|
||||||
|
|
@ -31,8 +31,7 @@ namespace Microsoft.Extensions.SecretManager.Tools
|
||||||
app.HelpOption();
|
app.HelpOption();
|
||||||
app.VersionOptionFromAssemblyAttributes(typeof(Program).GetTypeInfo().Assembly);
|
app.VersionOptionFromAssemblyAttributes(typeof(Program).GetTypeInfo().Assembly);
|
||||||
|
|
||||||
var optionVerbose = app.Option("-v|--verbose", "Verbose output",
|
var optionVerbose = app.VerboseOption();
|
||||||
CommandOptionType.NoValue, inherited: true);
|
|
||||||
|
|
||||||
var optionProject = app.Option("-p|--project <PROJECT>", "Path to project, default is current directory",
|
var optionProject = app.Option("-p|--project <PROJECT>", "Path to project, default is current directory",
|
||||||
CommandOptionType.SingleValue, inherited: true);
|
CommandOptionType.SingleValue, inherited: true);
|
||||||
|
|
@ -48,7 +47,7 @@ namespace Microsoft.Extensions.SecretManager.Tools
|
||||||
var options = new CommandLineOptions();
|
var options = new CommandLineOptions();
|
||||||
|
|
||||||
app.Command("set", c => SetCommand.Configure(c, options, console));
|
app.Command("set", c => SetCommand.Configure(c, options, console));
|
||||||
app.Command("remove", c => RemoveCommand.Configure(c, options, console));
|
app.Command("remove", c => RemoveCommand.Configure(c, options));
|
||||||
app.Command("list", c => ListCommand.Configure(c, options));
|
app.Command("list", c => ListCommand.Configure(c, options));
|
||||||
app.Command("clear", c => ClearCommand.Configure(c, options));
|
app.Command("clear", c => ClearCommand.Configure(c, options));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,62 +0,0 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using Microsoft.Extensions.CommandLineUtils;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
|
|
||||||
namespace Microsoft.Extensions.SecretManager.Tools
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Logger to print formatted command output.
|
|
||||||
/// </summary>
|
|
||||||
public class CommandOutputLogger : ILogger
|
|
||||||
{
|
|
||||||
private readonly CommandOutputProvider _provider;
|
|
||||||
private readonly AnsiConsole _outConsole;
|
|
||||||
|
|
||||||
public CommandOutputLogger(CommandOutputProvider commandOutputProvider, bool useConsoleColor)
|
|
||||||
{
|
|
||||||
_provider = commandOutputProvider;
|
|
||||||
_outConsole = AnsiConsole.GetOutput(useConsoleColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
public IDisposable BeginScope<TState>(TState state)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsEnabled(LogLevel logLevel)
|
|
||||||
{
|
|
||||||
if (logLevel < _provider.LogLevel)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
|
|
||||||
{
|
|
||||||
if (IsEnabled(logLevel))
|
|
||||||
{
|
|
||||||
_outConsole.WriteLine(string.Format("{0}: {1}", Caption(logLevel), formatter(state, exception)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private string Caption(LogLevel logLevel)
|
|
||||||
{
|
|
||||||
switch (logLevel)
|
|
||||||
{
|
|
||||||
case LogLevel.Trace: return "\x1b[35mtrace\x1b[39m";
|
|
||||||
case LogLevel.Debug: return "\x1b[35mdebug\x1b[39m";
|
|
||||||
case LogLevel.Information: return "\x1b[32minfo\x1b[39m";
|
|
||||||
case LogLevel.Warning: return "\x1b[33mwarn\x1b[39m";
|
|
||||||
case LogLevel.Error: return "\x1b[31mfail\x1b[39m";
|
|
||||||
case LogLevel.Critical: return "\x1b[31mcritical\x1b[39m";
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Exception("Unknown LogLevel");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
|
||||||
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
|
|
||||||
namespace Microsoft.Extensions.SecretManager.Tools
|
|
||||||
{
|
|
||||||
public class CommandOutputProvider : ILoggerProvider
|
|
||||||
{
|
|
||||||
public ILogger CreateLogger(string name)
|
|
||||||
{
|
|
||||||
var useConsoleColor = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
|
|
||||||
return new CommandOutputLogger(this, useConsoleColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public LogLevel LogLevel { get; set; } = LogLevel.Information;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Microsoft.Extensions.Tools.Internal;
|
using Microsoft.Extensions.Tools.Internal;
|
||||||
|
|
||||||
namespace Microsoft.Extensions.SecretManager.Tools.Internal
|
namespace Microsoft.Extensions.SecretManager.Tools.Internal
|
||||||
|
|
@ -10,16 +9,16 @@ namespace Microsoft.Extensions.SecretManager.Tools.Internal
|
||||||
{
|
{
|
||||||
public CommandContext(
|
public CommandContext(
|
||||||
SecretsStore store,
|
SecretsStore store,
|
||||||
ILogger logger,
|
IReporter reporter,
|
||||||
IConsole console)
|
IConsole console)
|
||||||
{
|
{
|
||||||
SecretStore = store;
|
SecretStore = store;
|
||||||
Logger = logger;
|
Reporter = reporter;
|
||||||
Console = console;
|
Console = console;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IConsole Console { get; }
|
public IConsole Console { get; }
|
||||||
public ILogger Logger { get; }
|
public IReporter Reporter { get; }
|
||||||
public SecretsStore SecretStore { get; }
|
public SecretsStore SecretStore { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using Microsoft.Extensions.CommandLineUtils;
|
using Microsoft.Extensions.CommandLineUtils;
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
|
|
@ -41,13 +40,13 @@ namespace Microsoft.Extensions.SecretManager.Tools.Internal
|
||||||
|
|
||||||
if (context.SecretStore.Count == 0)
|
if (context.SecretStore.Count == 0)
|
||||||
{
|
{
|
||||||
context.Logger.LogInformation(Resources.Error_No_Secrets_Found);
|
context.Reporter.Output(Resources.Error_No_Secrets_Found);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
foreach (var secret in context.SecretStore.AsEnumerable())
|
foreach (var secret in context.SecretStore.AsEnumerable())
|
||||||
{
|
{
|
||||||
context.Logger.LogInformation(Resources.FormatMessage_Secret_Value_Format(secret.Key, secret.Value));
|
context.Reporter.Output(Resources.FormatMessage_Secret_Value_Format(secret.Key, secret.Value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -60,10 +59,9 @@ namespace Microsoft.Extensions.SecretManager.Tools.Internal
|
||||||
jObject[item.Key] = item.Value;
|
jObject[item.Key] = item.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO logger would prefix each line.
|
context.Reporter.Output("//BEGIN");
|
||||||
context.Console.Out.WriteLine("//BEGIN");
|
context.Reporter.Output(jObject.ToString(Formatting.Indented));
|
||||||
context.Console.Out.WriteLine(jObject.ToString(Formatting.Indented));
|
context.Reporter.Output("//END");
|
||||||
context.Console.Out.WriteLine("//END");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -7,7 +7,6 @@ using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Microsoft.Extensions.Tools.Internal;
|
using Microsoft.Extensions.Tools.Internal;
|
||||||
|
|
||||||
namespace Microsoft.Extensions.SecretManager.Tools.Internal
|
namespace Microsoft.Extensions.SecretManager.Tools.Internal
|
||||||
|
|
@ -15,27 +14,32 @@ namespace Microsoft.Extensions.SecretManager.Tools.Internal
|
||||||
public class ProjectIdResolver : IDisposable
|
public class ProjectIdResolver : IDisposable
|
||||||
{
|
{
|
||||||
private const string TargetsFileName = "FindUserSecretsProperty.targets";
|
private const string TargetsFileName = "FindUserSecretsProperty.targets";
|
||||||
private readonly ILogger _logger;
|
private const string DefaultConfig = "Debug";
|
||||||
|
private readonly IReporter _reporter;
|
||||||
private readonly string _workingDirectory;
|
private readonly string _workingDirectory;
|
||||||
private readonly List<string> _tempFiles = new List<string>();
|
private readonly List<string> _tempFiles = new List<string>();
|
||||||
|
|
||||||
public ProjectIdResolver(ILogger logger, string workingDirectory)
|
public ProjectIdResolver(IReporter reporter, string workingDirectory)
|
||||||
{
|
{
|
||||||
_workingDirectory = workingDirectory;
|
_workingDirectory = workingDirectory;
|
||||||
_logger = logger;
|
_reporter = reporter;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Resolve(string project, string configuration = "Debug")
|
public string Resolve(string project, string configuration)
|
||||||
{
|
{
|
||||||
var finder = new MsBuildProjectFinder(_workingDirectory);
|
var finder = new MsBuildProjectFinder(_workingDirectory);
|
||||||
var projectFile = finder.FindMsBuildProject(project);
|
var projectFile = finder.FindMsBuildProject(project);
|
||||||
|
|
||||||
_logger.LogDebug(Resources.Message_Project_File_Path, projectFile);
|
_reporter.Verbose(Resources.FormatMessage_Project_File_Path(projectFile));
|
||||||
|
|
||||||
var targetFile = GetTargetFile();
|
var targetFile = GetTargetFile();
|
||||||
var outputFile = Path.GetTempFileName();
|
var outputFile = Path.GetTempFileName();
|
||||||
_tempFiles.Add(outputFile);
|
_tempFiles.Add(outputFile);
|
||||||
|
|
||||||
|
configuration = !string.IsNullOrEmpty(configuration)
|
||||||
|
? configuration
|
||||||
|
: DefaultConfig;
|
||||||
|
|
||||||
var args = new[]
|
var args = new[]
|
||||||
{
|
{
|
||||||
"msbuild",
|
"msbuild",
|
||||||
|
|
@ -53,13 +57,18 @@ namespace Microsoft.Extensions.SecretManager.Tools.Internal
|
||||||
RedirectStandardOutput = true,
|
RedirectStandardOutput = true,
|
||||||
RedirectStandardError = true
|
RedirectStandardError = true
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
_reporter.Verbose($"Invoking '{psi.FileName} {psi.Arguments}'");
|
||||||
|
#endif
|
||||||
|
|
||||||
var process = Process.Start(psi);
|
var process = Process.Start(psi);
|
||||||
process.WaitForExit();
|
process.WaitForExit();
|
||||||
|
|
||||||
if (process.ExitCode != 0)
|
if (process.ExitCode != 0)
|
||||||
{
|
{
|
||||||
_logger.LogDebug(process.StandardOutput.ReadToEnd());
|
_reporter.Verbose(process.StandardOutput.ReadToEnd());
|
||||||
_logger.LogDebug(process.StandardError.ReadToEnd());
|
_reporter.Verbose(process.StandardError.ReadToEnd());
|
||||||
throw new InvalidOperationException(Resources.FormatError_ProjectFailedToLoad(projectFile));
|
throw new InvalidOperationException(Resources.FormatError_ProjectFailedToLoad(projectFile));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,7 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
|
||||||
using Microsoft.Extensions.CommandLineUtils;
|
using Microsoft.Extensions.CommandLineUtils;
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Microsoft.Extensions.Tools.Internal;
|
using Microsoft.Extensions.Tools.Internal;
|
||||||
|
|
||||||
namespace Microsoft.Extensions.SecretManager.Tools.Internal
|
namespace Microsoft.Extensions.SecretManager.Tools.Internal
|
||||||
|
|
@ -12,7 +10,7 @@ namespace Microsoft.Extensions.SecretManager.Tools.Internal
|
||||||
{
|
{
|
||||||
private readonly string _keyName;
|
private readonly string _keyName;
|
||||||
|
|
||||||
public static void Configure(CommandLineApplication command, CommandLineOptions options, IConsole console)
|
public static void Configure(CommandLineApplication command, CommandLineOptions options)
|
||||||
{
|
{
|
||||||
command.Description = "Removes the specified user secret";
|
command.Description = "Removes the specified user secret";
|
||||||
command.HelpOption();
|
command.HelpOption();
|
||||||
|
|
@ -22,12 +20,10 @@ namespace Microsoft.Extensions.SecretManager.Tools.Internal
|
||||||
{
|
{
|
||||||
if (keyArg.Value == null)
|
if (keyArg.Value == null)
|
||||||
{
|
{
|
||||||
console.Error.WriteLine(Resources.FormatError_MissingArgument("name").Red());
|
throw new CommandParsingException(command, Resources.FormatError_MissingArgument("name"));
|
||||||
return 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
options.Command = new RemoveCommand(keyArg.Value);
|
options.Command = new RemoveCommand(keyArg.Value);
|
||||||
return 0;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -41,7 +37,7 @@ namespace Microsoft.Extensions.SecretManager.Tools.Internal
|
||||||
{
|
{
|
||||||
if (!context.SecretStore.ContainsKey(_keyName))
|
if (!context.SecretStore.ContainsKey(_keyName))
|
||||||
{
|
{
|
||||||
context.Logger.LogWarning(Resources.Error_Missing_Secret, _keyName);
|
context.Reporter.Warn(Resources.FormatError_Missing_Secret(_keyName));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.Configuration.UserSecrets;
|
using Microsoft.Extensions.Configuration.UserSecrets;
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Microsoft.Extensions.Tools.Internal;
|
using Microsoft.Extensions.Tools.Internal;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
|
|
@ -19,7 +18,7 @@ namespace Microsoft.Extensions.SecretManager.Tools.Internal
|
||||||
private readonly string _secretsFilePath;
|
private readonly string _secretsFilePath;
|
||||||
private IDictionary<string, string> _secrets;
|
private IDictionary<string, string> _secrets;
|
||||||
|
|
||||||
public SecretsStore(string userSecretsId, ILogger logger)
|
public SecretsStore(string userSecretsId, IReporter reporter)
|
||||||
{
|
{
|
||||||
Ensure.NotNull(userSecretsId, nameof(userSecretsId));
|
Ensure.NotNull(userSecretsId, nameof(userSecretsId));
|
||||||
|
|
||||||
|
|
@ -29,7 +28,7 @@ namespace Microsoft.Extensions.SecretManager.Tools.Internal
|
||||||
var secretDir = Path.GetDirectoryName(_secretsFilePath);
|
var secretDir = Path.GetDirectoryName(_secretsFilePath);
|
||||||
Directory.CreateDirectory(secretDir);
|
Directory.CreateDirectory(secretDir);
|
||||||
|
|
||||||
logger.LogDebug(Resources.Message_Secret_File_Path, _secretsFilePath);
|
reporter.Verbose(Resources.FormatMessage_Secret_File_Path(_secretsFilePath));
|
||||||
_secrets = Load(userSecretsId);
|
_secrets = Load(userSecretsId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,9 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Microsoft.Extensions.CommandLineUtils;
|
using Microsoft.Extensions.CommandLineUtils;
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Microsoft.Extensions.Tools.Internal;
|
using Microsoft.Extensions.Tools.Internal;
|
||||||
|
|
||||||
namespace Microsoft.Extensions.SecretManager.Tools.Internal
|
namespace Microsoft.Extensions.SecretManager.Tools.Internal
|
||||||
|
|
@ -34,23 +32,21 @@ Examples:
|
||||||
if (console.IsInputRedirected && nameArg.Value == null)
|
if (console.IsInputRedirected && nameArg.Value == null)
|
||||||
{
|
{
|
||||||
options.Command = new FromStdInStrategy();
|
options.Command = new FromStdInStrategy();
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
if (string.IsNullOrEmpty(nameArg.Value))
|
|
||||||
{
|
{
|
||||||
console.Error.WriteLine(Resources.FormatError_MissingArgument("name").Red());
|
if (string.IsNullOrEmpty(nameArg.Value))
|
||||||
return 1;
|
{
|
||||||
}
|
throw new CommandParsingException(command, Resources.FormatError_MissingArgument("name"));
|
||||||
|
}
|
||||||
|
|
||||||
if (valueArg.Value == null)
|
if (valueArg.Value == null)
|
||||||
{
|
{
|
||||||
console.Error.WriteLine(Resources.FormatError_MissingArgument("value").Red());
|
throw new CommandParsingException(command, Resources.FormatError_MissingArgument("value"));
|
||||||
return 1;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
options.Command = new ForOneValueStrategy(nameArg.Value, valueArg.Value);
|
options.Command = new ForOneValueStrategy(nameArg.Value, valueArg.Value);
|
||||||
return 0;
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -76,7 +72,7 @@ Examples:
|
||||||
context.SecretStore.Set(k.Key, k.Value);
|
context.SecretStore.Set(k.Key, k.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
context.Logger.LogInformation(Resources.Message_Saved_Secrets, provider.CurrentData.Count);
|
context.Reporter.Output(Resources.FormatMessage_Saved_Secrets(provider.CurrentData.Count));
|
||||||
|
|
||||||
context.SecretStore.Save();
|
context.SecretStore.Save();
|
||||||
}
|
}
|
||||||
|
|
@ -97,7 +93,7 @@ Examples:
|
||||||
{
|
{
|
||||||
context.SecretStore.Set(_keyName, _keyValue);
|
context.SecretStore.Set(_keyName, _keyValue);
|
||||||
context.SecretStore.Save();
|
context.SecretStore.Save();
|
||||||
context.Logger.LogInformation(Resources.Message_Saved_Secret, _keyName, _keyValue);
|
context.Reporter.Output(Resources.FormatMessage_Saved_Secret(_keyName, _keyValue));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,7 @@
|
||||||
<group targetFramework=".NETCoreApp1.0">
|
<group targetFramework=".NETCoreApp1.0">
|
||||||
<!-- MUST BE alphabetical -->
|
<!-- MUST BE alphabetical -->
|
||||||
<dependency id="Microsoft.Extensions.Configuration.UserSecrets" version="$dep_1$" />
|
<dependency id="Microsoft.Extensions.Configuration.UserSecrets" version="$dep_1$" />
|
||||||
<dependency id="Microsoft.Extensions.Logging" version="$dep_2$" />
|
<dependency id="Microsoft.NETCore.App" version="$dep_2$" />
|
||||||
<dependency id="Microsoft.NETCore.App" version="$dep_3$" />
|
|
||||||
</group>
|
</group>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</metadata>
|
</metadata>
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.CommandLineUtils;
|
||||||
using Microsoft.Extensions.SecretManager.Tools.Internal;
|
using Microsoft.Extensions.SecretManager.Tools.Internal;
|
||||||
using Microsoft.Extensions.Tools.Internal;
|
using Microsoft.Extensions.Tools.Internal;
|
||||||
|
|
||||||
|
|
@ -11,8 +11,6 @@ namespace Microsoft.Extensions.SecretManager.Tools
|
||||||
{
|
{
|
||||||
public class Program
|
public class Program
|
||||||
{
|
{
|
||||||
private ILogger _logger;
|
|
||||||
private CommandOutputProvider _loggerProvider;
|
|
||||||
private readonly IConsole _console;
|
private readonly IConsole _console;
|
||||||
private readonly string _workingDirectory;
|
private readonly string _workingDirectory;
|
||||||
|
|
||||||
|
|
@ -29,23 +27,6 @@ namespace Microsoft.Extensions.SecretManager.Tools
|
||||||
{
|
{
|
||||||
_console = console;
|
_console = console;
|
||||||
_workingDirectory = workingDirectory;
|
_workingDirectory = workingDirectory;
|
||||||
|
|
||||||
var loggerFactory = new LoggerFactory();
|
|
||||||
CommandOutputProvider = new CommandOutputProvider();
|
|
||||||
loggerFactory.AddProvider(CommandOutputProvider);
|
|
||||||
Logger = loggerFactory.CreateLogger<Program>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public ILogger Logger
|
|
||||||
{
|
|
||||||
get { return _logger; }
|
|
||||||
set { _logger = Ensure.NotNull(value, nameof(value)); }
|
|
||||||
}
|
|
||||||
|
|
||||||
public CommandOutputProvider CommandOutputProvider
|
|
||||||
{
|
|
||||||
get { return _loggerProvider; }
|
|
||||||
set { _loggerProvider = Ensure.NotNull(value, nameof(value)); }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool TryRun(string[] args, out int returnCode)
|
public bool TryRun(string[] args, out int returnCode)
|
||||||
|
|
@ -57,8 +38,9 @@ namespace Microsoft.Extensions.SecretManager.Tools
|
||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
Logger.LogDebug(exception.ToString());
|
var reporter = CreateReporter(verbose: true);
|
||||||
Logger.LogCritical(Resources.Error_Command_Failed, exception.Message);
|
reporter.Verbose(exception.ToString());
|
||||||
|
reporter.Error(Resources.FormatError_Command_Failed(exception.Message));
|
||||||
returnCode = 1;
|
returnCode = 1;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -66,7 +48,16 @@ namespace Microsoft.Extensions.SecretManager.Tools
|
||||||
|
|
||||||
internal int RunInternal(params string[] args)
|
internal int RunInternal(params string[] args)
|
||||||
{
|
{
|
||||||
var options = CommandLineOptions.Parse(args, _console);
|
CommandLineOptions options;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
options = CommandLineOptions.Parse(args, _console);
|
||||||
|
}
|
||||||
|
catch (CommandParsingException ex)
|
||||||
|
{
|
||||||
|
CreateReporter(verbose: false).Error(ex.Message);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
if (options == null)
|
if (options == null)
|
||||||
{
|
{
|
||||||
|
|
@ -78,36 +69,62 @@ namespace Microsoft.Extensions.SecretManager.Tools
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.IsVerbose)
|
var reporter = CreateReporter(options.IsVerbose);
|
||||||
{
|
|
||||||
CommandOutputProvider.LogLevel = LogLevel.Debug;
|
|
||||||
}
|
|
||||||
|
|
||||||
string userSecretsId;
|
string userSecretsId;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
userSecretsId = ResolveId(options);
|
userSecretsId = ResolveId(options, reporter);
|
||||||
}
|
}
|
||||||
catch (Exception ex) when (ex is InvalidOperationException || ex is FileNotFoundException)
|
catch (Exception ex) when (ex is InvalidOperationException || ex is FileNotFoundException)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex.Message);
|
reporter.Error(ex.Message);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
var store = new SecretsStore(userSecretsId, Logger);
|
var store = new SecretsStore(userSecretsId, reporter);
|
||||||
var context = new Internal.CommandContext(store, Logger, _console);
|
var context = new Internal.CommandContext(store, reporter, _console);
|
||||||
options.Command.Execute(context);
|
options.Command.Execute(context);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal string ResolveId(CommandLineOptions options)
|
private IReporter CreateReporter(bool verbose)
|
||||||
|
{
|
||||||
|
return new ReporterBuilder()
|
||||||
|
.WithConsole(_console)
|
||||||
|
.Verbose(f =>
|
||||||
|
{
|
||||||
|
f.When(() => verbose);
|
||||||
|
if (!_console.IsOutputRedirected)
|
||||||
|
{
|
||||||
|
f.WithColor(ConsoleColor.DarkGray);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.Warn(f =>
|
||||||
|
{
|
||||||
|
if (!_console.IsOutputRedirected)
|
||||||
|
{
|
||||||
|
f.WithColor(ConsoleColor.Yellow);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.Error(f =>
|
||||||
|
{
|
||||||
|
if (!_console.IsErrorRedirected)
|
||||||
|
{
|
||||||
|
f.WithColor(ConsoleColor.Red);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.Build();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal string ResolveId(CommandLineOptions options, IReporter reporter)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(options.Id))
|
if (!string.IsNullOrEmpty(options.Id))
|
||||||
{
|
{
|
||||||
return options.Id;
|
return options.Id;
|
||||||
}
|
}
|
||||||
|
|
||||||
using (var resolver = new ProjectIdResolver(Logger, _workingDirectory))
|
using (var resolver = new ProjectIdResolver(reporter, _workingDirectory))
|
||||||
{
|
{
|
||||||
return resolver.Resolve(options.Project, options.Configuration);
|
return resolver.Resolve(options.Project, options.Configuration);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,6 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Extensions.Configuration.UserSecrets": "1.0.0",
|
"Microsoft.Extensions.Configuration.UserSecrets": "1.0.0",
|
||||||
"Microsoft.Extensions.Logging": "1.0.0",
|
|
||||||
"Microsoft.NETCore.App": {
|
"Microsoft.NETCore.App": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"type": "platform"
|
"type": "platform"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Microsoft.Extensions.Tools.Internal
|
||||||
|
{
|
||||||
|
public static class CliContext
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// dotnet --verbose subcommand
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static bool IsGlobalVerbose()
|
||||||
|
{
|
||||||
|
bool globalVerbose;
|
||||||
|
bool.TryParse(Environment.GetEnvironmentVariable("DOTNET_CLI_CONTEXT_VERBOSE"), out globalVerbose);
|
||||||
|
return globalVerbose;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -11,6 +11,9 @@ namespace Microsoft.Extensions.CommandLineUtils
|
||||||
public static CommandOption HelpOption(this CommandLineApplication app)
|
public static CommandOption HelpOption(this CommandLineApplication app)
|
||||||
=> app.HelpOption("-?|-h|--help");
|
=> app.HelpOption("-?|-h|--help");
|
||||||
|
|
||||||
|
public static CommandOption VerboseOption(this CommandLineApplication app)
|
||||||
|
=> app.Option("-v|--verbose", "Show verbose output", CommandOptionType.NoValue, inherited: true);
|
||||||
|
|
||||||
public static void OnExecute(this CommandLineApplication app, Action action)
|
public static void OnExecute(this CommandLineApplication app, Action action)
|
||||||
=> app.OnExecute(() =>
|
=> app.OnExecute(() =>
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -13,5 +13,7 @@ namespace Microsoft.Extensions.Tools.Internal
|
||||||
TextWriter Error { get; }
|
TextWriter Error { get; }
|
||||||
TextReader In { get; }
|
TextReader In { get; }
|
||||||
bool IsInputRedirected { get; }
|
bool IsInputRedirected { get; }
|
||||||
|
bool IsOutputRedirected { get; }
|
||||||
|
bool IsErrorRedirected { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,5 +23,7 @@ namespace Microsoft.Extensions.Tools.Internal
|
||||||
public TextReader In => Console.In;
|
public TextReader In => Console.In;
|
||||||
public TextWriter Out => Console.Out;
|
public TextWriter Out => Console.Out;
|
||||||
public bool IsInputRedirected => Console.IsInputRedirected;
|
public bool IsInputRedirected => Console.IsInputRedirected;
|
||||||
|
public bool IsOutputRedirected => Console.IsOutputRedirected;
|
||||||
|
public bool IsErrorRedirected => Console.IsErrorRedirected;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Microsoft.Extensions.Tools.Internal
|
||||||
|
{
|
||||||
|
public class ColorFormatter : IFormatter
|
||||||
|
{
|
||||||
|
// resets foreground color only
|
||||||
|
private const string ResetColor = "\x1B[39m";
|
||||||
|
|
||||||
|
private static readonly IDictionary<ConsoleColor, int> AnsiColorCodes
|
||||||
|
= new Dictionary<ConsoleColor, int>
|
||||||
|
{
|
||||||
|
{ConsoleColor.Black, 30},
|
||||||
|
{ConsoleColor.DarkRed, 31},
|
||||||
|
{ConsoleColor.DarkGreen, 32},
|
||||||
|
{ConsoleColor.DarkYellow, 33},
|
||||||
|
{ConsoleColor.DarkBlue, 34},
|
||||||
|
{ConsoleColor.DarkMagenta, 35},
|
||||||
|
{ConsoleColor.DarkCyan, 36},
|
||||||
|
{ConsoleColor.Gray, 37},
|
||||||
|
{ConsoleColor.DarkGray, 90},
|
||||||
|
{ConsoleColor.Red, 91},
|
||||||
|
{ConsoleColor.Green, 92},
|
||||||
|
{ConsoleColor.Yellow, 93},
|
||||||
|
{ConsoleColor.Blue, 94},
|
||||||
|
{ConsoleColor.Magenta, 95},
|
||||||
|
{ConsoleColor.Cyan, 96},
|
||||||
|
{ConsoleColor.White, 97},
|
||||||
|
};
|
||||||
|
|
||||||
|
private readonly string _prefix;
|
||||||
|
|
||||||
|
public ColorFormatter(ConsoleColor color)
|
||||||
|
{
|
||||||
|
_prefix = GetAnsiCode(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Format(string text)
|
||||||
|
=> text?.Length > 0
|
||||||
|
? $"{_prefix}{text}{ResetColor}"
|
||||||
|
: text;
|
||||||
|
|
||||||
|
private static string GetAnsiCode(ConsoleColor color)
|
||||||
|
{
|
||||||
|
int code;
|
||||||
|
if (!AnsiColorCodes.TryGetValue(color, out code))
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(color), color, null);
|
||||||
|
}
|
||||||
|
return $"\x1B[{code}m";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
namespace Microsoft.Extensions.Tools.Internal
|
||||||
|
{
|
||||||
|
public class CompositeFormatter : IFormatter
|
||||||
|
{
|
||||||
|
private readonly IFormatter[] _formatters;
|
||||||
|
|
||||||
|
public CompositeFormatter(IFormatter[] formatters)
|
||||||
|
{
|
||||||
|
Ensure.NotNull(formatters, nameof(formatters));
|
||||||
|
_formatters = formatters;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Format(string text)
|
||||||
|
{
|
||||||
|
foreach (var formatter in _formatters)
|
||||||
|
{
|
||||||
|
text = formatter.Format(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Microsoft.Extensions.Tools.Internal
|
||||||
|
{
|
||||||
|
public class ConditionalFormatter : IFormatter
|
||||||
|
{
|
||||||
|
private readonly Func<bool> _predicate;
|
||||||
|
|
||||||
|
public ConditionalFormatter(Func<bool> predicate)
|
||||||
|
{
|
||||||
|
Ensure.NotNull(predicate, nameof(predicate));
|
||||||
|
|
||||||
|
_predicate = predicate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Format(string text)
|
||||||
|
=> _predicate()
|
||||||
|
? text
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
namespace Microsoft.Extensions.Tools.Internal
|
||||||
|
{
|
||||||
|
public class DefaultFormatter : IFormatter
|
||||||
|
{
|
||||||
|
public static readonly IFormatter Instance = new DefaultFormatter();
|
||||||
|
|
||||||
|
private DefaultFormatter() {}
|
||||||
|
public string Format(string text)
|
||||||
|
=> text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Microsoft.Extensions.Tools.Internal
|
||||||
|
{
|
||||||
|
public class FormatterBuilder
|
||||||
|
{
|
||||||
|
private readonly List<IFormatter> _formatters = new List<IFormatter>();
|
||||||
|
|
||||||
|
public FormatterBuilder WithColor(ConsoleColor color)
|
||||||
|
{
|
||||||
|
_formatters.Add(new ColorFormatter(color));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FormatterBuilder WithPrefix(string prefix)
|
||||||
|
{
|
||||||
|
_formatters.Add(new PrefixFormatter(prefix));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FormatterBuilder When(Func<bool> predicate)
|
||||||
|
{
|
||||||
|
_formatters.Add(new ConditionalFormatter(predicate));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IFormatter Build()
|
||||||
|
{
|
||||||
|
if (_formatters.Count == 0)
|
||||||
|
{
|
||||||
|
return DefaultFormatter.Instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_formatters.Count == 1)
|
||||||
|
{
|
||||||
|
return _formatters[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return new CompositeFormatter(_formatters.ToArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,62 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace Microsoft.Extensions.Tools.Internal
|
||||||
|
{
|
||||||
|
public class FormattingReporter : IReporter
|
||||||
|
{
|
||||||
|
private readonly object _writelock = new object();
|
||||||
|
private readonly IConsole _console;
|
||||||
|
private readonly IFormatter _verbose;
|
||||||
|
private readonly IFormatter _warn;
|
||||||
|
private readonly IFormatter _output;
|
||||||
|
private readonly IFormatter _error;
|
||||||
|
|
||||||
|
public FormattingReporter(IConsole console,
|
||||||
|
IFormatter verbose,
|
||||||
|
IFormatter output,
|
||||||
|
IFormatter warn,
|
||||||
|
IFormatter error)
|
||||||
|
{
|
||||||
|
Ensure.NotNull(console, nameof(console));
|
||||||
|
Ensure.NotNull(verbose, nameof(verbose));
|
||||||
|
Ensure.NotNull(output, nameof(output));
|
||||||
|
Ensure.NotNull(warn, nameof(warn));
|
||||||
|
Ensure.NotNull(error, nameof(error));
|
||||||
|
|
||||||
|
_console = console;
|
||||||
|
_verbose = verbose;
|
||||||
|
_output = output;
|
||||||
|
_warn = warn;
|
||||||
|
_error = error;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void Verbose(string message)
|
||||||
|
=> Write(_console.Out, _verbose.Format(message));
|
||||||
|
|
||||||
|
public void Output(string message)
|
||||||
|
=> Write(_console.Out, _output.Format(message));
|
||||||
|
|
||||||
|
public void Warn(string message)
|
||||||
|
=> Write(_console.Out, _warn.Format(message));
|
||||||
|
|
||||||
|
public void Error(string message)
|
||||||
|
=> Write(_console.Error, _error.Format(message));
|
||||||
|
|
||||||
|
private void Write(TextWriter writer, string message)
|
||||||
|
{
|
||||||
|
if (message == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
lock (_writelock)
|
||||||
|
{
|
||||||
|
writer.WriteLine(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
namespace Microsoft.Extensions.Tools.Internal
|
||||||
|
{
|
||||||
|
public interface IFormatter
|
||||||
|
{
|
||||||
|
string Format(string text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
namespace Microsoft.Extensions.Tools.Internal
|
||||||
|
{
|
||||||
|
public interface IReporter
|
||||||
|
{
|
||||||
|
void Verbose(string message);
|
||||||
|
void Output(string message);
|
||||||
|
void Warn(string message);
|
||||||
|
void Error(string message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
namespace Microsoft.Extensions.Tools.Internal
|
||||||
|
{
|
||||||
|
public class PrefixFormatter : IFormatter
|
||||||
|
{
|
||||||
|
private readonly string _prefix;
|
||||||
|
|
||||||
|
public PrefixFormatter(string prefix)
|
||||||
|
{
|
||||||
|
Ensure.NotNullOrEmpty(prefix, nameof(prefix));
|
||||||
|
|
||||||
|
_prefix = prefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Format(string text)
|
||||||
|
=> _prefix + text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Microsoft.Extensions.Tools.Internal
|
||||||
|
{
|
||||||
|
public class ReporterBuilder
|
||||||
|
{
|
||||||
|
private readonly FormatterBuilder _verbose = new FormatterBuilder();
|
||||||
|
private readonly FormatterBuilder _output = new FormatterBuilder();
|
||||||
|
private readonly FormatterBuilder _warn = new FormatterBuilder();
|
||||||
|
private readonly FormatterBuilder _error = new FormatterBuilder();
|
||||||
|
private IConsole _console;
|
||||||
|
|
||||||
|
public ReporterBuilder WithConsole(IConsole console)
|
||||||
|
{
|
||||||
|
_console = console;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FormatterBuilder Verbose() => _verbose;
|
||||||
|
public FormatterBuilder Output() => _output;
|
||||||
|
public FormatterBuilder Warn() => _warn;
|
||||||
|
public FormatterBuilder Error() => _error;
|
||||||
|
|
||||||
|
public ReporterBuilder Verbose(Action<FormatterBuilder> configure)
|
||||||
|
{
|
||||||
|
configure(_verbose);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReporterBuilder Output(Action<FormatterBuilder> configure)
|
||||||
|
{
|
||||||
|
configure(_output);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReporterBuilder Warn(Action<FormatterBuilder> configure)
|
||||||
|
{
|
||||||
|
configure(_warn);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReporterBuilder Error(Action<FormatterBuilder> configure)
|
||||||
|
{
|
||||||
|
configure(_error);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IReporter Build()
|
||||||
|
{
|
||||||
|
if (_console == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"Cannot build without first calling {nameof(WithConsole)}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new FormattingReporter(_console,
|
||||||
|
_verbose.Build(),
|
||||||
|
_output.Build(),
|
||||||
|
_warn.Build(),
|
||||||
|
_error.Build());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
|
||||||
|
|
||||||
namespace System
|
|
||||||
{
|
|
||||||
public static class StringExtensions
|
|
||||||
{
|
|
||||||
public static string Black(this string text)
|
|
||||||
=> "\x1B[30m" + text + "\x1B[39m";
|
|
||||||
|
|
||||||
public static string Red(this string text)
|
|
||||||
=> "\x1B[31m" + text + "\x1B[39m";
|
|
||||||
|
|
||||||
public static string Green(this string text)
|
|
||||||
=> "\x1B[32m" + text + "\x1B[39m";
|
|
||||||
|
|
||||||
public static string Yellow(this string text)
|
|
||||||
=> "\x1B[33m" + text + "\x1B[39m";
|
|
||||||
|
|
||||||
public static string Blue(this string text)
|
|
||||||
=> "\x1B[34m" + text + "\x1B[39m";
|
|
||||||
|
|
||||||
public static string Magenta(this string text)
|
|
||||||
=> "\x1B[35m" + text + "\x1B[39m";
|
|
||||||
|
|
||||||
public static string Cyan(this string text)
|
|
||||||
=> "\x1B[36m" + text + "\x1B[39m";
|
|
||||||
|
|
||||||
public static string White(this string text)
|
|
||||||
=> "\x1B[37m" + text + "\x1B[39m";
|
|
||||||
|
|
||||||
public static string Bold(this string text)
|
|
||||||
=> "\x1B[1m" + text + "\x1B[22m";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using Microsoft.Extensions.CommandLineUtils;
|
||||||
using Microsoft.Extensions.Tools.Internal;
|
using Microsoft.Extensions.Tools.Internal;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using Xunit.Abstractions;
|
using Xunit.Abstractions;
|
||||||
|
|
@ -14,14 +15,12 @@ namespace Microsoft.DotNet.Watcher.Tools.Tests
|
||||||
{
|
{
|
||||||
private readonly IConsole _console;
|
private readonly IConsole _console;
|
||||||
private readonly StringBuilder _stdout = new StringBuilder();
|
private readonly StringBuilder _stdout = new StringBuilder();
|
||||||
private readonly StringBuilder _stderr = new StringBuilder();
|
|
||||||
|
|
||||||
public CommandLineOptionsTests(ITestOutputHelper output)
|
public CommandLineOptionsTests(ITestOutputHelper output)
|
||||||
{
|
{
|
||||||
_console = new TestConsole(output)
|
_console = new TestConsole(output)
|
||||||
{
|
{
|
||||||
Out = new StringWriter(_stdout),
|
Out = new StringWriter(_stdout),
|
||||||
Error = new StringWriter(_stderr),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -57,8 +56,8 @@ namespace Microsoft.DotNet.Watcher.Tools.Tests
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CannotHaveQuietAndVerbose()
|
public void CannotHaveQuietAndVerbose()
|
||||||
{
|
{
|
||||||
Assert.Null(CommandLineOptions.Parse(new[] { "--quiet", "--verbose" }, _console));
|
var ex = Assert.Throws<CommandParsingException>(() => CommandLineOptions.Parse(new[] { "--quiet", "--verbose" }, _console));
|
||||||
Assert.Contains(Resources.Error_QuietAndVerboseSpecified, _stderr.ToString());
|
Assert.Equal(Resources.Error_QuietAndVerboseSpecified, ex.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ using System.Text.RegularExpressions;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.DotNet.Watcher.Internal;
|
using Microsoft.DotNet.Watcher.Internal;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Tools.Internal;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using Xunit.Abstractions;
|
using Xunit.Abstractions;
|
||||||
|
|
||||||
|
|
@ -17,11 +17,11 @@ namespace Microsoft.DotNet.Watcher.Tools.Tests
|
||||||
|
|
||||||
public class MsBuildFileSetFactoryTest : IDisposable
|
public class MsBuildFileSetFactoryTest : IDisposable
|
||||||
{
|
{
|
||||||
private ILogger _logger;
|
private readonly IReporter _reporter;
|
||||||
private readonly TemporaryDirectory _tempDir;
|
private readonly TemporaryDirectory _tempDir;
|
||||||
public MsBuildFileSetFactoryTest(ITestOutputHelper output)
|
public MsBuildFileSetFactoryTest(ITestOutputHelper output)
|
||||||
{
|
{
|
||||||
_logger = new XunitLogger(output);
|
_reporter = new TestReporter(output);
|
||||||
_tempDir = new TemporaryDirectory();
|
_tempDir = new TemporaryDirectory();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -255,7 +255,7 @@ namespace Microsoft.DotNet.Watcher.Tools.Tests
|
||||||
graph.Find("A").WithProjectReference(graph.Find("W"), watch: false);
|
graph.Find("A").WithProjectReference(graph.Find("W"), watch: false);
|
||||||
|
|
||||||
var output = new OutputSink();
|
var output = new OutputSink();
|
||||||
var filesetFactory = new MsBuildFileSetFactory(_logger, graph.GetOrCreate("A").Path, output)
|
var filesetFactory = new MsBuildFileSetFactory(_reporter, graph.GetOrCreate("A").Path, output)
|
||||||
{
|
{
|
||||||
// enables capturing markers to know which projects have been visited
|
// enables capturing markers to know which projects have been visited
|
||||||
BuildFlags = { "/p:_DotNetWatchTraceOutput=true" }
|
BuildFlags = { "/p:_DotNetWatchTraceOutput=true" }
|
||||||
|
|
@ -263,7 +263,7 @@ namespace Microsoft.DotNet.Watcher.Tools.Tests
|
||||||
|
|
||||||
var fileset = await GetFileSet(filesetFactory);
|
var fileset = await GetFileSet(filesetFactory);
|
||||||
|
|
||||||
_logger.LogInformation(string.Join(
|
_reporter.Output(string.Join(
|
||||||
Environment.NewLine,
|
Environment.NewLine,
|
||||||
output.Current.Lines.Select(l => "Sink output: " + l)));
|
output.Current.Lines.Select(l => "Sink output: " + l)));
|
||||||
|
|
||||||
|
|
@ -297,7 +297,7 @@ namespace Microsoft.DotNet.Watcher.Tools.Tests
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task<IFileSet> GetFileSet(TemporaryCSharpProject target)
|
private Task<IFileSet> GetFileSet(TemporaryCSharpProject target)
|
||||||
=> GetFileSet(new MsBuildFileSetFactory(_logger, target.Path));
|
=> GetFileSet(new MsBuildFileSetFactory(_reporter, target.Path));
|
||||||
private async Task<IFileSet> GetFileSet(MsBuildFileSetFactory filesetFactory)
|
private async Task<IFileSet> GetFileSet(MsBuildFileSetFactory filesetFactory)
|
||||||
{
|
{
|
||||||
_tempDir.Create();
|
_tempDir.Create();
|
||||||
|
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Xunit.Abstractions;
|
|
||||||
|
|
||||||
namespace Microsoft.DotNet.Watcher.Tools.Tests
|
|
||||||
{
|
|
||||||
internal class XunitLogger : ILogger
|
|
||||||
{
|
|
||||||
private readonly ITestOutputHelper _output;
|
|
||||||
public XunitLogger(ITestOutputHelper output)
|
|
||||||
{
|
|
||||||
_output = output;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IDisposable BeginScope<TState>(TState state)
|
|
||||||
=> NullScope.Instance;
|
|
||||||
|
|
||||||
public bool IsEnabled(LogLevel logLevel) => true;
|
|
||||||
|
|
||||||
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
|
|
||||||
{
|
|
||||||
_output.WriteLine($"{logLevel}: {formatter(state, exception)}");
|
|
||||||
}
|
|
||||||
|
|
||||||
private class NullScope : IDisposable
|
|
||||||
{
|
|
||||||
private NullScope() { }
|
|
||||||
public static NullScope Instance = new NullScope();
|
|
||||||
public void Dispose() { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -7,7 +7,6 @@ using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Microsoft.Extensions.Configuration.UserSecrets;
|
using Microsoft.Extensions.Configuration.UserSecrets;
|
||||||
using Microsoft.Extensions.Configuration.UserSecrets.Tests;
|
using Microsoft.Extensions.Configuration.UserSecrets.Tests;
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Microsoft.Extensions.Tools.Internal;
|
using Microsoft.Extensions.Tools.Internal;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using Xunit.Abstractions;
|
using Xunit.Abstractions;
|
||||||
|
|
@ -16,23 +15,24 @@ namespace Microsoft.Extensions.SecretManager.Tools.Tests
|
||||||
{
|
{
|
||||||
public class SecretManagerTests : IClassFixture<UserSecretsTestFixture>
|
public class SecretManagerTests : IClassFixture<UserSecretsTestFixture>
|
||||||
{
|
{
|
||||||
private readonly TestLogger _logger;
|
|
||||||
private readonly TestConsole _console;
|
private readonly TestConsole _console;
|
||||||
private readonly UserSecretsTestFixture _fixture;
|
private readonly UserSecretsTestFixture _fixture;
|
||||||
|
private readonly StringBuilder _output = new StringBuilder();
|
||||||
|
|
||||||
public SecretManagerTests(UserSecretsTestFixture fixture, ITestOutputHelper output)
|
public SecretManagerTests(UserSecretsTestFixture fixture, ITestOutputHelper output)
|
||||||
{
|
{
|
||||||
_fixture = fixture;
|
_fixture = fixture;
|
||||||
_logger = new TestLogger(output);
|
|
||||||
_console = new TestConsole(output);
|
_console = new TestConsole(output)
|
||||||
|
{
|
||||||
|
Error = new StringWriter(_output),
|
||||||
|
Out = new StringWriter(_output),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private Program CreateProgram()
|
private Program CreateProgram()
|
||||||
{
|
{
|
||||||
return new Program(_console, Directory.GetCurrentDirectory())
|
return new Program(_console, Directory.GetCurrentDirectory());
|
||||||
{
|
|
||||||
Logger = _logger
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
|
|
@ -44,7 +44,7 @@ namespace Microsoft.Extensions.SecretManager.Tools.Tests
|
||||||
var secretManager = CreateProgram();
|
var secretManager = CreateProgram();
|
||||||
|
|
||||||
secretManager.RunInternal("list", "-p", project);
|
secretManager.RunInternal("list", "-p", project);
|
||||||
Assert.Contains(Resources.FormatError_ProjectMissingId(project), _logger.Messages);
|
Assert.Contains(Resources.FormatError_ProjectMissingId(project), _output.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -54,7 +54,7 @@ namespace Microsoft.Extensions.SecretManager.Tools.Tests
|
||||||
var secretManager = CreateProgram();
|
var secretManager = CreateProgram();
|
||||||
|
|
||||||
secretManager.RunInternal("list", "-p", project);
|
secretManager.RunInternal("list", "-p", project);
|
||||||
Assert.Contains(Resources.FormatError_ProjectFailedToLoad(project), _logger.Messages);
|
Assert.Contains(Resources.FormatError_ProjectFailedToLoad(project), _output.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -64,7 +64,7 @@ namespace Microsoft.Extensions.SecretManager.Tools.Tests
|
||||||
var secretManager = CreateProgram();
|
var secretManager = CreateProgram();
|
||||||
|
|
||||||
secretManager.RunInternal("list", "--project", projectPath);
|
secretManager.RunInternal("list", "--project", projectPath);
|
||||||
Assert.Contains(Resources.FormatError_ProjectPath_NotFound(projectPath), _logger.Messages);
|
Assert.Contains(Resources.FormatError_ProjectPath_NotFound(projectPath), _output.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -73,12 +73,11 @@ namespace Microsoft.Extensions.SecretManager.Tools.Tests
|
||||||
var projectPath = _fixture.GetTempSecretProject();
|
var projectPath = _fixture.GetTempSecretProject();
|
||||||
var cwd = Path.Combine(projectPath, "nested1");
|
var cwd = Path.Combine(projectPath, "nested1");
|
||||||
Directory.CreateDirectory(cwd);
|
Directory.CreateDirectory(cwd);
|
||||||
var secretManager = new Program(_console, cwd) { Logger = _logger, CommandOutputProvider = _logger.CommandOutputProvider };
|
var secretManager = new Program(_console, cwd);
|
||||||
secretManager.CommandOutputProvider.LogLevel = LogLevel.Debug;
|
|
||||||
|
|
||||||
secretManager.RunInternal("list", "-p", ".." + Path.DirectorySeparatorChar, "--verbose");
|
secretManager.RunInternal("list", "-p", ".." + Path.DirectorySeparatorChar, "--verbose");
|
||||||
|
|
||||||
Assert.Contains(Resources.FormatMessage_Project_File_Path(Path.Combine(cwd, "..", "TestProject.csproj")), _logger.Messages);
|
Assert.Contains(Resources.FormatMessage_Project_File_Path(Path.Combine(cwd, "..", "TestProject.csproj")), _output.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
|
|
@ -98,7 +97,7 @@ namespace Microsoft.Extensions.SecretManager.Tools.Tests
|
||||||
var dir = fromCurrentDirectory
|
var dir = fromCurrentDirectory
|
||||||
? projectPath
|
? projectPath
|
||||||
: Path.GetTempPath();
|
: Path.GetTempPath();
|
||||||
var secretManager = new Program(_console, dir) { Logger = _logger };
|
var secretManager = new Program(_console, dir);
|
||||||
|
|
||||||
foreach (var secret in secrets)
|
foreach (var secret in secrets)
|
||||||
{
|
{
|
||||||
|
|
@ -108,30 +107,27 @@ namespace Microsoft.Extensions.SecretManager.Tools.Tests
|
||||||
secretManager.RunInternal(parameters);
|
secretManager.RunInternal(parameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
Assert.Equal(4, _logger.Messages.Count);
|
|
||||||
|
|
||||||
foreach (var keyValue in secrets)
|
foreach (var keyValue in secrets)
|
||||||
{
|
{
|
||||||
Assert.Contains(
|
Assert.Contains(
|
||||||
string.Format("Successfully saved {0} = {1} to the secret store.", keyValue.Key, keyValue.Value),
|
string.Format("Successfully saved {0} = {1} to the secret store.", keyValue.Key, keyValue.Value),
|
||||||
_logger.Messages);
|
_output.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.Messages.Clear();
|
_output.Clear();
|
||||||
var args = fromCurrentDirectory
|
var args = fromCurrentDirectory
|
||||||
? new string[] { "list" }
|
? new string[] { "list" }
|
||||||
: new string[] { "list", "-p", projectPath };
|
: new string[] { "list", "-p", projectPath };
|
||||||
secretManager.RunInternal(args);
|
secretManager.RunInternal(args);
|
||||||
Assert.Equal(4, _logger.Messages.Count);
|
|
||||||
foreach (var keyValue in secrets)
|
foreach (var keyValue in secrets)
|
||||||
{
|
{
|
||||||
Assert.Contains(
|
Assert.Contains(
|
||||||
string.Format("{0} = {1}", keyValue.Key, keyValue.Value),
|
string.Format("{0} = {1}", keyValue.Key, keyValue.Value),
|
||||||
_logger.Messages);
|
_output.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove secrets.
|
// Remove secrets.
|
||||||
_logger.Messages.Clear();
|
_output.Clear();
|
||||||
foreach (var secret in secrets)
|
foreach (var secret in secrets)
|
||||||
{
|
{
|
||||||
var parameters = fromCurrentDirectory ?
|
var parameters = fromCurrentDirectory ?
|
||||||
|
|
@ -141,13 +137,12 @@ namespace Microsoft.Extensions.SecretManager.Tools.Tests
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify secrets are removed.
|
// Verify secrets are removed.
|
||||||
_logger.Messages.Clear();
|
_output.Clear();
|
||||||
args = fromCurrentDirectory
|
args = fromCurrentDirectory
|
||||||
? new string[] { "list" }
|
? new string[] { "list" }
|
||||||
: new string[] { "list", "-p", projectPath };
|
: new string[] { "list", "-p", projectPath };
|
||||||
secretManager.RunInternal(args);
|
secretManager.RunInternal(args);
|
||||||
Assert.Equal(1, _logger.Messages.Count);
|
Assert.Contains(Resources.Error_No_Secrets_Found, _output.ToString());
|
||||||
Assert.Contains(Resources.Error_No_Secrets_Found, _logger.Messages);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -157,17 +152,14 @@ namespace Microsoft.Extensions.SecretManager.Tools.Tests
|
||||||
var secretManager = CreateProgram();
|
var secretManager = CreateProgram();
|
||||||
|
|
||||||
secretManager.RunInternal("set", "secret1", "value1", "-p", projectPath);
|
secretManager.RunInternal("set", "secret1", "value1", "-p", projectPath);
|
||||||
Assert.Equal(1, _logger.Messages.Count);
|
Assert.Contains("Successfully saved secret1 = value1 to the secret store.", _output.ToString());
|
||||||
Assert.Contains("Successfully saved secret1 = value1 to the secret store.", _logger.Messages);
|
|
||||||
secretManager.RunInternal("set", "secret1", "value2", "-p", projectPath);
|
secretManager.RunInternal("set", "secret1", "value2", "-p", projectPath);
|
||||||
Assert.Equal(2, _logger.Messages.Count);
|
Assert.Contains("Successfully saved secret1 = value2 to the secret store.", _output.ToString());
|
||||||
Assert.Contains("Successfully saved secret1 = value2 to the secret store.", _logger.Messages);
|
|
||||||
|
|
||||||
_logger.Messages.Clear();
|
_output.Clear();
|
||||||
|
|
||||||
secretManager.RunInternal("list", "-p", projectPath);
|
secretManager.RunInternal("list", "-p", projectPath);
|
||||||
Assert.Equal(1, _logger.Messages.Count);
|
Assert.Contains("secret1 = value2", _output.ToString());
|
||||||
Assert.Contains("secret1 = value2", _logger.Messages);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -175,22 +167,19 @@ namespace Microsoft.Extensions.SecretManager.Tools.Tests
|
||||||
{
|
{
|
||||||
string secretId;
|
string secretId;
|
||||||
var projectPath = _fixture.GetTempSecretProject(out secretId);
|
var projectPath = _fixture.GetTempSecretProject(out secretId);
|
||||||
_logger.SetLevel(LogLevel.Debug);
|
|
||||||
var secretManager = CreateProgram();
|
var secretManager = CreateProgram();
|
||||||
|
|
||||||
secretManager.RunInternal("-v", "set", "secret1", "value1", "-p", projectPath);
|
secretManager.RunInternal("-v", "set", "secret1", "value1", "-p", projectPath);
|
||||||
Assert.Equal(3, _logger.Messages.Count);
|
Assert.Contains(string.Format("Project file path {0}.", Path.Combine(projectPath, "TestProject.csproj")), _output.ToString());
|
||||||
Assert.Contains(string.Format("Project file path {0}.", Path.Combine(projectPath, "TestProject.csproj")), _logger.Messages);
|
Assert.Contains(string.Format("Secrets file path {0}.", PathHelper.GetSecretsPathFromSecretsId(secretId)), _output.ToString());
|
||||||
Assert.Contains(string.Format("Secrets file path {0}.", PathHelper.GetSecretsPathFromSecretsId(secretId)), _logger.Messages);
|
Assert.Contains("Successfully saved secret1 = value1 to the secret store.", _output.ToString());
|
||||||
Assert.Contains("Successfully saved secret1 = value1 to the secret store.", _logger.Messages);
|
_output.Clear();
|
||||||
_logger.Messages.Clear();
|
|
||||||
|
|
||||||
secretManager.RunInternal("-v", "list", "-p", projectPath);
|
secretManager.RunInternal("-v", "list", "-p", projectPath);
|
||||||
|
|
||||||
Assert.Equal(3, _logger.Messages.Count);
|
Assert.Contains(string.Format("Project file path {0}.", Path.Combine(projectPath, "TestProject.csproj")), _output.ToString());
|
||||||
Assert.Contains(string.Format("Project file path {0}.", Path.Combine(projectPath, "TestProject.csproj")), _logger.Messages);
|
Assert.Contains(string.Format("Secrets file path {0}.", PathHelper.GetSecretsPathFromSecretsId(secretId)), _output.ToString());
|
||||||
Assert.Contains(string.Format("Secrets file path {0}.", PathHelper.GetSecretsPathFromSecretsId(secretId)), _logger.Messages);
|
Assert.Contains("secret1 = value1", _output.ToString());
|
||||||
Assert.Contains("secret1 = value1", _logger.Messages);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -199,8 +188,7 @@ namespace Microsoft.Extensions.SecretManager.Tools.Tests
|
||||||
var projectPath = _fixture.GetTempSecretProject();
|
var projectPath = _fixture.GetTempSecretProject();
|
||||||
var secretManager = CreateProgram();
|
var secretManager = CreateProgram();
|
||||||
secretManager.RunInternal("remove", "secret1", "-p", projectPath);
|
secretManager.RunInternal("remove", "secret1", "-p", projectPath);
|
||||||
Assert.Equal(1, _logger.Messages.Count);
|
Assert.Contains("Cannot find 'secret1' in the secret store.", _output.ToString());
|
||||||
Assert.Contains("Cannot find 'secret1' in the secret store.", _logger.Messages);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -210,14 +198,13 @@ namespace Microsoft.Extensions.SecretManager.Tools.Tests
|
||||||
var secretManager = CreateProgram();
|
var secretManager = CreateProgram();
|
||||||
secretManager.RunInternal("set", "SeCreT1", "value", "-p", projectPath);
|
secretManager.RunInternal("set", "SeCreT1", "value", "-p", projectPath);
|
||||||
secretManager.RunInternal("list", "-p", projectPath);
|
secretManager.RunInternal("list", "-p", projectPath);
|
||||||
Assert.Contains("SeCreT1 = value", _logger.Messages);
|
Assert.Contains("SeCreT1 = value", _output.ToString());
|
||||||
secretManager.RunInternal("remove", "secret1", "-p", projectPath);
|
secretManager.RunInternal("remove", "secret1", "-p", projectPath);
|
||||||
|
|
||||||
Assert.Equal(2, _logger.Messages.Count);
|
_output.Clear();
|
||||||
_logger.Messages.Clear();
|
|
||||||
secretManager.RunInternal("list", "-p", projectPath);
|
secretManager.RunInternal("list", "-p", projectPath);
|
||||||
|
|
||||||
Assert.Contains(Resources.Error_No_Secrets_Found, _logger.Messages);
|
Assert.Contains(Resources.Error_No_Secrets_Found, _output.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -230,23 +217,20 @@ namespace Microsoft.Extensions.SecretManager.Tools.Tests
|
||||||
File.WriteAllText(secretsFile, @"{ ""AzureAd"": { ""ClientSecret"": ""abcd郩˙î""} }", Encoding.UTF8);
|
File.WriteAllText(secretsFile, @"{ ""AzureAd"": { ""ClientSecret"": ""abcd郩˙î""} }", Encoding.UTF8);
|
||||||
var secretManager = CreateProgram();
|
var secretManager = CreateProgram();
|
||||||
secretManager.RunInternal("list", "-p", projectPath);
|
secretManager.RunInternal("list", "-p", projectPath);
|
||||||
Assert.Equal(1, _logger.Messages.Count);
|
Assert.Contains("AzureAd:ClientSecret = abcd郩˙î", _output.ToString());
|
||||||
Assert.Contains("AzureAd:ClientSecret = abcd郩˙î", _logger.Messages);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void List_Json()
|
public void List_Json()
|
||||||
{
|
{
|
||||||
var output = new StringBuilder();
|
|
||||||
_console.Out = new StringWriter(output);
|
|
||||||
string id;
|
string id;
|
||||||
var projectPath = _fixture.GetTempSecretProject(out id);
|
var projectPath = _fixture.GetTempSecretProject(out id);
|
||||||
var secretsFile = PathHelper.GetSecretsPathFromSecretsId(id);
|
var secretsFile = PathHelper.GetSecretsPathFromSecretsId(id);
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(secretsFile));
|
Directory.CreateDirectory(Path.GetDirectoryName(secretsFile));
|
||||||
File.WriteAllText(secretsFile, @"{ ""AzureAd"": { ""ClientSecret"": ""abcd郩˙î""} }", Encoding.UTF8);
|
File.WriteAllText(secretsFile, @"{ ""AzureAd"": { ""ClientSecret"": ""abcd郩˙î""} }", Encoding.UTF8);
|
||||||
var secretManager = new Program(_console, Path.GetDirectoryName(projectPath)) { Logger = _logger };
|
var secretManager = new Program(_console, Path.GetDirectoryName(projectPath));
|
||||||
secretManager.RunInternal("list", "--id", id, "--json");
|
secretManager.RunInternal("list", "--id", id, "--json");
|
||||||
var stdout = output.ToString();
|
var stdout = _output.ToString();
|
||||||
Assert.Contains("//BEGIN", stdout);
|
Assert.Contains("//BEGIN", stdout);
|
||||||
Assert.Contains(@"""AzureAd:ClientSecret"": ""abcd郩˙î""", stdout);
|
Assert.Contains(@"""AzureAd:ClientSecret"": ""abcd郩˙î""", stdout);
|
||||||
Assert.Contains("//END", stdout);
|
Assert.Contains("//END", stdout);
|
||||||
|
|
@ -262,11 +246,9 @@ namespace Microsoft.Extensions.SecretManager.Tools.Tests
|
||||||
File.WriteAllText(secretsFile, @"{ ""AzureAd"": { ""ClientSecret"": ""abcd郩˙î""} }", Encoding.UTF8);
|
File.WriteAllText(secretsFile, @"{ ""AzureAd"": { ""ClientSecret"": ""abcd郩˙î""} }", Encoding.UTF8);
|
||||||
var secretManager = CreateProgram();
|
var secretManager = CreateProgram();
|
||||||
secretManager.RunInternal("set", "AzureAd:ClientSecret", "¡™£¢∞", "-p", projectPath);
|
secretManager.RunInternal("set", "AzureAd:ClientSecret", "¡™£¢∞", "-p", projectPath);
|
||||||
Assert.Equal(1, _logger.Messages.Count);
|
|
||||||
secretManager.RunInternal("list", "-p", projectPath);
|
secretManager.RunInternal("list", "-p", projectPath);
|
||||||
|
|
||||||
Assert.Equal(2, _logger.Messages.Count);
|
Assert.Contains("AzureAd:ClientSecret = ¡™£¢∞", _output.ToString());
|
||||||
Assert.Contains("AzureAd:ClientSecret = ¡™£¢∞", _logger.Messages);
|
|
||||||
var fileContents = File.ReadAllText(secretsFile, Encoding.UTF8);
|
var fileContents = File.ReadAllText(secretsFile, Encoding.UTF8);
|
||||||
Assert.Equal(@"{
|
Assert.Equal(@"{
|
||||||
""AzureAd:ClientSecret"": ""¡™£¢∞""
|
""AzureAd:ClientSecret"": ""¡™£¢∞""
|
||||||
|
|
@ -280,8 +262,7 @@ namespace Microsoft.Extensions.SecretManager.Tools.Tests
|
||||||
var projectPath = _fixture.GetTempSecretProject();
|
var projectPath = _fixture.GetTempSecretProject();
|
||||||
var secretManager = CreateProgram();
|
var secretManager = CreateProgram();
|
||||||
secretManager.RunInternal("list", "-p", projectPath);
|
secretManager.RunInternal("list", "-p", projectPath);
|
||||||
Assert.Equal(1, _logger.Messages.Count);
|
Assert.Contains(Resources.Error_No_Secrets_Found, _output.ToString());
|
||||||
Assert.Contains(Resources.Error_No_Secrets_Found, _logger.Messages);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
|
|
@ -295,7 +276,7 @@ namespace Microsoft.Extensions.SecretManager.Tools.Tests
|
||||||
? projectPath
|
? projectPath
|
||||||
: Path.GetTempPath();
|
: Path.GetTempPath();
|
||||||
|
|
||||||
var secretManager = new Program(_console, dir) { Logger = _logger };
|
var secretManager = new Program(_console, dir);
|
||||||
|
|
||||||
var secrets = new KeyValuePair<string, string>[]
|
var secrets = new KeyValuePair<string, string>[]
|
||||||
{
|
{
|
||||||
|
|
@ -313,39 +294,34 @@ namespace Microsoft.Extensions.SecretManager.Tools.Tests
|
||||||
secretManager.RunInternal(parameters);
|
secretManager.RunInternal(parameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
Assert.Equal(4, _logger.Messages.Count);
|
|
||||||
|
|
||||||
foreach (var keyValue in secrets)
|
foreach (var keyValue in secrets)
|
||||||
{
|
{
|
||||||
Assert.Contains(
|
Assert.Contains(
|
||||||
string.Format("Successfully saved {0} = {1} to the secret store.", keyValue.Key, keyValue.Value),
|
string.Format("Successfully saved {0} = {1} to the secret store.", keyValue.Key, keyValue.Value),
|
||||||
_logger.Messages);
|
_output.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify secrets are persisted.
|
// Verify secrets are persisted.
|
||||||
_logger.Messages.Clear();
|
_output.Clear();
|
||||||
var args = fromCurrentDirectory ?
|
var args = fromCurrentDirectory ?
|
||||||
new string[] { "list" } :
|
new string[] { "list" } :
|
||||||
new string[] { "list", "-p", projectPath };
|
new string[] { "list", "-p", projectPath };
|
||||||
secretManager.RunInternal(args);
|
secretManager.RunInternal(args);
|
||||||
Assert.Equal(4, _logger.Messages.Count);
|
|
||||||
foreach (var keyValue in secrets)
|
foreach (var keyValue in secrets)
|
||||||
{
|
{
|
||||||
Assert.Contains(
|
Assert.Contains(
|
||||||
string.Format("{0} = {1}", keyValue.Key, keyValue.Value),
|
string.Format("{0} = {1}", keyValue.Key, keyValue.Value),
|
||||||
_logger.Messages);
|
_output.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear secrets.
|
// Clear secrets.
|
||||||
_logger.Messages.Clear();
|
_output.Clear();
|
||||||
args = fromCurrentDirectory ? new string[] { "clear" } : new string[] { "clear", "-p", projectPath };
|
args = fromCurrentDirectory ? new string[] { "clear" } : new string[] { "clear", "-p", projectPath };
|
||||||
secretManager.RunInternal(args);
|
secretManager.RunInternal(args);
|
||||||
Assert.Equal(0, _logger.Messages.Count);
|
|
||||||
|
|
||||||
args = fromCurrentDirectory ? new string[] { "list" } : new string[] { "list", "-p", projectPath };
|
args = fromCurrentDirectory ? new string[] { "list" } : new string[] { "list", "-p", projectPath };
|
||||||
secretManager.RunInternal(args);
|
secretManager.RunInternal(args);
|
||||||
Assert.Equal(1, _logger.Messages.Count);
|
Assert.Contains(Resources.Error_No_Secrets_Found, _output.ToString());
|
||||||
Assert.Contains(Resources.Error_No_Secrets_Found, _logger.Messages);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,11 +1,9 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Microsoft.Extensions.SecretManager.Tools.Internal;
|
using Microsoft.Extensions.SecretManager.Tools.Internal;
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Microsoft.Extensions.Tools.Internal;
|
using Microsoft.Extensions.Tools.Internal;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using Xunit.Abstractions;
|
using Xunit.Abstractions;
|
||||||
|
|
@ -36,10 +34,10 @@ namespace Microsoft.Extensions.SecretManager.Tools.Tests
|
||||||
IsInputRedirected = true,
|
IsInputRedirected = true,
|
||||||
In = new StringReader(input)
|
In = new StringReader(input)
|
||||||
};
|
};
|
||||||
var secretStore = new TestSecretsStore();
|
var secretStore = new TestSecretsStore(_output);
|
||||||
var command = new SetCommand.FromStdInStrategy();
|
var command = new SetCommand.FromStdInStrategy();
|
||||||
|
|
||||||
command.Execute(new CommandContext(secretStore, NullLogger.Instance, testConsole));
|
command.Execute(new CommandContext(secretStore, new TestReporter(_output), testConsole));
|
||||||
|
|
||||||
Assert.Equal(3, secretStore.Count);
|
Assert.Equal(3, secretStore.Count);
|
||||||
Assert.Equal("str value", secretStore["Key1"]);
|
Assert.Equal("str value", secretStore["Key1"]);
|
||||||
|
|
@ -63,10 +61,10 @@ namespace Microsoft.Extensions.SecretManager.Tools.Tests
|
||||||
IsInputRedirected = true,
|
IsInputRedirected = true,
|
||||||
In = new StringReader(input)
|
In = new StringReader(input)
|
||||||
};
|
};
|
||||||
var secretStore = new TestSecretsStore();
|
var secretStore = new TestSecretsStore(_output);
|
||||||
var command = new SetCommand.FromStdInStrategy();
|
var command = new SetCommand.FromStdInStrategy();
|
||||||
|
|
||||||
command.Execute(new CommandContext(secretStore, NullLogger.Instance, testConsole));
|
command.Execute(new CommandContext(secretStore, new TestReporter(_output), testConsole));
|
||||||
|
|
||||||
Assert.Equal(3, secretStore.Count);
|
Assert.Equal(3, secretStore.Count);
|
||||||
Assert.True(secretStore.ContainsKey("Key1:nested"));
|
Assert.True(secretStore.ContainsKey("Key1:nested"));
|
||||||
|
|
@ -89,8 +87,8 @@ namespace Microsoft.Extensions.SecretManager.Tools.Tests
|
||||||
|
|
||||||
private class TestSecretsStore : SecretsStore
|
private class TestSecretsStore : SecretsStore
|
||||||
{
|
{
|
||||||
public TestSecretsStore()
|
public TestSecretsStore(ITestOutputHelper output)
|
||||||
: base("xyz", NullLogger.Instance)
|
: base("xyz", new TestReporter(output))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -105,25 +103,4 @@ namespace Microsoft.Extensions.SecretManager.Tools.Tests
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class NullLogger : ILogger
|
|
||||||
{
|
|
||||||
public static NullLogger Instance = new NullLogger();
|
|
||||||
|
|
||||||
private class NullScope : IDisposable
|
|
||||||
{
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public IDisposable BeginScope<TState>(TState state)
|
|
||||||
=> new NullScope();
|
|
||||||
|
|
||||||
public bool IsEnabled(LogLevel logLevel)
|
|
||||||
=> true;
|
|
||||||
|
|
||||||
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Xunit.Abstractions;
|
|
||||||
|
|
||||||
namespace Microsoft.Extensions.SecretManager.Tools.Tests
|
|
||||||
{
|
|
||||||
public class TestLogger : ILogger
|
|
||||||
{
|
|
||||||
private readonly ILogger _wrapped;
|
|
||||||
private readonly ITestOutputHelper _output;
|
|
||||||
|
|
||||||
public TestLogger(ITestOutputHelper output = null)
|
|
||||||
{
|
|
||||||
CommandOutputProvider = new CommandOutputProvider();
|
|
||||||
_wrapped = CommandOutputProvider.CreateLogger("");
|
|
||||||
_output = output;
|
|
||||||
}
|
|
||||||
public CommandOutputProvider CommandOutputProvider { get;}
|
|
||||||
|
|
||||||
public void SetLevel(LogLevel level)
|
|
||||||
{
|
|
||||||
CommandOutputProvider.LogLevel = level;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<string> Messages { get; set; } = new List<string>();
|
|
||||||
|
|
||||||
public IDisposable BeginScope<TState>(TState state)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsEnabled(LogLevel logLevel)
|
|
||||||
{
|
|
||||||
return _wrapped.IsEnabled(logLevel);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
|
|
||||||
{
|
|
||||||
if (IsEnabled(logLevel))
|
|
||||||
{
|
|
||||||
Messages.Add(formatter(state, exception));
|
|
||||||
_output?.WriteLine(formatter(state, exception));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
using Microsoft.Extensions.Tools.Internal;
|
using Microsoft.Extensions.Tools.Internal;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.DotNet.Watcher.Tools.Tests
|
namespace Microsoft.Extensions.Tools.Tests
|
||||||
{
|
{
|
||||||
public class ArgumentEscaperTests
|
public class ArgumentEscaperTests
|
||||||
{
|
{
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<PropertyGroup>
|
||||||
|
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
|
||||||
|
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
|
||||||
|
<PropertyGroup Label="Globals">
|
||||||
|
<ProjectGuid>{A24BF1D1-4326-4455-A528-09F1E20EDC83}</ProjectGuid>
|
||||||
|
<ProjectTypeGuids>{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}</ProjectTypeGuids>
|
||||||
|
<RootNamespace>Microsoft.Extensions.Tools.Tests</RootNamespace>
|
||||||
|
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
|
||||||
|
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
|
||||||
|
<TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<SchemaVersion>2.0</SchemaVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||||
|
</Project>
|
||||||
|
|
@ -0,0 +1,103 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
using Microsoft.Extensions.Tools.Internal;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.Extensions.Tools.Tests
|
||||||
|
{
|
||||||
|
public class ReporterTests
|
||||||
|
{
|
||||||
|
private static readonly string EOL = Environment.NewLine;
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(ConsoleColor.DarkGray, "\x1B[90m")]
|
||||||
|
[InlineData(ConsoleColor.Red, "\x1B[91m")]
|
||||||
|
[InlineData(ConsoleColor.Yellow, "\x1B[93m")]
|
||||||
|
public void WrapsWithAnsiColorCode(ConsoleColor color, string code)
|
||||||
|
{
|
||||||
|
Assert.Equal($"{code}sample\x1B[39m", new ColorFormatter(color).Format("sample"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SkipsColorCodesForEmptyOrNullInput()
|
||||||
|
{
|
||||||
|
var formatter = new ColorFormatter(ConsoleColor.Blue);
|
||||||
|
Assert.Empty(formatter.Format(string.Empty));
|
||||||
|
Assert.Null(formatter.Format(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void WritesToStandardStreams()
|
||||||
|
{
|
||||||
|
var testConsole = new TestConsole();
|
||||||
|
var reporter = new FormattingReporter(testConsole,
|
||||||
|
DefaultFormatter.Instance, DefaultFormatter.Instance,
|
||||||
|
DefaultFormatter.Instance, DefaultFormatter.Instance);
|
||||||
|
|
||||||
|
// stdout
|
||||||
|
reporter.Verbose("verbose");
|
||||||
|
Assert.Equal("verbose" + EOL, testConsole.GetOutput());
|
||||||
|
testConsole.Clear();
|
||||||
|
|
||||||
|
reporter.Output("out");
|
||||||
|
Assert.Equal("out" + EOL, testConsole.GetOutput());
|
||||||
|
testConsole.Clear();
|
||||||
|
|
||||||
|
reporter.Warn("warn");
|
||||||
|
Assert.Equal("warn" + EOL, testConsole.GetOutput());
|
||||||
|
testConsole.Clear();
|
||||||
|
|
||||||
|
// stderr
|
||||||
|
reporter.Error("error");
|
||||||
|
Assert.Equal("error" + EOL, testConsole.GetError());
|
||||||
|
testConsole.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void FailsToBuildWithoutConsole()
|
||||||
|
{
|
||||||
|
Assert.Throws<InvalidOperationException>(
|
||||||
|
() => new ReporterBuilder().Build());
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestConsole : IConsole
|
||||||
|
{
|
||||||
|
private readonly StringBuilder _out;
|
||||||
|
private readonly StringBuilder _error;
|
||||||
|
|
||||||
|
public TestConsole()
|
||||||
|
{
|
||||||
|
_out = new StringBuilder();
|
||||||
|
_error = new StringBuilder();
|
||||||
|
Out = new StringWriter(_out);
|
||||||
|
Error = new StringWriter(_error);
|
||||||
|
}
|
||||||
|
|
||||||
|
event ConsoleCancelEventHandler IConsole.CancelKeyPress
|
||||||
|
{
|
||||||
|
add { }
|
||||||
|
remove { }
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetOutput() => _out.ToString();
|
||||||
|
public string GetError() => _error.ToString();
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
_out.Clear();
|
||||||
|
_error.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public TextWriter Out { get; }
|
||||||
|
public TextWriter Error { get; }
|
||||||
|
public TextReader In { get; }
|
||||||
|
public bool IsInputRedirected { get; }
|
||||||
|
public bool IsOutputRedirected { get; }
|
||||||
|
public bool IsErrorRedirected { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
{
|
||||||
|
"buildOptions": {
|
||||||
|
"warningsAsErrors": true,
|
||||||
|
"keyFile": "../../tools/Key.snk",
|
||||||
|
"compile": "../../src/Shared/**/*.cs"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"dotnet-test-xunit": "2.2.0-preview2-build1029",
|
||||||
|
"Microsoft.DotNet.InternalAbstractions": "1.0.500-preview2-1-003177",
|
||||||
|
"xunit": "2.2.0-beta3-build3402"
|
||||||
|
},
|
||||||
|
"testRunner": "xunit",
|
||||||
|
"frameworks": {
|
||||||
|
"netcoreapp1.0": {
|
||||||
|
"dependencies": {
|
||||||
|
"Microsoft.NETCore.App": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"type": "platform"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -25,6 +25,8 @@ namespace Microsoft.Extensions.Tools.Internal
|
||||||
public TextWriter Out { get; set; }
|
public TextWriter Out { get; set; }
|
||||||
public TextReader In { get; set; } = new StringReader(string.Empty);
|
public TextReader In { get; set; } = new StringReader(string.Empty);
|
||||||
public bool IsInputRedirected { get; set; } = false;
|
public bool IsInputRedirected { get; set; } = false;
|
||||||
|
public bool IsOutputRedirected { get; } = false;
|
||||||
|
public bool IsErrorRedirected { get; } = false;
|
||||||
|
|
||||||
public ConsoleCancelEventArgs ConsoleCancelKey()
|
public ConsoleCancelEventArgs ConsoleCancelKey()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Xunit.Abstractions;
|
||||||
|
|
||||||
|
namespace Microsoft.Extensions.Tools.Internal
|
||||||
|
{
|
||||||
|
public class TestReporter : IReporter
|
||||||
|
{
|
||||||
|
private readonly ITestOutputHelper _output;
|
||||||
|
|
||||||
|
public TestReporter(ITestOutputHelper output)
|
||||||
|
{
|
||||||
|
_output = output;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Verbose(string message)
|
||||||
|
{
|
||||||
|
_output.WriteLine("verbose: " + message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Output(string message)
|
||||||
|
{
|
||||||
|
_output.WriteLine("output: " + message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Warn(string message)
|
||||||
|
{
|
||||||
|
_output.WriteLine("warn: " + message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Error(string message)
|
||||||
|
{
|
||||||
|
_output.WriteLine("error: " + message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue