Last active
June 24, 2021 10:56
-
-
Save gistlyn/bc590cd486c6b2588d8baf56b32c6f40 to your computer and use it in GitHub Desktop.
ArchiveServices backend for New Package UI
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// License BSD https://github.com/ServiceStack/servicestack-client/blob/master/LICENSE.txt | |
// Partial AppConfig used by ArchiveServices.cs | |
public class AppConfig | |
{ | |
public Dictionary<string, HashSet<string>> TemplateMixMap { get; set; } = new(); | |
} | |
public class AppHost : AppHostBase | |
{ | |
public override void Configure(Container container) | |
{ | |
var appConfig = new AppConfig(); | |
var templateMix = new TextFileSettings(Path.Combine(HostingEnvironment.ContentRootPath, "template-mix.txt")); | |
foreach (var entry in templateMix.GetAll()) | |
{ | |
appConfig.TemplateMixMap[entry.Key] = entry.Value.Split(',').Map(x => x.Trim()).ToSet(); | |
} | |
container.Register(c => appConfig); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// License BSD https://github.com/ServiceStack/servicestack-client/blob/master/LICENSE.txt | |
using System; | |
using System.Collections.Concurrent; | |
using System.Collections.Generic; | |
using System.IO; | |
using System.IO.Compression; | |
using System.Linq; | |
using System.Net; | |
using System.Text.RegularExpressions; | |
using System.Threading.Tasks; | |
using ServiceStack; | |
using ServiceStack.Caching; | |
using ServiceStack.FluentValidation.Internal; | |
using ServiceStack.IO; | |
using ServiceStack.Text; | |
using www.ServiceModel; | |
namespace www.ServiceInterface | |
{ | |
[Route("/archive/{User}/{Repo}")] | |
public class GetRepoArchive : IReturn<byte[]> | |
{ | |
[ValidateNotEmpty] | |
public string User { get; set; } | |
[ValidateNotEmpty] | |
public string Repo { get; set; } | |
public string Name { get; set; } | |
public string[] Mix { get; set; } | |
} | |
public class ArchiveServices : Service | |
{ | |
public ConcurrentDictionary<string, GistVirtualFiles> MixGists { get; } = new(); | |
public AppConfig AppConfig { get; set; } | |
public async Task<object> Get(GetRepoArchive request) | |
{ | |
var user = request.User; | |
var repo = request.Repo; | |
var projectName = request.Name ?? "MyApp"; | |
var projectDir = Path.Combine(ArchiveUtils.AppDataPath, "templates", user, repo); | |
var gateway = ArchiveUtils.CreateGateway(); | |
var now = DateTime.UtcNow; | |
var mix = request.Mix; | |
if (!Directory.Exists(projectDir)) | |
{ | |
var downloadUrl = await gateway.GetSourceZipUrlAsync(user, repo); | |
var cachedVersionPath = ArchiveUtils.DownloadCachedZipUrl(downloadUrl); | |
var tmpDir = Path.Combine(Path.GetTempPath(), "servicestack", user, repo); | |
ArchiveUtils.DeleteDirectory(tmpDir); | |
ArchiveUtils.Print($"ExtractToDirectory: {cachedVersionPath} -> {tmpDir}"); | |
ZipFile.ExtractToDirectory(cachedVersionPath, tmpDir); | |
ArchiveUtils.MoveDirectory(new DirectoryInfo(tmpDir).GetDirectories().First().FullName, | |
projectDir.AssertDir()); | |
} | |
var fs = new FileSystemVirtualFiles(projectDir); | |
string hostDir = null; | |
var projectFiles = new HashSet<string>(); | |
var installPackages = new Dictionary<string, string>(); | |
foreach (var file in fs.GetAllFiles()) | |
{ | |
var filePath = file.VirtualPath.ReplaceMyApp(projectName); | |
if (hostDir == null && ArchiveUtils.HostFiles.Any(f => filePath.EndsWith(f))) | |
hostDir = filePath.LastLeftPart('/'); | |
projectFiles.Add(filePath); | |
} | |
var gistFilesMap = new Dictionary<string, object>(); | |
if (!mix.IsEmpty()) | |
{ | |
if (AppConfig.TemplateMixMap.TryGetValue(user + "/" + repo, out var eligibleMixes)) | |
{ | |
var to = new List<string>(); | |
foreach (var alias in mix) | |
{ | |
if (eligibleMixes.Contains(alias)) | |
{ | |
to.Add(alias); | |
} | |
} | |
mix = to.ToArray(); | |
} | |
} | |
if (!mix.IsEmpty()) | |
{ | |
var links = gateway.GetGistApplyLinks(); | |
var unhandledAliases = mix.ResolveGistAliases(links); | |
foreach (var gistAlias in unhandledAliases) | |
{ | |
var gistLink = GistLink.Get(links, gistAlias); | |
if (gistLink == null) | |
throw new Exception($"No match found for '{gistAlias}'"); | |
var basePath = gistLink.To == "$HOST" | |
? hostDir ?? throw new Exception($"Could not determine $HOST folder") | |
: gistLink.To == "." | |
? "" | |
: gistLink.To.TrimStart('/'); | |
var gistFiles = gateway.GetGistFiles(gistLink.Url, out var gistUrl); | |
foreach (var gistFile in gistFiles) | |
{ | |
if (gistFile.Key.IndexOf("..", StringComparison.Ordinal) >= 0) | |
throw new Exception($"Invalid file name '{gistFile.Key}' from '{gistLink.Url}'"); | |
var fileName = gistFile.Key; | |
var fileContents = gistFile.Value; | |
if (fileName == "_init") | |
{ | |
foreach (var line in fileContents.ReadLines()) | |
{ | |
var addPrefix = "dotnet add package "; | |
if (line.StartsWith(addPrefix)) | |
{ | |
var package = line.Substring(addPrefix.Length); | |
installPackages[package] = ArchiveUtils.GetPackageVersion(package); | |
} | |
} | |
continue; | |
} | |
var resolvedFile = ArchiveUtils.osPaths(Path.Combine(basePath, gistFile.Key.ReplaceMyApp(projectName).TrimEnd('?'))); | |
var noOverride = gistFile.Key.EndsWith("?"); | |
if (noOverride && projectFiles.Contains(resolvedFile)) | |
{ | |
ArchiveUtils.Print($"Skipping existing optional file: {resolvedFile}"); | |
continue; | |
} | |
var filePath = resolvedFile; | |
if (resolvedFile.EndsWith("|base64")) | |
{ | |
try | |
{ | |
filePath = filePath.LastLeftPart('|'); | |
var fileBytes = Convert.FromBase64String(fileContents); | |
gistFilesMap[filePath] = fileBytes; | |
} | |
catch (Exception ex) | |
{ | |
ArchiveUtils.Print($"Could not Convert Base64 binary file '{filePath}': {ex.Message}"); | |
throw; | |
} | |
} | |
else | |
{ | |
var renamedTxt = fileContents.ReplaceMyApp(projectName); | |
gistFilesMap[filePath] = renamedTxt; | |
} | |
} | |
} | |
} | |
var ms = new MemoryStream(); | |
using (var archive = new ZipArchive(ms, ZipArchiveMode.Create, true)) | |
{ | |
foreach (var file in fs.GetAllFiles()) | |
{ | |
var filePath = file.VirtualPath.ReplaceMyApp(projectName); | |
projectFiles.Add(filePath); | |
var entry = archive.CreateEntry(filePath); | |
await using var entryStream = entry.Open(); | |
if (!file.IsBinary()) | |
{ | |
await using var streamWriter = new StreamWriter(entryStream); | |
var txt = file.ReadAllText(); | |
var renamedTxt = txt.ReplaceMyApp(projectName); | |
if (installPackages.Count > 0 && filePath.LastRightPart('/') == projectName + ".csproj") | |
{ | |
var packageRefItemGroupPos = renamedTxt.IndexOf("<PackageReference", StringComparison.Ordinal); | |
var itemGroupEndPos = renamedTxt.IndexOf("</ItemGroup>", packageRefItemGroupPos, StringComparison.Ordinal); | |
var prefix = renamedTxt.Substring(0, itemGroupEndPos); | |
var suffix = renamedTxt.Substring(itemGroupEndPos); | |
var packageRefs = StringBuilderCache.Allocate(); | |
foreach (var pkgEntry in installPackages) | |
{ | |
if (packageRefs.Length > 0) | |
packageRefs.Append(" "); | |
packageRefs.AppendLine($"<PackageReference Include=\"{pkgEntry.Key}\" Version=\"{pkgEntry.Value}\" />"); | |
} | |
var newCsProj = prefix + " " + StringBuilderCache.ReturnAndFree(packageRefs) + " " + suffix; | |
renamedTxt = newCsProj; | |
} | |
await streamWriter.WriteAsync(renamedTxt); | |
} | |
else | |
{ | |
await using var bs = file.OpenRead(); | |
await bs.CopyToAsync(entryStream); | |
} | |
} | |
var filePaths = gistFilesMap.Keys.ToList(); | |
foreach (var filePath in filePaths) | |
{ | |
var obj = gistFilesMap[filePath]; | |
if (obj is byte[] fileBytes) | |
{ | |
var entry = archive.CreateEntry(filePath); | |
await using var entryStream = entry.Open(); | |
await entryStream.WriteAsync(fileBytes); | |
} | |
else if (obj is string renamedTxt) | |
{ | |
var entry = archive.CreateEntry(filePath); | |
await using var entryStream = entry.Open(); | |
await using var streamWriter = new StreamWriter(entryStream); | |
await streamWriter.WriteAsync(renamedTxt); | |
} | |
} | |
} | |
ms.Position = 0; | |
var headerValue = | |
$"attachment; {HttpExt.GetDispositionFileName($"{projectName}.zip")}; size={ms.Length}; modification-date={DateTime.UtcNow.ToString("R").Replace(",", "")}"; | |
return new HttpResult(ms, MimeTypes.Binary) { | |
Headers = { | |
{HttpHeaders.ContentDisposition, headerValue}, | |
} | |
}; | |
} | |
} | |
public static class ArchiveUtils | |
{ | |
public const bool Verbose = false; | |
public const string UserAgent = "servicestack.net"; | |
public const string GitHubToken = "<Replace with GitHubToken>"; | |
public const string GistLinksId = "9b32b03f207a191099137429051ebde8"; | |
public static string ContentRootPath => AppHostBase.Instance.GetHostingEnvironment().ContentRootPath; | |
public static string AppDataPath => Path.Combine(ContentRootPath, "App_Data"); | |
public static GitHubGateway CreateGateway() => new() { | |
AccessToken = GitHubToken, | |
UserAgent = UserAgent, | |
GetJsonFilter = apiUrl => apiUrl.GetJsonFromUrl(req => req.ApplyRequestFilters()) | |
}; | |
public static void ApplyRequestFilters(this HttpWebRequest req) | |
{ | |
req.UserAgent = UserAgent; | |
if (!string.IsNullOrEmpty(GitHubToken)) | |
req.Headers["Authorization"] = "token " + GitHubToken; | |
// Ignore SSL Errors | |
// req.ServerCertificateValidationCallback = (webReq, cert, chain, errors) => true; | |
} | |
public static void Print(Exception ex) | |
{ | |
if (Verbose) $"ERROR: {ex.Message}".Print(); | |
} | |
public static void Print(string msg) | |
{ | |
if (Verbose) msg.Print(); | |
} | |
public static bool IsBinary(this IVirtualFile file) | |
{ | |
if (string.IsNullOrEmpty(file.Extension)) | |
return false; | |
var mimeType = MimeTypes.GetMimeType(file.Extension); | |
return MimeTypes.IsBinary(mimeType); | |
} | |
public static List<KeyValuePair<string, string>> ReplaceTokens { get; set; } = new(); | |
private static string CamelToKebab(string str) => | |
Regex.Replace((str ?? ""), "([a-z])([A-Z])", "$1-$2").ToLower(); | |
public static bool IsUrl(this string gistId) => gistId.IndexOf("://", StringComparison.Ordinal) >= 0; | |
public static readonly ConcurrentDictionary<string, Dictionary<string, string>> GistFilesCache = new(); | |
public static readonly ConcurrentDictionary<string, string> LatestPackageVersionCache = new(); | |
public static string GetPackageVersion(string package) | |
{ | |
if (package.StartsWith("ServiceStack")) | |
return "5.*"; | |
return LatestPackageVersionCache.GetOrAdd(package, key => { | |
var url = $"https://api.nuget.org/v3-flatcontainer/{key}/index.json"; | |
var json = url.GetJsonFromUrl(); | |
var obj = (Dictionary<string,object>) JSON.parse(json); | |
var versions = (List<object>) obj["versions"]; | |
versions.Reverse(); | |
var lastVersion = versions.Cast<string>().First(x => !x.Contains('-')); | |
return lastVersion; | |
}); | |
} | |
public static string SanitizeProjectName(string projectName) | |
{ | |
if (string.IsNullOrEmpty(projectName)) | |
return null; | |
var sepChars = new[] {' ', '-', '+', '_'}; | |
if (projectName.IndexOfAny(sepChars) == -1) | |
return projectName; | |
var sb = StringBuilderCache.Allocate(); | |
var words = projectName.Split(sepChars); | |
foreach (var word in words) | |
{ | |
if (string.IsNullOrEmpty(word)) | |
continue; | |
sb.Append(char.ToUpper(word[0])).Append(word.Substring(1)); | |
} | |
return StringBuilderCache.ReturnAndFree(sb); | |
} | |
public static List<string> HostFiles = new() { | |
"appsettings.json", | |
"Web.config", | |
"App.config", | |
"Startup.cs", | |
"Program.cs", | |
}; | |
public static string osPaths(string path) => path.Replace('\\', '/'); | |
public static Dictionary<string, string> GetGistFiles(this GitHubGateway gateway, string gistId) | |
{ | |
return GistFilesCache.GetOrAdd(gistId, gistKey => { | |
var json = gateway.GetJson($"/gists/{gistKey}"); | |
return FromJsonGist(json, gistKey); | |
}); | |
} | |
public static Dictionary<string, string> GetGistFilesFromUrl(this GitHubGateway gateway, string gistUrl) | |
{ | |
return GistFilesCache.GetOrAdd(gistUrl, gistKey => { | |
var json = gistUrl.GetJsonFromUrl(req => req.UserAgent = "ServiceStack"); | |
return FromJsonGist(json, gistKey); | |
}); | |
} | |
public static Dictionary<string, string> FromJsonGist(string json, string gistRef) | |
{ | |
var response = JSON.parse(json); | |
if (response is Dictionary<string, object> obj && | |
obj.TryGetValue("files", out var oFiles) && | |
oFiles is Dictionary<string, object> files) | |
{ | |
var to = new Dictionary<string, string>(); | |
foreach (var entry in files) | |
{ | |
var meta = (Dictionary<string, object>) entry.Value; | |
var contents = (string) meta["content"]; | |
var size = (int) meta["size"]; | |
if ((string.IsNullOrEmpty(contents) || contents.Length < size) && | |
meta["truncated"] is bool b && b) | |
{ | |
contents = DownloadCachedStringFromUrl((string) meta["raw_url"]); | |
} | |
to[entry.Key] = contents; | |
} | |
return to; | |
} | |
throw new NotSupportedException($"Invalid gist response returned for '{gistRef}'"); | |
} | |
internal static string DownloadCachedStringFromUrl(string url) | |
{ | |
var cachedPath = GetCachedFilePath(url); | |
var isCached = File.Exists(cachedPath); | |
if (Verbose && !isCached) $"Downloading uncached '{url}' ...".Print(); | |
if (File.Exists(cachedPath)) | |
return File.ReadAllText(cachedPath); | |
var text = url.GetStringFromUrl(requestFilter: req => req.UserAgent = UserAgent); | |
File.WriteAllText(cachedPath, text); | |
return text; | |
} | |
private static readonly ConcurrentDictionary<string, List<GistLink>> GistLinksCache = new(); | |
public static List<GistLink> GetGistApplyLinks(this GitHubGateway gateway) => | |
GetGistLinks(gateway, GistLinksId, "mix.md"); | |
private static string GetGistAliasesFilePath() => Path.Combine(ContentRootPath, "gist.aliases.txt"); | |
public static Dictionary<string, string> GetGistAliases(this GitHubGateway gateway) | |
{ | |
var aliasesPath = GetGistAliasesFilePath(); | |
if (!File.Exists(aliasesPath)) | |
return new Dictionary<string, string>(); | |
var aliases = File.ReadAllText(aliasesPath); | |
var aliasSettings = aliases.ParseKeyValueText(delimiter: " "); | |
return aliasSettings; | |
} | |
public static List<GistLink> GetGistLinks(this GitHubGateway gateway, string gistId, string name) | |
{ | |
var gistsIndex = gateway.GetGistFiles(gistId) | |
.FirstOrDefault(x => x.Key == name); | |
if (gistsIndex.Key == null) | |
throw new NotSupportedException($"Could not find '{name}' file in gist '{GistLinksId}'"); | |
return GistLinksCache.GetOrAdd(gistId + ":" + name, key => { | |
var links = GistLink.Parse(gistsIndex.Value); | |
return links; | |
}); | |
} | |
public static string[] ResolveGistAliases(this string[] gistAliases, List<GistLink> links) | |
{ | |
var hasNums = gistAliases.Any(x => int.TryParse(x, out _)); | |
if (hasNums) | |
{ | |
var resolvedAliases = new List<string>(); | |
foreach (var gistAlias in gistAliases) | |
{ | |
if (!int.TryParse(gistAlias, out var index)) | |
{ | |
resolvedAliases.Add(gistAlias); | |
continue; | |
} | |
if (index <= 0 || index > links.Count) | |
throw new ArgumentOutOfRangeException( | |
$"Invalid Index '{index}'. Valid Range: 1...{links.Count - 1}"); | |
resolvedAliases.Add(links[index - 1].Name); | |
} | |
gistAliases = resolvedAliases.ToArray(); | |
} | |
return gistAliases; | |
} | |
public static string GetRequiredString(this Dictionary<string, string> map, string name) => | |
map.TryGetValue(name, out var value) ? value : throw new Exception($"'{name}' does not exist"); | |
public static bool Exists(this Dictionary<string, string> map, string name) => map.ContainsKey(name); | |
private static bool IsGistId(string gistAlias) | |
{ | |
var testGistId = gistAlias.IndexOfAny(new[] {'-', '.', ':'}) >= 0 | |
? null | |
: gistAlias.IndexOf('/') >= 0 | |
? gistAlias.RightPart('/').Length == 40 | |
? gistAlias.LeftPart('/') | |
: null | |
: gistAlias; | |
return testGistId != null && (testGistId.Length == 20 || testGistId.Length == 32); | |
} | |
public static Dictionary<string, string> GetGistFiles(this GitHubGateway gateway, string gistId, out string gistLinkUrl) | |
{ | |
Dictionary<string, string> gistFiles; | |
if (!gistId.IsUrl()) | |
{ | |
gistLinkUrl = $"https://gist.github.com/{gistId}"; | |
gistFiles = gateway.GetGistFiles(gistId); | |
} | |
else if (gistId.StartsWith("https://gist.github.com/")) | |
{ | |
gistLinkUrl = gistId; | |
var gistParts = gistId.Substring("https://gist.github.com/".Length).Split('/'); | |
if (gistParts.Length == 3) | |
gistId = string.Join("/", gistParts.Skip(1)); | |
else if (gistParts.Length == 2) | |
gistId = gistParts[0].Length == 20 || gistParts[0].Length == 32 | |
? string.Join("/", gistParts) | |
: string.Join("/", gistParts.Skip(1)); | |
else if (gistParts.Length == 1) | |
gistId = gistParts[0]; | |
else throw new Exception($"Invalid Gist URL '{gistId}'"); | |
gistFiles = gateway.GetGistFiles(gistId); | |
} | |
else | |
{ | |
gistLinkUrl = gistId; | |
gistFiles = gateway.GetGistFilesFromUrl(gistId); | |
} | |
return gistFiles; | |
} | |
public static string ReplaceMyApp(this string input, string projectName) | |
{ | |
if (string.IsNullOrEmpty(input) || string.IsNullOrEmpty(projectName) || projectName == "MyApp") | |
return input; | |
var condensed = projectName.Replace("_", ""); | |
var projectNameKebab = CamelToKebab(condensed); | |
var splitPascalCase = condensed.SplitPascalCase(); | |
var ret = input | |
.Replace("My_App", projectName) | |
.Replace("MyApp", condensed) | |
.Replace("My App", splitPascalCase) | |
.Replace("my-app", projectNameKebab) | |
.Replace("myapp", condensed.ToLower()) | |
.Replace("my_app", projectName.ToLower()); | |
if (!Env.IsWindows) | |
ret = ret.Replace("\r", ""); | |
foreach (var replacePair in ReplaceTokens) | |
{ | |
ret = ret.Replace(replacePair.Key, replacePair.Value); | |
} | |
return ret; | |
} | |
public static string GetSafeFileName(this string path) | |
{ | |
var invalidFileNameChars = new HashSet<char>(Path.GetInvalidFileNameChars()) {':'}; | |
var safeFileName = new string(path.Where(c => !invalidFileNameChars.Contains(c)).ToArray()); | |
return safeFileName; | |
} | |
public static void DeleteDirectory(string dirPath) | |
{ | |
if (!Directory.Exists(dirPath)) return; | |
if (Verbose) $"RMDIR: {dirPath}".Print(); | |
try | |
{ | |
FileSystemVirtualFiles.DeleteDirectoryRecursive(dirPath); | |
} | |
catch (Exception ex) | |
{ | |
Print(ex); | |
} | |
try | |
{ | |
Directory.Delete(dirPath); | |
} | |
catch { } | |
} | |
public static string AssertDirectory(this DirectoryInfo dir) => | |
FileSystemVirtualFiles.AssertDirectory(dir?.FullName); | |
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 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(); | |
FileSystemVirtualFiles.AssertDirectory(Path.GetDirectoryName(tempFile)); | |
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(); | |
FileSystemVirtualFiles.AssertDirectory(Path.GetDirectoryName(cachedVersionPath)); | |
DownloadFile(zipUrl, cachedVersionPath); | |
} | |
return cachedVersionPath; | |
} | |
private static string GetCachedFilePath(string zipUrl) | |
{ | |
var safeFileName = zipUrl.GetSafeFileName(); | |
var cachedPath = Path.Combine(AppDataPath, "cache", safeFileName); | |
return cachedPath; | |
} | |
public static void DownloadFile(string downloadUrl, string fileName) | |
{ | |
try | |
{ | |
DownloadFileInternal(downloadUrl, fileName); | |
} | |
catch (WebException e) | |
{ | |
// New GitHub repos default to being created with 'main' branch, so try that if master doesn't work | |
if (e.GetStatus() == HttpStatusCode.NotFound && | |
downloadUrl.IndexOf("master.zip", StringComparison.Ordinal) >= 0) | |
{ | |
DownloadFileInternal(downloadUrl.Replace("master.zip", "main.zip"), fileName); | |
} | |
} | |
} | |
private static void DownloadFileInternal(string downloadUrl, string fileName) | |
{ | |
var webClient = new WebClient(); | |
webClient.Headers.Add(HttpHeaders.UserAgent, UserAgent); | |
try | |
{ | |
webClient.DownloadFile(downloadUrl, fileName); | |
} | |
catch (Exception ex) | |
{ | |
// Trying to download https://github.com/NetCoreTemplates/vue-desktop/archive/master.zip with token resulted in 404, changed to only use token as fallback | |
if (GitHubToken == null) | |
throw; | |
if (Verbose) $"Failed to download '{downloadUrl}': {ex.Message}\nRetrying with token...".Print(); | |
webClient.Headers.Add(HttpHeaders.Authorization, "token " + GitHubToken); | |
webClient.DownloadFile(downloadUrl, fileName); | |
} | |
} | |
public static void CopyAllTo(this string src, string dst, string[] excludePaths = null) | |
{ | |
var d = Path.DirectorySeparatorChar; | |
foreach (string dirPath in Directory.GetDirectories(src, "*.*", SearchOption.AllDirectories)) | |
{ | |
if (!excludePaths.IsEmpty() && excludePaths.Any(x => dirPath.StartsWith(x))) | |
continue; | |
if (Verbose) $"MAKEDIR {dirPath.Replace(src, dst)}".Print(); | |
try | |
{ | |
Directory.CreateDirectory(dirPath.Replace(src, dst)); | |
} | |
catch { } | |
} | |
foreach (string newPath in Directory.GetFiles(src, "*.*", SearchOption.AllDirectories)) | |
{ | |
if (!excludePaths.IsEmpty() && excludePaths.Any(x => newPath.StartsWith(x))) | |
continue; | |
try | |
{ | |
if (Verbose) $"COPY {newPath.Replace(src, dst)}".Print(); | |
if (newPath.EndsWith(".settings")) | |
{ | |
var text = File.ReadAllText(newPath); | |
if (text.Contains("debug true")) | |
{ | |
text = text.Replace("debug true", "debug false"); | |
File.WriteAllText(newPath.Replace(src, dst), text); | |
continue; | |
} | |
} | |
File.Copy(newPath, newPath.Replace(src, dst), overwrite: true); | |
} | |
catch (Exception ex) | |
{ | |
Console.WriteLine(Verbose ? ex.ToString() : ex.Message); | |
} | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
NetCoreTemplates/angular-lite-spa auth,auth-db,auth-redis,auth-dynamodb,auth-memory,postgres,sqlserver,mysql,firebird,oracle,redis,dynamodb,ravendb,mongodb,backgroundmq,rabbitmq,sqs,servicebus,redismq,autoquery,autocrudgen,validation,serverevents,openapi,postman2 | |
NetCoreTemplates/react-lite auth,auth-db,auth-redis,auth-dynamodb,auth-memory,postgres,sqlserver,mysql,firebird,oracle,redis,dynamodb,ravendb,mongodb,backgroundmq,rabbitmq,sqs,servicebus,redismq,autoquery,autocrudgen,validation,serverevents,openapi,postman2 | |
NetCoreTemplates/vue-lite auth,auth-db,auth-redis,auth-dynamodb,auth-memory,postgres,sqlserver,mysql,firebird,oracle,redis,dynamodb,ravendb,mongodb,backgroundmq,rabbitmq,sqs,servicebus,redismq,autoquery,autocrudgen,validation,serverevents,openapi,postman2 | |
NetCoreTemplates/grpc auth,auth-db,auth-redis,auth-dynamodb,auth-memory,postgres,sqlserver,mysql,firebird,oracle,redis,dynamodb,ravendb,mongodb,backgroundmq,rabbitmq,sqs,servicebus,redismq,autoquery,autocrudgen,validation,serverevents,openapi,postman2 | |
NetCoreTemplates/mvcidentity auth,auth-db,auth-redis,auth-dynamodb,auth-memory,postgres,sqlserver,mysql,firebird,oracle,redis,dynamodb,ravendb,mongodb,backgroundmq,rabbitmq,sqs,servicebus,redismq,autoquery,autocrudgen,validation,serverevents,openapi,postman2 | |
NetCoreTemplates/mvcidentityserver auth,auth-db,auth-redis,auth-dynamodb,auth-memory,postgres,sqlserver,mysql,firebird,oracle,redis,dynamodb,ravendb,mongodb,backgroundmq,rabbitmq,sqs,servicebus,redismq,autoquery,autocrudgen,validation,serverevents,openapi,postman2 | |
NetCoreTemplates/vue-desktop auth,auth-db,auth-redis,auth-dynamodb,auth-memory,postgres,sqlserver,mysql,firebird,oracle,redis,dynamodb,ravendb,mongodb,backgroundmq,rabbitmq,sqs,servicebus,redismq,autoquery,autocrudgen,validation,serverevents,openapi,postman2 | |
NetCoreTemplates/worker-rabbitmq auth,auth-db,auth-redis,auth-dynamodb,auth-memory,postgres,sqlserver,mysql,firebird,oracle,redis,dynamodb,ravendb,mongodb,backgroundmq,rabbitmq,sqs,servicebus,redismq,autoquery,autocrudgen,validation,serverevents,openapi,postman2 | |
NetCoreTemplates/worker-redismq auth,auth-db,auth-redis,auth-dynamodb,auth-memory,postgres,sqlserver,mysql,firebird,oracle,redis,dynamodb,ravendb,mongodb,backgroundmq,rabbitmq,sqs,servicebus,redismq,autoquery,autocrudgen,validation,serverevents,openapi,postman2 | |
NetCoreTemplates/worker-servicebus auth,auth-db,auth-redis,auth-dynamodb,auth-memory,postgres,sqlserver,mysql,firebird,oracle,redis,dynamodb,ravendb,mongodb,backgroundmq,rabbitmq,sqs,servicebus,redismq,autoquery,autocrudgen,validation,serverevents,openapi,postman2 | |
NetCoreTemplates/worker-sqs auth,auth-db,auth-redis,auth-dynamodb,auth-memory,postgres,sqlserver,mysql,firebird,oracle,redis,dynamodb,ravendb,mongodb,backgroundmq,rabbitmq,sqs,servicebus,redismq,autoquery,autocrudgen,validation,serverevents,openapi,postman2 | |
nukedbit/blazor-wasm-servicestack auth,auth-db,auth-redis,auth-dynamodb,auth-memory,postgres,sqlserver,mysql,firebird,oracle,redis,dynamodb,ravendb,mongodb,backgroundmq,rabbitmq,sqs,servicebus,redismq,autoquery,autocrudgen,validation,serverevents,openapi,postman2 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment