diff --git a/src/client.ts b/src/client.ts deleted file mode 100644 index f8395b13..00000000 --- a/src/client.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { promisify } from "util"; -import RegClient, { - AddUserParams, - AddUserResponse, - ClientCallback, - GetParams, -} from "another-npm-registry-client"; -import log from "./logger"; -import request from "request"; -import { AssertionError } from "assert"; -import { PkgInfo } from "./types/global"; - -export type NpmClient = { - rawClient: RegClient; - /** - * @throws {NpmClientError} - */ - get(uri: string, options: GetParams): Promise; - /** - * @throws {NpmClientError} - */ - adduser(uri: string, options: AddUserParams): Promise; -}; - -export class NpmClientError extends Error { - cause: Error; - response: request.Response; - - constructor(cause: Error, response: request.Response) { - super( - cause?.message ?? - "An error occurred while interacting with an Npm registry" - ); - this.name = "NpmClientError"; - this.cause = cause; - this.response = response; - } -} - -export function assertIsNpmClientError( - x: unknown -): asserts x is NpmClientError { - if (!(x instanceof NpmClientError)) - throw new AssertionError({ - message: "Given object was not an NpmClientError", - actual: x, - }); -} - -/** - * Normalizes a RegClient function. Specifically it merges it's multiple - * callback arguments into a single NormalizedError object. This function - * also takes care of binding and promisifying. - */ -function normalizeClientFunction( - client: RegClient, - fn: (uri: string, params: TParam, cb: ClientCallback) => void -): (uri: string, params: TParam) => Promise { - const bound = fn.bind(client); - const withNormalizedError = ( - uri: string, - params: TParam, - cb: (error: NpmClientError | null, data: TData) => void - ) => { - return bound(uri, params, (error, data, raw, res) => { - cb(error !== null ? new NpmClientError(error, res) : null, data); - }); - }; - return promisify(withNormalizedError); -} - -/** - * Return npm client - */ -export const getNpmClient = (): NpmClient => { - // create client - const client = new RegClient({ log }); - return { - // The instance of raw npm client - rawClient: client, - // Promisified methods - get: normalizeClientFunction(client, client.get), - adduser: normalizeClientFunction(client, client.adduser), - }; -}; diff --git a/src/cmd-add.ts b/src/cmd-add.ts index 4b9934f1..01284912 100644 --- a/src/cmd-add.ts +++ b/src/cmd-add.ts @@ -1,19 +1,17 @@ import log from "./logger"; import url from "url"; -import { - compareEditorVersion, - env, - fetchPackageDependencies, - fetchPackageInfo, - getLatestVersion, - loadManifest, - parseEditorVersion, - parseEnv, - saveManifest, -} from "./core"; import { isUrlVersion } from "./utils/pkg-version"; -import { splitPkgName } from "./utils/pkg-name"; +import { atVersion, splitPkgName } from "./utils/pkg-name"; import { GlobalOptions, PkgName, ScopedRegistry } from "./types/global"; +import { tryGetLatestVersion } from "./utils/pkg-info"; +import { loadManifest, saveManifest } from "./utils/manifest"; +import { env, parseEnv } from "./utils/env"; + +import { + compareEditorVersion, + tryParseEditorVersion, +} from "./utils/editor-version"; +import { fetchPackageDependencies, fetchPackageInfo } from "./registry-client"; export type AddOptions = { test?: boolean; @@ -86,7 +84,7 @@ const _add = async function ({ // verify version const versions = Object.keys(pkgInfo.versions); // eslint-disable-next-line require-atomic-updates - if (!version || version == "latest") version = getLatestVersion(pkgInfo); + if (!version || version == "latest") version = tryGetLatestVersion(pkgInfo); if (versions.filter((x) => x == version).length <= 0) { log.warn( "404", @@ -106,8 +104,8 @@ const _add = async function ({ ? versionInfo.unity + "." + versionInfo.unityRelease : versionInfo.unity; if (env.editorVersion) { - const editorVersionResult = parseEditorVersion(env.editorVersion); - const requiredEditorVersionResult = parseEditorVersion( + const editorVersionResult = tryParseEditorVersion(env.editorVersion); + const requiredEditorVersionResult = tryParseEditorVersion( requiredEditorVersion ); if (!editorVersionResult) { @@ -165,7 +163,10 @@ const _add = async function ({ if (!depObj.resolved) log.notice( "suggest", - `to install ${depObj.name}@${depObj.version} or a replaceable version manually` + `to install ${atVersion( + depObj.name, + depObj.version + )} or a replaceable version manually` ); } }); @@ -185,7 +186,7 @@ const _add = async function ({ manifest.dependencies[name] = version; if (!oldVersion) { // Log the added package - log.notice("manifest", `added ${name}@${version}`); + log.notice("manifest", `added ${atVersion(name, version)}`); dirty = true; } else if (oldVersion != version) { // Log the modified package version @@ -193,7 +194,7 @@ const _add = async function ({ dirty = true; } else { // Log the existed package - log.notice("manifest", `existed ${name}@${version}`); + log.notice("manifest", `existed ${atVersion(name, version)}`); } if (!isUpstreamPackage) { // add to scopedRegistries diff --git a/src/cmd-deps.ts b/src/cmd-deps.ts index f5672ac2..ee63424b 100644 --- a/src/cmd-deps.ts +++ b/src/cmd-deps.ts @@ -1,7 +1,8 @@ import log from "./logger"; -import { fetchPackageDependencies, parseEnv } from "./core"; -import { splitPkgName } from "./utils/pkg-name"; +import { atVersion, splitPkgName } from "./utils/pkg-name"; import { GlobalOptions, PkgName, PkgVersion } from "./types/global"; +import { parseEnv } from "./utils/env"; +import { fetchPackageDependencies } from "./registry-client"; export type DepsOptions = { deep?: boolean; @@ -36,13 +37,15 @@ const _deps = async function ({ }); depsValid .filter((x) => !x.self) - .forEach((x) => log.notice("dependency", `${x.name}@${x.version}`)); + .forEach((x) => + log.notice("dependency", `${atVersion(x.name, x.version)}`) + ); depsInvalid .filter((x) => !x.self) .forEach((x) => { let reason = "unknown"; if (x.reason == "package404") reason = "missing dependency"; else if (x.reason == "version404") reason = "missing dependency version"; - log.warn(reason, `${x.name}@${x.version}`); + log.warn(reason, atVersion(x.name, x.version)); }); }; diff --git a/src/cmd-login.ts b/src/cmd-login.ts index 1c34304d..e5198e47 100644 --- a/src/cmd-login.ts +++ b/src/cmd-login.ts @@ -2,17 +2,17 @@ import fs from "fs"; import path from "path"; import _ from "lodash"; import promptly from "promptly"; -import { assertIsNpmClientError, getNpmClient } from "./client"; +import { assertIsNpmClientError, getNpmClient } from "./registry-client"; import log from "./logger"; +import { GlobalOptions, Registry } from "./types/global"; import { getUpmConfigDir, loadUpmConfig, - parseEnv, saveUpmConfig, -} from "./core"; -import { GlobalOptions, Registry } from "./types/global"; +} from "./utils/upm-config"; +import { parseEnv } from "./utils/env"; export type LoginOptions = { username?: string; diff --git a/src/cmd-remove.ts b/src/cmd-remove.ts index 32f7832a..b051367d 100644 --- a/src/cmd-remove.ts +++ b/src/cmd-remove.ts @@ -1,7 +1,8 @@ import log from "./logger"; -import { env, loadManifest, parseEnv, saveManifest } from "./core"; -import { splitPkgName } from "./utils/pkg-name"; +import { atVersion, splitPkgName } from "./utils/pkg-name"; import { GlobalOptions, PkgName, ScopedRegistry } from "./types/global"; +import { loadManifest, saveManifest } from "./utils/manifest"; +import { env, parseEnv } from "./utils/env"; export type RemoveOptions = { _global: GlobalOptions; @@ -36,7 +37,7 @@ const _remove = async function (pkg: PkgName) { const name = split.name; let version = split.version; if (version) { - log.warn("", `please replace '${name}@${version}' with '${name}'`); + log.warn("", `please replace '${atVersion(name, version)}' with '${name}'`); return { code: 1, dirty }; } // load manifest @@ -48,7 +49,7 @@ const _remove = async function (pkg: PkgName) { if (manifest.dependencies) { version = manifest.dependencies[name]; if (version) { - log.notice("manifest", `removed ${name}@${version}`); + log.notice("manifest", `removed ${atVersion(name, version)}`); delete manifest.dependencies[name]; dirty = true; } else pkgsNotFound.push(pkg); diff --git a/src/cmd-search.ts b/src/cmd-search.ts index 01264e21..715fb198 100644 --- a/src/cmd-search.ts +++ b/src/cmd-search.ts @@ -1,8 +1,7 @@ -import npmSearch from "libnpmsearch"; +import npmSearch, { Options } from "libnpmsearch"; import npmFetch from "npm-registry-fetch"; import Table from "cli-table"; import log from "./logger"; -import { env, getLatestVersion, getNpmFetchOptions, parseEnv } from "./core"; import { is404Error, isHttpError } from "./utils/error-type-guards"; import * as os from "os"; import assert from "assert"; @@ -13,6 +12,8 @@ import { PkgVersion, Registry, } from "./types/global"; +import { tryGetLatestVersion } from "./utils/pkg-info"; +import { env, parseEnv } from "./utils/env"; type DateString = string; @@ -21,6 +22,16 @@ type TableRow = [PkgName, PkgVersion, DateString, ""]; export type SearchOptions = { _global: GlobalOptions; }; +// Get npm fetch options +const getNpmFetchOptions = function (): Options { + const opts: Options = { + log, + registry: env.registry, + }; + const auth = env.auth[env.registry]; + if (auth) Object.assign(opts, auth); + return opts; +}; const searchEndpoint = async function ( keyword: string, @@ -86,7 +97,7 @@ const getTable = function () { const getTableRow = function (pkg: PkgInfo): TableRow { const name = pkg.name; - const version = getLatestVersion(pkg); + const version = tryGetLatestVersion(pkg); let date = ""; if (pkg.time && pkg.time.modified) date = pkg.time.modified.split("T")[0]; if (pkg.date) { diff --git a/src/cmd-view.ts b/src/cmd-view.ts index 0b760c39..98664bd2 100644 --- a/src/cmd-view.ts +++ b/src/cmd-view.ts @@ -1,9 +1,11 @@ import chalk from "chalk"; import log from "./logger"; -import { env, fetchPackageInfo, getLatestVersion, parseEnv } from "./core"; import assert from "assert"; -import { splitPkgName } from "./utils/pkg-name"; +import { atVersion, splitPkgName } from "./utils/pkg-name"; import { GlobalOptions, PkgInfo, PkgName } from "./types/global"; +import { tryGetLatestVersion } from "./utils/pkg-info"; +import { env, parseEnv } from "./utils/env"; +import { fetchPackageInfo } from "./registry-client"; export type ViewOptions = { _global: GlobalOptions; @@ -16,7 +18,7 @@ export const view = async function (pkg: PkgName, options: ViewOptions) { // parse name const { name, version } = splitPkgName(pkg); if (version) { - log.warn("", `please replace '${name}@${version}' with '${name}'`); + log.warn("", `please replace '${atVersion(name, version)}' with '${name}'`); return 1; } // verify name @@ -34,7 +36,7 @@ export const view = async function (pkg: PkgName, options: ViewOptions) { const printInfo = function (pkg: PkgInfo) { const versionCount = Object.keys(pkg.versions).length; - const ver = getLatestVersion(pkg); + const ver = tryGetLatestVersion(pkg); assert(ver !== undefined); const verInfo = pkg.versions[ver]; const license = verInfo.license || "proprietary or unlicensed"; diff --git a/src/core.ts b/src/core.ts deleted file mode 100644 index b43ba6b0..00000000 --- a/src/core.ts +++ /dev/null @@ -1,524 +0,0 @@ -import fs from "fs"; -import path from "path"; -import url from "url"; -import _ from "lodash"; -import chalk from "chalk"; -import mkdirp from "mkdirp"; -import net from "node:net"; -import isWsl from "is-wsl"; -import TOML from "@iarna/toml"; -import yaml from "yaml"; -import execute from "./utils/process"; -import { getNpmClient } from "./client"; -import log from "./logger"; -import { assertIsError } from "./utils/error-type-guards"; -import search from "libnpmsearch"; -import assert from "assert"; -import { - Dependency, - Env, - GlobalOptions, - NameVersionPair, - PkgInfo, - PkgManifest, - PkgName, - PkgVersion, - Registry, - SemanticVersion, - UPMConfig, -} from "./types/global"; - -export const env: Env = { - auth: {}, - color: false, - cwd: "", - editorVersion: null, - manifestPath: "", - namespace: "", - region: "us", - registry: "", - systemUser: false, - upstream: false, - upstreamRegistry: "", - wsl: false, -}; - -// Parse env -export const parseEnv = async function ( - options: { _global: GlobalOptions } & Record, - { checkPath }: { checkPath: unknown } -) { - // 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; - // TODO: Check hostname for null - const hostname = url.parse(registry).hostname as string; - 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 ("token" in regAuth) { - env.auth[reg] = { - token: regAuth.token, - alwaysAuth: regAuth.alwaysAuth || false, - }; - } else if ("_auth" in regAuth) { - 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; -}; - -// Get npm fetch options -export const getNpmFetchOptions = function (): search.Options { - const opts: search.Options = { - log, - registry: env.registry, - }; - const auth = env.auth[env.registry]; - if (auth) Object.assign(opts, auth); - return opts; -}; - -// Fetch package info json from registry -export const fetchPackageInfo = async function ( - name: PkgName, - registry?: Registry -): Promise { - 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" - }, ... - ] - */ -export const fetchPackageDependencies = async function ({ - name, - version, - deep, -}: { - name: PkgName; - version: PkgVersion | undefined; - deep?: boolean; -}): Promise<[Dependency[], Dependency[]]> { - log.verbose("dependency", `fetch: ${name}@${version} deep=${deep}`); - // a list of pending dependency {name, version} - const pendingList: NameVersionPair[] = [{ 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: Record< - PkgVersion, - { pkgInfo: PkgInfo; upstream: boolean } - > = {}; - while (pendingList.length > 0) { - // NOTE: Guaranteed defined because of while loop logic - const entry = pendingList.shift() as NameVersionPair; - if (processedList.find((x) => _.isEqual(x, entry)) === undefined) { - // add entry to processed list - processedList.push(entry); - // create valid depedenency structure - const depObj: Dependency = { - ...entry, - internal: isInternalPackage(entry.name), - upstream: false, - self: entry.name == name, - reason: null, - }; - if (!depObj.internal) { - // try fetching package info from cache - const getResult = _.get(cachedPacakgeInfoDict, entry.name, { - pkgInfo: null, - upstream: false, - }); - let pkgInfo = getResult.pkgInfo; - const upstream = getResult.upstream; - if (pkgInfo !== null) { - depObj.upstream = upstream; - } - // try fetching package info from the default registry - if (pkgInfo === null) { - pkgInfo = (await fetchPackageInfo(entry.name)) ?? null; - 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)) ?? null; - 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") { - const latestVersion = getLatestVersion(pkgInfo); - assert(latestVersion !== undefined); - // eslint-disable-next-line require-atomic-updates - depObj.version = entry.version = latestVersion; - } - // 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: NameVersionPair[] = _.toPairs( - pkgInfo.versions[entry.version]["dependencies"] - ).map((x: [PkgName, PkgVersion]): NameVersionPair => { - 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 -export const getLatestVersion = function ( - pkgInfo: Partial -): PkgVersion | undefined { - if (pkgInfo["dist-tags"]?.["latest"] !== undefined) - return pkgInfo["dist-tags"]["latest"]; - else if (pkgInfo.version) return pkgInfo.version; -}; - -// Load manifest json file -export const loadManifest = function (): PkgManifest | null { - try { - const text = fs.readFileSync(env.manifestPath, { encoding: "utf8" }); - return JSON.parse(text); - } catch (err) { - assertIsError(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 -export const saveManifest = function (data: PkgManifest) { - const json = JSON.stringify(data, null, 2); - try { - fs.writeFileSync(env.manifestPath, json); - return true; - } catch (err) { - assertIsError(err); - log.error("manifest", "can not write manifest json file"); - log.error("manifest", err.message); - return false; - } -}; - -// Get .upmconfig.toml directory -export const getUpmConfigDir = async function (): Promise { - let dirPath: string | undefined = ""; - 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); - } - } - if (dirPath === undefined) - throw new Error("Could not resolve upm-config dir-path"); - return dirPath; -}; - -// Load .upmconfig.toml -export const loadUpmConfig = async function ( - configDir?: string -): Promise { - 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); - - // NOTE: We assume correct format - return config as UPMConfig; - } -}; - -// Save .upmconfig.toml -export const saveUpmConfig = async function ( - config: UPMConfig, - configDir: string -) { - 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. -export const compareEditorVersion = function (a: string, b: string) { - const verA = parseEditorVersion(a); - const verB = parseEditorVersion(b); - - if (verA === null || verB === null) - throw new Error("An editor version could not be parsed"); - - const editorVersionToArray = (ver: SemanticVersion) => [ - 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 - */ -export const parseEditorVersion = function ( - version: string | null -): SemanticVersion | null { - type RegexMatchGroups = { - major: `${number}`; - minor: `${number}`; - patch?: string; - flag?: "a" | "b" | "f" | "c"; - build?: `${number}`; - loc?: "c"; - locBuild?: `${number}`; - }; - - if (!version) return null; - const regex = - /^(?\d+)\.(?\d+)(\.(?\d+)((?a|b|f|c)(?\d+)((?c)(?\d+))?)?)?/; - const match = regex.exec(version); - if (!match) return null; - const groups = match.groups; - const result: SemanticVersion = { - major: parseInt(groups.major), - minor: parseInt(groups.minor), - }; - if (groups.patch) result.patch = parseInt(groups.patch); - if (groups.flag) { - result.flag = groups.flag; - 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 -export const isInternalPackage = function (name: PkgName): boolean { - 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); -}; diff --git a/src/registry-client.ts b/src/registry-client.ts new file mode 100644 index 00000000..2e0152be --- /dev/null +++ b/src/registry-client.ts @@ -0,0 +1,247 @@ +import { promisify } from "util"; +import RegClient, { + AddUserParams, + AddUserResponse, + ClientCallback, + GetParams, +} from "another-npm-registry-client"; +import log from "./logger"; +import request from "request"; +import assert, { AssertionError } from "assert"; +import { + Dependency, + NameVersionPair, + PkgInfo, + PkgName, + PkgVersion, + Registry, +} from "./types/global"; +import { env } from "./utils/env"; +import { atVersion, isInternalPackage } from "./utils/pkg-name"; +import _ from "lodash"; +import { tryGetLatestVersion } from "./utils/pkg-info"; + +export type NpmClient = { + rawClient: RegClient; + /** + * @throws {NpmClientError} + */ + get(uri: string, options: GetParams): Promise; + /** + * @throws {NpmClientError} + */ + adduser(uri: string, options: AddUserParams): Promise; +}; + +export class NpmClientError extends Error { + cause: Error; + response: request.Response; + + constructor(cause: Error, response: request.Response) { + super( + cause?.message ?? + "An error occurred while interacting with an Npm registry" + ); + this.name = "NpmClientError"; + this.cause = cause; + this.response = response; + } +} + +export function assertIsNpmClientError( + x: unknown +): asserts x is NpmClientError { + if (!(x instanceof NpmClientError)) + throw new AssertionError({ + message: "Given object was not an NpmClientError", + actual: x, + }); +} + +/** + * Normalizes a RegClient function. Specifically it merges it's multiple + * callback arguments into a single NormalizedError object. This function + * also takes care of binding and promisifying. + */ +function normalizeClientFunction( + client: RegClient, + fn: (uri: string, params: TParam, cb: ClientCallback) => void +): (uri: string, params: TParam) => Promise { + const bound = fn.bind(client); + const withNormalizedError = ( + uri: string, + params: TParam, + cb: (error: NpmClientError | null, data: TData) => void + ) => { + return bound(uri, params, (error, data, raw, res) => { + cb(error !== null ? new NpmClientError(error, res) : null, data); + }); + }; + return promisify(withNormalizedError); +} + +/** + * Return npm client + */ +export const getNpmClient = (): NpmClient => { + // create client + const client = new RegClient({ log }); + return { + // The instance of raw npm client + rawClient: client, + // Promisified methods + get: normalizeClientFunction(client, client.get), + adduser: normalizeClientFunction(client, client.adduser), + }; +}; +// Fetch package info json from registry +export const fetchPackageInfo = async function ( + name: PkgName, + registry?: Registry +): Promise { + 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" + }, ... + ] + */ +export const fetchPackageDependencies = async function ({ + name, + version, + deep, +}: { + name: PkgName; + version: PkgVersion | undefined; + deep?: boolean; +}): Promise<[Dependency[], Dependency[]]> { + log.verbose( + "dependency", + `fetch: ${ + version !== undefined ? atVersion(name, version) : name + } deep=${deep}` + ); + // a list of pending dependency {name, version} + const pendingList: NameVersionPair[] = [{ 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: Record< + PkgVersion, + { pkgInfo: PkgInfo; upstream: boolean } + > = {}; + while (pendingList.length > 0) { + // NOTE: Guaranteed defined because of while loop logic + const entry = pendingList.shift() as NameVersionPair; + if (processedList.find((x) => _.isEqual(x, entry)) === undefined) { + // add entry to processed list + processedList.push(entry); + // create valid depedenency structure + const depObj: Dependency = { + ...entry, + internal: isInternalPackage(entry.name), + upstream: false, + self: entry.name == name, + version: "", + reason: null, + }; + if (!depObj.internal) { + // try fetching package info from cache + const getResult = _.get(cachedPacakgeInfoDict, entry.name, { + pkgInfo: null, + upstream: false, + }); + let pkgInfo = getResult.pkgInfo; + const upstream = getResult.upstream; + if (pkgInfo !== null) { + depObj.upstream = upstream; + } + // try fetching package info from the default registry + if (pkgInfo === null) { + pkgInfo = (await fetchPackageInfo(entry.name)) ?? null; + 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)) ?? null; + 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") { + const latestVersion = tryGetLatestVersion(pkgInfo); + assert(latestVersion !== undefined); + // eslint-disable-next-line require-atomic-updates + depObj.version = entry.version = latestVersion; + } + // handle version not exist + if (!versions.find((x) => x == entry.version)) { + log.warn( + "404", + `package ${ + version !== undefined ? atVersion(name, version) : name + } 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: NameVersionPair[] = _.toPairs( + pkgInfo.versions[entry.version]["dependencies"] + ).map((x: [PkgName, PkgVersion]): NameVersionPair => { + return { name: x[0], version: x[1] }; + }); + deps.forEach((x) => pendingList.push(x)); + } + } + depsValid.push(depObj); + log.verbose( + "dependency", + `${ + entry.version !== undefined + ? atVersion(entry.name, entry.version) + : entry.name + } ${depObj.internal ? "[internal] " : ""}${ + depObj.upstream ? "[upstream]" : "" + }` + ); + } + } + return [depsValid, depsInvalid]; +}; diff --git a/src/types/global.ts b/src/types/global.ts index 3af06329..7f2cd06b 100644 --- a/src/types/global.ts +++ b/src/types/global.ts @@ -10,7 +10,7 @@ export type Region = "us" | "cn"; export type Registry = string; -export type SemanticVersion = { +export type EditorVersion = { major: number; minor: number; patch?: number; @@ -74,7 +74,7 @@ export type NameVersionPair = { export type Dependency = { name: PkgName; - version: PkgVersion | undefined; + version: PkgVersion; upstream: boolean; self: boolean; internal: boolean; diff --git a/src/utils/editor-version.ts b/src/utils/editor-version.ts new file mode 100644 index 00000000..aed12f0a --- /dev/null +++ b/src/utils/editor-version.ts @@ -0,0 +1,90 @@ +import { EditorVersion } from "../types/global"; + +/** + * Compares two editor versions for ordering + * @param a The first version + * @param b The second version + */ +export const compareEditorVersion = function ( + a: string, + b: string +): -1 | 0 | 1 { + const verA = tryParseEditorVersion(a); + const verB = tryParseEditorVersion(b); + + if (verA === null || verB === null) + throw new Error("An editor version could not be parsed"); + + const editorVersionToArray = (ver: EditorVersion) => [ + 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; +}; + +/** + * Attempts to parse 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 + */ +export const tryParseEditorVersion = function ( + version: string | null +): EditorVersion | null { + type RegexMatchGroups = { + major: `${number}`; + minor: `${number}`; + patch?: string; + flag?: "a" | "b" | "f" | "c"; + build?: `${number}`; + loc?: "c"; + locBuild?: `${number}`; + }; + + if (!version) return null; + const regex = + /^(?\d+)\.(?\d+)(\.(?\d+)((?a|b|f|c)(?\d+)((?c)(?\d+))?)?)?/; + const match = regex.exec(version); + if (!match) return null; + const groups = match.groups; + const result: EditorVersion = { + major: parseInt(groups.major), + minor: parseInt(groups.minor), + }; + if (groups.patch) result.patch = parseInt(groups.patch); + if (groups.flag) { + result.flag = groups.flag; + 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; +}; diff --git a/src/utils/env.ts b/src/utils/env.ts new file mode 100644 index 00000000..c49b6f1e --- /dev/null +++ b/src/utils/env.ts @@ -0,0 +1,149 @@ +import { Env, GlobalOptions } from "../types/global"; +import log from "../logger"; +import chalk from "chalk"; +import url from "url"; +import net from "node:net"; +import { loadUpmConfig } from "./upm-config"; +import path from "path"; +import fs from "fs"; +import yaml from "yaml"; + +export const env: Env = { + auth: {}, + color: false, + cwd: "", + editorVersion: null, + manifestPath: "", + namespace: "", + region: "us", + registry: "", + systemUser: false, + upstream: false, + upstreamRegistry: "", + wsl: false, +}; +// Parse env +export const parseEnv = async function ( + options: { _global: GlobalOptions } & Record, + { checkPath }: { checkPath: unknown } +) { + // 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; + // TODO: Check hostname for null + const hostname = url.parse(registry).hostname as string; + 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 ("token" in regAuth) { + env.auth[reg] = { + token: regAuth.token, + alwaysAuth: regAuth.alwaysAuth || false, + }; + } else if ("_auth" in regAuth) { + 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; +}; diff --git a/src/utils/manifest.ts b/src/utils/manifest.ts new file mode 100644 index 00000000..25fa3ed8 --- /dev/null +++ b/src/utils/manifest.ts @@ -0,0 +1,43 @@ +import { PkgManifest } from "../types/global"; +import fs from "fs"; +import { assertIsError } from "./error-type-guards"; +import log from "../logger"; +import { env } from "./env"; + +/** + * Attempts to load the manifest from the path specified in env + */ +export const loadManifest = function (): PkgManifest | null { + try { + const text = fs.readFileSync(env.manifestPath, { encoding: "utf8" }); + return JSON.parse(text); + } catch (err) { + assertIsError(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 to the path specified in env + */ +export const saveManifest = function (data: PkgManifest) { + const json = JSON.stringify(data, null, 2); + try { + fs.writeFileSync(env.manifestPath, json); + return true; + } catch (err) { + assertIsError(err); + log.error("manifest", "can not write manifest json file"); + log.error("manifest", err.message); + return false; + } +}; diff --git a/src/utils/pkg-info.ts b/src/utils/pkg-info.ts new file mode 100644 index 00000000..bb2bc466 --- /dev/null +++ b/src/utils/pkg-info.ts @@ -0,0 +1,18 @@ +import { PkgInfo, PkgVersion } from "../types/global"; + +const hasLatestDistTag = ( + pkgInfo: Partial +): pkgInfo is Partial & { "dist-tags": { latest: PkgVersion } } => { + return pkgInfo["dist-tags"]?.["latest"] !== undefined; +}; + +/** + * Attempt to get the latest version from a package + * @param pkgInfo The package. All properties are assumed to be potentially missing + */ +export const tryGetLatestVersion = function ( + pkgInfo: Partial +): PkgVersion | undefined { + if (hasLatestDistTag(pkgInfo)) return pkgInfo["dist-tags"].latest; + else if (pkgInfo.version) return pkgInfo.version; +}; diff --git a/src/utils/pkg-name.ts b/src/utils/pkg-name.ts index 146ea161..e663f4b1 100644 --- a/src/utils/pkg-name.ts +++ b/src/utils/pkg-name.ts @@ -16,3 +16,28 @@ export const splitPkgName = function (pkgName: PkgName): { : undefined; return { name, version }; }; + +/** + * Merges a package name and version to create a package name for that specific version + * @param name The name of the package + * @param version The version of the package + */ +export const atVersion = ( + name: ReverseDomainName, + version: PkgVersion +): PkgName => `${name}@${version}`; + +/** + * Detect if the given package name is an internal package + * @param name The name of the package + */ +export const isInternalPackage = (name: ReverseDomainName): boolean => { + 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); +}; diff --git a/src/utils/pkg-version.ts b/src/utils/pkg-version.ts index fac76da6..171f37cd 100644 --- a/src/utils/pkg-version.ts +++ b/src/utils/pkg-version.ts @@ -1,6 +1,7 @@ import { PkgVersion } from "../types/global"; const isGit = (version: PkgVersion): boolean => version.startsWith("git"); + const isHttp = (version: PkgVersion): boolean => version.startsWith("http"); const isLocal = (version: PkgVersion): boolean => version.startsWith("file"); diff --git a/src/utils/upm-config.ts b/src/utils/upm-config.ts new file mode 100644 index 00000000..2cdfc2fe --- /dev/null +++ b/src/utils/upm-config.ts @@ -0,0 +1,75 @@ +import { UPMConfig } from "../types/global"; +import mkdirp from "mkdirp"; +import path from "path"; +import TOML from "@iarna/toml"; +import fs from "fs"; +import log from "../logger"; +import isWsl from "is-wsl"; +import execute from "./process"; +import { env } from "./env"; + +/** + * Gets the path to directory in which the upm config is stored + */ +export const getUpmConfigDir = async (): Promise => { + let dirPath: string | undefined = ""; + 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); + } + } + if (dirPath === undefined) + throw new Error("Could not resolve upm-config dir-path"); + return dirPath; +}; + +/** + * Attempts to load the upm config + */ +export const loadUpmConfig = async ( + configDir?: string +): Promise => { + 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); + + // NOTE: We assume correct format + return config as UPMConfig; + } +}; + +/** + * Save the upm config + */ +export const saveUpmConfig = async (config: UPMConfig, configDir: string) => { + 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); +}; diff --git a/test/test-cmd-add.ts b/test/test-cmd-add.ts index 86b1c14d..3dff8b5a 100644 --- a/test/test-cmd-add.ts +++ b/test/test-cmd-add.ts @@ -1,7 +1,6 @@ import "assert"; import nock from "nock"; import "should"; -import { loadManifest } from "../src/core"; import { add } from "../src/cmd-add"; @@ -16,6 +15,7 @@ import { } from "./utils"; import testConsole from "test-console"; import assert from "assert"; +import { loadManifest } from "../src/utils/manifest"; describe("cmd-add.ts", function () { const options = { diff --git a/test/test-cmd-remove.ts b/test/test-cmd-remove.ts index abad6df7..11ac5244 100644 --- a/test/test-cmd-remove.ts +++ b/test/test-cmd-remove.ts @@ -1,6 +1,5 @@ import "assert"; import "should"; -import { loadManifest } from "../src/core"; import { remove } from "../src/cmd-remove"; @@ -13,6 +12,7 @@ import { } from "./utils"; import testConsole from "test-console"; import assert from "assert"; +import { loadManifest } from "../src/utils/manifest"; describe("cmd-remove.ts", function () { describe("remove", function () { diff --git a/test/test-core.ts b/test/test-core.ts deleted file mode 100644 index 04f3682c..00000000 --- a/test/test-core.ts +++ /dev/null @@ -1,453 +0,0 @@ -import "assert"; -import fs from "fs"; -import nock from "nock"; -import path from "path"; -import "should"; -import { - compareEditorVersion, - env, - fetchPackageInfo, - getLatestVersion, - isInternalPackage, - loadManifest, - parseEditorVersion, - parseEnv, - saveManifest, -} from "../src/core"; - -import { - createWorkDir, - getInspects, - getOutputs, - getWorkDir, - nockDown, - nockUp, - removeWorkDir, -} from "./utils"; -import testConsole from "test-console"; -import assert from "assert"; - -describe("cmd-core.ts", function () { - describe("parseEnv", function () { - let stdoutInspect: testConsole.Inspector = null!; - let stderrInspect: testConsole.Inspector = null!; - before(function () { - removeWorkDir("test-openupm-cli"); - removeWorkDir("test-openupm-cli-no-manifest"); - createWorkDir("test-openupm-cli", { - manifest: true, - editorVersion: " 2019.2.13f1", - }); - createWorkDir("test-openupm-cli-no-manifest", { - manifest: false, - editorVersion: " 2019.2.13f1", - }); - }); - after(function () { - removeWorkDir("test-openupm-cli"); - removeWorkDir("test-openupm-cli-no-manifest"); - }); - beforeEach(function () { - [stdoutInspect, stderrInspect] = getInspects(); - }); - afterEach(function () { - stdoutInspect.restore(); - stderrInspect.restore(); - }); - it("defaults", async function () { - (await parseEnv({ _global: {} }, { checkPath: false })).should.be.ok(); - env.registry.should.equal("https://package.openupm.com"); - env.upstream.should.be.ok(); - env.upstreamRegistry.should.equal("https://packages.unity.com"); - env.namespace.should.equal("com.openupm"); - env.cwd.should.equal(""); - env.manifestPath.should.equal(""); - (env.editorVersion === null).should.be.ok(); - }); - it("check path", async function () { - ( - await parseEnv( - { _global: { chdir: getWorkDir("test-openupm-cli") } }, - { checkPath: true } - ) - ).should.be.ok(); - env.cwd.should.be.equal(getWorkDir("test-openupm-cli")); - env.manifestPath.should.be.equal( - path.join(getWorkDir("test-openupm-cli"), "Packages/manifest.json") - ); - }); - it("can not resolve path", async function () { - ( - await parseEnv( - { _global: { chdir: getWorkDir("path-not-exist") } }, - { checkPath: true } - ) - ).should.not.be.ok(); - const [stdout] = getOutputs(stdoutInspect, stderrInspect); - stdout.includes("can not resolve path").should.be.ok(); - }); - it("can not locate manifest.json", async function () { - ( - await parseEnv( - { _global: { chdir: getWorkDir("test-openupm-cli-no-manifest") } }, - { checkPath: true } - ) - ).should.not.be.ok(); - const [stdout] = getOutputs(stdoutInspect, stderrInspect); - stdout.includes("can not locate manifest.json").should.be.ok(); - }); - it("custom registry", async function () { - ( - await parseEnv( - { _global: { registry: "https://registry.npmjs.org" } }, - { checkPath: false } - ) - ).should.be.ok(); - env.registry.should.be.equal("https://registry.npmjs.org"); - env.namespace.should.be.equal("org.npmjs"); - }); - it("custom registry with splash", async function () { - ( - await parseEnv( - { _global: { registry: "https://registry.npmjs.org/" } }, - { checkPath: false } - ) - ).should.be.ok(); - env.registry.should.be.equal("https://registry.npmjs.org"); - env.namespace.should.be.equal("org.npmjs"); - }); - it("custom registry with extra path", async function () { - ( - await parseEnv( - { _global: { registry: "https://registry.npmjs.org/some" } }, - { checkPath: false } - ) - ).should.be.ok(); - env.registry.should.be.equal("https://registry.npmjs.org/some"); - env.namespace.should.be.equal("org.npmjs"); - }); - it("custom registry with extra path and splash", async function () { - ( - await parseEnv( - { _global: { registry: "https://registry.npmjs.org/some/" } }, - { checkPath: false } - ) - ).should.be.ok(); - env.registry.should.be.equal("https://registry.npmjs.org/some"); - env.namespace.should.be.equal("org.npmjs"); - }); - it("custom registry without http", async function () { - ( - await parseEnv( - { _global: { registry: "registry.npmjs.org" } }, - { checkPath: false } - ) - ).should.be.ok(); - env.registry.should.be.equal("http://registry.npmjs.org"); - env.namespace.should.be.equal("org.npmjs"); - }); - it("custom registry with ipv4+port", async function () { - ( - await parseEnv( - { _global: { registry: "http://127.0.0.1:4873" } }, - { checkPath: false } - ) - ).should.be.ok(); - env.registry.should.be.equal("http://127.0.0.1:4873"); - env.namespace.should.be.equal("127.0.0.1"); - }); - it("custom registry with ipv6+port", async function () { - ( - await parseEnv( - { _global: { registry: "http://[1:2:3:4:5:6:7:8]:4873" } }, - { checkPath: false } - ) - ).should.be.ok(); - env.registry.should.be.equal("http://[1:2:3:4:5:6:7:8]:4873"); - env.namespace.should.be.equal("1:2:3:4:5:6:7:8"); - }); - it("upstream", async function () { - ( - await parseEnv({ _global: { upstream: false } }, { checkPath: false }) - ).should.be.ok(); - env.upstream.should.not.be.ok(); - }); - it("editorVersion", async function () { - ( - await parseEnv( - { _global: { chdir: getWorkDir("test-openupm-cli") } }, - { checkPath: true } - ) - ).should.be.ok(); - assert(env.editorVersion !== null); - env.editorVersion.should.be.equal("2019.2.13f1"); - }); - it("region cn", async function () { - ( - await parseEnv({ _global: { cn: true } }, { checkPath: false }) - ).should.be.ok(); - env.registry.should.be.equal("https://package.openupm.cn"); - env.upstreamRegistry.should.be.equal("https://packages.unity.cn"); - env.region.should.be.equal("cn"); - }); - it("region cn with a custom registry", async function () { - ( - await parseEnv( - { _global: { cn: true, registry: "https://reg.custom-package.com" } }, - { checkPath: false } - ) - ).should.be.ok(); - env.registry.should.be.equal("https://reg.custom-package.com"); - env.upstreamRegistry.should.be.equal("https://packages.unity.cn"); - env.region.should.be.equal("cn"); - }); - }); - - describe("loadManifest/SaveManifest", function () { - let stdoutInspect: testConsole.Inspector = null!; - let stderrInspect: testConsole.Inspector = null!; - beforeEach(function () { - removeWorkDir("test-openupm-cli"); - createWorkDir("test-openupm-cli", { manifest: true }); - createWorkDir("test-openupm-cli-wrong-json", { - manifest: true, - }); - fs.writeFileSync( - path.join( - getWorkDir("test-openupm-cli-wrong-json"), - "Packages/manifest.json" - ), - "wrong-json" - ); - [stdoutInspect, stderrInspect] = getInspects(); - }); - afterEach(function () { - removeWorkDir("test-openupm-cli"); - removeWorkDir("test-openupm-cli-wrong-json"); - stdoutInspect.restore(); - stderrInspect.restore(); - }); - it("loadManifest", async function () { - ( - await parseEnv( - { _global: { chdir: getWorkDir("test-openupm-cli") } }, - { checkPath: true } - ) - ).should.be.ok(); - const manifest = loadManifest(); - assert(manifest !== null); - manifest.should.be.deepEqual({ dependencies: {} }); - }); - it("no manifest file", async function () { - ( - await parseEnv( - { _global: { chdir: getWorkDir("path-not-exist") } }, - { checkPath: false } - ) - ).should.be.ok(); - const manifest = loadManifest(); - (manifest === null).should.be.ok(); - const [stdout] = getOutputs(stdoutInspect, stderrInspect); - stdout.includes("does not exist").should.be.ok(); - }); - it("wrong json content", async function () { - ( - await parseEnv( - { _global: { chdir: getWorkDir("test-openupm-cli-wrong-json") } }, - { checkPath: true } - ) - ).should.be.ok(); - const manifest = loadManifest(); - (manifest === null).should.be.ok(); - const [stdout] = getOutputs(stdoutInspect, stderrInspect); - stdout.includes("failed to parse").should.be.ok(); - }); - it("saveManifest", async function () { - ( - await parseEnv( - { _global: { chdir: getWorkDir("test-openupm-cli") } }, - { checkPath: true } - ) - ).should.be.ok(); - const manifest = loadManifest(); - assert(manifest !== null); - manifest.should.be.deepEqual({ dependencies: {} }); - manifest.dependencies["some-pack"] = "1.0.0"; - saveManifest(manifest).should.be.ok(); - const manifest2 = loadManifest(); - assert(manifest2 !== null); - manifest2.should.be.deepEqual(manifest); - }); - }); - - describe("fetchPackageInfo", function () { - beforeEach(function () { - nockUp(); - }); - afterEach(function () { - nockDown(); - }); - it("simple", async function () { - ( - await parseEnv( - { _global: { registry: "http://example.com" } }, - { checkPath: false } - ) - ).should.be.ok(); - const pkgInfoRemote = { name: "com.littlebigfun.addressable-importer" }; - nock("http://example.com") - .get("/package-a") - .reply(200, pkgInfoRemote, { "Content-Type": "application/json" }); - const info = await fetchPackageInfo("package-a"); - assert(info !== undefined); - info.should.deepEqual(pkgInfoRemote); - }); - it("404", async function () { - ( - await parseEnv( - { _global: { registry: "http://example.com" } }, - { checkPath: false } - ) - ).should.be.ok(); - - nock("http://example.com").get("/package-a").reply(404); - const info = await fetchPackageInfo("package-a"); - (info === undefined).should.be.ok(); - }); - }); - - describe("getLatestVersion", function () { - it("from dist-tags", async function () { - const version = getLatestVersion({ "dist-tags": { latest: "1.0.0" } }); - assert(version !== undefined); - version.should.equal("1.0.0"); - }); - }); - describe("parseEditorVersion", function () { - it("test null", function () { - (parseEditorVersion(null) === null).should.be.ok(); - }); - it("test x.y", function () { - const version = parseEditorVersion("2019.2"); - assert(version !== null); - version.should.deepEqual({ major: 2019, minor: 2 }); - }); - it("test x.y.z", function () { - const version = parseEditorVersion("2019.2.1"); - assert(version !== null); - version.should.deepEqual({ - major: 2019, - minor: 2, - patch: 1, - }); - }); - it("test x.y.zan", function () { - const version = parseEditorVersion("2019.2.1a5"); - assert(version !== null); - version.should.deepEqual({ - major: 2019, - minor: 2, - patch: 1, - flag: "a", - flagValue: 0, - build: 5, - }); - }); - it("test x.y.zbn", function () { - const version = parseEditorVersion("2019.2.1b5"); - assert(version !== null); - version.should.deepEqual({ - major: 2019, - minor: 2, - patch: 1, - flag: "b", - flagValue: 1, - build: 5, - }); - }); - it("test x.y.zfn", function () { - const version = parseEditorVersion("2019.2.1f5"); - assert(version !== null); - version.should.deepEqual({ - major: 2019, - minor: 2, - patch: 1, - flag: "f", - flagValue: 2, - build: 5, - }); - }); - it("test x.y.zcn", function () { - const version = parseEditorVersion("2019.2.1f1c5"); - assert(version !== null); - version.should.deepEqual({ - major: 2019, - minor: 2, - patch: 1, - flag: "f", - flagValue: 2, - build: 1, - loc: "c", - locValue: 1, - locBuild: 5, - }); - }); - it("test invalid version", function () { - (parseEditorVersion("2019") === null).should.be.ok(); - }); - }); - - describe("compareEditorVersion", function () { - it("test 2019.1 == 2019.1", function () { - compareEditorVersion("2019.1", "2019.1").should.equal(0); - }); - it("test 2019.1.1 == 2019.1.1", function () { - compareEditorVersion("2019.1.1", "2019.1.1").should.equal(0); - }); - it("test 2019.1.1f1 == 2019.1.1f1", function () { - compareEditorVersion("2019.1.1f1", "2019.1.1f1").should.equal(0); - }); - it("test 2019.1.1f1c1 == 2019.1.1f1c1", function () { - compareEditorVersion("2019.1.1f1c1", "2019.1.1f1c1").should.equal(0); - }); - it("test 2019.2 > 2019.1", function () { - compareEditorVersion("2019.2", "2019.1").should.equal(1); - }); - it("test 2020.2 > 2019.1", function () { - compareEditorVersion("2020.1", "2019.1").should.equal(1); - }); - it("test 2019.1 < 2019.2", function () { - compareEditorVersion("2019.1", "2019.2").should.equal(-1); - }); - it("test 2019.1 < 2020.1", function () { - compareEditorVersion("2019.1", "2020.1").should.equal(-1); - }); - it("test 2019.1 < 2019.1.1", function () { - compareEditorVersion("2019.1", "2019.1.1").should.equal(-1); - }); - it("test 2019.1.1 < 2019.1.1f1", function () { - compareEditorVersion("2019.1.1", "2019.1.1f1").should.equal(-1); - }); - it("test 2019.1.1a1 < 2020.1.1b1", function () { - compareEditorVersion("2019.1.1a1", "2020.1.1b1").should.equal(-1); - }); - it("test 2019.1.1b1 < 2020.1.1f1", function () { - compareEditorVersion("2019.1.1b1", "2020.1.1f1").should.equal(-1); - }); - it("test 2019.1.1f1 < 2020.1.1f1c1", function () { - compareEditorVersion("2019.1.1f1", "2020.1.1f1c1").should.equal(-1); - }); - }); - - describe("isInternalPackage", function () { - it("test com.otherorg.software", function () { - isInternalPackage("com.otherorg.software").should.not.be.ok(); - }); - it("test com.unity.ugui", function () { - isInternalPackage("com.unity.ugui").should.be.ok(); - }); - it("test com.unity.modules.tilemap", function () { - isInternalPackage("com.unity.modules.tilemap").should.be.ok(); - }); - }); -}); diff --git a/test/test-editor-version.ts b/test/test-editor-version.ts new file mode 100644 index 00000000..2fca64c2 --- /dev/null +++ b/test/test-editor-version.ts @@ -0,0 +1,125 @@ +import { describe } from "mocha"; +import { + compareEditorVersion, + tryParseEditorVersion, +} from "../src/utils/editor-version"; +import "should"; +import assert from "assert"; + +describe("editor-version", function () { + describe("parseEditorVersion", function () { + it("test null", function () { + (tryParseEditorVersion(null) === null).should.be.ok(); + }); + it("test x.y", function () { + const version = tryParseEditorVersion("2019.2"); + assert(version !== null); + version.should.deepEqual({ major: 2019, minor: 2 }); + }); + it("test x.y.z", function () { + const version = tryParseEditorVersion("2019.2.1"); + assert(version !== null); + version.should.deepEqual({ + major: 2019, + minor: 2, + patch: 1, + }); + }); + it("test x.y.zan", function () { + const version = tryParseEditorVersion("2019.2.1a5"); + assert(version !== null); + version.should.deepEqual({ + major: 2019, + minor: 2, + patch: 1, + flag: "a", + flagValue: 0, + build: 5, + }); + }); + it("test x.y.zbn", function () { + const version = tryParseEditorVersion("2019.2.1b5"); + assert(version !== null); + version.should.deepEqual({ + major: 2019, + minor: 2, + patch: 1, + flag: "b", + flagValue: 1, + build: 5, + }); + }); + it("test x.y.zfn", function () { + const version = tryParseEditorVersion("2019.2.1f5"); + assert(version !== null); + version.should.deepEqual({ + major: 2019, + minor: 2, + patch: 1, + flag: "f", + flagValue: 2, + build: 5, + }); + }); + it("test x.y.zcn", function () { + const version = tryParseEditorVersion("2019.2.1f1c5"); + assert(version !== null); + version.should.deepEqual({ + major: 2019, + minor: 2, + patch: 1, + flag: "f", + flagValue: 2, + build: 1, + loc: "c", + locValue: 1, + locBuild: 5, + }); + }); + it("test invalid version", function () { + (tryParseEditorVersion("2019") === null).should.be.ok(); + }); + }); + + describe("compareEditorVersion", function () { + it("test 2019.1 == 2019.1", function () { + compareEditorVersion("2019.1", "2019.1").should.equal(0); + }); + it("test 2019.1.1 == 2019.1.1", function () { + compareEditorVersion("2019.1.1", "2019.1.1").should.equal(0); + }); + it("test 2019.1.1f1 == 2019.1.1f1", function () { + compareEditorVersion("2019.1.1f1", "2019.1.1f1").should.equal(0); + }); + it("test 2019.1.1f1c1 == 2019.1.1f1c1", function () { + compareEditorVersion("2019.1.1f1c1", "2019.1.1f1c1").should.equal(0); + }); + it("test 2019.2 > 2019.1", function () { + compareEditorVersion("2019.2", "2019.1").should.equal(1); + }); + it("test 2020.2 > 2019.1", function () { + compareEditorVersion("2020.1", "2019.1").should.equal(1); + }); + it("test 2019.1 < 2019.2", function () { + compareEditorVersion("2019.1", "2019.2").should.equal(-1); + }); + it("test 2019.1 < 2020.1", function () { + compareEditorVersion("2019.1", "2020.1").should.equal(-1); + }); + it("test 2019.1 < 2019.1.1", function () { + compareEditorVersion("2019.1", "2019.1.1").should.equal(-1); + }); + it("test 2019.1.1 < 2019.1.1f1", function () { + compareEditorVersion("2019.1.1", "2019.1.1f1").should.equal(-1); + }); + it("test 2019.1.1a1 < 2020.1.1b1", function () { + compareEditorVersion("2019.1.1a1", "2020.1.1b1").should.equal(-1); + }); + it("test 2019.1.1b1 < 2020.1.1f1", function () { + compareEditorVersion("2019.1.1b1", "2020.1.1f1").should.equal(-1); + }); + it("test 2019.1.1f1 < 2020.1.1f1c1", function () { + compareEditorVersion("2019.1.1f1", "2020.1.1f1c1").should.equal(-1); + }); + }); +}); diff --git a/test/test-env.ts b/test/test-env.ts new file mode 100644 index 00000000..8c21d288 --- /dev/null +++ b/test/test-env.ts @@ -0,0 +1,189 @@ +import testConsole from "test-console"; +import { + createWorkDir, + getInspects, + getOutputs, + getWorkDir, + removeWorkDir, +} from "./utils"; +import "should"; +import { env, parseEnv } from "../src/utils/env"; +import path from "path"; +import assert from "assert"; + +describe("env", function () { + describe("parseEnv", function () { + let stdoutInspect: testConsole.Inspector = null!; + let stderrInspect: testConsole.Inspector = null!; + before(function () { + removeWorkDir("test-openupm-cli"); + removeWorkDir("test-openupm-cli-no-manifest"); + createWorkDir("test-openupm-cli", { + manifest: true, + editorVersion: " 2019.2.13f1", + }); + createWorkDir("test-openupm-cli-no-manifest", { + manifest: false, + editorVersion: " 2019.2.13f1", + }); + }); + after(function () { + removeWorkDir("test-openupm-cli"); + removeWorkDir("test-openupm-cli-no-manifest"); + }); + beforeEach(function () { + [stdoutInspect, stderrInspect] = getInspects(); + }); + afterEach(function () { + stdoutInspect.restore(); + stderrInspect.restore(); + }); + it("defaults", async function () { + (await parseEnv({ _global: {} }, { checkPath: false })).should.be.ok(); + env.registry.should.equal("https://package.openupm.com"); + env.upstream.should.be.ok(); + env.upstreamRegistry.should.equal("https://packages.unity.com"); + env.namespace.should.equal("com.openupm"); + env.cwd.should.equal(""); + env.manifestPath.should.equal(""); + (env.editorVersion === null).should.be.ok(); + }); + it("check path", async function () { + ( + await parseEnv( + { _global: { chdir: getWorkDir("test-openupm-cli") } }, + { checkPath: true } + ) + ).should.be.ok(); + env.cwd.should.be.equal(getWorkDir("test-openupm-cli")); + env.manifestPath.should.be.equal( + path.join(getWorkDir("test-openupm-cli"), "Packages/manifest.json") + ); + }); + it("can not resolve path", async function () { + ( + await parseEnv( + { _global: { chdir: getWorkDir("path-not-exist") } }, + { checkPath: true } + ) + ).should.not.be.ok(); + const [stdout] = getOutputs(stdoutInspect, stderrInspect); + stdout.includes("can not resolve path").should.be.ok(); + }); + it("can not locate manifest.json", async function () { + ( + await parseEnv( + { _global: { chdir: getWorkDir("test-openupm-cli-no-manifest") } }, + { checkPath: true } + ) + ).should.not.be.ok(); + const [stdout] = getOutputs(stdoutInspect, stderrInspect); + stdout.includes("can not locate manifest.json").should.be.ok(); + }); + it("custom registry", async function () { + ( + await parseEnv( + { _global: { registry: "https://registry.npmjs.org" } }, + { checkPath: false } + ) + ).should.be.ok(); + env.registry.should.be.equal("https://registry.npmjs.org"); + env.namespace.should.be.equal("org.npmjs"); + }); + it("custom registry with splash", async function () { + ( + await parseEnv( + { _global: { registry: "https://registry.npmjs.org/" } }, + { checkPath: false } + ) + ).should.be.ok(); + env.registry.should.be.equal("https://registry.npmjs.org"); + env.namespace.should.be.equal("org.npmjs"); + }); + it("custom registry with extra path", async function () { + ( + await parseEnv( + { _global: { registry: "https://registry.npmjs.org/some" } }, + { checkPath: false } + ) + ).should.be.ok(); + env.registry.should.be.equal("https://registry.npmjs.org/some"); + env.namespace.should.be.equal("org.npmjs"); + }); + it("custom registry with extra path and splash", async function () { + ( + await parseEnv( + { _global: { registry: "https://registry.npmjs.org/some/" } }, + { checkPath: false } + ) + ).should.be.ok(); + env.registry.should.be.equal("https://registry.npmjs.org/some"); + env.namespace.should.be.equal("org.npmjs"); + }); + it("custom registry without http", async function () { + ( + await parseEnv( + { _global: { registry: "registry.npmjs.org" } }, + { checkPath: false } + ) + ).should.be.ok(); + env.registry.should.be.equal("http://registry.npmjs.org"); + env.namespace.should.be.equal("org.npmjs"); + }); + it("custom registry with ipv4+port", async function () { + ( + await parseEnv( + { _global: { registry: "http://127.0.0.1:4873" } }, + { checkPath: false } + ) + ).should.be.ok(); + env.registry.should.be.equal("http://127.0.0.1:4873"); + env.namespace.should.be.equal("127.0.0.1"); + }); + it("custom registry with ipv6+port", async function () { + ( + await parseEnv( + { _global: { registry: "http://[1:2:3:4:5:6:7:8]:4873" } }, + { checkPath: false } + ) + ).should.be.ok(); + env.registry.should.be.equal("http://[1:2:3:4:5:6:7:8]:4873"); + env.namespace.should.be.equal("1:2:3:4:5:6:7:8"); + }); + it("upstream", async function () { + ( + await parseEnv({ _global: { upstream: false } }, { checkPath: false }) + ).should.be.ok(); + env.upstream.should.not.be.ok(); + }); + it("editorVersion", async function () { + ( + await parseEnv( + { _global: { chdir: getWorkDir("test-openupm-cli") } }, + { checkPath: true } + ) + ).should.be.ok(); + assert(env.editorVersion !== null); + env.editorVersion.should.be.equal("2019.2.13f1"); + }); + it("region cn", async function () { + ( + await parseEnv({ _global: { cn: true } }, { checkPath: false }) + ).should.be.ok(); + env.registry.should.be.equal("https://package.openupm.cn"); + env.upstreamRegistry.should.be.equal("https://packages.unity.cn"); + env.region.should.be.equal("cn"); + }); + it("region cn with a custom registry", async function () { + ( + await parseEnv( + { _global: { cn: true, registry: "https://reg.custom-package.com" } }, + { checkPath: false } + ) + ).should.be.ok(); + env.registry.should.be.equal("https://reg.custom-package.com"); + env.upstreamRegistry.should.be.equal("https://packages.unity.cn"); + env.region.should.be.equal("cn"); + }); + }); +}); diff --git a/test/test-manifest.ts b/test/test-manifest.ts new file mode 100644 index 00000000..d168784e --- /dev/null +++ b/test/test-manifest.ts @@ -0,0 +1,92 @@ +import testConsole from "test-console"; +import { + createWorkDir, + getInspects, + getOutputs, + getWorkDir, + removeWorkDir, +} from "./utils"; +import fs from "fs"; +import "should"; +import path from "path"; +import { loadManifest, saveManifest } from "../src/utils/manifest"; +import assert from "assert"; +import { describe } from "mocha"; +import { parseEnv } from "../src/utils/env"; + +describe("manifest", function () { + let stdoutInspect: testConsole.Inspector = null!; + let stderrInspect: testConsole.Inspector = null!; + beforeEach(function () { + removeWorkDir("test-openupm-cli"); + createWorkDir("test-openupm-cli", { manifest: true }); + createWorkDir("test-openupm-cli-wrong-json", { + manifest: true, + }); + fs.writeFileSync( + path.join( + getWorkDir("test-openupm-cli-wrong-json"), + "Packages/manifest.json" + ), + "wrong-json" + ); + [stdoutInspect, stderrInspect] = getInspects(); + }); + afterEach(function () { + removeWorkDir("test-openupm-cli"); + removeWorkDir("test-openupm-cli-wrong-json"); + stdoutInspect.restore(); + stderrInspect.restore(); + }); + it("loadManifest", async function () { + ( + await parseEnv( + { _global: { chdir: getWorkDir("test-openupm-cli") } }, + { checkPath: true } + ) + ).should.be.ok(); + const manifest = loadManifest(); + assert(manifest !== null); + manifest.should.be.deepEqual({ dependencies: {} }); + }); + it("no manifest file", async function () { + ( + await parseEnv( + { _global: { chdir: getWorkDir("path-not-exist") } }, + { checkPath: false } + ) + ).should.be.ok(); + const manifest = loadManifest(); + (manifest === null).should.be.ok(); + const [stdout] = getOutputs(stdoutInspect, stderrInspect); + stdout.includes("does not exist").should.be.ok(); + }); + it("wrong json content", async function () { + ( + await parseEnv( + { _global: { chdir: getWorkDir("test-openupm-cli-wrong-json") } }, + { checkPath: true } + ) + ).should.be.ok(); + const manifest = loadManifest(); + (manifest === null).should.be.ok(); + const [stdout] = getOutputs(stdoutInspect, stderrInspect); + stdout.includes("failed to parse").should.be.ok(); + }); + it("saveManifest", async function () { + ( + await parseEnv( + { _global: { chdir: getWorkDir("test-openupm-cli") } }, + { checkPath: true } + ) + ).should.be.ok(); + const manifest = loadManifest(); + assert(manifest !== null); + manifest.should.be.deepEqual({ dependencies: {} }); + manifest.dependencies["some-pack"] = "1.0.0"; + saveManifest(manifest).should.be.ok(); + const manifest2 = loadManifest(); + assert(manifest2 !== null); + manifest2.should.be.deepEqual(manifest); + }); +}); diff --git a/test/test-pgk-info.ts b/test/test-pgk-info.ts new file mode 100644 index 00000000..92e0bd75 --- /dev/null +++ b/test/test-pgk-info.ts @@ -0,0 +1,14 @@ +import { tryGetLatestVersion } from "../src/utils/pkg-info"; +import assert from "assert"; +import "should"; +import { describe } from "mocha"; + +describe("pkg-info", function () { + describe("tryGetLatestVersion", function () { + it("from dist-tags", async function () { + const version = tryGetLatestVersion({ "dist-tags": { latest: "1.0.0" } }); + assert(version !== undefined); + version.should.equal("1.0.0"); + }); + }); +}); diff --git a/test/test-pkg-name.ts b/test/test-pkg-name.ts index 3adb7006..592efd26 100644 --- a/test/test-pkg-name.ts +++ b/test/test-pkg-name.ts @@ -1,6 +1,6 @@ import "assert"; import "should"; -import { splitPkgName } from "../src/utils/pkg-name"; +import { isInternalPackage, splitPkgName } from "../src/utils/pkg-name"; describe("pkg-name.ts", function () { describe("splitPkgName", function () { @@ -41,4 +41,15 @@ describe("pkg-name.ts", function () { }); }); }); + describe("isInternalPackage", function () { + it("test com.otherorg.software", function () { + isInternalPackage("com.otherorg.software").should.not.be.ok(); + }); + it("test com.unity.ugui", function () { + isInternalPackage("com.unity.ugui").should.be.ok(); + }); + it("test com.unity.modules.tilemap", function () { + isInternalPackage("com.unity.modules.tilemap").should.be.ok(); + }); + }); }); diff --git a/test/test-registry-client.ts b/test/test-registry-client.ts new file mode 100644 index 00000000..280ac205 --- /dev/null +++ b/test/test-registry-client.ts @@ -0,0 +1,46 @@ +import "assert"; +import nock from "nock"; +import "should"; + +import { nockDown, nockUp } from "./utils"; +import assert from "assert"; +import { parseEnv } from "../src/utils/env"; +import { fetchPackageInfo } from "../src/registry-client"; + +describe("registry-client", function () { + describe("fetchPackageInfo", function () { + beforeEach(function () { + nockUp(); + }); + afterEach(function () { + nockDown(); + }); + it("simple", async function () { + ( + await parseEnv( + { _global: { registry: "http://example.com" } }, + { checkPath: false } + ) + ).should.be.ok(); + const pkgInfoRemote = { name: "com.littlebigfun.addressable-importer" }; + nock("http://example.com") + .get("/package-a") + .reply(200, pkgInfoRemote, { "Content-Type": "application/json" }); + const info = await fetchPackageInfo("package-a"); + assert(info !== undefined); + info.should.deepEqual(pkgInfoRemote); + }); + it("404", async function () { + ( + await parseEnv( + { _global: { registry: "http://example.com" } }, + { checkPath: false } + ) + ).should.be.ok(); + + nock("http://example.com").get("/package-a").reply(404); + const info = await fetchPackageInfo("package-a"); + (info === undefined).should.be.ok(); + }); + }); +});