feat: add Launcher Next .NET 8 foundation
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
namespace Launcher.Next.Core;
|
||||
|
||||
public enum LauncherMode { Legacy, NextPilot, NextPreferred }
|
||||
public sealed record ProductInstallation(string ExecutablePath, string ProductVersion, string Architecture, IReadOnlyList<string> Profiles);
|
||||
public sealed record PackageFile(string Path, long Length, string Sha256, bool Optional = false);
|
||||
public sealed record StartupDefinition(string Type, string Command);
|
||||
public sealed record PackageManifest(
|
||||
int SchemaVersion, string PackageId, string Version, string Product, int ProductYear,
|
||||
string Architecture, string EntryAssembly, IReadOnlyList<PackageFile> Files, StartupDefinition Startup,
|
||||
string? MinimumLauncherVersion = null, IReadOnlyList<string>? CompatibleAutoCadSeries = null,
|
||||
string? RequiredRuntime = null, IReadOnlyList<string>? Dependencies = null,
|
||||
IReadOnlyList<string>? OptionalResources = null, string? RolloutChannel = null,
|
||||
bool Disabled = false, bool RollbackSafe = false, string? ReleaseNotesReference = null);
|
||||
public sealed record ValidationIssue(string Code, string Message);
|
||||
public sealed record ValidationResult(IReadOnlyList<ValidationIssue> Issues)
|
||||
{ public bool IsValid => Issues.Count == 0; public static ValidationResult Success { get; } = new([]); }
|
||||
public sealed record PackageProgress(string Stage, int Completed, int Total);
|
||||
public sealed record ActivationResult(string CurrentVersion, string? PreviousVersion);
|
||||
public sealed record LaunchRequest(ProductInstallation Product, string Profile, string StartupScriptPath, bool DryRun);
|
||||
public sealed record PreparedLaunch(string Executable, IReadOnlyList<string> Arguments, string WorkingDirectory, bool WouldStart);
|
||||
|
||||
public interface IProductDiscovery { ProductInstallation? DiscoverPlant3D(int year); }
|
||||
public interface IPackageCatalogClient { Task<PackageManifest?> GetManifestAsync(string packageId, CancellationToken cancellationToken); }
|
||||
public interface IPackageManifestValidator { ValidationResult Validate(PackageManifest manifest, int productYear, string architecture); }
|
||||
public interface IPackageSynchronizer { Task<ActivationResult> SynchronizeAsync(PackageManifest manifest, string sourceRoot, CancellationToken cancellationToken, IProgress<PackageProgress>? progress = null); }
|
||||
public interface ILocalPackageStore { Task<string?> GetCurrentVersionAsync(string packageId, CancellationToken cancellationToken); Task<string?> GetPreviousVersionAsync(string packageId, CancellationToken cancellationToken); }
|
||||
public interface IPackageActivator { Task<ActivationResult> ActivateAsync(string packageId, string version, CancellationToken cancellationToken); }
|
||||
public interface IRollbackManager { Task<string?> RollbackAsync(string packageId, CancellationToken cancellationToken); }
|
||||
public interface IAutodeskProcessLauncher { PreparedLaunch Prepare(LaunchRequest request); }
|
||||
public interface IStartupScriptBuilder { string BuildNetloadScript(string validatedPackageRoot, string entryAssembly); }
|
||||
public interface ILauncherConfigurationProvider { LauncherMode GetMode(); }
|
||||
public interface ILauncherTelemetry { void Write(string eventType, IReadOnlyDictionary<string, object?>? fields = null); }
|
||||
public interface IClock { DateTimeOffset UtcNow { get; } }
|
||||
public interface IFileHasher { Task<string> Sha256Async(string path, CancellationToken cancellationToken); }
|
||||
@@ -0,0 +1 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk" />
|
||||
@@ -0,0 +1,27 @@
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Launcher.Next.Core;
|
||||
|
||||
public sealed class PackageManifestValidator : IPackageManifestValidator
|
||||
{
|
||||
private static readonly string[] Forbidden = ["acmgd.dll", "acdbmgd.dll", "accoremgd.dll", "adwindows.dll"];
|
||||
public ValidationResult Validate(PackageManifest manifest, int productYear, string architecture)
|
||||
{
|
||||
var issues = new List<ValidationIssue>();
|
||||
if (manifest.SchemaVersion != 1) issues.Add(new("schema", "Unsupported schemaVersion."));
|
||||
if (string.IsNullOrWhiteSpace(manifest.PackageId) || string.IsNullOrWhiteSpace(manifest.Version)) issues.Add(new("identity", "packageId and version are required."));
|
||||
if (!string.Equals(manifest.Product, "Plant3D", StringComparison.OrdinalIgnoreCase) || manifest.ProductYear != productYear) issues.Add(new("product", "Package is incompatible with Plant 3D."));
|
||||
if (!string.Equals(manifest.Architecture, architecture, StringComparison.OrdinalIgnoreCase)) issues.Add(new("architecture", "Package architecture is incompatible."));
|
||||
var paths = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var file in manifest.Files)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(file.Path) || Path.IsPathRooted(file.Path) || file.Path.Split(['/', '\\']).Any(x => x is ".." or "")) issues.Add(new("path", $"Invalid package path: {file.Path}"));
|
||||
if (!paths.Add(file.Path)) issues.Add(new("duplicate", $"Duplicate package path: {file.Path}"));
|
||||
var name = Path.GetFileName(file.Path);
|
||||
if (Forbidden.Contains(name, StringComparer.OrdinalIgnoreCase) || name.StartsWith("PnP", StringComparison.OrdinalIgnoreCase) || name.StartsWith("Autodesk.ProcessPower", StringComparison.OrdinalIgnoreCase)) issues.Add(new("autodesk", $"Forbidden Autodesk assembly: {name}"));
|
||||
if (!Regex.IsMatch(file.Sha256 ?? string.Empty, "^[A-Fa-f0-9]{64}$")) issues.Add(new("hash", $"Invalid SHA-256 for {file.Path}"));
|
||||
}
|
||||
if (!paths.Contains(manifest.EntryAssembly)) issues.Add(new("entry", "entryAssembly must be listed in files."));
|
||||
return issues.Count == 0 ? ValidationResult.Success : new ValidationResult(issues);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
using System.Text.Json;
|
||||
namespace Launcher.Next.Core;
|
||||
public static class PackageManifestJson
|
||||
{
|
||||
private static readonly JsonSerializerOptions Options = new() { PropertyNameCaseInsensitive = true };
|
||||
public static PackageManifest Parse(string json) => JsonSerializer.Deserialize<PackageManifest>(json, Options) ?? throw new InvalidOperationException("MANIFEST_EMPTY");
|
||||
}
|
||||
Reference in New Issue
Block a user