const { log } = require("./logger"); const url = require("url"); const { compareEditorVersion, env, fetchPackageDependencies, fetchPackageInfo, getLatestVersion, loadManifest, parseEditorVersion, parseEnv, parseName, saveManifest } = require("./core"); const add = async function(pkgs, options) { if (!Array.isArray(pkgs)) pkgs = [pkgs]; // parse env const envOk = await parseEnv(options, { checkPath: true }); if (!envOk) return 1; // add const results = []; for (const pkg of pkgs) results.push( await _add({ pkg, testables: options.test, force: options.force }) ); const result = { code: results.filter(x => x.code != 0).length > 0 ? 1 : 0, dirty: results.filter(x => x.dirty).length > 0 }; // print manifest notice if (result.dirty) log.notice("", "please open Unity project to apply changes"); return result.code; }; const _add = async function({ pkg, testables, force }) { // dirty flag let dirty = false; // is upstream package flag let isUpstreamPackage = false; // parse name let { name, version } = parseName(pkg); // load manifest let manifest = loadManifest(); if (manifest === null) return { code: 1, dirty }; // ensure manifest.dependencies if (!manifest.dependencies) { manifest.dependencies = {}; } // packages that added to scope registry const pkgsInScope = []; const isGitOrLocal = version && (version.startsWith("git") || version.startsWith("file") || version.startsWith("http")); if (!isGitOrLocal) { // verify name let pkgInfo = await fetchPackageInfo(name); if (!pkgInfo && env.upstream) { pkgInfo = await fetchPackageInfo(name, env.upstreamRegistry); if (pkgInfo) isUpstreamPackage = true; } if (!pkgInfo) { log.error("404", `package not found: ${name}`); return { code: 1, dirty }; } // verify version const versions = Object.keys(pkgInfo.versions); // eslint-disable-next-line require-atomic-updates if (!version || version == "latest") version = getLatestVersion(pkgInfo); if (versions.filter(x => x == version).length <= 0) { log.warn( "404", `version ${version} is not a valid choice of: ${versions .reverse() .join(", ")}` ); return { code: 1, dirty }; } const versionInfo = pkgInfo.versions[version]; // verify editor version if (versionInfo.unity) { const requiredEditorVersion = versionInfo.unityRelease ? versionInfo.unity + "." + versionInfo.unityRelease : versionInfo.unity; if (env.editorVersion) { const editorVersionResult = parseEditorVersion(env.editorVersion); const requiredEditorVersionResult = parseEditorVersion( requiredEditorVersion ); if (!editorVersionResult) { log.warn( "editor.version", `${env.editorVersion} is unknown, the editor version check is disabled` ); } if (!requiredEditorVersionResult) { log.warn("package.unity", `${requiredEditorVersion} is not valid`); if (!force) { log.notice( "suggest", "contact the package author to fix the issue, or run with option -f to ignore the warning" ); return { code: 1, dirty }; } } if ( editorVersionResult && requiredEditorVersionResult && compareEditorVersion(env.editorVersion, requiredEditorVersion) < 0 ) { log.warn( "editor.version", `requires ${requiredEditorVersion} but found ${env.editorVersion}` ); if (!force) { log.notice( "suggest", `upgrade the editor to ${requiredEditorVersion}, or run with option -f to ignore the warning` ); return { code: 1, dirty }; } } } } // pkgsInScope if (!isUpstreamPackage) { const [depsValid, depsInvalid] = await fetchPackageDependencies({ name, version, deep: true }); // add depsValid to pkgsInScope. depsValid .filter(x => !x.upstream && !x.internal) .map(x => x.name) .forEach(name => pkgsInScope.push(name)); // print suggestion for depsInvalid depsInvalid.forEach(depObj => { if (depObj.reason == "package404" || depObj.reason == "version404") { const resolvedVersion = manifest.dependencies[depObj.name]; depObj.resolved = Boolean(resolvedVersion); if (!depObj.resolved) log.notice( "suggest", `to install ${depObj.name}@${depObj.version} or a replaceable version manually` ); } }); if (depsInvalid.filter(x => !x.resolved).length > 0) { if (!force) { log.error( "missing dependencies", "please resolve thie issue or run with option -f to ignore the warning" ); return { code: 1, dirty }; } } } else pkgsInScope.push(name); } // add to dependencies const oldVersion = manifest.dependencies[name]; manifest.dependencies[name] = version; if (!oldVersion) { // Log the added package log.notice("manifest", `added ${name}@${version}`); dirty = true; } else if (oldVersion != version) { // Log the modified package version log.notice("manifest", `modified ${name} ${oldVersion} => ${version}`); dirty = true; } else { // Log the existed package log.notice("manifest", `existed ${name}@${version}`); } if (!isUpstreamPackage) { // add to scopedRegistries if (!manifest.scopedRegistries) { manifest.scopedRegistries = []; dirty = true; } const filterEntry = x => { let addr = x.url || ""; if (addr.endsWith("/")) addr = addr.slice(0, -1); return addr == env.registry; }; if (manifest.scopedRegistries.filter(filterEntry).length <= 0) { manifest.scopedRegistries.push({ name: url.parse(env.registry).hostname, url: env.registry, scopes: [] }); dirty = true; } let entry = manifest.scopedRegistries.filter(filterEntry)[0]; // apply pkgsInScope let scopesSet = new Set(entry.scopes || []); pkgsInScope.push(env.namespace); pkgsInScope.forEach(name => { if (!scopesSet.has(name)) { scopesSet.add(name); dirty = true; } }); entry.scopes = Array.from(scopesSet).sort(); } if (testables) { if (!manifest.testables) { manifest.testables = []; } if (manifest.testables.indexOf(name) === -1) { manifest.testables.push(name); } } // save manifest if (dirty) { if (!saveManifest(manifest)) return { code: 1, dirty }; } return { code: 0, dirty }; }; module.exports = add;