5 Commits
Author SHA1 Message Date
cneicy c42d24bcde fix(installer): refresh package state after resolution
Publish UPM package / publish (push) Successful in 2s
2026-08-26 13:01:52 +08:00
cneicy 96480dd488 feat(installer): rebuild package manager with ui toolkit
Publish UPM package / publish (push) Successful in 2s
2026-08-26 12:48:35 +08:00
cneicy 9bdc57fd88 fix(installer): resolve updated manifests
Publish UPM package / publish (push) Successful in 3s
2026-08-26 12:02:48 +08:00
cneicy c346eef5b2 fix(installer): tolerate diagnostics during resolution
Publish UPM package / publish (push) Successful in 6s
2026-08-26 11:56:13 +08:00
cneicy a4eebd0393 fix(installer): pin module dependency releases
Publish UPM package / publish (push) Successful in 2s
2026-08-26 11:04:22 +08:00
8 changed files with 728 additions and 128 deletions
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 7a6ed64b6b38b5e4eae9b86516add8b7
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,74 @@
#if UNITY_EDITOR
using System;
using System.Collections;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
public static class ShrinkInstallerDevelopmentValidation
{
public static void Run()
{
try
{
var windowType = FindType("ShrinkSDK.Installer.ShrinkSdkInstallerWindow");
var window = ScriptableObject.CreateInstance(windowType) as EditorWindow
?? throw new InvalidOperationException("无法创建包管理窗口。");
try
{
windowType.GetMethod("CreateGUI", BindingFlags.Public | BindingFlags.Instance)?.Invoke(window, null);
var texts = window.rootVisualElement.Query<Label>().ToList()
.Select(label => label.text)
.Where(text => !string.IsNullOrWhiteSpace(text))
.ToArray();
Require(texts.Contains("ShrinkSDK 包管理"), "缺少中文窗口标题。");
Require(texts.Any(text => text.Contains("以 Context 为组合基础")), "缺少 Context 架构说明。");
Require(window.rootVisualElement.Query<Button>().ToList().Any(button => button.text == "安装组合"),
"缺少组合安装按钮。");
Require(window.rootVisualElement.Query<Foldout>().ToList().Any(), "组合内容未使用 UI Toolkit Foldout。");
var catalog = FindType("ShrinkSDK.Installer.ShrinkSdkPackageCatalog");
var packages = catalog.GetField("Packages", BindingFlags.Static | BindingFlags.NonPublic)
?.GetValue(null) as IEnumerable;
var bundles = catalog.GetField("Bundles", BindingFlags.Static | BindingFlags.NonPublic)
?.GetValue(null) as IEnumerable;
Require(Count(packages) == 20, "包目录数量不是 20。");
Require(Count(bundles) == 8, "推荐组合数量不是 8。");
}
finally
{
UnityEngine.Object.DestroyImmediate(window);
}
Debug.Log("ShrinkInstaller development validation passed: UI Toolkit, Chinese UI, package status surface and Context bundles.");
EditorApplication.Exit(0);
}
catch (Exception exception)
{
Debug.LogException(exception);
EditorApplication.Exit(1);
}
}
private static int Count(IEnumerable enumerable)
{
var count = 0;
foreach (var _ in enumerable)
count++;
return count;
}
private static Type FindType(string fullName) => AppDomain.CurrentDomain.GetAssemblies()
.Select(assembly => assembly.GetType(fullName, false))
.FirstOrDefault(type => type != null)
?? throw new InvalidOperationException("未加载类型:" + fullName);
private static void Require(bool condition, string message)
{
if (!condition)
throw new InvalidOperationException(message);
}
}
#endif
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b3983ab793020f44bbf8750d13b3f2b6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+435 -78
View File
@@ -2,138 +2,495 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEditor.PackageManager;
using UnityEditor.PackageManager.Requests;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.UIElements;
namespace ShrinkSDK.Installer
{
internal sealed class ShrinkSdkInstallerWindow : EditorWindow
{
private readonly Dictionary<string, UnityEditor.PackageManager.PackageInfo> _installed =
new(StringComparer.Ordinal);
private ListRequest? _listRequest;
private int _selectedPackageIndex;
private string _status = "Ready";
private MessageType _statusType = MessageType.Info;
private bool _resolvePending;
private double _resolveRequestedAt;
private bool _listRetryPending;
private double _nextListRetryAt;
private int _listRetryCount;
private HelpBox? _statusBox;
private Label? _registryState;
private VisualElement? _page;
private ToolbarToggle? _bundleTab;
private ToolbarToggle? _packageTab;
private string _searchText = string.Empty;
private bool _showBundles = true;
[MenuItem("ShrinkSDK/Packages")]
[MenuItem("ShrinkSDK/包管理")]
private static void Open()
{
GetWindow<ShrinkSdkInstallerWindow>("ShrinkSDK Packages");
var window = GetWindow<ShrinkSdkInstallerWindow>("ShrinkSDK 包管理");
window.minSize = new Vector2(820f, 560f);
window.Show();
}
private void OnEnable()
public void CreateGUI()
{
RefreshDiagnostics();
var root = rootVisualElement;
root.Clear();
root.style.flexDirection = FlexDirection.Column;
root.Add(BuildHeader());
root.Add(BuildToolbar());
_page = new ScrollView(ScrollViewMode.Vertical);
_page.style.flexGrow = 1f;
_page.style.paddingLeft = 16f;
_page.style.paddingRight = 16f;
_page.style.paddingTop = 12f;
_page.style.paddingBottom = 16f;
root.Add(_page);
_statusBox = new HelpBox("正在读取已安装包……", HelpBoxMessageType.Info);
_statusBox.style.marginLeft = 16f;
_statusBox.style.marginRight = 16f;
_statusBox.style.marginBottom = 12f;
root.Add(_statusBox);
RenderPage();
RefreshPackages();
}
private void OnGUI()
private VisualElement BuildHeader()
{
EditorGUILayout.LabelField("ShrinkSDK", EditorStyles.boldLabel);
EditorGUILayout.Space(4f);
EditorGUILayout.LabelField("Registry", ShrinkSdkManifestStore.HasShrinkSdkRegistry()
? ShrinkSdkPackageCatalog.RegistryUrl
: "Not configured");
var header = new VisualElement();
header.style.paddingLeft = 16f;
header.style.paddingRight = 16f;
header.style.paddingTop = 14f;
header.style.paddingBottom = 10f;
header.style.borderBottomWidth = 1f;
header.style.borderBottomColor = new Color(0.25f, 0.25f, 0.25f);
header.style.minHeight = 104f;
using (new EditorGUI.DisabledScope(IsBusy))
var title = new Label("ShrinkSDK 包管理");
title.style.fontSize = 20f;
title.style.unityFontStyleAndWeight = FontStyle.Bold;
title.style.height = 28f;
title.style.unityTextAlign = TextAnchor.MiddleLeft;
header.Add(title);
var description = new Label("以 Context 为组合基础,按用途安装固定版本的模块与集成包。");
description.style.marginTop = 4f;
description.style.minHeight = 20f;
description.style.whiteSpace = WhiteSpace.Normal;
header.Add(description);
var registryRow = new VisualElement();
registryRow.style.flexDirection = FlexDirection.Row;
registryRow.style.alignItems = Align.Center;
registryRow.style.marginTop = 10f;
registryRow.style.minHeight = 22f;
registryRow.Add(new Label("软件源:"));
_registryState = new Label();
_registryState.style.flexGrow = 1f;
_registryState.style.unityFontStyleAndWeight = FontStyle.Bold;
registryRow.Add(_registryState);
var configure = new Button(ConfigureRegistry) { text = "配置软件源" };
configure.style.width = 110f;
registryRow.Add(configure);
header.Add(registryRow);
RefreshRegistryState();
return header;
}
private VisualElement BuildToolbar()
{
var toolbar = new Toolbar();
_bundleTab = new ToolbarToggle { text = "推荐组合", value = true };
_packageTab = new ToolbarToggle { text = "全部包" };
_bundleTab.RegisterValueChangedCallback(evt =>
{
if (GUILayout.Button("Configure Registry"))
{
ConfigureRegistry();
}
if (!evt.newValue)
return;
_showBundles = true;
_packageTab?.SetValueWithoutNotify(false);
RenderPage();
});
_packageTab.RegisterValueChangedCallback(evt =>
{
if (!evt.newValue)
return;
_showBundles = false;
_bundleTab?.SetValueWithoutNotify(false);
RenderPage();
});
toolbar.Add(_bundleTab);
toolbar.Add(_packageTab);
toolbar.Add(new ToolbarSpacer { flex = true });
EditorGUILayout.Space(8f);
if (GUILayout.Button("Install Starter Basic"))
{
BeginInstall(ShrinkSdkPackageCatalog.StarterBasic);
}
var labels = ShrinkSdkPackageCatalog.Packages.Select(entry =>
entry.DisplayName + " " + entry.Version).ToArray();
_selectedPackageIndex = EditorGUILayout.Popup("Package", _selectedPackageIndex, labels);
if (GUILayout.Button("Install Selected Package"))
{
BeginInstall(ShrinkSdkPackageCatalog.Packages[_selectedPackageIndex]);
}
if (GUILayout.Button("Refresh Diagnostics"))
{
RefreshDiagnostics();
}
}
EditorGUILayout.Space(8f);
EditorGUILayout.HelpBox(_status, _statusType);
var search = new ToolbarSearchField();
search.style.width = 230f;
search.RegisterValueChangedCallback(evt =>
{
_searchText = evt.newValue?.Trim() ?? string.Empty;
RenderPage();
});
toolbar.Add(search);
toolbar.Add(new ToolbarButton(RefreshPackages) { text = "刷新状态" });
return toolbar;
}
private bool IsBusy => _listRequest != null;
private void Update()
private void RenderPage()
{
if (_listRequest != null && _listRequest.IsCompleted)
if (_page == null)
return;
_page.Clear();
if (_showBundles)
RenderBundles(_page);
else
RenderPackages(_page);
}
private void RenderBundles(VisualElement parent)
{
foreach (var bundle in ShrinkSdkPackageCatalog.Bundles.Where(MatchesSearch))
{
var request = _listRequest;
_listRequest = null;
if (request.Status == StatusCode.Success)
var card = CreateCard();
var titleRow = new VisualElement();
titleRow.style.flexDirection = FlexDirection.Row;
titleRow.style.alignItems = Align.Center;
var title = new Label(bundle.DisplayName + (bundle.Recommended ? " · 推荐" : string.Empty));
title.style.fontSize = 15f;
title.style.unityFontStyleAndWeight = FontStyle.Bold;
title.style.flexGrow = 1f;
titleRow.Add(title);
var exact = bundle.Packages.Count(IsExactInstalled);
var button = new Button(() => BeginInstall(bundle.InstallRoots));
if (exact == bundle.Packages.Count)
{
var installed = request.Result.Count(package => package.name.StartsWith("com.cneicy.", StringComparison.Ordinal));
SetStatus("Registry is configured. Installed ShrinkSDK packages: " + installed, MessageType.Info);
button.text = "已完成";
button.SetEnabled(false);
}
else
{
SetStatus("Package diagnostics failed: " + request.Error.message, MessageType.Warning);
button.text = exact > 0 ? "补齐组合" : "安装组合";
button.SetEnabled(!IsBusy);
}
button.style.width = 100f;
titleRow.Add(button);
card.Add(titleRow);
var summary = new Label(bundle.Summary);
summary.style.whiteSpace = WhiteSpace.Normal;
summary.style.marginTop = 5f;
card.Add(summary);
var progress = new Label($"已就绪 {exact}/{bundle.Packages.Count}");
progress.style.marginTop = 8f;
progress.style.color = exact == bundle.Packages.Count
? new Color(0.35f, 0.75f, 0.45f)
: new Color(0.65f, 0.65f, 0.65f);
card.Add(progress);
var contents = new Foldout { text = "包含的包", value = false };
contents.style.marginTop = 4f;
foreach (var package in bundle.Packages)
contents.Add(CreateCompactPackageRow(package));
card.Add(contents);
parent.Add(card);
}
}
private void RenderPackages(VisualElement parent)
{
var packages = ShrinkSdkPackageCatalog.Packages.Where(MatchesSearch).ToArray();
foreach (var group in packages.GroupBy(entry => entry.Layer))
{
var heading = new Label(GetLayerName(group.Key));
heading.style.fontSize = 15f;
heading.style.unityFontStyleAndWeight = FontStyle.Bold;
heading.style.marginTop = 8f;
heading.style.marginBottom = 4f;
parent.Add(heading);
foreach (var package in group)
parent.Add(CreatePackageCard(package));
}
}
private VisualElement CreatePackageCard(ShrinkSdkPackageEntry package)
{
var card = CreateCard();
var row = new VisualElement();
row.style.flexDirection = FlexDirection.Row;
row.style.alignItems = Align.Center;
var text = new VisualElement();
text.style.flexGrow = 1f;
var title = new Label(package.DisplayName);
title.style.unityFontStyleAndWeight = FontStyle.Bold;
text.Add(title);
var id = new Label(package.PackageName + " · 固定版本 " + package.Version);
id.style.fontSize = 10f;
id.style.color = new Color(0.58f, 0.58f, 0.58f);
id.style.marginTop = 2f;
text.Add(id);
var summary = new Label(package.Summary);
summary.style.whiteSpace = WhiteSpace.Normal;
summary.style.marginTop = 4f;
text.Add(summary);
row.Add(text);
var action = CreatePackageAction(package);
action.style.width = 118f;
action.style.marginLeft = 12f;
row.Add(action);
card.Add(row);
return card;
}
private VisualElement CreateCompactPackageRow(ShrinkSdkPackageEntry package)
{
var row = new VisualElement();
row.style.flexDirection = FlexDirection.Row;
row.style.alignItems = Align.Center;
row.style.marginTop = 3f;
var name = new Label(package.DisplayName);
name.style.flexGrow = 1f;
row.Add(name);
var status = new Label(GetInstalledStatus(package));
status.style.fontSize = 10f;
status.style.color = IsExactInstalled(package)
? new Color(0.35f, 0.75f, 0.45f)
: new Color(0.65f, 0.65f, 0.65f);
row.Add(status);
return row;
}
private Button CreatePackageAction(ShrinkSdkPackageEntry package)
{
var button = new Button(() => BeginInstall(new[] { package }));
if (!_installed.TryGetValue(package.PackageName, out var installed))
button.text = "安装 " + package.Version;
else if (string.Equals(installed.version, package.Version, StringComparison.Ordinal))
{
button.text = "已安装 " + installed.version;
button.SetEnabled(false);
}
else
button.text = installed.version + " → " + package.Version;
if (IsBusy)
button.SetEnabled(false);
return button;
}
private static VisualElement CreateCard()
{
var card = new VisualElement();
card.style.marginBottom = 8f;
card.style.paddingLeft = 12f;
card.style.paddingRight = 12f;
card.style.paddingTop = 10f;
card.style.paddingBottom = 10f;
card.style.borderLeftWidth = 1f;
card.style.borderRightWidth = 1f;
card.style.borderTopWidth = 1f;
card.style.borderBottomWidth = 1f;
card.style.borderLeftColor = new Color(0.28f, 0.28f, 0.28f);
card.style.borderRightColor = new Color(0.28f, 0.28f, 0.28f);
card.style.borderTopColor = new Color(0.28f, 0.28f, 0.28f);
card.style.borderBottomColor = new Color(0.28f, 0.28f, 0.28f);
card.style.borderTopLeftRadius = 4f;
card.style.borderTopRightRadius = 4f;
card.style.borderBottomLeftRadius = 4f;
card.style.borderBottomRightRadius = 4f;
return card;
}
private bool MatchesSearch(ShrinkSdkPackageBundle bundle) =>
string.IsNullOrEmpty(_searchText) ||
bundle.DisplayName.IndexOf(_searchText, StringComparison.OrdinalIgnoreCase) >= 0 ||
bundle.Summary.IndexOf(_searchText, StringComparison.OrdinalIgnoreCase) >= 0 ||
bundle.Packages.Any(MatchesSearch);
private bool MatchesSearch(ShrinkSdkPackageEntry package) =>
string.IsNullOrEmpty(_searchText) ||
package.DisplayName.IndexOf(_searchText, StringComparison.OrdinalIgnoreCase) >= 0 ||
package.Summary.IndexOf(_searchText, StringComparison.OrdinalIgnoreCase) >= 0 ||
package.PackageName.IndexOf(_searchText, StringComparison.OrdinalIgnoreCase) >= 0;
private bool IsExactInstalled(ShrinkSdkPackageEntry package) =>
_installed.TryGetValue(package.PackageName, out var installed) &&
string.Equals(installed.version, package.Version, StringComparison.Ordinal);
private string GetInstalledStatus(ShrinkSdkPackageEntry package)
{
if (!_installed.TryGetValue(package.PackageName, out var installed))
return "未安装 · " + package.Version;
return string.Equals(installed.version, package.Version, StringComparison.Ordinal)
? "已安装 " + installed.version
: "已安装 " + installed.version + " · 固定 " + package.Version;
}
private bool IsBusy => _listRequest != null || _resolvePending || _listRetryPending;
private void Update()
{
if (_resolvePending && EditorApplication.timeSinceStartup - _resolveRequestedAt >= 0.5d)
{
_resolvePending = false;
SetStatus("正在等待依赖解析并刷新安装状态。", HelpBoxMessageType.Info);
StartListRequest();
}
if (_listRetryPending && EditorApplication.timeSinceStartup >= _nextListRetryAt)
{
_listRetryPending = false;
StartListRequest();
}
if (_listRequest == null || !_listRequest.IsCompleted)
return;
var list = _listRequest;
_listRequest = null;
if (list.Status == StatusCode.Success)
{
_listRetryCount = 0;
_installed.Clear();
foreach (var package in list.Result)
_installed[package.name] = package;
var count = _installed.Keys.Count(name => name.StartsWith("com.cneicy.", StringComparison.Ordinal));
SetStatus("状态已刷新:当前项目共解析到 " + count + " 个 ShrinkSDK 包。", HelpBoxMessageType.Info);
}
else if (string.IsNullOrWhiteSpace(list.Error?.message) && _listRetryCount < 4)
{
_listRetryCount++;
_listRetryPending = true;
_nextListRetryAt = EditorApplication.timeSinceStartup + 1d;
SetStatus("Unity 正在完成依赖解析,稍后自动重试安装状态(" + _listRetryCount + "/4)。",
HelpBoxMessageType.Info);
}
else
{
SetStatus("读取安装状态失败:" + GetRequestError(list.Error), HelpBoxMessageType.Warning);
}
RenderPage();
}
private void ConfigureRegistry()
{
if (IsBusy)
return;
try
{
ShrinkSdkManifestStore.EnsureShrinkSdkRegistry();
Client.Resolve();
SetStatus("Registry configuration written. Unity is resolving packages.", MessageType.Info);
RefreshRegistryState();
BeginResolve("软件源已写入,Unity 正在解析依赖。");
}
catch (Exception exception)
{
SetStatus(exception.Message, MessageType.Error);
SetStatus(exception.Message, HelpBoxMessageType.Error);
}
}
private void BeginInstall(ShrinkSdkPackageEntry entry)
{
try
{
ShrinkSdkManifestStore.EnsurePackageInstallation(entry);
Client.Resolve();
SetStatus(
"Pinned " + entry.PackageName + " " + entry.Version + " in Packages/manifest.json. Unity is resolving packages.",
MessageType.Info);
}
catch (Exception exception)
{
SetStatus(exception.Message, MessageType.Error);
}
}
private void RefreshDiagnostics()
private void BeginInstall(IEnumerable<ShrinkSdkPackageEntry> packages)
{
if (IsBusy)
{
return;
try
{
var roots = ShrinkSdkPackageCatalog.DistinctInstallRoots(packages);
ShrinkSdkManifestStore.EnsurePackageInstallation(roots);
RefreshRegistryState();
BeginResolve("已写入 " + roots.Count + " 个固定版本入口,Unity 正在安装并解析依赖。");
}
catch (Exception exception)
{
SetStatus(exception.Message, HelpBoxMessageType.Error);
}
_listRequest = Client.List(true);
SetStatus("Refreshing installed package diagnostics.", MessageType.Info);
}
private void SetStatus(string message, MessageType messageType)
private void BeginResolve(string status)
{
_status = message;
_statusType = messageType;
Repaint();
_listRequest = null;
Client.Resolve();
_listRetryPending = false;
_listRetryCount = 0;
_resolvePending = true;
_resolveRequestedAt = EditorApplication.timeSinceStartup;
SetStatus(status, HelpBoxMessageType.Info);
RenderPage();
}
private void RefreshPackages()
{
if (IsBusy)
return;
_listRetryCount = 0;
StartListRequest();
SetStatus("正在读取当前项目的直接与间接依赖。", HelpBoxMessageType.Info);
RenderPage();
}
private void StartListRequest()
{
if (_listRequest == null)
_listRequest = Client.List(true, true);
}
private void RefreshRegistryState()
{
if (_registryState == null)
return;
try
{
var configured = ShrinkSdkManifestStore.HasShrinkSdkRegistry();
_registryState.text = configured ? "已配置 · " + ShrinkSdkPackageCatalog.RegistryUrl : "未配置";
_registryState.style.color = configured
? new Color(0.35f, 0.75f, 0.45f)
: new Color(0.85f, 0.55f, 0.25f);
}
catch (Exception exception)
{
_registryState.text = "无法读取 Packages/manifest.json";
_registryState.style.color = new Color(0.85f, 0.35f, 0.3f);
SetStatus(exception.Message, HelpBoxMessageType.Error);
}
}
private void SetStatus(string message, HelpBoxMessageType type)
{
if (_statusBox == null)
return;
_statusBox.text = message;
_statusBox.messageType = type;
}
private static string GetRequestError(Error? error)
{
var message = error?.message;
return string.IsNullOrWhiteSpace(message)
? "Unity Package Manager 未返回详细原因,请在依赖解析结束后刷新状态。"
: message!;
}
private static string GetLayerName(ShrinkSdkPackageLayer layer) => layer switch
{
ShrinkSdkPackageLayer.Context => "Context 组合基础",
ShrinkSdkPackageLayer.Host => "应用与组合根",
ShrinkSdkPackageLayer.Feature => "独立功能模块",
ShrinkSdkPackageLayer.Integration => "模块集成",
ShrinkSdkPackageLayer.Tooling => "编译与工具基础",
_ => layer.ToString()
};
}
}
#endif
+10 -15
View File
@@ -31,19 +31,22 @@ namespace ShrinkSDK.Installer
AtomicWrite(manifest.ToString(Formatting.Indented) + Environment.NewLine);
}
internal static void EnsurePackageInstallation(ShrinkSdkPackageEntry package)
internal static void EnsurePackageInstallation(
System.Collections.Generic.IEnumerable<ShrinkSdkPackageEntry> packages)
{
var manifest = ReadManifest();
EnsureShrinkSdkRegistry(manifest);
EnsurePinnedDependency(manifest, package.PackageName, package.Version);
if (package.RequiresUniTask)
var requiresUniTask = false;
foreach (var package in packages)
{
EnsurePinnedDependency(
manifest,
ShrinkSdkPackageCatalog.UniTaskPackageName,
ShrinkSdkPackageCatalog.UniTaskGitUrl);
EnsurePinnedDependency(manifest, package.PackageName, package.Version);
requiresUniTask |= package.RequiresUniTask;
}
if (requiresUniTask)
EnsurePinnedDependency(manifest, ShrinkSdkPackageCatalog.UniTaskPackageName,
ShrinkSdkPackageCatalog.UniTaskGitUrl);
AtomicWrite(manifest.ToString(Formatting.Indented) + Environment.NewLine);
}
@@ -104,14 +107,6 @@ namespace ShrinkSDK.Installer
manifest["dependencies"] = dependencies;
}
var existing = dependencies[packageName];
if (existing != null && !string.Equals(existing.Value<string>(), version, StringComparison.Ordinal))
{
throw new InvalidOperationException(
"Packages/manifest.json already pins " + packageName + " to " + existing.Value<string>() +
". ShrinkSDK requires the fixed version " + version + ".");
}
dependencies[packageName] = version;
}
+175 -26
View File
@@ -2,25 +2,61 @@
#nullable enable
using System.Collections.Generic;
using System.Linq;
namespace ShrinkSDK.Installer
{
internal enum ShrinkSdkPackageLayer
{
Context,
Host,
Feature,
Integration,
Tooling
}
internal sealed class ShrinkSdkPackageEntry
{
public ShrinkSdkPackageEntry(string displayName, string packageName, string version, bool requiresUniTask = false)
public ShrinkSdkPackageEntry(string displayName, string summary, string packageName, string version,
ShrinkSdkPackageLayer layer, bool requiresUniTask = false)
{
DisplayName = displayName;
Summary = summary;
PackageName = packageName;
Version = version;
Layer = layer;
RequiresUniTask = requiresUniTask;
}
public string DisplayName { get; }
public string Summary { get; }
public string PackageName { get; }
public string Version { get; }
public ShrinkSdkPackageLayer Layer { get; }
public bool RequiresUniTask { get; }
}
internal sealed class ShrinkSdkPackageBundle
{
public ShrinkSdkPackageBundle(string displayName, string summary,
IReadOnlyList<ShrinkSdkPackageEntry> packages,
IReadOnlyList<ShrinkSdkPackageEntry>? installRoots = null,
bool recommended = false)
{
DisplayName = displayName;
Summary = summary;
Packages = packages;
InstallRoots = installRoots ?? packages;
Recommended = recommended;
}
public string DisplayName { get; }
public string Summary { get; }
public IReadOnlyList<ShrinkSdkPackageEntry> Packages { get; }
public IReadOnlyList<ShrinkSdkPackageEntry> InstallRoots { get; }
public bool Recommended { get; }
}
internal static class ShrinkSdkPackageCatalog
{
internal const string RegistryName = "ShrinkSDK";
@@ -29,35 +65,148 @@ namespace ShrinkSDK.Installer
internal const string UniTaskPackageName = "com.cysharp.unitask";
internal const string UniTaskGitUrl = "https://github.com/Cysharp/UniTask.git?path=src/UniTask/Assets/Plugins/UniTask#7c0f199fe0d3fc528024488ccd671e6c7b27745b";
internal static readonly ShrinkSdkPackageEntry StarterBasic = new(
"ShrinkApp Starter Basic",
"com.cneicy.shrink-app-starter-basic",
"0.1.1",
requiresUniTask: true);
internal static readonly ShrinkSdkPackageEntry ContextCore = Package(
"Context 核心", "可逆效应、响应式依赖与组件生命周期,是框架的组合基础。",
"com.cneicy.shrink-context-core", "0.1.0", ShrinkSdkPackageLayer.Context, true);
internal static readonly ShrinkSdkPackageEntry ContextAppAdapter = Package(
"Context 应用适配", "用 ContextLoader 承载 ShrinkApp 组合根和运行时诊断。",
"com.cneicy.shrink-context-app-adapter", "0.1.3", ShrinkSdkPackageLayer.Context, true);
internal static readonly ShrinkSdkPackageEntry ContextEventBusAdapter = Package(
"Context 事件适配", "把事件订阅纳入 Context 的安装与自动撤回。",
"com.cneicy.shrink-context-eventbus-adapter", "0.1.0", ShrinkSdkPackageLayer.Context, true);
internal static readonly ShrinkSdkPackageEntry AppCore = Package(
"应用宿主", "统一启动、服务门面与应用生命周期。",
"com.cneicy.shrink-app-core", "0.1.2", ShrinkSdkPackageLayer.Host, true);
internal static readonly ShrinkSdkPackageEntry StarterBasic = Package(
"基础应用起步包", "默认 Context 组合根,包含命令、存档、网络及其集成。",
"com.cneicy.shrink-app-starter-basic", "0.2.0", ShrinkSdkPackageLayer.Host, true);
internal static readonly ShrinkSdkPackageEntry Command = Package(
"命令系统", "命令路由、参数、权限与自动注册。",
"com.cneicy.shrink-command", "0.2.0", ShrinkSdkPackageLayer.Feature, true);
internal static readonly ShrinkSdkPackageEntry DataSaver = Package(
"存档系统", "异步存档、设置、备份恢复与数据观察。",
"com.cneicy.shrink-datasaver", "2.2.1", ShrinkSdkPackageLayer.Feature, true);
internal static readonly ShrinkSdkPackageEntry EventBus = Package(
"事件总线", "类型安全事件、请求结果、调度与代码生成。",
"com.cneicy.shrink-eventbus", "2.0.0", ShrinkSdkPackageLayer.Feature, true);
internal static readonly ShrinkSdkPackageEntry EventBusEntities = Package(
"Entities 事件适配", "将事件总线接入 Unity Entities。",
"com.cneicy.shrink-eventbus-entities", "0.1.0", ShrinkSdkPackageLayer.Integration, true);
internal static readonly ShrinkSdkPackageEntry ModFramework = Package(
"模组框架", "Context 托管的模组生命周期、热替换与外部 DLL 加载。",
"com.cneicy.shrink-mod-framework", "0.2.2", ShrinkSdkPackageLayer.Feature, true);
internal static readonly ShrinkSdkPackageEntry Network = Package(
"网络系统", "消息、RPC、权限、序列化与多种传输层。",
"com.cneicy.shrink-network", "0.2.1", ShrinkSdkPackageLayer.Feature, true);
internal static readonly ShrinkSdkPackageEntry Tutorial = Package(
"引导系统", "可复用的步骤式引导、遮罩、锚点与进度存储。",
"com.cneicy.shrink-tutorial", "0.1.2", ShrinkSdkPackageLayer.Feature);
internal static readonly ShrinkSdkPackageEntry CommandApp = Package(
"命令 - 应用集成", "以原生 Context 组件向应用宿主发布命令服务。",
"com.cneicy.shrink-command-integration-app", "0.1.1", ShrinkSdkPackageLayer.Integration, true);
internal static readonly ShrinkSdkPackageEntry CommandEventBus = Package(
"命令 - 事件集成", "通过事件总线执行命令并发布执行状态。",
"com.cneicy.shrink-command-integration-eventbus", "0.1.1", ShrinkSdkPackageLayer.Integration, true);
internal static readonly ShrinkSdkPackageEntry CommandNetwork = Package(
"命令 - 网络集成", "通过网络 RPC 执行远程命令。",
"com.cneicy.shrink-command-integration-network", "0.1.1", ShrinkSdkPackageLayer.Integration, true);
internal static readonly ShrinkSdkPackageEntry DataSaverApp = Package(
"存档 - 应用集成", "以原生 Context 组件向应用宿主发布存档服务。",
"com.cneicy.shrink-datasaver-integration-app", "0.1.1", ShrinkSdkPackageLayer.Integration, true);
internal static readonly ShrinkSdkPackageEntry DataSaverEventBus = Package(
"存档 - 事件集成", "通过事件总线请求读写并观察存档变化。",
"com.cneicy.shrink-datasaver-integration-eventbus", "2.1.1", ShrinkSdkPackageLayer.Integration, true);
internal static readonly ShrinkSdkPackageEntry NetworkApp = Package(
"网络 - 应用集成", "以原生 Context 组件向应用宿主发布网络服务。",
"com.cneicy.shrink-network-integration-app", "0.1.1", ShrinkSdkPackageLayer.Integration, true);
internal static readonly ShrinkSdkPackageEntry NetworkEventBus = Package(
"网络 - 事件集成", "把网络消息与事件请求桥接为可撤回的 Context 组件。",
"com.cneicy.shrink-network-integration-eventbus", "0.1.2", ShrinkSdkPackageLayer.Integration, true);
internal static readonly ShrinkSdkPackageEntry SharedCodeGen = Package(
"共享代码生成", "各功能包使用的编译期注册表基础设施。",
"com.cneicy.shrink-shared-codegen", "0.1.0", ShrinkSdkPackageLayer.Tooling);
internal static readonly IReadOnlyList<ShrinkSdkPackageEntry> Packages = new[]
{
new ShrinkSdkPackageEntry("ShrinkApp Core", "com.cneicy.shrink-app-core", "0.1.1", requiresUniTask: true),
StarterBasic,
new ShrinkSdkPackageEntry("ShrinkCommand", "com.cneicy.shrink-command", "0.2.0", requiresUniTask: true),
new ShrinkSdkPackageEntry("ShrinkCommand Integration App", "com.cneicy.shrink-command-integration-app", "0.1.0", requiresUniTask: true),
new ShrinkSdkPackageEntry("ShrinkCommand Integration EventBus", "com.cneicy.shrink-command-integration-eventbus", "0.1.1", requiresUniTask: true),
new ShrinkSdkPackageEntry("ShrinkCommand Integration Network", "com.cneicy.shrink-command-integration-network", "0.1.0", requiresUniTask: true),
new ShrinkSdkPackageEntry("ShrinkContext App Adapter", "com.cneicy.shrink-context-app-adapter", "0.1.1", requiresUniTask: true),
new ShrinkSdkPackageEntry("ShrinkContext Core", "com.cneicy.shrink-context-core", "0.1.0", requiresUniTask: true),
new ShrinkSdkPackageEntry("ShrinkContext EventBus Adapter", "com.cneicy.shrink-context-eventbus-adapter", "0.1.0", requiresUniTask: true),
new ShrinkSdkPackageEntry("ShrinkDataSaver", "com.cneicy.shrink-datasaver", "2.2.0", requiresUniTask: true),
new ShrinkSdkPackageEntry("ShrinkDataSaver Integration App", "com.cneicy.shrink-datasaver-integration-app", "0.1.0", requiresUniTask: true),
new ShrinkSdkPackageEntry("ShrinkDataSaver Integration EventBus", "com.cneicy.shrink-datasaver-integration-eventbus", "2.1.0", requiresUniTask: true),
new ShrinkSdkPackageEntry("ShrinkEventBus", "com.cneicy.shrink-eventbus", "2.0.0", requiresUniTask: true),
new ShrinkSdkPackageEntry("ShrinkEventBus Entities", "com.cneicy.shrink-eventbus-entities", "0.1.0", requiresUniTask: true),
new ShrinkSdkPackageEntry("ShrinkModFramework", "com.cneicy.shrink-mod-framework", "0.2.1", requiresUniTask: true),
new ShrinkSdkPackageEntry("ShrinkNetwork", "com.cneicy.shrink-network", "0.2.0", requiresUniTask: true),
new ShrinkSdkPackageEntry("ShrinkNetwork Integration App", "com.cneicy.shrink-network-integration-app", "0.1.0", requiresUniTask: true),
new ShrinkSdkPackageEntry("ShrinkNetwork Integration EventBus", "com.cneicy.shrink-network-integration-eventbus", "0.1.1", requiresUniTask: true),
new ShrinkSdkPackageEntry("ShrinkShared CodeGen", "com.cneicy.shrink-shared-codegen", "0.1.0"),
new ShrinkSdkPackageEntry("ShrinkTutorial", "com.cneicy.shrink-tutorial", "0.1.0")
ContextCore, ContextAppAdapter, ContextEventBusAdapter,
AppCore, StarterBasic,
Command, DataSaver, EventBus, ModFramework, Network, Tutorial,
CommandApp, CommandEventBus, CommandNetwork,
DataSaverApp, DataSaverEventBus,
NetworkApp, NetworkEventBus, EventBusEntities,
SharedCodeGen
};
internal static readonly IReadOnlyList<ShrinkSdkPackageBundle> Bundles = new[]
{
new ShrinkSdkPackageBundle(
"基础应用(推荐)",
"直接获得以 ContextLoader 为组合根的命令、存档、网络基础应用。",
new[]
{
ContextCore, ContextAppAdapter, ContextEventBusAdapter, AppCore,
Command, DataSaver, EventBus, Network,
CommandApp, CommandEventBus, CommandNetwork,
DataSaverApp, DataSaverEventBus, NetworkApp, NetworkEventBus,
StarterBasic
},
new[] { StarterBasic },
recommended: true),
new ShrinkSdkPackageBundle(
"Context 最小基座",
"只安装组合内核,适合自行编写 IShrinkComponent 与组合根。",
new[] { ContextCore }),
new ShrinkSdkPackageBundle(
"Context 应用宿主",
"安装应用宿主、ContextLoader 适配与事件生命周期适配,不预装业务模块。",
new[] { ContextCore, EventBus, AppCore, ContextAppAdapter, ContextEventBusAdapter },
new[] { ContextAppAdapter, ContextEventBusAdapter }),
new ShrinkSdkPackageBundle(
"事件与命令",
"安装 Context 托管的事件订阅和命令执行链。",
new[] { ContextCore, EventBus, ContextEventBusAdapter, Command, CommandEventBus },
new[] { ContextEventBusAdapter, CommandEventBus }),
new ShrinkSdkPackageBundle(
"存档能力",
"安装存档系统及 Context 托管的事件桥接。",
new[] { ContextCore, EventBus, ContextEventBusAdapter, DataSaver, DataSaverEventBus },
new[] { ContextEventBusAdapter, DataSaverEventBus }),
new ShrinkSdkPackageBundle(
"网络能力",
"安装网络系统及 Context 托管的事件桥接。",
new[] { ContextCore, EventBus, ContextEventBusAdapter, Network, NetworkEventBus },
new[] { ContextEventBusAdapter, NetworkEventBus }),
new ShrinkSdkPackageBundle(
"模组开发",
"安装以 Context 管理生命周期和热替换的模组框架。",
new[] { ContextCore, EventBus, ModFramework },
new[] { ModFramework }),
new ShrinkSdkPackageBundle(
"常用完整套件",
"基础应用、模组框架与引导系统;不包含需要 Entities 的可选适配。",
new[]
{
ContextCore, ContextAppAdapter, ContextEventBusAdapter, AppCore,
Command, DataSaver, EventBus, Network, ModFramework, Tutorial,
CommandApp, CommandEventBus, CommandNetwork,
DataSaverApp, DataSaverEventBus, NetworkApp, NetworkEventBus,
StarterBasic
},
new[] { StarterBasic, ModFramework, Tutorial })
};
internal static IReadOnlyList<ShrinkSdkPackageEntry> DistinctInstallRoots(
IEnumerable<ShrinkSdkPackageEntry> packages) => packages
.GroupBy(entry => entry.PackageName)
.Select(group => group.First())
.ToArray();
private static ShrinkSdkPackageEntry Package(string displayName, string summary, string packageName,
string version, ShrinkSdkPackageLayer layer, bool requiresUniTask = false) =>
new(displayName, summary, packageName, version, layer, requiresUniTask);
}
}
#endif
+12 -6
View File
@@ -1,13 +1,19 @@
# ShrinkSDK Installer
# ShrinkSDK 包管理
`com.cneicy.shrink-installer` is the Git URL bootstrap package for ShrinkSDK.
`com.cneicy.shrink-installer` 是 ShrinkSDK 的固定版本引导包。通过不可变 Git 标签安装后,在 Unity 中打开 `ShrinkSDK/包管理`
Install the package from an immutable tag, then open `ShrinkSDK/Packages` in the Unity editor. The window merges the public ShrinkSDK scoped registry without replacing other registries and writes only catalogue-pinned dependencies to `Packages/manifest.json`. Packages which require UniTask also receive its fixed Git revision.
窗口使用 UI Toolkit 构建,提供:
The installer does not copy templates into `Assets`, does not store credentials, and does not add an unpinned package version.
- 当前项目直接与间接依赖的真实安装状态;
- 已安装版本与目录固定版本的对比;
-`Context` 组合基础、应用与组合根、独立功能、模块集成、编译工具为层级的全部包列表;
- 基础应用、Context 最小基座、Context 应用宿主、事件与命令、存档、网络、模组开发、常用完整套件八种组合;
- 保留既有 scoped registry 的安全合并,以及固定版本安装或版本切换。
## Bootstrap URL
安装器只修改 `Packages/manifest.json` 的 registry 与依赖数据,不写认证信息,不复制模板到 `Assets`,也不添加浮动版本。需要 UniTask 的安装入口会同时固定到目录指定的 Git revision。
## 引导地址
```text
https://git.crash.work/ShrinkSDK/Installer.git#v0.1.4
https://git.crash.work/ShrinkSDK/Installer.git#v0.2.1
```
+3 -3
View File
@@ -1,8 +1,8 @@
{
"name": "com.cneicy.shrink-installer",
"version": "0.1.4",
"displayName": "ShrinkSDK Installer",
"description": "ShrinkSDK registry bootstrapper and fixed-version package installer.",
"version": "0.2.1",
"displayName": "ShrinkSDK 包管理",
"description": "基于 UI Toolkit 的 ShrinkSDK 固定版本包管理器,提供安装状态、Context 分层与推荐组合。",
"unity": "2022.3",
"documentationUrl": "https://git.crash.work/ShrinkSDK/Installer",
"dependencies": {