using System; using System.Collections.Generic; using System.IO; using System.Text.RegularExpressions; using UnityEditor; using UnityEngine; namespace Amanotes { internal static class LocalPackagesManager { [Serializable] internal class LocalPackageInfo { public string packageName; public string displayName; public string version; public string folderName; public string absolutePath; public bool enabled; } internal static string FindLocalPackagesRoot() { var projectRoot = Path.GetFullPath(Path.Combine(Application.dataPath, "..")); var repoRoot = Path.GetDirectoryName(projectRoot); if (string.IsNullOrEmpty(repoRoot)) return null; var localPkgs = Path.Combine(repoRoot, "LocalPackages"); return Directory.Exists(localPkgs) ? localPkgs : null; } private static string GetRelativeFileRef(string folderName) { var packagesDir = Path.GetFullPath(Path.Combine(Application.dataPath, "..", "Packages")); var root = FindLocalPackagesRoot(); if (root == null) return null; var pkgPath = Path.Combine(root, folderName); var uri = new Uri(packagesDir + "/"); var relative = uri.MakeRelativeUri(new Uri(pkgPath)).ToString(); return "file:" + relative; } internal static List DetectLocalPackages() { var result = new List(); var root = FindLocalPackagesRoot(); if (root == null) return result; var manifestJson = ManifestEditor.Read(); var dirs = Directory.GetDirectories(root); for (int i = 0; i < dirs.Length; i++) { var dir = dirs[i]; var folderName = Path.GetFileName(dir); var pjPath = Path.Combine(dir, "package.json"); if (!File.Exists(pjPath)) continue; var json = File.ReadAllText(pjPath); var nameMatch = Regex.Match(json, "\"name\"\\s*:\\s*\"([^\"]+)\""); var displayMatch = Regex.Match(json, "\"displayName\"\\s*:\\s*\"([^\"]+)\""); var versionMatch = Regex.Match(json, "\"version\"\\s*:\\s*\"([^\"]+)\""); if (!nameMatch.Success) continue; var pkgName = nameMatch.Groups[1].Value; if (pkgName == "com.amanotes.amadev") continue; result.Add(new LocalPackageInfo { packageName = pkgName, displayName = displayMatch.Success ? displayMatch.Groups[1].Value : folderName, version = versionMatch.Success ? versionMatch.Groups[1].Value : "", folderName = folderName, absolutePath = Path.GetFullPath(dir), enabled = ManifestEditor.Contains(manifestJson, pkgName) }); } return result; } /// /// SyncManifest is intentionally a NO-OP now. /// Previously it would auto-add/remove local packages from the manifest on every reload, /// causing packages to be re-enabled after the user explicitly disabled them. /// All manifest changes are now done explicitly via EnableLocalPackage/DisableLocalPackage. /// internal static void SyncManifest(List packages) { // Intentionally empty — do not auto-modify the manifest. // The old behavior caused packages to re-enable on script reload because: // 1. DetectLocalPackages sets enabled based on package.json existing (not manifest state) // 2. SyncManifest would then force-add any "enabled" package back into manifest // 3. This undid explicit user actions (disabling packages, switching to version mode) } internal static bool EnablePackage(LocalPackageInfo pkg) { var pjPath = Path.Combine(pkg.absolutePath, "package.json"); var bakPath = Path.Combine(pkg.absolutePath, "package.json.bak"); if (!File.Exists(bakPath)) { Debug.LogWarning($"[LocalPackages] Cannot enable {pkg.packageName}: no .bak file found"); return false; } if (File.Exists(pjPath)) { Debug.LogWarning($"[LocalPackages] {pkg.packageName} already has package.json"); return false; } File.Move(bakPath, pjPath); pkg.enabled = true; if (ManifestEditor.Exists()) { var json = ManifestEditor.Read(); var fileRef = GetRelativeFileRef(pkg.folderName); if (ManifestEditor.GetValue(json, pkg.packageName) == null) { json = ManifestEditor.AddEntry(json, pkg.packageName, fileRef); ManifestEditor.Write(json); } } Debug.Log($"[LocalPackages] Enabled {pkg.packageName}"); PackageResolveScheduler.Schedule(); return true; } internal static bool DisablePackage(LocalPackageInfo pkg) { var pjPath = Path.Combine(pkg.absolutePath, "package.json"); var bakPath = Path.Combine(pkg.absolutePath, "package.json.bak"); if (!File.Exists(pjPath)) { Debug.LogWarning($"[LocalPackages] Cannot disable {pkg.packageName}: no package.json found"); return false; } if (ManifestEditor.Exists()) { var json = ManifestEditor.Read(); if (ManifestEditor.GetValue(json, pkg.packageName) != null) { json = ManifestEditor.RemoveEntry(json, pkg.packageName); ManifestEditor.Write(json); } } File.Move(pjPath, bakPath); pkg.enabled = false; Debug.Log($"[LocalPackages] Disabled {pkg.packageName}"); PackageResolveScheduler.Schedule(); return true; } internal static bool EnableLocalPackage(LocalPackageInfo pkg) { if (!ManifestEditor.Exists()) return false; var json = ManifestEditor.Read(); var fileRef = GetRelativeFileRef(pkg.folderName); var currentValue = ManifestEditor.GetValue(json, pkg.packageName); if (currentValue != null) { // Already in manifest — switch to file: ref if not already if (!currentValue.StartsWith("file:")) { // Save original version to DevModeState for restore var state = DevModeState.Load(); state.SetOriginalValue(pkg.packageName, currentValue); json = ManifestEditor.SetValue(json, pkg.packageName, fileRef); ManifestEditor.Write(json); } } else { // Not in manifest yet — add with file: ref // Save the package version as original for future restore if (!string.IsNullOrEmpty(pkg.version)) { var state = DevModeState.Load(); state.SetOriginalValue(pkg.packageName, pkg.version); } json = ManifestEditor.AddEntry(json, pkg.packageName, fileRef); ManifestEditor.Write(json); } // Save local path to config for dev mode support var config = DevModeConfig.Load(); config.SetLocalPath(pkg.packageName, pkg.absolutePath); pkg.enabled = true; PackageResolveScheduler.Schedule(); return true; } internal static bool DisableLocalPackage(LocalPackageInfo pkg) { if (!ManifestEditor.Exists()) return false; var json = ManifestEditor.Read(); if (ManifestEditor.GetValue(json, pkg.packageName) == null) return false; json = ManifestEditor.RemoveEntry(json, pkg.packageName); ManifestEditor.Write(json); pkg.enabled = false; PackageResolveScheduler.Schedule(); return true; } } }