267 lines
8.6 KiB
C#
267 lines
8.6 KiB
C#
// #define UNITY_EDITOR_OSX
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Net;
|
|
using System.Text.RegularExpressions;
|
|
using System.Threading.Tasks;
|
|
using Newtonsoft.Json.Linq;
|
|
using UnityEngine;
|
|
using UnityEngine.Networking;
|
|
using UnityEngine.Rendering;
|
|
#if UNITY_EDITOR_OSX || UNITY_EDITOR_LINUX
|
|
using System.Linq;
|
|
#endif
|
|
|
|
namespace Needle.Engine.Utils
|
|
{
|
|
public static class NpmUtils
|
|
{
|
|
#if UNITY_EDITOR_WIN
|
|
internal static readonly string NpmCacheDirectory = Environment.ExpandEnvironmentVariables("%AppData%/../Local/npm-cache");
|
|
#else
|
|
internal static readonly string NpmCacheDirectory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) + "/.npm";
|
|
#endif
|
|
|
|
internal static readonly string[] NpmCacheDirectories = new string[]
|
|
{
|
|
NpmCacheDirectory + "/_cacache",
|
|
NpmCacheDirectory + "/_npx",
|
|
};
|
|
internal static readonly string NpxCacheDirectory = NpmCacheDirectory + "/_npx";
|
|
|
|
public static Task<bool> UpdatePackages(IList<string> packages, string workingDirectory)
|
|
{
|
|
var cmd = "npm set registry https://registry.npmjs.org";
|
|
cmd += " && npm update " + string.Join(" ", packages);
|
|
Debug.Log($"Running npm update for {packages.Count} packages\n{string.Join(", ", packages)}");
|
|
return ProcessHelper.RunCommand(cmd, workingDirectory, null, true, false);
|
|
}
|
|
|
|
public static string GetPackageUrl(string packageName)
|
|
{
|
|
return $"https://www.npmjs.com/package/{packageName}";
|
|
}
|
|
|
|
public static string GetCodeUrl(string packageName, string version)
|
|
{
|
|
return $"https://www.npmjs.com/package/{packageName}/v/{version.AsSpecificSemVer()}?activeTab=code";
|
|
}
|
|
|
|
public const string NpmRegistryUrl = "https://registry.npmjs.org/";
|
|
public const string NpmInstallFlags = "--install-links=false";
|
|
public const string NpmNoProgressAuditFundArgs = "--progress=false --no-audit --no-fund";
|
|
|
|
private static readonly char[] _rangeChars = {' ', '^', '~', 'v'};
|
|
private static readonly Regex _getSemverRegex = new Regex(@"(?<semver>\d{1,}\.\d{1,}(?<patch>\.(\d|x){1,})?(\-\w+)?(\.\d+)?)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
|
|
|
public static string AsSpecificSemVer(this string version)
|
|
{
|
|
if (version.StartsWith("npm:"))
|
|
{
|
|
// e.g. npm:@needle-tools/three@^1.2.3
|
|
version = version.Substring(4);
|
|
var idx = version.LastIndexOf('@');
|
|
// split out version (e.g. ^1.2.3)
|
|
var actualVersion = idx >= 0 ? version.Substring(idx + 1) : version;
|
|
actualVersion = actualVersion.AsSpecificSemVer();
|
|
// concat again with prefix
|
|
version = version.Substring(0, idx + 1) + actualVersion;
|
|
return version;
|
|
}
|
|
|
|
// we use this to get the first semver in a range (e.g. <= 3.0.0 > 5.0.0-alpha would then result in 3.0.0)
|
|
// https://regex101.com/r/yHZ0Jg/1
|
|
var match = _getSemverRegex.Match(version);
|
|
if (match.Success)
|
|
{
|
|
var semver = match.Groups["semver"];
|
|
if (semver.Success)
|
|
{
|
|
version = semver.Value;
|
|
// if a version is just "2.1" and the patch version is missing, we add a ".0" to make it a valid semver
|
|
var patch = match.Groups["patch"];
|
|
if (!patch.Success)
|
|
{
|
|
version += ".0";
|
|
}
|
|
}
|
|
}
|
|
|
|
return version.TrimStart(_rangeChars)
|
|
.Replace("*", "0")
|
|
.Replace("x", "0");
|
|
}
|
|
|
|
public static string GetNpmRegistryEndpointUrlForPackage(string packageName)
|
|
{
|
|
return NpmRegistryUrl + "/" + packageName;
|
|
}
|
|
|
|
public static async Task<JObject> TryGetCompletePackageInfo(string packageName)
|
|
{
|
|
try
|
|
{
|
|
using var client = new WebClient();
|
|
var json = await client.DownloadStringTaskAsync(GetNpmRegistryEndpointUrlForPackage(packageName));
|
|
return JObject.Parse(json);
|
|
}
|
|
catch
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public static async Task<JObject> TryGetPackageVersionInfo(string packageName, string packageVersion)
|
|
{
|
|
try
|
|
{
|
|
using var client = new WebClient();
|
|
var json = await client.DownloadStringTaskAsync(GetNpmRegistryEndpointUrlForPackage(packageName) + "/" + packageVersion.AsSpecificSemVer());
|
|
return JObject.Parse(json);
|
|
}
|
|
catch
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public static async Task<bool> PackageExists(string packageName, string packageVersion)
|
|
{
|
|
try
|
|
{
|
|
if (packageVersion.StartsWith("^") || packageVersion.StartsWith(">") || packageVersion.StartsWith("<") || packageVersion.Contains("*") || packageVersion.Contains("x"))
|
|
{
|
|
var specific = packageVersion.AsSpecificSemVer();
|
|
var packageUrl = GetNpmRegistryEndpointUrlForPackage(packageName) + "/" + specific;
|
|
Debug.Log($"Checking if \"{packageName}@{specific}\" exists (can not check version range yet)".LowContrast());
|
|
var res = await WebHelper.MakeHeaderOnlyRequest(packageUrl);
|
|
if (res.result == UnityWebRequest.Result.Success) return true;
|
|
|
|
// For when there's no patch version with a 0 (e.g. 4.4.0 vs 4.4.1)
|
|
var procRes = await ProcessHelper.RunCommand($"npm view {packageName}@{packageVersion} --json", null, null, true, false);
|
|
return procRes;
|
|
}
|
|
if (packageVersion.StartsWith("npm:"))
|
|
{
|
|
// In this case the package name is a scoped package name
|
|
// maybe we just need to check for @ instead to cover all cases?
|
|
var scopedVersionString = packageVersion.AsSpecificSemVer();
|
|
var separatorIndex = scopedVersionString.LastIndexOf('@');
|
|
packageName = scopedVersionString.Substring(0, separatorIndex);
|
|
packageVersion = scopedVersionString.Substring(separatorIndex + 1);
|
|
var url = GetNpmRegistryEndpointUrlForPackage(packageName) + "/" + packageVersion;
|
|
var res = await WebHelper.MakeHeaderOnlyRequest(url);
|
|
return res.result == UnityWebRequest.Result.Success;
|
|
}
|
|
else
|
|
{
|
|
var url = GetNpmRegistryEndpointUrlForPackage(packageName) + "/" + packageVersion.AsSpecificSemVer();
|
|
var res = await WebHelper.MakeHeaderOnlyRequest(url);
|
|
return res.result == UnityWebRequest.Result.Success;
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public static async Task<(bool success, JObject rawObj)> ViewPackage(string packageName, string packageVersion)
|
|
{
|
|
var cmd = "npm view --json " + packageName + "@" + packageVersion;
|
|
var str = "";
|
|
var success = await ProcessHelper.RunCommand(cmd, null, null, true, false, -1, default, (log, msg) =>
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(msg))
|
|
{
|
|
str += msg;
|
|
}
|
|
});
|
|
var res = (success: success && str.StartsWith("{"), rawObj: default(JObject));
|
|
if (res.success)
|
|
{
|
|
var obj = JObject.Parse(str);
|
|
res.rawObj = obj;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
public static string GetStartCommand(string projectDirectory)
|
|
{
|
|
if (PackageUtils.TryGetScripts(projectDirectory + "/package.json", out var scripts))
|
|
{
|
|
foreach (var kvp in scripts)
|
|
{
|
|
if (kvp.Value.Contains("next dev")) return "npm run " + kvp.Key;
|
|
}
|
|
}
|
|
return "npm start";
|
|
}
|
|
|
|
public static string GetInstallCommand(string projectDirectory)
|
|
{
|
|
if (PackageUtils.TryGetScripts(projectDirectory + "/package.json", out var scripts))
|
|
{
|
|
if (scripts.TryGetValue("install", out var cmd))
|
|
return cmd;
|
|
}
|
|
if (File.Exists(projectDirectory + "/yarn.lock"))
|
|
return $"yarn install {NpmInstallFlags} {NpmNoProgressAuditFundArgs}";
|
|
return $"npm install {NpmInstallFlags} {NpmNoProgressAuditFundArgs} --include=optional";
|
|
}
|
|
|
|
public static bool IsInstallationCommand(string cmd)
|
|
{
|
|
if (cmd.Contains("npm install")) return true;
|
|
if (cmd.Contains("npm i")) return true;
|
|
if (cmd.Contains("yarn install")) return true;
|
|
if (cmd.Contains("yarn add")) return true;
|
|
return false;
|
|
}
|
|
|
|
internal static string TryFindNvmInstallDirectory()
|
|
{
|
|
#if UNITY_EDITOR_OSX || UNITY_EDITOR_LINUX
|
|
var path = default(string);
|
|
var userDirectory = System.Environment.GetFolderPath(System.Environment.SpecialFolder.UserProfile);
|
|
var npmDirectory = System.IO.Path.Combine(userDirectory, ".nvm/versions/node");
|
|
if (Directory.Exists(npmDirectory))
|
|
{
|
|
var versions = System.IO.Directory.GetDirectories(npmDirectory);
|
|
if (versions.Length > 0)
|
|
{
|
|
var latestVersion = versions.Last();
|
|
path = System.IO.Path.Combine(latestVersion, "bin");
|
|
if (!Directory.Exists(path)) path = null;
|
|
|
|
}
|
|
}
|
|
return path;
|
|
#else
|
|
return null;
|
|
#endif
|
|
}
|
|
|
|
public static void LogPaths()
|
|
{
|
|
#if UNITY_EDITOR_WIN
|
|
var npmPaths = new List<string>();
|
|
foreach (var line in ProcessHelper.RunCommandEnumerable("echo %PATH%"))
|
|
{
|
|
var parts = line.Split(';');
|
|
foreach (var part in parts)
|
|
{
|
|
if (part.Contains("npm") || part.Contains("nodejs"))
|
|
npmPaths.Add(part);
|
|
}
|
|
}
|
|
if (npmPaths.Count > 0)
|
|
Debug.Log(string.Join("\n", npmPaths));
|
|
else
|
|
Debug.Log("No npm paths found in PATH environment variable");
|
|
#endif
|
|
}
|
|
}
|
|
} |