fix: automate package versions and workspace validation
Validate ShrinkSDK Workspace / unity (push) Failing after 5s
Validate ShrinkSDK Workspace / unity (push) Failing after 5s
This commit is contained in:
@@ -2,9 +2,12 @@
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
@@ -13,30 +16,11 @@ namespace ShrinkSDK.WorkspaceValidation
|
||||
{
|
||||
public static class ShrinkSdkWorkspaceValidation
|
||||
{
|
||||
private static readonly ExpectedPackage[] ExpectedPackages =
|
||||
{
|
||||
new ExpectedPackage("ShrinkApp.Core", "com.cneicy.shrink-app-core", "0.1.4"),
|
||||
new ExpectedPackage("ShrinkApp.Starter.Basic", "com.cneicy.shrink-app-starter-basic", "0.2.2"),
|
||||
new ExpectedPackage("ShrinkCommand", "com.cneicy.shrink-command", "0.2.0"),
|
||||
new ExpectedPackage("ShrinkCommand.Integration.App", "com.cneicy.shrink-command-integration-app", "0.1.3"),
|
||||
new ExpectedPackage("ShrinkCommand.Integration.EventBus", "com.cneicy.shrink-command-integration-eventbus", "0.1.2"),
|
||||
new ExpectedPackage("ShrinkCommand.Integration.Network", "com.cneicy.shrink-command-integration-network", "0.1.1"),
|
||||
new ExpectedPackage("ShrinkContext.AppAdapter", "com.cneicy.shrink-context-app-adapter", "0.1.5"),
|
||||
new ExpectedPackage("ShrinkContext.Core", "com.cneicy.shrink-context-core", "0.1.0"),
|
||||
new ExpectedPackage("ShrinkContext.EventBusAdapter", "com.cneicy.shrink-context-eventbus-adapter", "0.1.1"),
|
||||
new ExpectedPackage("ShrinkDataSaver", "com.cneicy.shrink-datasaver", "2.2.2"),
|
||||
new ExpectedPackage("ShrinkDataSaver.Integration.App", "com.cneicy.shrink-datasaver-integration-app", "0.1.3"),
|
||||
new ExpectedPackage("ShrinkDataSaver.Integration.EventBus", "com.cneicy.shrink-datasaver-integration-eventbus", "2.1.3"),
|
||||
new ExpectedPackage("ShrinkEventBus", "com.cneicy.shrink-eventbus", "2.0.1"),
|
||||
new ExpectedPackage("ShrinkEventBus.Entities", "com.cneicy.shrink-eventbus-entities", "0.1.1"),
|
||||
new ExpectedPackage("ShrinkInstaller", "com.cneicy.shrink-installer", "0.2.4"),
|
||||
new ExpectedPackage("ShrinkModFramework", "com.cneicy.shrink-mod-framework", "0.2.4"),
|
||||
new ExpectedPackage("ShrinkNetwork", "com.cneicy.shrink-network", "0.2.1"),
|
||||
new ExpectedPackage("ShrinkNetwork.Integration.App", "com.cneicy.shrink-network-integration-app", "0.1.3"),
|
||||
new ExpectedPackage("ShrinkNetwork.Integration.EventBus", "com.cneicy.shrink-network-integration-eventbus", "0.1.3"),
|
||||
new ExpectedPackage("ShrinkShared.CodeGen", "com.cneicy.shrink-shared-codegen", "0.1.0"),
|
||||
new ExpectedPackage("ShrinkTutorial", "com.cneicy.shrink-tutorial", "0.1.4")
|
||||
};
|
||||
private const string InstallerPackageName = "com.cneicy.shrink-installer";
|
||||
private const string InstallerCatalogTypeName = "ShrinkSDK.Installer.ShrinkSdkPackageCatalog";
|
||||
private static readonly Regex SemanticVersion = new Regex(
|
||||
@"^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$",
|
||||
RegexOptions.CultureInvariant);
|
||||
|
||||
public static void Run()
|
||||
{
|
||||
@@ -45,6 +29,7 @@ namespace ShrinkSDK.WorkspaceValidation
|
||||
var workspaceRoot = Path.GetFullPath(Path.Combine(Application.dataPath, ".."));
|
||||
var packages = ReadPackages(workspaceRoot);
|
||||
ValidateGraph(packages);
|
||||
ValidateInstallerCatalog(packages);
|
||||
Debug.Log($"ShrinkSDK Workspace validation passed: packages={packages.Count}");
|
||||
EditorApplication.Exit(0);
|
||||
}
|
||||
@@ -57,36 +42,97 @@ namespace ShrinkSDK.WorkspaceValidation
|
||||
|
||||
private static Dictionary<string, PackageDefinition> ReadPackages(string workspaceRoot)
|
||||
{
|
||||
var result = new Dictionary<string, PackageDefinition>(StringComparer.Ordinal);
|
||||
foreach (var expected in ExpectedPackages)
|
||||
var modulesRoot = Path.Combine(workspaceRoot, "Assets", "Modules");
|
||||
if (!Directory.Exists(modulesRoot))
|
||||
{
|
||||
var manifestPath = Path.Combine(workspaceRoot, "Assets", "Modules", expected.Directory, "package.json");
|
||||
throw new DirectoryNotFoundException($"Workspace package directory is missing: {modulesRoot}");
|
||||
}
|
||||
|
||||
var declaredDirectories = ReadDeclaredPackageDirectories(workspaceRoot);
|
||||
var manifestDirectories = Directory.EnumerateDirectories(modulesRoot)
|
||||
.Where(directory => File.Exists(Path.Combine(directory, "package.json")))
|
||||
.Select(Path.GetFullPath)
|
||||
.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
var undeclared = manifestDirectories
|
||||
.Where(directory => !declaredDirectories.Contains(directory))
|
||||
.OrderBy(directory => directory, StringComparer.OrdinalIgnoreCase)
|
||||
.ToArray();
|
||||
if (undeclared.Length > 0)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Package directories are not declared as submodules: " + string.Join(", ", undeclared));
|
||||
}
|
||||
|
||||
var result = new Dictionary<string, PackageDefinition>(StringComparer.Ordinal);
|
||||
foreach (var directory in declaredDirectories.OrderBy(value => value, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
var manifestPath = Path.Combine(directory, "package.json");
|
||||
if (!File.Exists(manifestPath))
|
||||
{
|
||||
throw new FileNotFoundException($"Required package manifest is missing: {expected.Directory}", manifestPath);
|
||||
throw new FileNotFoundException("Declared package submodule has no package.json.", manifestPath);
|
||||
}
|
||||
|
||||
var manifest = JObject.Parse(File.ReadAllText(manifestPath));
|
||||
var packageName = manifest.Value<string>("name");
|
||||
var version = manifest.Value<string>("version");
|
||||
if (packageName == null || version == null || packageName.Length == 0 || version.Length == 0)
|
||||
if (string.IsNullOrWhiteSpace(packageName) || string.IsNullOrWhiteSpace(version))
|
||||
{
|
||||
throw new InvalidOperationException($"{expected.Directory} has no valid package name or version.");
|
||||
throw new InvalidOperationException($"{directory} has no valid package name or version.");
|
||||
}
|
||||
|
||||
if (!string.Equals(packageName, expected.Name, StringComparison.Ordinal) ||
|
||||
!string.Equals(version, expected.Version, StringComparison.Ordinal))
|
||||
var validPackageName = packageName!;
|
||||
var validVersion = version!;
|
||||
if (!SemanticVersion.IsMatch(validVersion))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"{expected.Directory} must be {expected.Name}@{expected.Version}, found {packageName}@{version}.");
|
||||
throw new InvalidOperationException($"{validPackageName} has an invalid semantic version: {validVersion}");
|
||||
}
|
||||
|
||||
if (result.ContainsKey(packageName))
|
||||
if (result.ContainsKey(validPackageName))
|
||||
{
|
||||
throw new InvalidOperationException($"Duplicate package manifest name: {packageName}");
|
||||
throw new InvalidOperationException($"Duplicate package manifest name: {validPackageName}");
|
||||
}
|
||||
|
||||
result.Add(packageName, new PackageDefinition(packageName, version, manifest));
|
||||
result.Add(validPackageName, new PackageDefinition(validPackageName, validVersion, manifest));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static HashSet<string> ReadDeclaredPackageDirectories(string workspaceRoot)
|
||||
{
|
||||
var gitModulesPath = Path.Combine(workspaceRoot, ".gitmodules");
|
||||
if (!File.Exists(gitModulesPath))
|
||||
{
|
||||
throw new FileNotFoundException("Workspace .gitmodules is missing.", gitModulesPath);
|
||||
}
|
||||
|
||||
var result = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var line in File.ReadLines(gitModulesPath))
|
||||
{
|
||||
var match = Regex.Match(line, @"^\s*path\s*=\s*(?<path>.+?)\s*$", RegexOptions.CultureInvariant);
|
||||
if (!match.Success)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var relativePath = match.Groups["path"].Value.Replace('\\', '/');
|
||||
if (!relativePath.StartsWith("Assets/Modules/", StringComparison.Ordinal) ||
|
||||
relativePath.Substring("Assets/Modules/".Length).Contains("/"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var fullPath = Path.GetFullPath(Path.Combine(workspaceRoot, relativePath));
|
||||
if (!result.Add(fullPath))
|
||||
{
|
||||
throw new InvalidOperationException($"Duplicate package submodule path: {relativePath}");
|
||||
}
|
||||
}
|
||||
|
||||
if (result.Count == 0)
|
||||
{
|
||||
throw new InvalidOperationException("No Assets/Modules package submodules are declared.");
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -158,6 +204,63 @@ namespace ShrinkSDK.WorkspaceValidation
|
||||
}
|
||||
}
|
||||
|
||||
private static void ValidateInstallerCatalog(IReadOnlyDictionary<string, PackageDefinition> packages)
|
||||
{
|
||||
var catalogType = AppDomain.CurrentDomain.GetAssemblies()
|
||||
.Select(assembly => assembly.GetType(InstallerCatalogTypeName, false))
|
||||
.FirstOrDefault(type => type != null)
|
||||
?? throw new InvalidOperationException($"Installer catalog type was not loaded: {InstallerCatalogTypeName}");
|
||||
var packagesField = catalogType.GetField("Packages", BindingFlags.Static | BindingFlags.NonPublic)
|
||||
?? throw new InvalidOperationException("Installer catalog Packages field was not found.");
|
||||
var catalogItems = packagesField.GetValue(null) as IEnumerable
|
||||
?? throw new InvalidOperationException("Installer catalog Packages field is not enumerable.");
|
||||
|
||||
var catalog = new Dictionary<string, string>(StringComparer.Ordinal);
|
||||
foreach (var item in catalogItems)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
throw new InvalidOperationException("Installer catalog contains a null package entry.");
|
||||
}
|
||||
|
||||
var itemType = item.GetType();
|
||||
var packageName = itemType.GetProperty("PackageName")?.GetValue(item) as string;
|
||||
var version = itemType.GetProperty("Version")?.GetValue(item) as string;
|
||||
if (string.IsNullOrWhiteSpace(packageName) || string.IsNullOrWhiteSpace(version))
|
||||
{
|
||||
throw new InvalidOperationException("Installer catalog contains an invalid package entry.");
|
||||
}
|
||||
|
||||
var validPackageName = packageName!;
|
||||
var validVersion = version!;
|
||||
if (!catalog.TryAdd(validPackageName, validVersion))
|
||||
{
|
||||
throw new InvalidOperationException($"Installer catalog contains duplicate package: {validPackageName}");
|
||||
}
|
||||
}
|
||||
|
||||
var expectedNames = packages.Keys
|
||||
.Where(name => !string.Equals(name, InstallerPackageName, StringComparison.Ordinal))
|
||||
.ToHashSet(StringComparer.Ordinal);
|
||||
var missing = expectedNames.Except(catalog.Keys).OrderBy(name => name, StringComparer.Ordinal).ToArray();
|
||||
var unexpected = catalog.Keys.Except(expectedNames).OrderBy(name => name, StringComparer.Ordinal).ToArray();
|
||||
if (missing.Length > 0 || unexpected.Length > 0)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Installer catalog package set differs from Workspace. Missing=[{string.Join(", ", missing)}], " +
|
||||
$"unexpected=[{string.Join(", ", unexpected)}]");
|
||||
}
|
||||
|
||||
foreach (var packageName in expectedNames)
|
||||
{
|
||||
if (!string.Equals(catalog[packageName], packages[packageName].Version, StringComparison.Ordinal))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Installer catalog requires {packageName} {catalog[packageName]}, but the Workspace provides {packages[packageName].Version}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class PackageDefinition
|
||||
{
|
||||
public PackageDefinition(string name, string version, JObject manifest)
|
||||
@@ -171,20 +274,6 @@ namespace ShrinkSDK.WorkspaceValidation
|
||||
public string Version { get; }
|
||||
public JObject Manifest { get; }
|
||||
}
|
||||
|
||||
private readonly struct ExpectedPackage
|
||||
{
|
||||
public ExpectedPackage(string directory, string name, string version)
|
||||
{
|
||||
Directory = directory;
|
||||
Name = name;
|
||||
Version = version;
|
||||
}
|
||||
|
||||
public string Directory { get; }
|
||||
public string Name { get; }
|
||||
public string Version { get; }
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
Submodule Assets/Modules/ShrinkEventBus.Entities updated: 368870f0df...b888d8d247
Submodule Assets/Modules/ShrinkInstaller updated: 9e92d38a53...30a6a11344
Reference in New Issue
Block a user