Files
YMhut-box-C-/src/YMhut.Box.Core/DevEnvironments/DevEnvironmentBuildScript.cs
T

115 lines
4.3 KiB
C#

namespace YMhut.Box.Core.DevEnvironments;
public static class DevEnvironmentBuildScript
{
public static IReadOnlyList<string> Create(DevEnvironmentInstallPlan plan, string archivePath)
{
ArgumentNullException.ThrowIfNull(plan);
ArgumentException.ThrowIfNullOrWhiteSpace(archivePath);
var recipeCommands = plan.BuildRecipe
.Split(["\r\n", "\n"], StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
.Skip(1)
.ToArray();
var environmentId = SafePathPart(plan.EnvironmentId);
var version = SafePathPart(plan.Version.Version);
var lines = new List<string>
{
"@echo off",
"setlocal EnableExtensions",
"chcp 65001 >nul 2>&1",
"set \"SCRIPT_PATH=%~f0\"",
$"set \"ARCHIVE={EscapeSetValue(archivePath)}\"",
$"set \"WORKDIR=%USERPROFILE%\\YMhutBuilds\\{environmentId}-{version}\"",
$"title YMhut Build - {EscapeEcho(environmentId)} {EscapeEcho(version)}",
$"echo Waiting for {EscapeEcho(plan.EnvironmentName)} {EscapeEcho(plan.Version.Version)} source archive...",
":wait_download",
"if not exist \"%ARCHIVE%\" (",
" timeout /t 2 >nul",
" goto :wait_download",
")",
"if not exist \"%WORKDIR%\" mkdir \"%WORKDIR%\"",
"if errorlevel 1 goto :failed",
"cd /d \"%WORKDIR%\"",
"if errorlevel 1 goto :failed",
"echo Extracting source archive...",
"tar -xf \"%ARCHIVE%\" --strip-components=1",
"if errorlevel 1 goto :failed"
};
foreach (var command in recipeCommands)
{
var executable = NormalizeRecipeCommand(command);
if (string.IsNullOrWhiteSpace(executable))
{
continue;
}
lines.Add($"echo ^> {EscapeEcho(executable)}");
lines.Add(executable);
lines.Add("if errorlevel 1 goto :failed");
}
lines.AddRange([
"echo.",
"echo Build completed successfully.",
"del \"%SCRIPT_PATH%\" >nul 2>&1",
"exit /b 0",
":failed",
"set \"EXIT_CODE=%ERRORLEVEL%\"",
"if \"%EXIT_CODE%\"==\"0\" set \"EXIT_CODE=1\"",
"echo.",
"echo [ERROR] Source build failed with exit code %EXIT_CODE%.",
"echo [ERROR] Working directory: %WORKDIR%",
"echo [ERROR] This script was kept at: %SCRIPT_PATH%",
"pause",
"exit /b %EXIT_CODE%"
]);
return lines;
}
private static string NormalizeRecipeCommand(string command)
{
if (command.StartsWith("tar -xf ", StringComparison.OrdinalIgnoreCase))
{
return string.Empty;
}
if (command.StartsWith("cd ", StringComparison.OrdinalIgnoreCase))
{
var target = command[3..].Trim().Trim('"');
if (target.Contains('*') || target.Contains('?'))
{
return string.Empty;
}
if (target.Equals("go\\src", StringComparison.OrdinalIgnoreCase))
{
return "cd /d \"%WORKDIR%\\src\"";
}
return $"cd /d \"%WORKDIR%\\{target}\"";
}
var firstToken = command.Split(' ', 2, StringSplitOptions.RemoveEmptyEntries)[0].Trim('"');
return firstToken.EndsWith(".bat", StringComparison.OrdinalIgnoreCase) ||
firstToken.EndsWith(".cmd", StringComparison.OrdinalIgnoreCase)
? "call " + command
: command;
}
private static string SafePathPart(string value)
=> string.Concat((value ?? string.Empty).Select(character =>
Path.GetInvalidFileNameChars().Contains(character) ? '_' : character));
private static string EscapeSetValue(string value)
=> value.Replace("%", "%%", StringComparison.Ordinal);
private static string EscapeEcho(string value)
=> value.Replace("^", "^^", StringComparison.Ordinal)
.Replace("&", "^&", StringComparison.Ordinal)
.Replace("|", "^|", StringComparison.Ordinal)
.Replace("<", "^<", StringComparison.Ordinal)
.Replace(">", "^>", StringComparison.Ordinal);
}