Skip to content
Permalink
master
Switch branches/tags
Go to file
 
 
Cannot retrieve contributors at this time
using System;
using System.Collections.Concurrent;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net;
using System.Net.Http;
using System.Runtime.InteropServices;
using System.Runtime.Loader;
using System.Runtime.Serialization;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Funq;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.CodeAnalysis;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using NUglify;
using ServiceStack;
using ServiceStack.IO;
using ServiceStack.Auth;
using ServiceStack.Text;
using ServiceStack.Data;
using ServiceStack.Redis;
using ServiceStack.OrmLite;
using ServiceStack.Configuration;
using ServiceStack.Azure.Storage;
using ServiceStack.Desktop;
using ServiceStack.Html;
using ServiceStack.Logging;
using ServiceStack.NativeTypes.CSharp;
using ServiceStack.Pcl;
using ServiceStack.Script;
namespace Web
{
public class WebAppContext
{
public string Tool { get; set; }
public string[] Arguments { get; set; }
public string WebSettingsPath { get; set; }
public string StartUrl { get; set; }
public string UseUrls { get; set; }
public string IconPath { get; set; }
public string AppDir { get; set; }
public string ToolPath { get; set; }
public string FavIcon { get; set; }
public string RunProcess { get; set; }
public bool DebugMode { get; set; }
public IWebHostBuilder Builder { get; set; }
public IAppSettings AppSettings { get; set; }
public IWebHost Build()
{
AppLoader.Init(AppDir);
return Builder.Build();
}
public string GetDebugString() => new Dictionary<string, object>
{
[nameof(Tool)] = Tool,
[nameof(Arguments)] = Arguments,
[nameof(WebSettingsPath)] = WebSettingsPath,
[nameof(StartUrl)] = StartUrl,
[nameof(UseUrls)] = UseUrls,
[nameof(IconPath)] = IconPath,
[nameof(AppDir)] = AppDir,
[nameof(ToolPath)] = ToolPath,
[nameof(FavIcon)] = FavIcon,
[nameof(RunProcess)] = RunProcess,
[nameof(DebugMode)] = DebugMode,
}.Dump();
}
public delegate void CreateShortcutDelegate(string fileName, string targetPath, string arguments, string workingDirectory, string iconPath);
public class WebAppEvents
{
public CreateShortcutDelegate CreateShortcut { get; set; }
public Action<string> OpenBrowser { get; set; }
public Action<WebAppContext> HandleUnknownCommand { get; set; }
public Action<WebAppContext> RunNetCoreProcess { get; set; }
}
public partial class Startup : ModularStartup
{
public static WebAppEvents Events { get; set; }
public static Func<IAppHost, AppHostInstructions> GetAppHostInstructions { get; set; }
public static string GitHubSource { get; set; } = "sharp-apps Sharp Apps";
public static string GitHubSourceTemplates { get; set; } = "NetCoreTemplates .NET Core C# Templates;NetFrameworkTemplates .NET Framework C# Templates;NetFrameworkCoreTemplates ASP.NET Core Framework Templates";
public static string GistAppsId { get; set; } = "802daba52b6fe6e2ed1430348dc596cb";
public static string GrpcSource { get; set; } = "https://grpc.servicestack.net";
public static List<GistLink> GetGistAppsLinks() => GetGistLinks(GistAppsId, "apps.md");
public static string GetAppsPath(string gistAlias)
{
var homeDir = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
return Path.Combine(homeDir, ".sharp-apps", gistAlias);
}
public static string GetGistsAppPath(string gistDir)
{
var homeDir = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
return Path.Combine(homeDir, "apps", gistDir);
}
public static bool? DebugMode { get; set; }
public static string[] DebugArgs = CreateArgs("debug", withFlag:'d');
public static string[] ReleaseArgs = CreateArgs("release", withFlag:'c');
public static string[] DescriptionArgs = CreateArgs("desc");
public static string[] IncludeArgs = CreateArgs("include");
public static string[] TargetArgs = CreateArgs("target");
public static string Target { get; set; }
public static string[] ArgumentsArgs = CreateArgs("arguments");
public static string Arguments { get; set; }
public static string[] WorkDirArgs = CreateArgs("workdir");
public static string WorkDir { get; set; }
public static string Description { get; set; }
static string[] TokenArgs = CreateArgs("token");
static string[] PathArgs = CreateArgs("path");
public static string[] EvalArgs = CreateArgs("eval", withFlag:'e');
public static string[] LangArgs = CreateArgs("lang");
public static string Lang { get; set; }
public static string[] Includes = { };
public static string PathArg { get; set; }
public static string RunScript { get; set; }
public static bool WatchScript { get; set; }
public static string EvalScript { get; set; }
public static bool GistNew { get; set; }
public static bool GistUpdate { get; set; }
public static bool GistOpen { get; set; }
public static Dictionary<string, object> RunScriptArgs = new();
public static List<string> RunScriptArgV = new();
public static bool Open { get; set; }
public static string ToolFavIcon = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!, "favicon.ico");
public static GistVirtualFiles GistVfs;
public static Task<Gist> GistVfsTask;
public static Task GistVfsLoadTask;
public static async Task<WebAppContext> CreateWebHost(string tool, string[] args, WebAppEvents events = null)
{
Events = events;
if (args.Length > 0 && args[0] == "mix")
{
await Mix($"{tool} mix", args.Skip(1).ToArray());
return null;
}
var dotnetArgs = new List<string>();
if (!string.IsNullOrEmpty("APP_SOURCE".ToolSetting()))
GitHubSource = "APP_SOURCE".ToolSetting();
if (!string.IsNullOrEmpty("APP_SOURCE_TEMPLATES".ToolSetting()))
GitHubSourceTemplates = "APP_SOURCE_TEMPLATES".ToolSetting();
if (!string.IsNullOrEmpty("APP_SOURCE_GISTS".ToolSetting()))
GistLinksId = "APP_SOURCE_GISTS".ToolSetting();
if (!string.IsNullOrEmpty("APP_SOURCE_APPS".ToolSetting()))
GistAppsId = "APP_SOURCE_APPS".ToolSetting();
if (!string.IsNullOrEmpty("GITHUB_GIST_TOKEN".ToolSetting()))
GitHubToken = "GITHUB_GIST_TOKEN".ToolSetting();
if (!string.IsNullOrEmpty("GITHUB_TOKEN".ToolSetting()))
GitHubToken = "GITHUB_TOKEN".ToolSetting();
if (!string.IsNullOrEmpty("APP_SOURCE_GRPC".ToolSetting()))
GrpcSource = "APP_SOURCE_GRPC".ToolSetting();
InitMix();
var createShortcut = false;
var publish = false;
var copySelf = false;
string copySelfTo = ".";
string createShortcutFor = null;
string runProcess = null;
var runSharpApp = false;
var runLispRepl = false;
var appSettingPaths = new[]
{
"app.settings", "../app/app.settings", "app/app.settings",
};
for (var i = 0; i < args.Length; i++)
{
var arg = args[i];
if (arg.EndsWith(".settings"))
{
appSettingPaths = new[] { arg };
continue;
}
if (arg.EndsWith(".dll") || arg.EndsWith(".exe"))
{
if (Events?.RunNetCoreProcess == null)
throw new NotSupportedException($"This {tool} tool does not support running processes");
runProcess = arg;
continue;
}
if (ProcessFlags(args, arg, ref i))
continue;
if (arg == "shortcut")
{
createShortcut = true;
if (i + 1 < args.Length && (args[i + 1].EndsWith(".dll") || args[i + 1].EndsWith(".exe")))
createShortcutFor = args[++i];
continue;
}
if (arg == "lisp")
{
runLispRepl = runSharpApp = true;
continue;
}
if (EvalArgs.Contains(arg))
{
runSharpApp = true;
if (i + 1 >= args.Length)
{
$"Usage: {tool} -e \"<expression>\"".Print();
return null;
}
EvalScript = args[++i];
if (EvalScript.StartsWith('"') && EvalScript.EndsWith('"'))
EvalScript = (string) JSON.parse(EvalScript);
continue;
}
if (arg == "gist-open" || arg == "open-gist")
{
GistOpen = true;
continue;
}
if (arg == "gist-new")
{
GistNew = true;
continue;
}
if (arg == "gist-update")
{
GistUpdate = true;
continue;
}
if (arg == "publish" || arg == ".publish")
{
publish = true;
continue;
}
if (arg == "--copy-self")
{
copySelf = true;
if (i + 1 < args.Length && !(args[i + 1].StartsWith("-") || args[i + 1].StartsWith("/")))
copySelfTo = args[++i];
var fromDir = Path.GetDirectoryName(typeof(Startup).Assembly.Location);
fromDir.CopyAllTo(copySelfTo);
return null;
}
if (arg == "mix" || arg == "-mix")
{
if (!await Mix($"{tool} mix", new[] {args[++i]}))
return null;
continue;
}
if (arg == "run" || arg == "watch")
{
if (i + 1 >= args.Length)
{
runSharpApp = true;
continue;
}
var script = args[i + 1];
if (script.EndsWith(".settings"))
{
runSharpApp = true;
appSettingPaths = new[] { script };
i++;
continue;
}
if (!(script.EndsWith(".html") || script.EndsWith(".ss") || script.EndsWith(".sc") || script.EndsWith(".l")))
{
// Run SharpApp
string appsDir = GetAppsPath(script);
if (Directory.Exists(appsDir))
{
RetryExec(() => Directory.SetCurrentDirectory(appsDir));
runSharpApp = true;
// Run Gist SharpApp
var gistFile = appsDir + ".gist";
if (File.Exists(gistFile))
{
if (Verbose) $"Loading GistVirtualFiles from: {gistFile}".Print();
var gistJson = await File.ReadAllTextAsync(gistFile);
var gist = gistJson.FromJson<Gist>();
GistVfs = new GistVirtualFiles(gist);
GistVfsTask = GistVfs.GetGistAsync(); // fire to load asynchronously
}
i++;
continue;
}
throw new ArgumentException(script.IndexOf('.', StringComparison.Ordinal) >= 0
? "Only .ss. .sc. .l or .html scripts can be run"
: $"No '{script}' App installed");
}
RunScript = script;
WatchScript = arg == "watch";
i += 2; //'run' 'script.ss'
for (; i < args.Length; i++)
{
var key = args[i];
if (key == "mix" || key == "-mix")
{
if (++i >= args.Length)
throw new Exception($"Usage: {tool} run <name> mix <gist>");
ForceApproval = Silent = true;
if (!await Mix($"{tool} mix", new[] { args[i] }))
return null;
continue;
}
if (ProcessFlags(args, key, ref i))
continue;
RunScriptArgV.Add(key);
if (!(key.FirstCharEquals('-') || key.FirstCharEquals('/')))
continue;
var hasArgValue = i + 1 < args.Length;
RunScriptArgs[key.Substring(1)] = hasArgValue ? args[i + 1] : null;
if (hasArgValue) RunScriptArgV.Add(args[i++ + 1]);
}
continue;
}
if (arg == "open")
{
if (i + 1 >= args.Length)
{
PrintGistLinks(tool, GetGistAppsLinks(), usage:$"Usage: {tool} open <name>");
return null;
}
var target = args[i+1];
for (var j=i+2; j<args.Length; j++)
ProcessFlags(args, args[j], ref j);
RegisterStat(tool, target, "open");
var isGitHubUrl = target.StartsWith("https://gist.github.com/") ||
target.StartsWith("https://github.com/");
if (!isGitHubUrl && !target.IsUrl() && target.IndexOf('/') >= 0)
{
target = "https://github.com/" + target;
isGitHubUrl = true;
}
var gistLinks = !isGitHubUrl ? GetGistAppsLinks() : null;
var gistLink = GetGistAliasLink(target) ?? gistLinks?.FirstOrDefault(x => x.Name == target);
if (!InstallGistApp(tool, target, gistLink, gistLinks, out var appsDir))
return null;
runSharpApp = true;
Open = true;
i += 2; //'open' 'target'
for (; i < args.Length; i++)
{
var key = args[i];
if (key == "mix" || key == "-mix")
{
if (++i >= args.Length)
throw new Exception($"Usage: {tool} open <name> mix <gist>");
RetryExec(() => Directory.SetCurrentDirectory(appsDir));
ForceApproval = Silent = true;
if (!await Mix($"{tool} mix", new[] { args[i] }))
return null;
continue;
}
if (ProcessFlags(args, key, ref i))
continue;
RunScriptArgV.Add(key);
if (!(key.FirstCharEquals('-') || key.FirstCharEquals('/')))
continue;
var hasArgValue = i + 1 < args.Length;
RunScriptArgs[key.Substring(1)] = hasArgValue ? args[i + 1] : null;
if (hasArgValue) RunScriptArgV.Add(args[i++ + 1]);
}
continue;
}
if (arg == "install" || arg == "i")
{
var gistLinks = GetGistAppsLinks();
if (i + 1 >= args.Length)
{
PrintGistLinks(tool, gistLinks, usage:$"Usage: {tool} open <name>");
return null;
}
var target = args[i+1];
for (var j=i+2; j<args.Length; j++)
ProcessFlags(args, args[j], ref j);
RegisterStat(tool, target, "install");
var isGitHubUrl = target.StartsWith("https://gist.github.com/") ||
target.StartsWith("https://github.com/");
if (!isGitHubUrl && !target.IsUrl() && target.IndexOf('/') >= 0)
{
target = "https://github.com/" + target;
isGitHubUrl = true;
}
if (isGitHubUrl)
{
InstallGistApp(tool, target, null, null, out var appsDir);
return null;
}
var gistLink = GetGistAliasLink(target) ?? gistLinks.FirstOrDefault(x => x.Name == target);
if (gistLink == null)
{
$"No match found for '{target}', available Apps:".Print();
PrintGistLinks(tool, gistLinks, usage:$"Usage: {tool} open <name>");
return null;
}
if (gistLink.GistId != null)
{
if (!InstallGistApp(tool, target, gistLink, gistLinks, out var appsDir))
return null;
var gist = await GistVfsTask;
GistVfsLoadTask = GistVfs.LoadAllTruncatedFilesAsync();
await GistVfsLoadTask;
SerializeGistAppFiles();
$"Gist App Installed, run with:".Print();
$" {tool} run {target}".Print();
return null;
}
if (gistLink.Repo != null)
{
InstallRepo(gistLink.Url.EndsWith(".zip")
? gistLink.Url
: await GitHubUtils.Gateway.GetSourceZipUrlAsync(gistLink.User, gistLink.Repo),
target);
}
"".Print();
$"Installation successful, run with:".Print();
"".Print();
$" {tool} run {target}".Print();
return null;
}
if (arg == "uninstall")
{
if (i + 1 >= args.Length)
{
PrintAppUsage(tool, arg);
return null;
}
var target = args[i + 1];
for (var j=i+2; j<args.Length; j++)
ProcessFlags(args, args[j], ref j);
var installDir = GetAppsPath(target);
var gistFile = installDir + ".gist";
if (!Directory.Exists(installDir) && !File.Exists(gistFile))
{
"".Print();
$"App '{target}' is not installed.".Print();
PrintAppUsage(tool, arg);
return null;
}
if (Directory.Exists(installDir))
DeleteDirectory(installDir);
if (File.Exists(gistFile))
DeleteFile(gistFile);
"".Print();
$"App '{target}' was uninstalled.".Print();
return null;
}
if (arg == "proto-langs")
{
var client = new JsonServiceClient(GrpcSource);
var response = client.Get(new GetLanguages());
"".Print();
$"gRPC Supported Languages:".Print();
"".Print();
var maxKeyLen = response.Results.Max(x => x.Key.Length);
foreach (var kvp in response.Results)
{
$" {kvp.Key.PadRight(maxKeyLen)} {kvp.Value}".Print();
}
"".Print();
"Usage:".Print();
$"{tool} proto-<lang> <url> Add gRPC .proto and generate language".Print();
"".Print();
$"{tool} proto-<lang> <file|dir> Update gRPC .proto and re-gen language".Print();
$"{tool} proto-<lang> Update all gRPC .proto's and re-gen lang".Print();
"".Print();
"Options:".Print();
" --out <dir> Save generated gRPC language sources to <dir>".Print();
return null;
}
if (arg == "alias")
{
var settings = GetGistAliases();
if (i + 1 >= args.Length)
{
var keys = settings.GetAllKeys();
if (keys.Count == 0)
{
"No gist aliases have been defined.".Print();
$"Usage: {tool} alias <alias> <gist-id>".Print();
}
else
{
foreach (var key in keys)
{
var gistId = settings.GetRequiredString(key);
$"{key} {gistId}".Print();
}
}
return null;
}
var target = args[i + 1];
if (i + 2 >= args.Length)
{
settings.GetRequiredString(target).Print();
}
else
{
var gistId = args[i + 2];
if (gistId.StartsWith("https://gist.github.com/"))
gistId = gistId.ToGistId();
if (gistId.Length != 20 && gistId.Length != 32)
{
$"'{args[i + 2]}' is not a valid gist id or URL".Print();
return null;
}
var aliasPath = GetGistAliasesFilePath();
if (settings.Exists(target))
{
var newAliases = new StringBuilder();
foreach (var line in await File.ReadAllLinesAsync(aliasPath))
{
newAliases.AppendLine(line.StartsWith(target + " ") ? $"{target} {gistId}" : line);
}
await File.WriteAllTextAsync(aliasPath, newAliases.ToString());
}
else
{
using var fs = File.AppendText(aliasPath);
await fs.WriteLineAsync($"{target} {gistId}");
}
}
return null;
}
if (arg == "unalias")
{
if (i + 1 >= args.Length)
{
var settings = GetGistAliases();
var keys = settings.GetAllKeys();
if (keys.Count == 0)
{
"No gist aliases have been defined.".Print();
}
else
{
foreach (var key in keys)
{
var gistId = settings.GetRequiredString(key);
$"{key} {gistId}".Print();
}
}
return null;
}
var removeAliases = new List<string>();
for (var j = i + i; j < args.Length; j++)
{
removeAliases.Add(args[j]);
}
var aliasPath = GetGistAliasesFilePath();
var newAliases = new StringBuilder();
foreach (var line in await File.ReadAllLinesAsync(aliasPath))
{
if (removeAliases.Any(x => line.StartsWith(x + " ")))
continue;
newAliases.AppendLine(line);
}
await File.WriteAllTextAsync(aliasPath, newAliases.ToString());
return null;
}
if (arg == "scripts" || arg == "s")
{
var target = i + 1 >= args.Length
? null
: args[i + 1];
if (!File.Exists("package.json"))
{
$"{Path.Combine(Environment.CurrentDirectory,"package.json")} does not exist".Print();
return null;
}
return await RunPackageJsonScript(target);
}
dotnetArgs.Add(arg);
}
if (publish)
{
await PublishToGist(tool);
return null;
}
var allow = new[] { "-h", "-help", "--help", "-v", "-version", "--version", "-location", "--location", "-clear", "--clear", "-clean", "--clean", "-include", "--include" };
var unknownFlag = dotnetArgs.FirstOrDefault(x => x.StartsWith("-") && !allow.Contains(x));
if (unknownFlag != null)
throw new Exception($"Unknown flag: '{unknownFlag}'");
if (Verbose)
{
$"args: '{dotnetArgs.Join(" ")}'".Print();
$"APP_SOURCE={GitHubSource}".Print();
if (runProcess != null)
$"Run Process: {runProcess}".Print();
if (createShortcut)
$"Create Shortcut {createShortcutFor}".Print();
if (GistNew)
$"Command: gist-new".Print();
if (GistUpdate)
$"Command: gist-update".Print();
if (copySelf)
$"Command: publish-exe".Print();
if (RunScript != null)
$"Command: run {RunScript} {RunScriptArgs.ToJsv()}".Print();
if (runLispRepl)
$"Command: LISP REPL".Print();
if (EvalScript != null)
$"Command: eval".Print();
}
if (runProcess != null)
{
RegisterStat(tool, runProcess, "run");
var publishDir = Path.GetDirectoryName(Path.GetFullPath(runProcess)).AssertDirectory();
Events.RunNetCoreProcess(new WebAppContext {
Arguments = dotnetArgs.ToArray(),
RunProcess = runProcess,
AppDir = publishDir,
FavIcon = File.Exists(Path.Combine(publishDir, "favicon.ico"))
? Path.Combine(publishDir, "favicon.ico")
: ToolFavIcon,
});
return null;
}
var instruction = await HandledCommandAsync(tool, dotnetArgs.ToArray());
if (instruction?.Handled == true)
return null;
string appSettingsPath = instruction?.AppSettingsPath;
foreach (var path in appSettingPaths)
{
var fullPath = Path.GetFullPath(path);
if (File.Exists(fullPath))
{
appSettingsPath = fullPath;
break;
}
}
if (!runSharpApp && RunScript == null && dotnetArgs.Count == 0 && appSettingsPath == null && createShortcutFor == null)
{
PrintUsage(tool);
return null;
}
var appDir = appSettingsPath != null
? Path.GetDirectoryName(appSettingsPath)
: createShortcutFor != null
? Path.GetDirectoryName(Path.GetFullPath(createShortcutFor))
: Environment.CurrentDirectory;
var ctx = new WebAppContext
{
Tool = tool,
Arguments = dotnetArgs.ToArray(),
RunProcess = runProcess,
WebSettingsPath = appSettingsPath,
AppSettings = WebTemplateUtils.AppSettings,
AppDir = appDir.AssertDirectory(),
ToolPath = Assembly.GetExecutingAssembly().Location,
DebugMode = DebugMode ?? false,
};
if (instruction == null && dotnetArgs.Count > 0)
{
if (Events?.HandleUnknownCommand != null)
{
Events.HandleUnknownCommand(ctx);
}
else
{
$"Unknown command '{dotnetArgs.Join(" ")}'".Print();
PrintUsage(tool);
}
return null;
}
var appSettingsContent = File.Exists(appSettingsPath)
? await File.ReadAllTextAsync(appSettingsPath)
: null;
if (GistVfsTask != null)
{
var gist = await GistVfsTask;
if (string.IsNullOrEmpty(appSettingsContent))
{
appSettingsContent = gist.Files.TryGetValue("app.settings", out var file)
? (string.IsNullOrEmpty(file.Content) && file.Truncated
? DownloadCachedStringFromUrl(file.Raw_Url)
: file.Content)
: null;
}
// start downloading any truncated gist content whilst AppHost initializes
GistVfsLoadTask = GistVfs.LoadAllTruncatedFilesAsync();
if (string.IsNullOrEmpty(appSettingsContent))
{
appSettingsPath = Path.Combine(appDir, "app.settings");
appSettingsContent = File.Exists(appSettingsPath)
? await File.ReadAllTextAsync(appSettingsPath)
: $"debug false{Environment.NewLine}name {gist.Description ?? "Gist App"}{Environment.NewLine}";
}
}
if (appSettingsContent == null && (appSettingsPath == null && createShortcutFor == null && RunScript == null && !runLispRepl && EvalScript == null))
{
if (Directory.Exists(GetAppsPath("")) && Directory.GetDirectories(GetAppsPath("")).Length > 0)
{
PrintAppUsage(tool, "run");
return null;
}
throw new Exception($"'{appSettingPaths[0]}' does not exist.\n\nView Help: {tool} ?");
}
var usingWebSettings = File.Exists(appSettingsPath);
if (Verbose || (usingWebSettings && !createShortcut && (tool == "x") && instruction == null && appSettingsPath != null))
$"Using '{appSettingsPath}'".Print();
if (appSettingsContent == null && RunScript == null)
{
appSettingsContent = usingWebSettings
? await File.ReadAllTextAsync(appSettingsPath)
: "debug false";
}
var appSettings = appSettingsContent != null
? new DictionarySettings(appSettingsContent.ParseKeyValueText(delimiter:" "))
: new DictionarySettings();
if (RunScript != null)
{
var context = new ScriptContext().Init();
var page = OneTimePage(context, await File.ReadAllTextAsync(RunScript));
if (page.Args.Count > 0)
appSettings = new DictionarySettings(page.Args.ToStringDictionary());
}
WebTemplateUtils.AppSettings = new MultiAppSettings(
appSettings,
new EnvironmentVariableSettings());
var bind = "bind".GetAppSetting("localhost");
var ssl = "ssl".GetAppSetting(defaultValue: false);
var port = "port".GetAppSetting(defaultValue: ssl ? "5001-" : "5000-");
if (port.IndexOf('-') >= 0)
{
var startPort = int.TryParse(port.LeftPart('-'), out var val) ? val : 5000;
var endPort = int.TryParse(port.RightPart('-'), out val) ? val : 65535;
port = HostContext.FindFreeTcpPort(startPort, endPort).ToString();
}
var scheme = ssl ? "https" : "http";
var useUrls = "ASPNETCORE_URLS".ToolSetting() ?? $"{scheme}://{bind}:{port}/";
ctx.UseUrls = useUrls;
ctx.StartUrl = useUrls.Replace("://*", "://localhost");
ctx.DebugMode = GetDebugMode();
ctx.FavIcon = GetIconPath(appDir);
if (createShortcut || instruction?.Command == "shortcut")
{
if (instruction?.Command != "shortcut")
RegisterStat(tool, createShortcutFor, "shortcut");
var shortcutPath = createShortcutFor == null
? Path.Combine(appDir, "name".GetAppSetting(defaultValue: "Sharp App"))
: Path.GetFullPath(createShortcutFor.LastLeftPart('.'));
var toolPath = ctx.ToolPath;
var arguments = createShortcutFor == null
? $"\"{ctx.WebSettingsPath}\""
: $"\"{createShortcutFor}\"";
var targetPath = toolPath;
if (toolPath.EndsWith(".dll"))
{
targetPath = "dotnet";
arguments = $"{toolPath} {arguments}";
}
var icon = GetIconPath(appDir, createShortcutFor);
if (!string.IsNullOrEmpty(Target))
targetPath = Target.Replace("^%","%");
if (!string.IsNullOrEmpty(Arguments))
arguments = Arguments.Replace("^%","%");
if (!string.IsNullOrEmpty(WorkDir))
appDir = WorkDir.Replace("^%","%");
if (Verbose) $"CreateShortcut: {shortcutPath}, {targetPath}, {arguments}, {appDir}, {icon}".Print();
CreateShortcut(shortcutPath, targetPath, arguments, appDir, icon, ctx);
if (instruction != null && tool == "app")
$"{Environment.NewLine}Shortcut: {new DirectoryInfo(Path.GetDirectoryName(shortcutPath)).Name}{Path.DirectorySeparatorChar}{Path.GetFileName(shortcutPath)}".Print();
return null;
}
if (RunScript != null || runLispRepl || EvalScript != null)
{
void ExecScript(SharpPagesFeature feature)
{
var ErrorPrefix = $"FAILED run {RunScript} [{string.Join(' ', RunScriptArgV)}]:";
var script = File.ReadAllText(RunScript);
var scriptPage = OneTimePage(feature, script);
EvaluateScript(feature, scriptPage, ErrorPrefix);
}
void EvaluateScript(SharpPagesFeature feature, SharpPage page, string errorPrefix)
{
try
{
var pageResult = new PageResult(page) {
Args = {
["ARGV"] = RunScriptArgV.ToArray(),
}
};
RunScriptArgs.Each(entry => pageResult.Args[entry.Key] = entry.Value);
var output = pageResult.RenderToStringAsync().Result;
output.Print();
if (!Silent && pageResult.LastFilterError != null)
{
errorPrefix.Print();
pageResult.LastFilterStackTrace.Map(x => " at " + x)
.Join(Environment.NewLine).Print();
"".Print();
pageResult.LastFilterError.Message.Print();
pageResult.LastFilterError.ToString().Print();
}
}
catch (Exception ex)
{
ex = ex.UnwrapIfSingleException();
if (ex is StopFilterExecutionException)
{
$"{errorPrefix} {ex.InnerException?.Message}".Print();
return;
}
Verbose = true;
errorPrefix.Print();
throw;
}
}
bool breakLoop = false;
try
{
Console.TreatControlCAsInput = false;
Console.CancelKeyPress += delegate {
// if (Verbose) $"Console.CancelKeyPress".Print();
breakLoop = true;
};
}
catch {} // fails when called from unit test
RegisterStat(tool, RunScript, WatchScript ? "watch" : "run");
var (contentRoot, useWebRoot) = GetDirectoryRoots(ctx);
AppLoader.Init(contentRoot);
var builder = new WebHostBuilder()
.UseFakeServer()
.UseSetting(WebHostDefaults.SuppressStatusMessagesKey, "True")
.UseContentRoot(contentRoot)
.UseWebRoot(useWebRoot)
.ConfigureLogging(config =>
{
if (!GetDebugMode() && !Verbose &&
Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") != "Development")
{
config.ClearProviders();
config.SetMinimumLevel(LogLevel.None);
}
})
.UseModularStartup<Startup>();
using (var webHost = builder.Build())
{
var cts = new CancellationTokenSource();
var task = webHost.RunAsync(cts.Token);
var appHost = WebTemplateUtils.AppHost;
if (StartupException != null) throw StartupException;
var feature = appHost.AssertPlugin<SharpPagesFeature>();
if (runLispRepl)
{
Console.WriteLine($"\nWelcome to #Script Lisp! The time now is: {DateTime.Now.ToShortTimeString()}");
Lisp.RunRepl(feature);
return null;
}
if (EvalScript != null)
{
EvaluateScript(feature, feature.CodeSharpPage(EvalScript), "Eval failed:");
return null;
}
var script = new FileInfo(RunScript);
var lastWriteAt = DateTime.MinValue;
if (WatchScript)
{
$"Watching '{RunScript}' (Ctrl+C to stop):".Print();
while (!breakLoop)
{
do
{
if (breakLoop)
break;
await Task.Delay(100, cts.Token);
script.Refresh();
} while(script.LastWriteTimeUtc == lastWriteAt);
if (breakLoop)
break;
try
{
Console.Clear();
ExecScript(feature);
}
catch (Exception ex)
{
ex = ex.UnwrapIfSingleException();
Console.WriteLine(ex.ToString());
}
lastWriteAt = script.LastWriteTimeUtc;
}
if (Verbose) $"breakLoop = {breakLoop}".Print();
cts.Cancel();
}
else
{
ExecScript(feature);
}
}
return null;
}
return CreateWebAppContext(ctx);
}
private static async Task<WebAppContext> RunPackageJsonScript(string target)
{
var json = await File.ReadAllTextAsync("package.json");
if (!(JSON.parse(json) is Dictionary<string, object> packageJson))
{
$"{Path.Combine(Environment.CurrentDirectory, "package.json")} is invalid".Print();
return null;
}
if (packageJson.TryGetValue("scripts", out var oScripts) &&
oScripts is Dictionary<string, object> scripts)
{
if (target != null)
{
if (scripts.TryGetValue(target, out var oTargetSh) &&
oTargetSh is string targetSh)
{
var procInfo = Env.IsWindows
? new ProcessStartInfo("cmd.exe", "/C " + targetSh) {
WorkingDirectory = Environment.CurrentDirectory
}
: new ProcessStartInfo("/bin/bash", $"-c \"{targetSh.Replace("\"", "\\\"")}\"") {
WorkingDirectory = Environment.CurrentDirectory
};
await ProcessUtils.RunAsync(procInfo, null,
onOut: Console.WriteLine,
onError: Console.Error.WriteLine);
return null;
}
$"Missing package.json script: {target}".Print();
return null;
}
$"\nAvailable package.json scripts:\n".Print();
scripts.Keys.Each(x => $" {x}".Print());
return null;
}
$"No package.json scripts are defined".Print();
return null;
}
private static async Task PublishToGist(string tool)
{
RegisterStat(tool, "publish");
AssertGitHubToken();
var files = new Dictionary<string, object>();
Environment.CurrentDirectory.CopyAllToDictionary(files,
excludePaths:WebTemplateUtils.GetExcludedFolderNames(Environment.CurrentDirectory).ToArray(),
excludeExtensions: WebTemplateUtils.ExcludeFileExtensions.ToArray());
string publishUrl = null;
string appName = null;
var isSharpApp = File.Exists("app.settings");
if (isSharpApp)
{
foreach (var line in await File.ReadAllLinesAsync("app.settings"))
{
if (line.StartsWith("description") || (line.StartsWith("name ") && string.IsNullOrEmpty(Description)))
{
Description = line.RightPart(' ');
}
if (line.StartsWith("appName "))
{
appName = line.RightPart(' ');
}
if (line.StartsWith("publish "))
{
publishUrl = line.RightPart(' ').Trim();
}
}
}
if (File.Exists(".publish"))
{
foreach (var line in await File.ReadAllLinesAsync(".publish"))
{
if (line.StartsWith("gist "))
{
publishUrl = line.RightPart(' ').Trim();
}
}
}
var gateway = new GitHubGateway(GitHubToken);
"".Print();
Task<RegisterSharpAppResponse> registerTask = null;
var client = new JsonServiceClient("https://servicestack.net");
var createGist = string.IsNullOrEmpty(publishUrl);
if (createGist)
{
var defaultName = new DirectoryInfo(Environment.CurrentDirectory).Name.Replace("_", " ");
var gist = gateway.CreateGithubGist(
Description ?? defaultName + (isSharpApp ? " Sharp App" : ""),
isPublic: true,
files: files);
// Html_Url doesn't include username in URL, which it redirects to.
// Use URL with username instead so app listing can extract username
var htmlUrl = !string.IsNullOrEmpty(gist.Owner?.Login)
? $"https://gist.github.com/{gist.Owner.Login}/{gist.Id}"
: gist.Html_Url;
$"published to: {htmlUrl}".Print();
$"\nview in: https://gist.cafe/{gist.Id}".Print();
await File.WriteAllTextAsync(".publish", $"gist {htmlUrl}");
if (isSharpApp && appName != null && gist.Url != null)
{
registerTask = client.PostAsync(new RegisterSharpApp {
AppName = appName,
Publish = htmlUrl,
});
}
}
else
{
#if !NETCOREAPP2_1
var gistId = publishUrl.LastRightPart('/');
gateway.WriteGistFiles(gistId, files, Description, deleteMissing:true);
$"updated: {publishUrl}".Print();
$"\nview in: https://gist.cafe/{gistId}".Print();
#endif
}
if (isSharpApp)
{
"".Print();
if (appName == null)
{
"Publish App to the public registry by re-publishing with app.settings:".Print();
"".Print();
"appName <app alias> # required: alpha-numeric snake-case characters only, 30 chars max".Print();
"description <app summary> # optional: 20-150 chars".Print();
"tags <app tags> # optional: space delimited, alpha-numeric snake-case, 3 tags max".Print();
}
else if (registerTask != null)
{
try
{
registerTask.Wait();
}
catch (WebServiceException ex)
{
$"REGISTRY ERROR: {ex.Message}".Print();
}
"Run published App:".Print();
"".Print();
$" {tool} open {appName}".Print();
}
}
}
public static GistLink GetGistAliasLink(string alias)
{
var localAliases = GetGistAliases();
if (localAliases.Exists(alias))
{
var gistId = localAliases.GetRequiredString(alias);
return new GistLink {
GistId = gistId,
Url = $"https://gist.github.com/{gistId}",
To = ".",
};
}
return null;
}
private static void AssertGitHubToken()
{
if (string.IsNullOrEmpty(GitHubToken))
{
var CR = Environment.NewLine;
throw new Exception($"GitHub Access Token required to publish App to Gist.{CR}" +
$"Specify Token with --token <token> or GITHUB_TOKEN Environment Variable.{CR}" +
$"Generate Access Token at: https://github.com/settings/tokens");
}
}
private static bool ProcessFlags(string[] args, string arg, ref int i)
{
string NextArg(ref int iRef) => iRef + 1 < args.Length ? args[++iRef] : null;
if (VerboseArgs.Contains(arg))
{
Verbose = true;
return true;
}
if (QuietArgs.Contains(arg))
{
Silent = true;
return true;
}
if (SourceArgs.Contains(arg))
{
GitHubSource = GitHubSourceTemplates = NextArg(ref i);
return true;
}
if (ForceArgs.Contains(arg) || YesArgs.Contains(arg))
{
ForceApproval = true;
Silent = true;
return true;
}
if (PreserveArgs.Contains(arg))
{
Preserve = true;
return true;
}
if (TokenArgs.Contains(arg))
{
Token = GitHubToken = NextArg(ref i);
return true;
}
if (LangArgs.Contains(arg))
{
Lang = NextArg(ref i);
return true;
}
if (IncludeArgs.Contains(arg))
{
Includes = NextArg(ref i).Split(';');
return true;
}
if (TargetArgs.Contains(arg))
{
Target = NextArg(ref i);
return true;
}
if (ArgumentsArgs.Contains(arg))
{
Arguments = NextArg(ref i);
return true;
}
if (WorkDirArgs.Contains(arg))
{
WorkDir = NextArg(ref i);
return true;
}
if (DebugArgs.Contains(arg))
{
DebugMode = true;
return true;
}
if (ReleaseArgs.Contains(arg))
{
DebugMode = false;
return true;
}
if (OutArgs.Contains(arg))
{
OutDir = NextArg(ref i);
return true;
}
if (RawArgs.Contains(arg))
{
Raw = true;
return true;
}
if (JsonArgs.Contains(arg))
{
Json = true;
return true;
}
if (BasicAuthArgs.Contains(arg))
{
BasicAuth = NextArg(ref i);
return true;
}
if (AuthSecretArgs.Contains(arg))
{
AuthSecret = NextArg(ref i);
return true;
}
if (SsIdArgs.Contains(arg))
{
SsId = NextArg(ref i);
return true;
}
if (SsPidArgs.Contains(arg))
{
SsPid = NextArg(ref i);
return true;
}
if (CookiesArgs.Contains(arg))
{
Cookies = NextArg(ref i);
return true;
}
if (NameArgs.Contains(arg))
{
Name = NextArg(ref i);
return true;
}
if (UseArgs.Contains(arg))
{
Use = NextArg(ref i);
return true;
}
if (PathArgs.Contains(arg))
{
PathArg = NextArg(ref i);
return true;
}
if (DescriptionArgs.Contains(arg))
{
Description = NextArg(ref i);
return true;
}
if (IgnoreSslErrorsArgs.Contains(arg))
{
IgnoreSslErrors = true;
return true;
}
return false;
}
private static SharpPage OneTimePage(ScriptContext context, string script)
{
if (RunScript == null)
return context.Pages.OneTimePage(script, ".html");
return RunScript.EndsWith(".sc")
? context.CodeSharpPage(script)
: RunScript.EndsWith(".l")
? context.LispSharpPage(script)
: context.Pages.OneTimePage(script, ".html");
}
private static bool InstallGistApp(string tool, string target, GistLink gistLink, List<GistLink> gistLinks, out string appsDir)
{
appsDir = GetAppsPath(target);
var gistId = gistLink?.GistId;
if (gistId == null)
{
if (target.StartsWith("https://gist.github.com/"))
{
gistId = target.ToGistId();
appsDir = GetAppsPath(gistId);
}
else if (target.StartsWith("https://github.com/"))
{
var pathInfo = target.Substring("https://github.com/".Length);
var user = pathInfo.LeftPart('/');
var repo = pathInfo.RightPart('/').LeftPart('/');
appsDir = InstallRepo(target.EndsWith(".zip")
? target
: GitHubUtils.Gateway.GetSourceZipUrl(user, repo),
repo);
if (!Directory.Exists(appsDir))
{
$"Could not install {target}".Print();
return false;
}
}
else if (target.Length == GistAppsId.Length)
{
gistId = target;
}
else if (gistLink?.Repo != null)
{
appsDir = InstallRepo(gistLink.Url.EndsWith(".zip")
? gistLink.Url
: GitHubUtils.Gateway.GetSourceZipUrl(gistLink.User, gistLink.Repo),
target);
if (!Directory.Exists(appsDir))
{
$"Could not install {target}".Print();
return false;
}
}
else
{
$"No match found for '{target}', available Apps:".Print();
PrintGistLinks(tool, gistLinks ?? GetGistAppsLinks(), usage: $"Usage: {tool} open <name>");
return false;
}
}
if (gistId != null)
{
GistVfs = new GistVirtualFiles(gistId);
GistVfsTask = GistVfs.GetGistAsync(); // fire to load asynchronously
}
var useDir = appsDir;
if (!Directory.Exists(useDir))
{
RetryExec(() => Directory.CreateDirectory(useDir));
}
RetryExec(() => Directory.SetCurrentDirectory(useDir));
return true;
}
private static void PrintAppUsage(string tool, string cmd)
{
"".Print();
$"Usage: {tool} {cmd} <app>".Print();
var appsDir = GetAppsPath("");
var appNames = Directory.GetDirectories(appsDir);
if (appNames.Length > 0)
{
"".Print();
"Installed Apps:\n".Print();
appNames.Each(x => $" {new DirectoryInfo(x).Name}".Print());
}
}
private static string InstallRepo(string downloadUrl, string appName)
{
var installDir = GetAppsPath(appName);
var gistFile = installDir + ".gist";
DeleteFile(gistFile);
var cachedVersionPath = DownloadCachedZipUrl(downloadUrl);
var tmpDir = Path.Combine(Path.GetTempPath(), "servicestack", appName);
DeleteDirectory(tmpDir);
if (Verbose) $"ExtractToDirectory: {cachedVersionPath} -> {tmpDir}".Print();
ZipFile.ExtractToDirectory(cachedVersionPath, tmpDir);
DeleteDirectory(installDir);
MoveDirectory(new DirectoryInfo(tmpDir).GetDirectories().First().FullName, installDir);
$"Installed App '{appName}'".Print();
return installDir;
}
private static string DownloadCachedZipUrl(string zipUrl)
{
var noCache = zipUrl.IndexOf("master.zip", StringComparison.OrdinalIgnoreCase) >= 0 ||
zipUrl.IndexOf("main.zip", StringComparison.OrdinalIgnoreCase) >= 0;
if (noCache)
{
var tempFile = Path.GetTempFileName();
if (Verbose) $"Downloading {zipUrl} => {tempFile} (nocache)".Print();
Path.GetDirectoryName(tempFile).AssertDirectory();
GitHubUtils.DownloadFile(zipUrl, tempFile);
return tempFile;
}
var cachedVersionPath = GetCachedFilePath(zipUrl);
var isCached = File.Exists(cachedVersionPath);
if (Verbose) ((isCached ? "Using cached release: " : "Using new release: ") + cachedVersionPath).Print();
if (!isCached)
{
if (Verbose) $"Downloading {zipUrl} => {cachedVersionPath}".Print();
Path.GetDirectoryName(cachedVersionPath).AssertDirectory();
GitHubUtils.DownloadFile(zipUrl, cachedVersionPath);
}
return cachedVersionPath;
}
public static void MoveDirectory(string fromPath, string toPath)
{
if (Verbose) $"Directory Move: {fromPath} -> {toPath}".Print();
try
{
Directory.GetParent(toPath).AssertDirectory();
Directory.Move(fromPath, toPath);
}
catch (IOException ex) //Source and destination path must have identical roots. Move will not work across volumes.
{
if (Verbose) $"Directory Move failed: '{ex.Message}', trying COPY Directory...".Print();
if (Verbose) $"Directory Copy: {fromPath} -> {toPath}".Print();
fromPath.CopyAllTo(toPath);
}
}
public static bool GetDebugMode() => DebugMode ?? "debug".GetAppSetting(Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") != "Production");
public static void CreateShortcut(string filePath, string targetPath, string arguments, string workingDirectory, string iconPath, WebAppContext ctx)
{
if (Events?.CreateShortcut != null)
{
Events.CreateShortcut(filePath, targetPath, arguments, workingDirectory, iconPath);
}
else
{
filePath = Path.Combine(Path.GetDirectoryName(filePath)!, new DefaultScripts().generateSlug(Path.GetFileName(filePath)));
var cmd = filePath + (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ".bat" : ".sh");
if (ctx == null)
return;
var openBrowserCmd = string.IsNullOrEmpty(ctx?.StartUrl) || targetPath.EndsWith(".exe") ? "" :
(RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
? $"start {ctx.StartUrl}"
: RuntimeInformation.IsOSPlatform(OSPlatform.OSX)
? $"open {ctx.StartUrl}"
: $"xdg-open {ctx.StartUrl}") + Environment.NewLine;
File.WriteAllText(cmd, $"{openBrowserCmd}{targetPath} {arguments}");
}
}
private static void CreatePublishShortcut(WebAppContext ctx, string publishDir, string publishAppDir, string publishToolDir, string toolName)
{
var appDir = publishAppDir;
var toolFilePath = Path.Combine(publishToolDir, toolName);
var targetPath = toolFilePath.EndsWith(".dll") ? "dotnet" : toolFilePath;
var arguments = toolFilePath.EndsWith(".dll") ? $"\"{toolFilePath}\"" : "";
var icon = GetIconPath(appDir);
var shortcutPath = Path.Combine(publishDir, "name".GetAppSetting(defaultValue: "SharpApp"));
if (Verbose) $"CreateShortcut: {shortcutPath}, {targetPath}, {arguments}, {appDir}, {icon}".Print();
CreateShortcut(shortcutPath, targetPath, arguments, appDir, icon, ctx);
}
private static string GetIconPath(string appDir, string createShortcutFor=null) => createShortcutFor == null
? "icon".GetAppSettingPath(appDir) ?? GetFavIcon()
: GetFavIcon();
private static string GetFavIcon() => File.Exists("favicon.ico") ? Path.GetFullPath("favicon.ico") : ToolFavIcon;
private static (string publishDir, string publishAppDir, string publishToolDir) GetPublishDirs(string toolName, string appDir)
{
var publishDir = Path.Combine(appDir, "publish");
var publishAppDir = Path.Combine(publishDir, "app");
var publishToolDir = Path.Combine(publishDir, toolName);
publishDir.AssertDirectory();
publishAppDir.AssertDirectory();
publishToolDir.AssertDirectory();
return (publishDir, publishAppDir, publishToolDir);
}
private static (string contentRoot, string useWebRoot) GetDirectoryRoots(WebAppContext ctx)
{
var appDir = ctx.AppDir;
var contentRoot = "contentRoot".GetAppSettingPath(appDir) ?? appDir;
var wwwrootPath = Path.Combine(appDir, "wwwroot");
var webRoot = Directory.Exists(wwwrootPath)
? wwwrootPath
: contentRoot;
var useWebRoot = "webRoot".GetAppSettingPath(appDir) ?? webRoot;
return (contentRoot, useWebRoot);
}
private static WebAppContext CreateWebAppContext(WebAppContext ctx)
{
var (contentRoot, useWebRoot) = GetDirectoryRoots(ctx);
var builder = WebHost.CreateDefaultBuilder(ctx.Arguments)
.UseContentRoot(contentRoot)
.UseWebRoot(useWebRoot)
.UseModularStartup<Startup>()
.ConfigureLogging(config =>
{
if (!GetDebugMode() && !Verbose &&
Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") != "Development")
{
config.ClearProviders();
config.SetMinimumLevel(LogLevel.None);
}
})
.UseUrls(ctx.UseUrls);
ctx.Builder = builder;
if (Verbose) ctx.GetDebugString().Print();
return ctx;
}
public static void PrintUsage(string tool)
{
var runProcess = "";
if (Events?.RunNetCoreProcess != null)
{
runProcess = $" {tool} <name>.dll Run external .NET Core App{Environment.NewLine}";
runProcess += $" {tool} <name>.exe Run external self-contained .NET Core App{Environment.NewLine}";
}
var additional = new StringBuilder();
var indt = "".PadLeft(tool.Length, ' ');
string USAGE = $@"
Version: {GetVersion()}
Usage:
{tool} new List available Project Templates
{tool} new <template> <name> Create New Project From Template
{tool} download <user>/<repo> Download latest GitHub Repo Release
{tool} get <url> Download URL to file (-out <file|dir>)
{tool} stream <url> Stream URL contents to console stdout
{tool} mix Show available gists to mixin (Alias '+')
{tool} mix <name> Write gist files locally, e.g: (Alias +init)
{tool} mix init Create empty .NET Core ServiceStack App
{tool} mix [tag] Search available gists
{tool} mix <gist-url> Write all Gist text files to current directory
{tool} gist <gist-id> Write all Gist text files to current directory
{tool} publish Publish Current Directory to Gist (requires token)
{tool} gist-new <dir> Create new Gist with Directory Files (requires token)
{tool} gist-update <id> <dir> Update Gist ID with Directory Files (requires token)
{tool} gist-open <gist> Download and open Gist folder (-out <dir>)
{tool} <lang> Update all ServiceStack References in directory (recursive)
{tool} <file> Update existing ServiceStack Reference (e.g. dtos.cs)
{tool} <lang> <url> <file> Add ServiceStack Reference and save to file name
{tool} csharp <url> Add C# ServiceStack Reference (Alias 'cs')
{tool} typescript <url> Add TypeScript ServiceStack Reference (Alias 'ts')
{tool} python <url> Add Python ServiceStack Reference (Alias 'py')
{tool} swift <url> Add Swift ServiceStack Reference (Alias 'sw')
{tool} java <url> Add Java ServiceStack Reference (Alias 'ja')
{tool} kotlin <url> Add Kotlin ServiceStack Reference (Alias 'kt')
{tool} dart <url> Add Dart ServiceStack Reference (Alias 'da')
{tool} fsharp <url> Add F# ServiceStack Reference (Alias 'fs')
{tool} vbnet <url> Add VB.NET ServiceStack Reference (Alias 'vb')
{tool} tsd <url> Add TypeScript Definition ServiceStack Reference
{tool} proto <url> Add gRPC .proto ServiceStack Reference
{tool} proto <url> <name> Add gRPC .proto and save to <name>.services.proto
{tool} proto Update all gRPC *.services.proto ServiceStack References
{tool} proto-langs Display list of gRPC supported languages
{tool} proto-<lang> <url> Add gRPC .proto and generate language (-out <dir>)
{tool} proto-<lang> <file|dir> Update gRPC .proto and re-gen language (-out <dir>)
{tool} proto-<lang> Update all gRPC .proto's and re-gen lang (-out <dir>)
{tool} inspect <url> Show info about a ServiceStack App
{tool} inspect <url> <request> Show info about a ServiceStack API Request
{tool} inspect-jwt Show info about a decoded JWT
{tool} send Show usage info info for Post Command
{tool} jupyter-python Show usage info for generating Python Jupyter Notebooks
{tool} jupyter-csharp Show usage info for generating C# Jupyter Notebooks
{tool} jupyter-fsharp Show usage info for generating F# Jupyter Notebooks
{tool} open List of available Sharp Apps
{tool} open <app> Install and run Sharp App
{tool} run Run Sharp App in current directory
{tool} run <name> Run Installed Sharp App
{tool} run path/app.settings Run Sharp App at directory containing specified app.settings
{runProcess}
{tool} install List available Sharp Apps to install (Alias 'l')
{tool} install <app> Install Sharp App (Alias 'i')
{tool} uninstall List Installed Sharp Apps
{tool} uninstall <app> Uninstall Sharp App
{tool} alias Show all local gist aliases (for usage in mix or app's)
{tool} alias <alias> Print local alias value
{tool} alias <alias> <gist-id> Set local alias with Gist Id or Gist URL
{tool} unalias <alias> Remove local alias
{tool} shortcut Create Shortcut for Sharp App
{tool} shortcut <name>.dll Create Shortcut for .NET Core App
{tool} scripts List all available package.json scripts (Alias 's')
{tool} scripts <name> Run package.json script (Alias 's')
{tool} run <name>.ss Run #Script within context of AppHost (or <name>.html)
{tool} watch <name>.ss Watch #Script within context of AppHost (or <name>.html)
{indt} Language File Extensions:
{indt} .ss - #Script source file
{indt} .sc - #Script `code` source file
{indt} .l - #Script `lisp` source file
{tool} lisp Start Lisp REPL
{tool} base64 <text> Convert text to Base64 (or base64url)
{tool} base64 < file Convert redirected input to Base64 (or base64url)
{tool} unbase64 <base64> Convert from Base64 to text (or unbase64url)
{tool} unbase64 < file Convert redirected Base64 input to text (or unbase64url)
{additional}
dotnet tool update -g {tool} Update to latest version
Options:
-h, --help, ? Print this message
-v, --version Print this version
-d, --debug Run in Debug mode for Development
-r, --release Run in Release mode for Production
-s, --source Change GitHub Source for App Directory
-f, --force Quiet mode, always approve, never prompt (Alias 'y')
-p, --preserve Don't overwrite existing files
-e, --eval Evaluate #Script Code
--token Use GitHub Auth Token
--clean Delete downloaded caches
--verbose Display verbose logging
--ignore-ssl-errors Ignore SSL Errors
This tool collects anonymous usage to determine the most used commands to improve your experience.
To disable set SERVICESTACK_TELEMETRY_OPTOUT=1 environment variable to 1 using your favorite shell.";
Console.WriteLine(USAGE);
}
public static void OpenBrowser(string url)
{
try
{
Process.Start(url);
}
catch
{
// hack because of this: https://github.com/dotnet/corefx/issues/10361
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
url = url.Replace("&", "^&");
Process.Start(new ProcessStartInfo("cmd", $"/c start {url}") { CreateNoWindow = true });
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
Process.Start("xdg-open", url);
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
Process.Start("open", url);
}
else
{
throw;
}
}
}
public class Instruction
{
public string Command;
public string AppDir;
public string AppSettingsPath;
public bool Handled;
}
public static async Task<Instruction> HandledCommandAsync(string tool, string[] args)
{
if (args.Length == 0)
return null;
var cmd = Regex.Replace(args[0], "^-+", "/");
Task<string> checkUpdatesAndQuit = null;
var arg = args[0];
if (RefAlias.Keys.Contains(arg) || RefAlias.Values.Contains(arg))
{
var lang = RefAlias.TryGetValue(arg, out var value)
? value
: arg;
var dtosExt = RefExt[lang];
if (args.Length == 1)
{
UpdateAllReferences(tool, lang, Environment.CurrentDirectory, dtosExt);
}
else if (args.Length >= 2)
{
var target = args[1];
if (target.IsUrl())
{
var fileName = args.Length == 3 ? args[2] : null;
var filePath = GetDtoTypesFilePath(target, dtosExt, fileName);
var typesUrl = target.IndexOf($"/types/{lang}", StringComparison.Ordinal) == -1
? string.IsNullOrEmpty(PathArg)
? target.CombineWith($"/types/{lang}")
: target.CombineWith(PathArg)
: target;
SaveReference(tool, lang, typesUrl, filePath);
}
else
{
UpdateReference(tool, lang, Path.GetFullPath(target));
}
}
return new Instruction { Handled = true };
}
if (arg.StartsWith("proto-"))
{
var lang = arg.RightPart('-');
var target = args.Length > 1 ? args[1] : null;
if (lang == null)
return null;
if (target == null) //find first *.proto + re-gen dir
{
var fs = new FileSystemVirtualFiles(Environment.CurrentDirectory);
var protoFiles = fs.GetAllMatchingFiles("*services.proto");
var firstProto = protoFiles.FirstOrDefault();
if (firstProto == null)
throw new Exception("Could not find any *.services.proto gRPC Service Descriptions");
fs = new FileSystemVirtualFiles(firstProto.Directory.RealPath);
protoFiles = fs.GetAllMatchingFiles("*.proto");
var protoFilePaths = protoFiles.Map(x => x.RealPath);
WriteGrpcFiles(lang, protoFilePaths, GetOutDir(firstProto.RealPath));
}
else if (target.IsUrl())
{
var fileName = args.Length > 2 ? args[2] : null;
if (fileName != null && !fileName.IsValidFileName())
throw new Exception($"Not a valid file name '{fileName}'");
var filePath = GetDtoTypesFilePath(target, "services.proto", fileName);
var typesUrl = target.IndexOf($"/types/", StringComparison.Ordinal ) == -1
? target.CombineWith($"/types/proto")
: target;
var writtenFiles = SaveReference(tool, lang, typesUrl, filePath);
WriteGrpcFiles(lang, writtenFiles, GetOutDir(filePath));
}
else if (File.Exists(target))
{
WriteGrpcFiles(lang, new[]{ target }, GetOutDir(target));
}
else if (Directory.Exists(target))
{
var fs = new FileSystemVirtualFiles(target);
var protoFiles = fs.GetAllMatchingFiles("*.proto");
var protoFilePaths = protoFiles.Map(x => x.RealPath);
WriteGrpcFiles(lang, protoFilePaths, GetOutDir(target));
}
else throw new Exception($"Could not find valid *.services.proto from '{target}'");
return new Instruction { Handled = true };
}
if (arg == "base64")
{
var (redirectedInput, text) = await args.CaptureRemainingArgsOrRedirectedInputAsync(1);
if (text == null)
{
$"Usage: {tool} base64 <text>".Print();
$" {tool} base64 < file.txt".Print();
$" {tool} base64url <text>".Print();
$" {tool} base64url < file.txt".Print();
}
else
{
Convert.ToBase64String(text.ToUtf8Bytes()).Print();
}
return new Instruction { Handled = true };
}
if (arg == "base64url")
{
var (redirectedInput, text) = await args.CaptureRemainingArgsOrRedirectedInputAsync(1);
if (text == null)
{
$"Usage: {tool} base64 <text>".Print();
$" {tool} base64 < file.txt".Print();
$" {tool} base64url <text>".Print();
$" {tool} base64url < file.txt".Print();
}
else
{
text.ToUtf8Bytes().ToBase64UrlSafe().Print();
}
return new Instruction { Handled = true };
}
if (arg == "unbase64")
{
var (redirectedInput, base64) = await args.CaptureRemainingArgsOrRedirectedInputAsync(1);
if (base64 == null)
{
$"Usage: {tool} unbase64 <text>".Print();
$" {tool} unbase64 < file.txt".Print();
$" {tool} unbase64url <text>".Print();
$" {tool} unbase64url < file.txt".Print();
}
else
{
Convert.FromBase64String(base64).FromUtf8Bytes().Print();
}
return new Instruction { Handled = true };
}
if (arg == "unbase64url")
{
var (redirectedInput, base64) = await args.CaptureRemainingArgsOrRedirectedInputAsync(1);
if (base64 == null)
{
$"Usage: {tool} unbase64 <text>".Print();
$" {tool} unbase64 < file.txt".Print();
$" {tool} unbase64url <text>".Print();
$" {tool} unbase64url < file.txt".Print();
}
else
{
base64.FromBase64UrlSafe().FromUtf8Bytes().Print();
}
return new Instruction { Handled = true };
}
if (arg == "inspect-jwt")
{
var (redirectedInput, jwt) = await args.CaptureRemainingArgsOrRedirectedInputAsync(1);
if (jwt == null)
{
$"Usage: {tool} inspect-jwt <jwt>".Print();
$" {tool} inspect-jwt < file.txt".Print();
}
else
{
var header = JwtAuthProviderReader.ExtractHeader(jwt);
"".Print();
"[JWT Header]".Print();
header.PrintFriendlyObject();
var payload = JwtAuthProviderReader.ExtractPayload(jwt);
if (payload.TryGetValue("iat", out var iatObj) && iatObj is int iatEpoch)
payload["iat"] = $"{iatEpoch} ({iatEpoch.FromUnixTime():R})";
if (payload.TryGetValue("exp", out var expObj) && expObj is int expEpoch)
payload["exp"] = $"{expEpoch} ({expEpoch.FromUnixTime():R})";
"".Print();
"[JWT Payload]".Print();
payload.PrintFriendlyObject();
}
return new Instruction { Handled = true };
}
var verbs = new[] { "send", "SEND", "GET", "POST", "PUT", "DELETE", "PATCH" }.ToList();
if (verbs.Contains(arg))
{
var target = args.Length > 1 ? args[1] : null;
var requestDto = args.Length > 2 ? args[2] : null;
var (redirectedInput, requestArgs) = await args.CaptureRemainingArgsOrRedirectedInputAsync(3);