trouble-in-terror-town/node_modules/openupm-cli/lib/core.js
Mikolaj 2bbacbea09 did some more work on networking and removed EOS in favor of LRM
did some more work on networking and removed EOS in favor of Light Reflective Mirror
2022-05-31 15:04:31 +02:00

484 lines
14 KiB
JavaScript

const fs = require("fs");
const os = require("os");
const path = require("path");
const url = require("url");
const _ = require("lodash");
const chalk = require("chalk");
const mkdirp = require("mkdirp");
const net = require('node:net');
const isWsl = require("is-wsl");
const TOML = require("@iarna/toml");
const yaml = require("yaml");
const { execute } = require("./utils/process");
const { getNpmClient } = require("./client");
const { log } = require("./logger");
const env = {};
// Parse env
const parseEnv = async function(options, { checkPath }) {
// set defaults
env.registry = "https://package.openupm.com";
env.namespace = "com.openupm";
env.cwd = "";
env.manifestPath = "";
env.upstream = true;
env.color = true;
env.upstreamRegistry = "https://packages.unity.com";
env.systemUser = false;
env.wsl = false;
env.editorVersion = null;
env.region = "us";
// the npmAuth field of .upmconfig.toml
env.npmAuth = {};
// the dict of auth param for npm registry API
env.auth = {};
// log level
log.level = options._global.verbose ? "verbose" : "notice";
// color
if (options._global.color === false) env.color = false;
if (process.env.NODE_ENV == "test") env.color = false;
if (!env.color) {
chalk.level = 0;
log.disableColor();
}
// upstream
if (options._global.upstream === false) env.upstream = false;
// region cn
if (options._global.cn === true) {
env.registry = "https://package.openupm.cn";
env.upstreamRegistry = "https://packages.unity.cn";
env.region = "cn";
log.notice("region", "cn");
}
// registry
if (options._global.registry) {
let registry = options._global.registry;
if (!registry.toLowerCase().startsWith("http"))
registry = "http://" + registry;
if (registry.endsWith("/")) registry = registry.slice(0, -1);
env.registry = registry;
const hostname = url.parse(registry).hostname;
if (net.isIP(hostname)) env.namespace = hostname;
else
env.namespace = hostname
.split(".")
.reverse()
.slice(0, 2)
.join(".");
}
// auth
if (options._global.systemUser) env.systemUser = true;
if (options._global.wsl) env.wsl = true;
const upmConfig = await loadUpmConfig();
if (upmConfig) {
env.npmAuth = upmConfig.npmAuth;
if (env.npmAuth) {
for (const reg in env.npmAuth) {
const regAuth = env.npmAuth[reg];
if (regAuth.token) {
env.auth[reg] = {
token: regAuth.token,
alwaysAuth: regAuth.alwaysAuth || false
};
} else if (regAuth._auth) {
const buf = Buffer.from(regAuth._auth, "base64");
const text = buf.toString("utf-8");
const [username, password] = text.split(":", 2);
env.auth[reg] = {
username,
password: Buffer.from(password).toString("base64"),
email: regAuth.email,
alwaysAuth: regAuth.alwaysAuth || false
};
} else {
log.warn(
"env.auth",
`failed to parse auth info for ${reg} in .upmconfig.toml: missing token or _auth fields`
);
log.warn("env.auth", regAuth);
}
}
}
}
// log.verbose("env.npmAuth", env.npmAuth);
// log.verbose("env.auth", env.auth);
// return if no need to check path
if (!checkPath) return true;
// cwd
if (options._global.chdir) {
const cwd = path.resolve(options._global.chdir);
if (!fs.existsSync(cwd)) {
log.error("env", `can not resolve path ${cwd}`);
return false;
}
env.cwd = cwd;
} else env.cwd = process.cwd();
// manifest path
const manifestPath = path.join(env.cwd, "Packages/manifest.json");
if (!fs.existsSync(manifestPath)) {
log.error(
"manifest",
`can not locate manifest.json at path ${manifestPath}`
);
return false;
} else env.manifestPath = manifestPath;
// editor version
const projectVersionPath = path.join(
env.cwd,
"ProjectSettings/ProjectVersion.txt"
);
if (!fs.existsSync(projectVersionPath)) {
log.warn(
"ProjectVersion",
`can not locate ProjectVersion.text at path ${projectVersionPath}`
);
} else {
const projectVersionData = fs.readFileSync(projectVersionPath, "utf8");
const projectVersionContent = yaml.parse(projectVersionData);
env.editorVersion = projectVersionContent.m_EditorVersion;
}
// return
return true;
};
// Parse name to {name, version}
const parseName = function(pkg) {
const segs = pkg.split("@");
const name = segs[0];
const version =
segs.length > 1 ? segs.slice(1, segs.length).join("@") : undefined;
return { name, version };
};
// Get npm fetch options
const getNpmFetchOptions = function() {
const opts = {
log,
registry: env.registry
};
const auth = env.auth[env.registry];
if (auth) {
opts.alwaysAuth = auth.alwaysAuth;
opts.email = auth.email;
opts.password = auth.password;
opts.token = auth.token;
opts.username = auth.username;
}
return opts;
};
// Fetch package info json from registry
const fetchPackageInfo = async function(name, registry) {
if (!registry) registry = env.registry;
const pkgPath = `${registry}/${name}`;
const client = getNpmClient();
try {
return await client.get(pkgPath, { auth: env.auth[registry] || undefined });
// eslint-disable-next-line no-empty
} catch (err) {}
};
/* Fetch package [valid dependencies, invalid dependencies] with a structure of
[
{
name,
version,
upstream, // whether belongs to upstream registry
self, // whether is the source package
internal, // whether is an internal package
reason // invalid reason of "version404", "package404"
}, ...
]
*/
const fetchPackageDependencies = async function({ name, version, deep }) {
log.verbose("dependency", `fetch: ${name}@${version} deep=${deep}`);
// a list of pending dependency {name, version}
const pendingList = [{ name, version }];
// a list of processed dependency {name, version}
const processedList = [];
// a list of dependency entry exists on the registry
const depsValid = [];
// a list of dependency entry doesn't exist on the registry
const depsInvalid = [];
// cached dict: {pkg-name: pkgInfo}
const cachedPacakgeInfoDict = {};
while (pendingList.length > 0) {
const entry = pendingList.shift();
if (processedList.find(x => _.isEqual(x, entry)) === undefined) {
// add entry to processed list
processedList.push(entry);
// create valid depedenency structure
const depObj = {
...entry,
internal: isInternalPackage(entry.name),
upstream: false,
self: entry.name == name,
reason: null
};
if (!depObj.internal) {
// try fetching package info from cache
let { pkgInfo, upstream } = _.get(cachedPacakgeInfoDict, entry.name, {
pkgInfo: null,
upstream: false
});
if (pkgInfo) {
depObj.upstream = upstream;
}
// try fetching package info from the default registry
if (!pkgInfo) {
pkgInfo = await fetchPackageInfo(entry.name);
if (pkgInfo) {
depObj.upstream = false;
cachedPacakgeInfoDict[entry.name] = { pkgInfo, upstream: false };
}
}
// try fetching package info from the upstream registry
if (!pkgInfo) {
pkgInfo = await fetchPackageInfo(entry.name, env.upstreamRegistry);
if (pkgInfo) {
depObj.upstream = true;
cachedPacakgeInfoDict[entry.name] = { pkgInfo, upstream: true };
}
}
// handle package not exist
if (!pkgInfo) {
log.warn("404", `package not found: ${entry.name}`);
depObj.reason = "package404";
depsInvalid.push(depObj);
continue;
}
// verify version
const versions = Object.keys(pkgInfo.versions);
if (!entry.version || entry.version == "latest") {
// eslint-disable-next-line require-atomic-updates
depObj.version = entry.version = getLatestVersion(pkgInfo);
}
// handle version not exist
if (!versions.find(x => x == entry.version)) {
log.warn(
"404",
`package ${entry.name}@${
entry.version
} is not a valid choice of ${versions.reverse().join(", ")}`
);
depObj.reason = "version404";
// eslint-disable-next-line require-atomic-updates
// depObj.version = entry.version = getLatestVersion(pkgInfo);
// log.warn("notarget", `fallback to ${entry.name}@${entry.version}`);
depsInvalid.push(depObj);
continue;
}
// add dependencies to pending list
if (depObj.self || deep) {
const deps = _.toPairs(
pkgInfo.versions[entry.version]["dependencies"]
).map(x => {
return { name: x[0], version: x[1] };
});
deps.forEach(x => pendingList.push(x));
}
}
depsValid.push(depObj);
log.verbose(
"dependency",
`${entry.name}@${entry.version} ${
depObj.internal ? "[internal] " : ""
}${depObj.upstream ? "[upstream]" : ""}`
);
}
}
return [depsValid, depsInvalid];
};
// Get latest version from package info
const getLatestVersion = function(pkgInfo) {
if (pkgInfo["dist-tags"] && pkgInfo["dist-tags"]["latest"])
return pkgInfo["dist-tags"]["latest"];
else if (pkgInfo.versions)
return Object.keys(pkgInfo.versions).find(
key => pkgInfo.versions[key] == "latest"
);
};
// Load manifest json file
const loadManifest = function() {
try {
let text = fs.readFileSync(env.manifestPath, { encoding: "utf8" });
return JSON.parse(text);
} catch (err) {
if (err.code == "ENOENT")
log.error("manifest", "file Packages/manifest.json does not exist");
else {
log.error(
"manifest",
`failed to parse Packages/manifest.json at ${env.manifestPath}`
);
log.error("manifest", err.message);
}
return null;
}
};
// Save manifest json file
const saveManifest = function(data) {
let json = JSON.stringify(data, null, 2);
try {
fs.writeFileSync(env.manifestPath, json);
return true;
} catch (err) {
log.error("manifest", "can not write manifest json file");
log.error("manifest", err.message);
return false;
}
};
// Get .upmconfig.toml directory
const getUpmConfigDir = async function() {
let dirPath = "";
const systemUserSubPath = "Unity/config/ServiceAccounts";
if (env.wsl) {
if (!isWsl) {
throw new Error("no WSL detected");
}
if (env.systemUser) {
const allUserProfilePath = await execute(
'wslpath "$(wslvar ALLUSERSPROFILE)"',
{ trim: true }
);
dirPath = path.join(allUserProfilePath, systemUserSubPath);
} else {
dirPath = await execute('wslpath "$(wslvar USERPROFILE)"', {
trim: true
});
}
} else {
dirPath = process.env.USERPROFILE
? process.env.USERPROFILE
: process.env.HOME;
if (env.systemUser) {
if (!process.env.ALLUSERSPROFILE) {
throw new Error("env ALLUSERSPROFILE is empty");
}
dirPath = path.join(process.env.ALLUSERSPROFILE, systemUserSubPath);
}
}
return dirPath;
};
// Load .upmconfig.toml
const loadUpmConfig = async function(configDir) {
if (configDir === undefined) configDir = await getUpmConfigDir();
const configPath = path.join(configDir, ".upmconfig.toml");
if (fs.existsSync(configPath)) {
const content = fs.readFileSync(configPath, "utf8");
const config = TOML.parse(content);
return config;
}
};
// Save .upmconfig.toml
const saveUpmConfig = async function(config, configDir) {
if (configDir === undefined) configDir = await getUpmConfigDir();
mkdirp.sync(configDir);
const configPath = path.join(configDir, ".upmconfig.toml");
const content = TOML.stringify(config);
fs.writeFileSync(configPath, content, "utf8");
log.notice("config", "saved unity config at " + configPath);
};
// Compare unity editor version and return -1, 0, or 1.
const compareEditorVersion = function(a, b) {
const verA = parseEditorVersion(a);
const verB = parseEditorVersion(b);
const editorVersionToArray = ver => [
ver.major,
ver.minor,
ver.patch || 0,
ver.flagValue || 0,
ver.build || 0,
ver.locValue || 0,
ver.locBuild || 0
];
const arrA = editorVersionToArray(verA);
const arrB = editorVersionToArray(verB);
for (let i = 0; i < arrA.length; i++) {
const valA = arrA[i];
const valB = arrB[i];
if (valA > valB) return 1;
else if (valA < valB) return -1;
}
return 0;
};
/**
* Prase editor version string to groups.
*
* E.g. 2020.2.0f2c4
* major: 2020
* minor: 2
* patch: 0
* flag: 'f'
* flagValue: 2
* build: 2
* loc: 'c'
* locValue: 1
* locBuild: 4
*/
const parseEditorVersion = function(version) {
if (!version) return null;
const regex = /^(?<major>\d+)\.(?<minor>\d+)(\.(?<patch>\d+)((?<flag>a|b|f|c)(?<build>\d+)((?<loc>c)(?<locBuild>\d+))?)?)?/;
const match = regex.exec(version);
if (!match) return null;
const groups = match.groups;
const result = {
major: parseInt(groups.major),
minor: parseInt(groups.minor)
};
if (groups.patch) result.patch = parseInt(groups.patch);
if (groups.flag) {
result.flag = groups.flag.toLowerCase();
if (result.flag == "a") result.flagValue = 0;
if (result.flag == "b") result.flagValue = 1;
if (result.flag == "f") result.flagValue = 2;
if (groups.build) result.build = parseInt(groups.build);
}
if (groups.loc) {
result.loc = groups.loc.toLowerCase();
if (result.loc == "c") result.locValue = 1;
if (groups.locBuild) result.locBuild = parseInt(groups.locBuild);
}
return result;
};
// Detect if the given package name is an internal package
const isInternalPackage = function(name) {
const internals = [
"com.unity.ugui",
"com.unity.2d.sprite",
"com.unity.2d.tilemap",
"com.unity.package-manager-ui",
"com.unity.ugui"
];
return /com.unity.modules/i.test(name) || internals.includes(name);
};
module.exports = {
compareEditorVersion,
env,
fetchPackageDependencies,
fetchPackageInfo,
getLatestVersion,
getNpmFetchOptions,
getUpmConfigDir,
isInternalPackage,
loadManifest,
loadUpmConfig,
parseEnv,
parseName,
parseEditorVersion,
saveManifest,
saveUpmConfig
};