18 Commits
Author SHA1 Message Date
cneicy e97692e768 docs: update Unity installation guide 2026-09-05 02:46:09 +08:00
cneicy 2abf367f36 fix: compare installed package versions semantically 2026-09-05 02:33:59 +08:00
cneicy 012728ffac chore: release 0.2.4
Publish UPM package / publish (push) Successful in 2s
2026-08-28 04:31:05 +08:00
cneicy 885da4366d chore(release): 发布 v0.2.3
Publish UPM package / publish (push) Successful in 2s
2026-08-28 03:44:10 +08:00
cneicy 7096459763 fix(installer): complete context bundle roots
Publish UPM package / publish (push) Successful in 2s
2026-08-26 13:22:01 +08:00
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
cneicy 2ef9ad827b fix(installer): pin patched starter release
Publish UPM package / publish (push) Successful in 2s
2026-08-26 08:23:04 +08:00
cneicy ece3f072dd ci: mirror Git package dependencies 2026-08-26 07:07:03 +08:00
cneicy afc58da579 test: enable standalone host discovery 2026-08-26 06:56:05 +08:00
cneicy 6fa2ad2623 fix(installer): write pinned package dependencies
Publish UPM package / publish (push) Successful in 2s
2026-08-26 06:37:53 +08:00
ShrinkSDK Automation 7de7bbbcdc ci: fetch immutable release archive 2026-08-26 04:29:42 +08:00
ShrinkSDK Automation 182bacbb09 fix(installer): pin standalone ModFramework support
Publish UPM package / publish (push) Successful in 5m32s
2026-08-26 04:13:17 +08:00
ShrinkSDK Automation 098c4b098f fix(installer): support Unity 2022 package resolution
Publish UPM package / publish (push) Successful in 17s
2026-08-26 03:44:51 +08:00
ShrinkSDK Automation deea36068c ci: install Git in package publisher 2026-08-26 03:09:52 +08:00
28 changed files with 1150 additions and 153 deletions
+27 -9
View File
@@ -15,22 +15,39 @@ jobs:
env:
NODE_AUTH_TOKEN: ${{ secrets.SHRINKSDK_PACKAGE_TOKEN }}
steps:
- name: Fetch tagged revision
- name: Fetch exact tagged release archive
env:
GITEA_REF: ${{ gitea.ref }}
shell: bash
run: |
set -eu
ref="${{ gitea.sha }}"
test -n "$ref"
git init .
git remote add origin "https://git.crash.work/ShrinkSDK/Installer.git"
git fetch --depth=1 origin "$ref"
git checkout --detach FETCH_HEAD
tag="${GITEA_REF#refs/tags/}"
case "$tag" in
v[0-9]*) ;;
*) echo "Expected a version tag ref, got: $GITEA_REF" >&2; exit 1 ;;
esac
export SHRINKSDK_ARCHIVE_URL="https://git.crash.work/ShrinkSDK/Installer/archive/${tag}.tar.gz"
node --input-type=module <<'NODE'
import { writeFile } from 'node:fs/promises';
const response = await fetch(process.env.SHRINKSDK_ARCHIVE_URL);
if (!response.ok) {
throw new Error(`Release archive download failed: ${response.status} ${response.statusText}`);
}
await writeFile('release.tar.gz', new Uint8Array(await response.arrayBuffer()));
NODE
mkdir release
tar -xzf release.tar.gz --strip-components=1 -C release
rm -f release.tar.gz
printf '%s' "$tag" > release/.shrink-sdk-release-tag
- name: Validate immutable release version
shell: bash
run: |
set -eu
tag="$(git describe --exact-match --tags HEAD)"
cd release
tag="$(cat .shrink-sdk-release-tag)"
version="$(node -p "require('./package.json').version")"
test "$tag" = "v$version"
npm pack --dry-run
@@ -40,10 +57,11 @@ jobs:
run: |
set -eu
: "${NODE_AUTH_TOKEN:?SHRINKSDK_PACKAGE_TOKEN is required}"
cd release
npmrc="$HOME/.npmrc"
cleanup() { rm -f "$npmrc"; }
trap cleanup EXIT
printf '%s\n' \
'registry=https://git.crash.work/api/packages/ShrinkSDK/npm/' \
'//git.crash.work/api/packages/ShrinkSDK/npm/:_authToken=${NODE_AUTH_TOKEN}' > "$npmrc"
npm publish --registry=https://git.crash.work/api/packages/ShrinkSDK/npm/
npm publish --registry=https://git.crash.work/api/packages/ShrinkSDK/npm/
+6 -1
View File
@@ -26,12 +26,17 @@ jobs:
shell: bash
run: |
set -eu
machine_id_file="/root/.local/share/unity3d/Unity/.machine-id"
if test -s "$machine_id_file"; then
cat "$machine_id_file" > /etc/machine-id
echo "Unity machine identity restored"
fi
git config --global url."https://ghfast.top/https://github.com/".insteadOf "https://github.com/"
unity_bin="$(command -v unity-editor || command -v unity || command -v Unity || true)"
test -n "$unity_bin"
"$unity_bin" \
-batchmode \
-nographics \
-quit \
-projectPath "$PWD/Development~/UnityProject" \
-runTests \
-testPlatform EditMode \
+23
View File
@@ -0,0 +1,23 @@
# Changelog
本文件记录 `ShrinkInstaller` 的包内变更。
## [0.2.5] - 2026-09-05
### Fixed
- 按 SemVer 比较已安装版本与目录版本;已安装较新版本不再被误判为旧版本,也不会提供降级操作。
- 更新内置包目录,使版本与当前 ShrinkSDK 模块清单一致。
## [0.2.4] - 2026-08-28
### Changed
- 目录切换到 `ShrinkEventBus 2.0.1` 及其精确依赖链,常用完整套件仍只安装四个直接根。
## [0.2.3] - 2026-08-28
### Changed
- 目录固定到 App Core 0.1.3、Context App Adapter 0.1.4、Starter Basic 0.2.1、DataSaver 2.2.2、ModFramework 0.2.3 及对应 Integration 稳定版本。
- 常用完整套件继续只安装 Starter、Context EventBus Adapter、ModFramework 与 Tutorial 四个直接根,并固定 UniTask revision。
+7
View File
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 827b7796ca4548bcb5e249b351489ed1
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 7a6ed64b6b38b5e4eae9b86516add8b7
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,115 @@
#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。");
Require(BundleContainsInstallRoot(bundles, "基础应用(推荐)",
"com.cneicy.shrink-context-eventbus-adapter"),
"基础应用组合没有显式安装 Context 事件适配。");
Require(BundleContainsInstallRoot(bundles, "常用完整套件",
"com.cneicy.shrink-context-eventbus-adapter"),
"常用完整套件没有显式安装 Context 事件适配。");
Require(BundleContainsInstallRoot(bundles, "常用完整套件",
"com.cneicy.shrink-app-starter-basic"),
"常用完整套件没有显式安装 Starter。");
Require(BundleContainsInstallRoot(bundles, "常用完整套件",
"com.cneicy.shrink-mod-framework"),
"常用完整套件没有显式安装 ModFramework。");
Require(BundleContainsInstallRoot(bundles, "常用完整套件",
"com.cneicy.shrink-tutorial"),
"常用完整套件没有显式安装 Tutorial。");
}
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 bool BundleContainsInstallRoot(IEnumerable bundles, string bundleName, string packageName)
{
foreach (var bundle in bundles)
{
var bundleType = bundle.GetType();
if (!string.Equals(bundleType.GetProperty("DisplayName")?.GetValue(bundle) as string, bundleName,
StringComparison.Ordinal))
continue;
var roots = bundleType.GetProperty("InstallRoots")?.GetValue(bundle) as IEnumerable;
if (roots == null)
return false;
foreach (var root in roots)
{
var value = root.GetType().GetProperty("PackageName")?.GetValue(root) as string;
if (string.Equals(value, packageName, StringComparison.Ordinal))
return true;
}
return false;
}
return false;
}
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:
@@ -11,5 +11,8 @@
"dependencies": {
"com.unity.test-framework": "1.1.33",
"com.cneicy.shrink-installer": "file:../../.."
}
},
"testables": [
"com.cneicy.shrink-installer"
]
}
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: eac67073f2eb71043bb104ae8931248b
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 87984663a86071f408003b68afa18e0c
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+117
View File
@@ -0,0 +1,117 @@
#nullable enable
using System;
namespace ShrinkSDK.Installer
{
public enum ShrinkPackageVersionRelation
{
Unknown,
Older,
Equal,
Newer
}
public static class ShrinkPackageVersion
{
public static ShrinkPackageVersionRelation Classify(string? installedVersion, string? targetVersion)
{
return TryCompare(installedVersion, targetVersion, out var comparison)
? comparison < 0
? ShrinkPackageVersionRelation.Older
: comparison > 0
? ShrinkPackageVersionRelation.Newer
: ShrinkPackageVersionRelation.Equal
: ShrinkPackageVersionRelation.Unknown;
}
public static bool TryCompare(string? left, string? right, out int comparison)
{
comparison = 0;
if (!TryParse(left, out var leftVersion) || !TryParse(right, out var rightVersion))
return false;
for (var index = 0; index < 3; index++)
{
comparison = leftVersion.Core[index].CompareTo(rightVersion.Core[index]);
if (comparison != 0)
return true;
}
comparison = ComparePrerelease(leftVersion.Prerelease, rightVersion.Prerelease);
return true;
}
private static bool TryParse(string? raw, out ParsedVersion version)
{
version = default;
if (string.IsNullOrWhiteSpace(raw))
return false;
var value = raw.Trim();
if (value.StartsWith("v", StringComparison.OrdinalIgnoreCase))
value = value.Substring(1);
var buildIndex = value.IndexOf('+');
if (buildIndex >= 0)
value = value.Substring(0, buildIndex);
var prereleaseIndex = value.IndexOf('-');
var coreText = prereleaseIndex >= 0 ? value.Substring(0, prereleaseIndex) : value;
var prerelease = prereleaseIndex >= 0 ? value.Substring(prereleaseIndex + 1) : string.Empty;
var coreParts = coreText.Split('.');
if (coreParts.Length is < 1 or > 3)
return false;
var core = new int[3];
for (var index = 0; index < coreParts.Length; index++)
{
if (!int.TryParse(coreParts[index], out core[index]) || core[index] < 0)
return false;
}
if (prereleaseIndex >= 0 && string.IsNullOrWhiteSpace(prerelease))
return false;
version = new ParsedVersion(core, prerelease);
return true;
}
private static int ComparePrerelease(string left, string right)
{
var leftEmpty = string.IsNullOrEmpty(left);
var rightEmpty = string.IsNullOrEmpty(right);
if (leftEmpty || rightEmpty)
return leftEmpty == rightEmpty ? 0 : leftEmpty ? 1 : -1;
var leftParts = left.Split('.');
var rightParts = right.Split('.');
var count = Math.Min(leftParts.Length, rightParts.Length);
for (var index = 0; index < count; index++)
{
var leftNumeric = int.TryParse(leftParts[index], out var leftValue);
var rightNumeric = int.TryParse(rightParts[index], out var rightValue);
int comparison;
if (leftNumeric && rightNumeric)
comparison = leftValue.CompareTo(rightValue);
else if (leftNumeric != rightNumeric)
comparison = leftNumeric ? -1 : 1;
else
comparison = string.Compare(leftParts[index], rightParts[index], StringComparison.Ordinal);
if (comparison != 0)
return comparison;
}
return leftParts.Length.CompareTo(rightParts.Length);
}
private readonly struct ParsedVersion
{
public ParsedVersion(int[] core, string prerelease)
{
Core = core;
Prerelease = prerelease;
}
public int[] Core { get; }
public string Prerelease { get; }
}
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e6e04e7ab0ec4fbe87bb30cb04f7ce18
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+444 -105
View File
@@ -2,174 +2,513 @@
#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 AddRequest? _addRequest;
private readonly Dictionary<string, UnityEditor.PackageManager.PackageInfo> _installed =
new(StringComparer.Ordinal);
private ListRequest? _listRequest;
private ResolveRequest? _resolveRequest;
private ShrinkSdkPackageEntry? _pendingInstall;
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 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;
}
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]);
}
private void RenderPage()
{
if (_page == null)
return;
_page.Clear();
if (_showBundles)
RenderBundles(_page);
else
RenderPackages(_page);
}
if (GUILayout.Button("Refresh Diagnostics"))
private void RenderBundles(VisualElement parent)
{
foreach (var bundle in ShrinkSdkPackageCatalog.Bundles.Where(MatchesSearch))
{
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 ready = bundle.Packages.Count(IsInstalledAtLeast);
var button = new Button(() => BeginInstall(bundle.InstallRoots));
if (ready == bundle.Packages.Count)
{
RefreshDiagnostics();
button.text = "已完成";
button.SetEnabled(false);
}
else
{
button.text = ready > 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($"已就绪 {ready}/{bundle.Packages.Count}");
progress.style.marginTop = 8f;
progress.style.color = ready == 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 = IsInstalledAtLeast(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
{
switch (ShrinkPackageVersion.Classify(installed.version, package.Version))
{
case ShrinkPackageVersionRelation.Equal:
button.text = "已安装 " + installed.version;
button.SetEnabled(false);
break;
case ShrinkPackageVersionRelation.Newer:
button.text = "较新版本 " + installed.version;
button.SetEnabled(false);
break;
case ShrinkPackageVersionRelation.Older:
button.text = "升级 " + installed.version + " → " + package.Version;
break;
default:
button.text = installed.version + " → " + package.Version;
break;
}
}
EditorGUILayout.Space(8f);
EditorGUILayout.HelpBox(_status, _statusType);
if (IsBusy)
button.SetEnabled(false);
return button;
}
private bool IsBusy => _resolveRequest != null || _addRequest != null || _listRequest != null;
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 IsInstalledAtLeast(ShrinkSdkPackageEntry package) =>
_installed.TryGetValue(package.PackageName, out var installed) &&
ShrinkPackageVersion.Classify(installed.version, package.Version) is
ShrinkPackageVersionRelation.Equal or ShrinkPackageVersionRelation.Newer;
private string GetInstalledStatus(ShrinkSdkPackageEntry package)
{
if (!_installed.TryGetValue(package.PackageName, out var installed))
return "未安装 · " + package.Version;
return ShrinkPackageVersion.Classify(installed.version, package.Version) switch
{
ShrinkPackageVersionRelation.Equal => "已安装 " + installed.version,
ShrinkPackageVersionRelation.Newer => "已安装较新版本 " + installed.version,
ShrinkPackageVersionRelation.Older => "可升级 " + installed.version + " → " + package.Version,
_ => "已安装 " + installed.version + " · 目标 " + package.Version
};
}
private bool IsBusy => _listRequest != null || _resolvePending || _listRetryPending;
private void Update()
{
if (_resolveRequest != null && _resolveRequest.IsCompleted)
if (_resolvePending && EditorApplication.timeSinceStartup - _resolveRequestedAt >= 0.5d)
{
var request = _resolveRequest;
_resolveRequest = null;
if (request.Status != StatusCode.Success)
{
SetStatus("Registry resolve failed: " + request.Error.message, MessageType.Error);
return;
}
if (_pendingInstall != null)
{
_addRequest = Client.Add(_pendingInstall.PackageSpecifier);
SetStatus("Installing " + _pendingInstall.PackageSpecifier, MessageType.Info);
}
_resolvePending = false;
SetStatus("正在等待依赖解析并刷新安装状态。", HelpBoxMessageType.Info);
StartListRequest();
}
if (_addRequest != null && _addRequest.IsCompleted)
if (_listRetryPending && EditorApplication.timeSinceStartup >= _nextListRetryAt)
{
var request = _addRequest;
_addRequest = null;
if (request.Status == StatusCode.Success)
{
SetStatus("Installed " + request.Result.name + " " + request.Result.version, MessageType.Info);
}
else
{
SetStatus("Package install failed: " + request.Error.message, MessageType.Error);
}
_pendingInstall = null;
_listRetryPending = false;
StartListRequest();
}
if (_listRequest != null && _listRequest.IsCompleted)
if (_listRequest == null || !_listRequest.IsCompleted)
return;
var list = _listRequest;
_listRequest = null;
if (list.Status == StatusCode.Success)
{
var request = _listRequest;
_listRequest = null;
if (request.Status == StatusCode.Success)
{
var installed = request.Result.Count(package => package.name.StartsWith("com.cneicy.", StringComparison.Ordinal));
SetStatus("Registry is configured. Installed ShrinkSDK packages: " + installed, MessageType.Info);
}
else
{
SetStatus("Package diagnostics failed: " + request.Error.message, MessageType.Warning);
}
_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();
_pendingInstall = null;
_resolveRequest = Client.Resolve();
SetStatus("Registry configuration written. 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.EnsureShrinkSdkRegistry();
_pendingInstall = entry;
_resolveRequest = Client.Resolve();
SetStatus("Resolving registry for " + entry.PackageSpecifier, 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
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 87b0b212685b2564eaa5586bb567bfab
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+36 -1
View File
@@ -27,6 +27,31 @@ namespace ShrinkSDK.Installer
internal static void EnsureShrinkSdkRegistry()
{
var manifest = ReadManifest();
EnsureShrinkSdkRegistry(manifest);
AtomicWrite(manifest.ToString(Formatting.Indented) + Environment.NewLine);
}
internal static void EnsurePackageInstallation(
System.Collections.Generic.IEnumerable<ShrinkSdkPackageEntry> packages)
{
var manifest = ReadManifest();
EnsureShrinkSdkRegistry(manifest);
var requiresUniTask = false;
foreach (var package in packages)
{
EnsurePinnedDependency(manifest, package.PackageName, package.Version);
requiresUniTask |= package.RequiresUniTask;
}
if (requiresUniTask)
EnsurePinnedDependency(manifest, ShrinkSdkPackageCatalog.UniTaskPackageName,
ShrinkSdkPackageCatalog.UniTaskGitUrl);
AtomicWrite(manifest.ToString(Formatting.Indented) + Environment.NewLine);
}
private static void EnsureShrinkSdkRegistry(JObject manifest)
{
var registries = manifest["scopedRegistries"] as JArray;
if (registries == null)
{
@@ -71,8 +96,18 @@ namespace ShrinkSDK.Installer
registry["scopes"] = scopes;
}
}
AtomicWrite(manifest.ToString(Formatting.Indented) + Environment.NewLine);
private static void EnsurePinnedDependency(JObject manifest, string packageName, string version)
{
var dependencies = manifest["dependencies"] as JObject;
if (dependencies == null)
{
dependencies = new JObject();
manifest["dependencies"] = dependencies;
}
dependencies[packageName] = version;
}
private static JObject ReadManifest()
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c7a718e89696b5d499781f9b04f63c27
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+182 -26
View File
@@ -2,22 +2,59 @@
#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)
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 string PackageSpecifier => PackageName + "@" + Version;
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
@@ -25,35 +62,154 @@ namespace ShrinkSDK.Installer
internal const string RegistryName = "ShrinkSDK";
internal const string RegistryUrl = "https://git.crash.work/api/packages/ShrinkSDK/npm/";
internal const string RegistryScope = "com.cneicy";
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.0");
internal static readonly ShrinkSdkPackageEntry ContextCore = Package(
"Context 核心", "可逆效应、响应式依赖与组件生命周期,是框架的组合基础。",
"com.cneicy.shrink-context-core", "0.2.0", ShrinkSdkPackageLayer.Context, true);
internal static readonly ShrinkSdkPackageEntry ContextAppAdapter = Package(
"Context 应用适配", "用 ContextLoader 承载 ShrinkApp 组合根和运行时诊断。",
"com.cneicy.shrink-context-app-adapter", "0.2.0", ShrinkSdkPackageLayer.Context, true);
internal static readonly ShrinkSdkPackageEntry ContextEventBusAdapter = Package(
"Context 事件适配", "把事件订阅纳入 Context 的安装与自动撤回。",
"com.cneicy.shrink-context-eventbus-adapter", "0.1.1", ShrinkSdkPackageLayer.Context, true);
internal static readonly ShrinkSdkPackageEntry AppCore = Package(
"应用宿主", "统一启动、服务门面与应用生命周期。",
"com.cneicy.shrink-app-core", "0.2.0", ShrinkSdkPackageLayer.Host, true);
internal static readonly ShrinkSdkPackageEntry StarterBasic = Package(
"基础应用起步包", "默认 Context 组合根,包含命令、存档、网络及其集成。",
"com.cneicy.shrink-app-starter-basic", "0.3.0", ShrinkSdkPackageLayer.Host, true);
internal static readonly ShrinkSdkPackageEntry Command = Package(
"命令系统", "命令路由、参数、权限与自动注册。",
"com.cneicy.shrink-command", "0.3.0", ShrinkSdkPackageLayer.Feature, true);
internal static readonly ShrinkSdkPackageEntry DataSaver = Package(
"存档系统", "异步存档、设置、备份恢复与数据观察。",
"com.cneicy.shrink-datasaver", "2.3.0", ShrinkSdkPackageLayer.Feature, true);
internal static readonly ShrinkSdkPackageEntry EventBus = Package(
"事件总线", "类型安全事件、请求结果、调度与代码生成。",
"com.cneicy.shrink-eventbus", "2.1.0", ShrinkSdkPackageLayer.Feature, true);
internal static readonly ShrinkSdkPackageEntry EventBusEntities = Package(
"Entities 事件适配", "将事件总线接入 Unity Entities。",
"com.cneicy.shrink-eventbus-entities", "0.1.1", ShrinkSdkPackageLayer.Integration, true);
internal static readonly ShrinkSdkPackageEntry ModFramework = Package(
"模组框架", "Context 托管的模组生命周期、热替换与外部 DLL 加载。",
"com.cneicy.shrink-mod-framework", "0.3.0", ShrinkSdkPackageLayer.Feature, true);
internal static readonly ShrinkSdkPackageEntry Network = Package(
"网络系统", "消息、RPC、权限、序列化与多种传输层。",
"com.cneicy.shrink-network", "0.3.0", ShrinkSdkPackageLayer.Feature, true);
internal static readonly ShrinkSdkPackageEntry Tutorial = Package(
"引导系统", "可复用的步骤式引导、遮罩、锚点与进度存储。",
"com.cneicy.shrink-tutorial", "0.2.0", ShrinkSdkPackageLayer.Feature);
internal static readonly ShrinkSdkPackageEntry CommandApp = Package(
"命令 - 应用集成", "以原生 Context 组件向应用宿主发布命令服务。",
"com.cneicy.shrink-command-integration-app", "0.2.0", ShrinkSdkPackageLayer.Integration, true);
internal static readonly ShrinkSdkPackageEntry CommandEventBus = Package(
"命令 - 事件集成", "通过事件总线执行命令并发布执行状态。",
"com.cneicy.shrink-command-integration-eventbus", "0.2.0", ShrinkSdkPackageLayer.Integration, true);
internal static readonly ShrinkSdkPackageEntry CommandNetwork = Package(
"命令 - 网络集成", "通过网络 RPC 执行远程命令。",
"com.cneicy.shrink-command-integration-network", "0.2.0", ShrinkSdkPackageLayer.Integration, true);
internal static readonly ShrinkSdkPackageEntry DataSaverApp = Package(
"存档 - 应用集成", "以原生 Context 组件向应用宿主发布存档服务。",
"com.cneicy.shrink-datasaver-integration-app", "0.2.0", ShrinkSdkPackageLayer.Integration, true);
internal static readonly ShrinkSdkPackageEntry DataSaverEventBus = Package(
"存档 - 事件集成", "通过事件总线请求读写并观察存档变化。",
"com.cneicy.shrink-datasaver-integration-eventbus", "2.2.0", ShrinkSdkPackageLayer.Integration, true);
internal static readonly ShrinkSdkPackageEntry NetworkApp = Package(
"网络 - 应用集成", "以原生 Context 组件向应用宿主发布网络服务。",
"com.cneicy.shrink-network-integration-app", "0.2.0", ShrinkSdkPackageLayer.Integration, true);
internal static readonly ShrinkSdkPackageEntry NetworkEventBus = Package(
"网络 - 事件集成", "把网络消息与事件请求桥接为可撤回的 Context 组件。",
"com.cneicy.shrink-network-integration-eventbus", "0.2.0", ShrinkSdkPackageLayer.Integration, true);
internal static readonly ShrinkSdkPackageEntry SharedCodeGen = Package(
"共享代码生成", "各功能包使用的编译期注册表基础设施。",
"com.cneicy.shrink-shared-codegen", "0.1.0", ShrinkSdkPackageLayer.Tooling);
internal static readonly ShrinkSdkPackageEntry RuntimeAbstractions = Package(
"运行时抽象", "Unity 与其他 .NET 宿主共用的平台服务合同。",
"com.cneicy.shrink-runtime-abstractions", "0.1.0", ShrinkSdkPackageLayer.Tooling);
internal static readonly IReadOnlyList<ShrinkSdkPackageEntry> Packages = new[]
{
new ShrinkSdkPackageEntry("ShrinkApp Core", "com.cneicy.shrink-app-core", "0.1.1"),
StarterBasic,
new ShrinkSdkPackageEntry("ShrinkCommand", "com.cneicy.shrink-command", "0.2.0"),
new ShrinkSdkPackageEntry("ShrinkCommand Integration App", "com.cneicy.shrink-command-integration-app", "0.1.0"),
new ShrinkSdkPackageEntry("ShrinkCommand Integration EventBus", "com.cneicy.shrink-command-integration-eventbus", "0.1.1"),
new ShrinkSdkPackageEntry("ShrinkCommand Integration Network", "com.cneicy.shrink-command-integration-network", "0.1.0"),
new ShrinkSdkPackageEntry("ShrinkContext App Adapter", "com.cneicy.shrink-context-app-adapter", "0.1.0"),
new ShrinkSdkPackageEntry("ShrinkContext Core", "com.cneicy.shrink-context-core", "0.1.0"),
new ShrinkSdkPackageEntry("ShrinkContext EventBus Adapter", "com.cneicy.shrink-context-eventbus-adapter", "0.1.0"),
new ShrinkSdkPackageEntry("ShrinkDataSaver", "com.cneicy.shrink-datasaver", "2.2.0"),
new ShrinkSdkPackageEntry("ShrinkDataSaver Integration App", "com.cneicy.shrink-datasaver-integration-app", "0.1.0"),
new ShrinkSdkPackageEntry("ShrinkDataSaver Integration EventBus", "com.cneicy.shrink-datasaver-integration-eventbus", "2.1.0"),
new ShrinkSdkPackageEntry("ShrinkEventBus", "com.cneicy.shrink-eventbus", "2.0.0"),
new ShrinkSdkPackageEntry("ShrinkEventBus Entities", "com.cneicy.shrink-eventbus-entities", "0.1.0"),
new ShrinkSdkPackageEntry("ShrinkModFramework", "com.cneicy.shrink-mod-framework", "0.2.0"),
new ShrinkSdkPackageEntry("ShrinkNetwork", "com.cneicy.shrink-network", "0.2.0"),
new ShrinkSdkPackageEntry("ShrinkNetwork Integration App", "com.cneicy.shrink-network-integration-app", "0.1.0"),
new ShrinkSdkPackageEntry("ShrinkNetwork Integration EventBus", "com.cneicy.shrink-network-integration-eventbus", "0.1.1"),
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, RuntimeAbstractions
};
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, ContextEventBusAdapter },
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, ContextEventBusAdapter, 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
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7a785469a5003c64ba3c5a7663d703fb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+14 -6
View File
@@ -1,13 +1,21 @@
# ShrinkSDK Installer
# ShrinkSDK 包管理
`com.cneicy.shrink-installer` is the Git URL bootstrap package for ShrinkSDK.
`com.cneicy.shrink-installer` 是 ShrinkSDK 的固定版本引导包。在 Unity Package Manager 中通过 Git URL 安装后,打开 `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 installs only catalogued package versions.
窗口使用 UI Toolkit 构建,提供:
The installer does not copy templates into `Assets`, does not store credentials, and does not add an unpinned package version.
- 当前项目直接与间接依赖的真实安装状态;
- 按 SemVer 比较已安装版本与目录固定版本,较新版本不会被误判或降级;
-`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.0
https://git.crash.work/ShrinkSDK/Installer.git#main
```
正式项目应在对应版本发布后将 `main` 换成固定 tag。安装完成后等待 Unity 编译,再打开 `ShrinkSDK/包管理`,选择单个模块或推荐组合。
+7
View File
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: cce500f16cff60c4a975bb202bc57b0c
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 238fc591f33a45dca54d074857959f87
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: a5027bfa9e76434983f82652539bc42a
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,22 @@
{
"name": "ShrinkInstaller.Editor.Tests",
"rootNamespace": "ShrinkSDK.Installer.Tests",
"references": [
"ShrinkInstaller.Editor"
],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"autoReferenced": false,
"defineConstraints": [
"UNITY_INCLUDE_TESTS"
],
"versionDefines": [],
"noEngineReferences": false,
"optionalUnityReferences": [
"TestAssemblies"
]
}
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 00a17f283465481592e764466d9c980d
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+22
View File
@@ -0,0 +1,22 @@
#nullable enable
using NUnit.Framework;
namespace ShrinkSDK.Installer.Tests
{
public sealed class ShrinkPackageVersionTests
{
[TestCase("1.10.0", "1.9.0", ShrinkPackageVersionRelation.Newer)]
[TestCase("2.1.0", "2.1.0", ShrinkPackageVersionRelation.Equal)]
[TestCase("2.0.9", "2.1.0", ShrinkPackageVersionRelation.Older)]
[TestCase("1.0.0", "1.0.0-preview.9", ShrinkPackageVersionRelation.Newer)]
[TestCase("1.0.0-preview.10", "1.0.0-preview.2", ShrinkPackageVersionRelation.Newer)]
[TestCase("1.0.0+build.2", "1.0.0+build.1", ShrinkPackageVersionRelation.Equal)]
[TestCase("git+https://example.invalid/package", "1.0.0", ShrinkPackageVersionRelation.Unknown)]
public void Classify_UsesSemanticVersionOrdering(string installed, string target,
ShrinkPackageVersionRelation expected)
{
Assert.AreEqual(expected, ShrinkPackageVersion.Classify(installed, target));
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9152d73a7f3141429d048528d92b1ecf
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+5 -4
View File
@@ -1,10 +1,11 @@
{
"name": "com.cneicy.shrink-installer",
"version": "0.1.0",
"displayName": "ShrinkSDK Installer",
"description": "ShrinkSDK registry bootstrapper and fixed-version package installer.",
"version": "0.2.5",
"displayName": "ShrinkSDK 包管理",
"description": "基于 UI Toolkit 的 ShrinkSDK 固定版本包管理器,提供安装状态、Context 分层与推荐组合。",
"unity": "2022.3",
"documentationUrl": "https://git.crash.work/ShrinkSDK/Installer",
"documentationUrl": "https://git.crash.work/ShrinkSDK/Installer",
"changelogUrl": "https://git.crash.work/ShrinkSDK/Installer/src/branch/main/CHANGELOG.md",
"dependencies": {
"com.unity.nuget.newtonsoft-json": "3.2.2"
},
+7
View File
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: c41475e2d6261484695ef0f146b55a01
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant: