Skip to content

Commit

Permalink
Adds support for showing in-product announcements (#895)
Browse files Browse the repository at this point in the history
  • Loading branch information
waldekmastykarz authored Sep 26, 2024
1 parent 7910f51 commit c07b90a
Show file tree
Hide file tree
Showing 2 changed files with 149 additions and 116 deletions.
31 changes: 31 additions & 0 deletions dev-proxy/Announcement.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

namespace Microsoft.DevProxy;

static class Announcement
{
private static readonly string announcementUrl = "https://aka.ms/devproxy/announcement";

public static async Task ShowAsync()
{
var announcement = await GetAsync();
if (!string.IsNullOrEmpty(announcement))
{
await Console.Error.WriteLineAsync(announcement);
}
}

public static async Task<string?> GetAsync()
{
try
{
using var client = new HttpClient();
return await client.GetStringAsync(announcementUrl);
}
catch
{
return null;
}
}
}
234 changes: 118 additions & 116 deletions dev-proxy/Program.cs
Original file line number Diff line number Diff line change
@@ -1,116 +1,118 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using Microsoft.DevProxy;
using Microsoft.DevProxy.Abstractions;
using Microsoft.DevProxy.Abstractions.LanguageModel;
using Microsoft.DevProxy.CommandHandlers;
using Microsoft.DevProxy.Logging;
using Microsoft.Extensions.Logging.Console;
using System.CommandLine;

PluginEvents pluginEvents = new();

ILogger BuildLogger()
{
var loggerFactory = LoggerFactory.Create(builder =>
{
builder
.AddConsole(options =>
{
options.FormatterName = "devproxy";
options.LogToStandardErrorThreshold = LogLevel.Warning;
})
.AddConsoleFormatter<ProxyConsoleFormatter, ConsoleFormatterOptions>(options => {
options.IncludeScopes = true;
})
.AddRequestLogger(pluginEvents)
.SetMinimumLevel(ProxyHost.LogLevel ?? ProxyCommandHandler.Configuration.LogLevel);
});
return loggerFactory.CreateLogger("devproxy");
}

var logger = BuildLogger();

var lmClient = new OllamaLanguageModelClient(ProxyCommandHandler.Configuration.LanguageModel, logger);
IProxyContext context = new ProxyContext(ProxyCommandHandler.Configuration, ProxyEngine.Certificate, lmClient);
ProxyHost proxyHost = new();

// this is where the root command is created which contains all commands and subcommands
RootCommand rootCommand = proxyHost.GetRootCommand(logger);

// store the global options that are created automatically for us
// rootCommand doesn't return the global options, so we have to store them manually
string[] globalOptions = ["--version", "--help", "-h", "/h", "-?", "/?"];

// check if any of the global options are present
var hasGlobalOption = args.Any(arg => globalOptions.Contains(arg));

// get the list of available subcommands
var subCommands = rootCommand.Children.OfType<Command>().Select(c => c.Name).ToArray();

// check if any of the subcommands are present
var hasSubCommand = args.Any(arg => subCommands.Contains(arg));

if (hasGlobalOption || hasSubCommand)
{
// we don't need to load plugins if the user is using a global option or using a subcommand, so we can exit early
await rootCommand.InvokeAsync(args);
return;
}

var pluginLoader = new PluginLoader(logger);
PluginLoaderResult loaderResults = await pluginLoader.LoadPluginsAsync(pluginEvents, context);
// have all the plugins init
pluginEvents.RaiseInit(new InitArgs());

var options = loaderResults.ProxyPlugins
.SelectMany(p => p.GetOptions())
// remove duplicates by comparing the option names
.GroupBy(o => o.Name)
.Select(g => g.First())
.ToList();
options.ForEach(rootCommand.AddOption);
// register all plugin commands
loaderResults.ProxyPlugins
.SelectMany(p => p.GetCommands())
.ToList()
.ForEach(rootCommand.AddCommand);

rootCommand.Handler = proxyHost.GetCommandHandler(pluginEvents, [.. options], loaderResults.UrlsToWatch, logger);

// filter args to retrieve options
var incomingOptions = args.Where(arg => arg.StartsWith('-')).ToArray();

// remove the global options from the incoming options
incomingOptions = incomingOptions.Except(globalOptions).ToArray();

// compare the incoming options against the root command options
foreach (var option in rootCommand.Options)
{
// get the option aliases
var aliases = option.Aliases.ToArray();

// iterate over aliases
foreach (string alias in aliases)
{
// if the alias is present
if (incomingOptions.Contains(alias))
{
// remove the option from the incoming options
incomingOptions = incomingOptions.Where(val => val != alias).ToArray();
}
}
}

// list the remaining incoming options as unknown in the output
if (incomingOptions.Length > 0)
{
logger.LogError("Unknown option(s): {unknownOptions}", string.Join(" ", incomingOptions));
logger.LogInformation("TIP: Use --help view available options");
logger.LogInformation("TIP: Are you missing a plugin? See: https://aka.ms/devproxy/plugins");
}
else
{
await rootCommand.InvokeAsync(args);
}
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using Microsoft.DevProxy;
using Microsoft.DevProxy.Abstractions;
using Microsoft.DevProxy.Abstractions.LanguageModel;
using Microsoft.DevProxy.CommandHandlers;
using Microsoft.DevProxy.Logging;
using Microsoft.Extensions.Logging.Console;
using System.CommandLine;

_ = Announcement.ShowAsync();

PluginEvents pluginEvents = new();

ILogger BuildLogger()
{
var loggerFactory = LoggerFactory.Create(builder =>
{
builder
.AddConsole(options =>
{
options.FormatterName = "devproxy";
options.LogToStandardErrorThreshold = LogLevel.Warning;
})
.AddConsoleFormatter<ProxyConsoleFormatter, ConsoleFormatterOptions>(options => {
options.IncludeScopes = true;
})
.AddRequestLogger(pluginEvents)
.SetMinimumLevel(ProxyHost.LogLevel ?? ProxyCommandHandler.Configuration.LogLevel);
});
return loggerFactory.CreateLogger("devproxy");
}

var logger = BuildLogger();

var lmClient = new OllamaLanguageModelClient(ProxyCommandHandler.Configuration.LanguageModel, logger);
IProxyContext context = new ProxyContext(ProxyCommandHandler.Configuration, ProxyEngine.Certificate, lmClient);
ProxyHost proxyHost = new();

// this is where the root command is created which contains all commands and subcommands
RootCommand rootCommand = proxyHost.GetRootCommand(logger);

// store the global options that are created automatically for us
// rootCommand doesn't return the global options, so we have to store them manually
string[] globalOptions = ["--version", "--help", "-h", "/h", "-?", "/?"];

// check if any of the global options are present
var hasGlobalOption = args.Any(arg => globalOptions.Contains(arg));

// get the list of available subcommands
var subCommands = rootCommand.Children.OfType<Command>().Select(c => c.Name).ToArray();

// check if any of the subcommands are present
var hasSubCommand = args.Any(arg => subCommands.Contains(arg));

if (hasGlobalOption || hasSubCommand)
{
// we don't need to load plugins if the user is using a global option or using a subcommand, so we can exit early
await rootCommand.InvokeAsync(args);
return;
}

var pluginLoader = new PluginLoader(logger);
PluginLoaderResult loaderResults = await pluginLoader.LoadPluginsAsync(pluginEvents, context);
// have all the plugins init
pluginEvents.RaiseInit(new InitArgs());

var options = loaderResults.ProxyPlugins
.SelectMany(p => p.GetOptions())
// remove duplicates by comparing the option names
.GroupBy(o => o.Name)
.Select(g => g.First())
.ToList();
options.ForEach(rootCommand.AddOption);
// register all plugin commands
loaderResults.ProxyPlugins
.SelectMany(p => p.GetCommands())
.ToList()
.ForEach(rootCommand.AddCommand);

rootCommand.Handler = proxyHost.GetCommandHandler(pluginEvents, [.. options], loaderResults.UrlsToWatch, logger);

// filter args to retrieve options
var incomingOptions = args.Where(arg => arg.StartsWith('-')).ToArray();

// remove the global options from the incoming options
incomingOptions = incomingOptions.Except(globalOptions).ToArray();

// compare the incoming options against the root command options
foreach (var option in rootCommand.Options)
{
// get the option aliases
var aliases = option.Aliases.ToArray();

// iterate over aliases
foreach (string alias in aliases)
{
// if the alias is present
if (incomingOptions.Contains(alias))
{
// remove the option from the incoming options
incomingOptions = incomingOptions.Where(val => val != alias).ToArray();
}
}
}

// list the remaining incoming options as unknown in the output
if (incomingOptions.Length > 0)
{
logger.LogError("Unknown option(s): {unknownOptions}", string.Join(" ", incomingOptions));
logger.LogInformation("TIP: Use --help view available options");
logger.LogInformation("TIP: Are you missing a plugin? See: https://aka.ms/devproxy/plugins");
}
else
{
await rootCommand.InvokeAsync(args);
}

0 comments on commit c07b90a

Please sign in to comment.