From 335887a9d0ae5ad66bc343a012eb5dba606ffdff Mon Sep 17 00:00:00 2001 From: Ramon Brullo Date: Tue, 13 Aug 2024 21:07:47 +0200 Subject: [PATCH 01/26] refactor: upmconfig registry logic (#385) Currently we get the auth information for registries on startup, regardless of whether they will be used. This has a small performance penalty, but mostly it introduces some software architecture issues, by coupling all commands to the parse-env logic and upmconfig. This change makes it so that the config is only loaded on demand and cached. We also don't parse the whole config, only requested entries. --- src/cli/cmd-add.ts | 37 ++++-- src/cli/cmd-deps.ts | 31 ++++-- src/cli/cmd-search.ts | 14 ++- src/cli/cmd-view.ts | 28 +++-- src/cli/index.ts | 25 +++-- src/domain/registry-url.ts | 2 + src/domain/registry.ts | 7 +- src/domain/upm-config.ts | 39 ------- src/services/get-registry-auth.ts | 105 +++++++++++------- src/services/parse-env.ts | 83 +++----------- test/cli/cmd-add.test.ts | 8 +- test/cli/cmd-deps.test.ts | 31 +++--- test/cli/cmd-login.test.ts | 24 ++-- test/cli/cmd-remove.test.ts | 18 ++- test/cli/cmd-search.test.ts | 24 ++-- test/cli/cmd-view.test.ts | 30 +++-- test/domain/upm-config.test.ts | 32 ------ test/services/get-registry-auth.test.ts | 142 +++++++++++++----------- test/services/parse-env.test.ts | 109 ++---------------- 19 files changed, 353 insertions(+), 436 deletions(-) delete mode 100644 src/domain/upm-config.ts delete mode 100644 test/domain/upm-config.test.ts diff --git a/src/cli/cmd-add.ts b/src/cli/cmd-add.ts index 76e4dfb5..efcc5a94 100644 --- a/src/cli/cmd-add.ts +++ b/src/cli/cmd-add.ts @@ -42,6 +42,8 @@ import { CmdOptions } from "./options"; import { ResultCodes } from "./result-codes"; import { ResolvePackumentVersionError } from "../domain/packument"; +import { unityRegistry } from "../domain/registry"; +import { GetRegistryAuth } from "../services/get-registry-auth"; import { GetRegistryPackumentVersion } from "../services/get-registry-packument-version"; import { isZod } from "../utils/zod-utils"; @@ -124,6 +126,7 @@ export function makeAddCmd( loadProjectManifest: LoadProjectManifest, saveProjectManifest: SaveProjectManifest, determineEditorVersion: DetermineEditorVersion, + getRegistryAuth: GetRegistryAuth, log: Logger, debugLog: DebugLog ): AddCmd { @@ -141,6 +144,11 @@ export function makeAddCmd( `${editorVersion} is unknown, the editor version check is disabled` ); + const primaryRegistry = await getRegistryAuth( + env.systemUser, + env.primaryRegistryUrl + ); + const tryAddToManifest = async function ( manifest: UnityProjectManifest, pkg: PackageReference @@ -160,13 +168,13 @@ export function makeAddCmd( let resolveResult = await getRegistryPackumentVersion( name, requestedVersion, - env.registry + primaryRegistry ).promise; if (resolveResult.isErr() && env.upstream) { const upstreamResult = await getRegistryPackumentVersion( name, requestedVersion, - env.upstreamRegistry + unityRegistry ).promise; if (upstreamResult.isOk()) { resolveResult = upstreamResult; @@ -213,7 +221,7 @@ export function makeAddCmd( if (!isUpstreamPackage) { debugLog(`fetch: ${makePackageReference(name, requestedVersion)}`); const dependencyGraph = await resolveDependencies( - [env.registry, env.upstreamRegistry], + [primaryRegistry, unityRegistry], name, versionToAdd, true @@ -295,15 +303,20 @@ export function makeAddCmd( } if (!isUpstreamPackage && pkgsInScope.length > 0) { - manifest = mapScopedRegistry(manifest, env.registry.url, (initial) => { - let updated = initial ?? makeEmptyScopedRegistryFor(env.registry.url); - - updated = pkgsInScope.reduce(addScope, updated!); - dirty = - !areArraysEqual(updated!.scopes, initial?.scopes ?? []) || dirty; - - return updated; - }); + manifest = mapScopedRegistry( + manifest, + primaryRegistry.url, + (initial) => { + let updated = + initial ?? makeEmptyScopedRegistryFor(primaryRegistry.url); + + updated = pkgsInScope.reduce(addScope, updated!); + dirty = + !areArraysEqual(updated!.scopes, initial?.scopes ?? []) || dirty; + + return updated; + } + ); } if (options.test) manifest = addTestable(manifest, name); diff --git a/src/cli/cmd-deps.ts b/src/cli/cmd-deps.ts index 8db6c890..b55beafa 100644 --- a/src/cli/cmd-deps.ts +++ b/src/cli/cmd-deps.ts @@ -1,23 +1,25 @@ -import { ParseEnv } from "../services/parse-env"; -import { PackageUrl } from "../domain/package-url"; +import chalk from "chalk"; +import { Logger } from "npmlog"; +import os from "os"; +import { PackumentNotFoundError } from "../common-errors"; import { makePackageReference, PackageReference, splitPackageReference, } from "../domain/package-reference"; -import { CmdOptions } from "./options"; -import { PackumentNotFoundError } from "../common-errors"; -import { ResolveDependencies } from "../services/dependency-resolving"; -import { Logger } from "npmlog"; +import { PackageUrl } from "../domain/package-url"; +import { unityRegistry } from "../domain/registry"; +import { SemanticVersion } from "../domain/semantic-version"; import { DebugLog } from "../logging"; -import { ResultCodes } from "./result-codes"; +import { ResolveDependencies } from "../services/dependency-resolving"; import { GetLatestVersion } from "../services/get-latest-version"; -import { SemanticVersion } from "../domain/semantic-version"; +import { GetRegistryAuth } from "../services/get-registry-auth"; +import { ParseEnv } from "../services/parse-env"; +import { queryAllRegistriesLazy } from "../utils/sources"; import { isZod } from "../utils/zod-utils"; import { stringifyDependencyGraph } from "./dependency-logging"; -import os from "os"; -import chalk from "chalk"; -import { queryAllRegistriesLazy } from "../utils/sources"; +import { CmdOptions } from "./options"; +import { ResultCodes } from "./result-codes"; /** * Options passed to the deps command. @@ -51,13 +53,18 @@ export function makeDepsCmd( parseEnv: ParseEnv, resolveDependencies: ResolveDependencies, resolveLatestVersion: GetLatestVersion, + getRegistryAuth: GetRegistryAuth, log: Logger, debugLog: DebugLog ): DepsCmd { return async (pkg, options) => { // parse env const env = await parseEnv(options); - const sources = [env.registry, env.upstreamRegistry]; + const primaryRegistry = await getRegistryAuth( + env.systemUser, + env.primaryRegistryUrl + ); + const sources = [primaryRegistry, unityRegistry]; const [packageName, requestedVersion] = splitPackageReference(pkg); diff --git a/src/cli/cmd-search.ts b/src/cli/cmd-search.ts index 6d678aa2..bd369ba9 100644 --- a/src/cli/cmd-search.ts +++ b/src/cli/cmd-search.ts @@ -1,10 +1,11 @@ +import { Logger } from "npmlog"; import * as os from "os"; +import { DebugLog } from "../logging"; +import { GetRegistryAuth } from "../services/get-registry-auth"; import { ParseEnv } from "../services/parse-env"; +import { SearchPackages } from "../services/search-packages"; import { CmdOptions } from "./options"; import { formatAsTable } from "./output-formatting"; -import { Logger } from "npmlog"; -import { SearchPackages } from "../services/search-packages"; -import { DebugLog } from "../logging"; import { ResultCodes } from "./result-codes"; /** @@ -33,15 +34,20 @@ export type SearchCmd = ( export function makeSearchCmd( parseEnv: ParseEnv, searchPackages: SearchPackages, + getRegistryAuth: GetRegistryAuth, log: Logger, debugLog: DebugLog ): SearchCmd { return async (keyword, options) => { // parse env const env = await parseEnv(options); + const primaryRegistry = await getRegistryAuth( + env.systemUser, + env.primaryRegistryUrl + ); let usedEndpoint = "npmsearch"; - const results = await searchPackages(env.registry, keyword, () => { + const results = await searchPackages(primaryRegistry, keyword, () => { usedEndpoint = "endpoint.all"; log.warn("", "fast search endpoint is not available, using old search."); }); diff --git a/src/cli/cmd-view.ts b/src/cli/cmd-view.ts index c262bee7..187e67b1 100644 --- a/src/cli/cmd-view.ts +++ b/src/cli/cmd-view.ts @@ -1,19 +1,21 @@ -import chalk from "chalk"; import assert from "assert"; -import { tryGetLatestVersion, UnityPackument } from "../domain/packument"; -import { ParseEnv } from "../services/parse-env"; +import chalk from "chalk"; +import { Logger } from "npmlog"; +import { PackumentNotFoundError } from "../common-errors"; import { hasVersion, PackageReference, splitPackageReference, } from "../domain/package-reference"; -import { CmdOptions } from "./options"; -import { recordKeys } from "../utils/record-utils"; -import { Logger } from "npmlog"; -import { ResultCodes } from "./result-codes"; +import { tryGetLatestVersion, UnityPackument } from "../domain/packument"; +import { unityRegistry } from "../domain/registry"; import { GetRegistryPackument } from "../io/packument-io"; +import { GetRegistryAuth } from "../services/get-registry-auth"; +import { ParseEnv } from "../services/parse-env"; +import { recordKeys } from "../utils/record-utils"; import { queryAllRegistriesLazy } from "../utils/sources"; -import { PackumentNotFoundError } from "../common-errors"; +import { CmdOptions } from "./options"; +import { ResultCodes } from "./result-codes"; /** * Options passed to the view command. @@ -110,11 +112,16 @@ const printInfo = function (packument: UnityPackument) { export function makeViewCmd( parseEnv: ParseEnv, getRegistryPackument: GetRegistryPackument, + getRegistryAuth: GetRegistryAuth, log: Logger ): ViewCmd { return async (pkg, options) => { // parse env const env = await parseEnv(options); + const primaryRegistry = await getRegistryAuth( + env.systemUser, + env.primaryRegistryUrl + ); // parse name if (hasVersion(pkg)) { @@ -124,10 +131,7 @@ export function makeViewCmd( } // verify name - const sources = [ - env.registry, - ...(env.upstream ? [env.upstreamRegistry] : []), - ]; + const sources = [primaryRegistry, ...(env.upstream ? [unityRegistry] : [])]; const packumentFromRegistry = await queryAllRegistriesLazy( sources, (source) => getRegistryPackument(source, pkg) diff --git a/src/cli/index.ts b/src/cli/index.ts index 8acc6b5f..b9a2b601 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -39,13 +39,7 @@ import { const log = npmlog; -const parseEnv = makeParseEnv( - log, - getUpmConfigPath, - getRegistryAuth, - getProcessCwd, - npmDebugLog -); +const parseEnv = makeParseEnv(log, getProcessCwd); const addCmd = makeAddCmd( parseEnv, @@ -54,20 +48,33 @@ const addCmd = makeAddCmd( loadProjectManifest, saveProjectManifest, determineEditorVersion, + getRegistryAuth, log, npmDebugLog ); const loginCmd = makeLoginCmd(parseEnv, getUpmConfigPath, login, log); -const searchCmd = makeSearchCmd(parseEnv, searchPackages, log, npmDebugLog); +const searchCmd = makeSearchCmd( + parseEnv, + searchPackages, + getRegistryAuth, + log, + npmDebugLog +); const depsCmd = makeDepsCmd( parseEnv, resolveDependencies, getLatestVersion, + getRegistryAuth, log, npmDebugLog ); const removeCmd = makeRemoveCmd(parseEnv, removePackages, log); -const viewCmd = makeViewCmd(parseEnv, getRegistryPackument, log); +const viewCmd = makeViewCmd( + parseEnv, + getRegistryPackument, + getRegistryAuth, + log +); // update-notifier diff --git a/src/domain/registry-url.ts b/src/domain/registry-url.ts index e7b6bd26..2660e2ee 100644 --- a/src/domain/registry-url.ts +++ b/src/domain/registry-url.ts @@ -29,3 +29,5 @@ export function coerceRegistryUrl(s: string): RegistryUrl { } export const unityRegistryUrl = RegistryUrl.parse("https://packages.unity.com"); + +export const openupmRegistryUrl = RegistryUrl.parse("https://package.openupm.com"); diff --git a/src/domain/registry.ts b/src/domain/registry.ts index 9749d0d2..a5d8eb2c 100644 --- a/src/domain/registry.ts +++ b/src/domain/registry.ts @@ -1,4 +1,4 @@ -import { RegistryUrl } from "./registry-url"; +import { RegistryUrl, unityRegistryUrl } from "./registry-url"; import { NpmAuth } from "another-npm-registry-client"; /** @@ -15,3 +15,8 @@ export type Registry = Readonly<{ */ auth: NpmAuth | null; }>; + +export const unityRegistry: Registry = { + url: unityRegistryUrl, + auth: null, +}; diff --git a/src/domain/upm-config.ts b/src/domain/upm-config.ts deleted file mode 100644 index c1523475..00000000 --- a/src/domain/upm-config.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { RegistryUrl } from "./registry-url"; -import { NpmAuth } from "another-npm-registry-client"; - -/** - * Abstraction of an upmconfig.toml file. - */ -export type UpmConfig = Readonly>; - -export const emptyUpmConfig: UpmConfig = {}; - -/** - * Attempts to get the {@link NpmAuth} information for a specific registry - * from a {@link UpmConfig} object. - * @param upmConfig The config. - * @param registry The registry. - * @returns The auth information or null if the registry does not exist - * in the config. - */ -export function tryGetAuthForRegistry( - upmConfig: UpmConfig, - registry: RegistryUrl -): NpmAuth | null { - return upmConfig[registry] ?? null; -} - -/** - * Adds an entry to an upm-config. - * @param upmConfig The config. - * @param registry The registry for which to authenticate. - * @param auth The autentication information. - * @returns A new config with the entry added. - */ -export function addAuth( - upmConfig: UpmConfig, - registry: RegistryUrl, - auth: NpmAuth -): UpmConfig { - return { ...upmConfig, [registry]: auth }; -} diff --git a/src/services/get-registry-auth.ts b/src/services/get-registry-auth.ts index c86cdb7c..9657a173 100644 --- a/src/services/get-registry-auth.ts +++ b/src/services/get-registry-auth.ts @@ -1,74 +1,101 @@ import { NpmAuth } from "another-npm-registry-client"; import { decodeBase64 } from "../domain/base64"; -import { coerceRegistryUrl } from "../domain/registry-url"; -import { addAuth, emptyUpmConfig, UpmConfig } from "../domain/upm-config"; +import { Registry } from "../domain/registry"; import { + openupmRegistryUrl, + RegistryUrl, + unityRegistryUrl, +} from "../domain/registry-url"; +import { + getUpmConfigPath, + GetUpmConfigPath, loadUpmConfig, LoadUpmConfig, UpmAuth, UpmConfigContent, } from "../io/upm-config-io"; -import { recordEntries } from "../utils/record-utils"; +import { DebugLog, npmDebugLog } from "../logging"; import { trySplitAtFirstOccurrenceOf } from "../utils/string-utils"; import { removeExplicitUndefined } from "../utils/zod-utils"; /** * Service function for getting registry authentication. - * @param upmConfigPath The path to the upmconfig.toml file. + * @param systemUser Whether to authenticate as a Windows system-user. + * @param url The url for which to get authentication. */ -export type GetRegistryAuth = (upmConfigPath: string) => Promise; +export type GetRegistryAuth = ( + systemUser: boolean, + url: RegistryUrl +) => Promise; /** * Makes a {@link GetRegistryAuth} function which gets it's information from * the users upm config. */ export function LoadRegistryAuthFromUpmConfig( - loadUpmConfig: LoadUpmConfig + getUpmConfigPath: GetUpmConfigPath, + loadUpmConfig: LoadUpmConfig, + debugLog: DebugLog ): GetRegistryAuth { - return async (upmConfigPath) => { - function importNpmAuth(input: UpmAuth): NpmAuth { - // Basic auth - if ("_auth" in input) { - const decoded = decodeBase64(input._auth); - const [username, password] = trySplitAtFirstOccurrenceOf(decoded, ":"); - if (password === null) - throw new Error( - "Auth Base64 string was not in the user:pass format." - ); - return removeExplicitUndefined({ - username, - password, - email: input.email, - alwaysAuth: input.alwaysAuth, - }); - } - - // Bearer auth + function importNpmAuth(input: UpmAuth): NpmAuth { + // Basic auth + if ("_auth" in input) { + const decoded = decodeBase64(input._auth); + const [username, password] = trySplitAtFirstOccurrenceOf(decoded, ":"); + if (password === null) + throw new Error("Auth Base64 string was not in the user:pass format."); return removeExplicitUndefined({ - token: input.token, + username, + password, + email: input.email, alwaysAuth: input.alwaysAuth, }); } - function importUpmConfig(input: UpmConfigContent): UpmConfig { - if (input.npmAuth === undefined) return {}; - return recordEntries(input.npmAuth) - .map( - ([url, auth]) => - [coerceRegistryUrl(url), importNpmAuth(auth)] as const - ) - .reduce( - (upmConfig, [url, auth]) => addAuth(upmConfig, url, auth), - emptyUpmConfig + return removeExplicitUndefined({ + token: input.token, + alwaysAuth: input.alwaysAuth, + }); + } + + let cachedConfig: UpmConfigContent | null = null; + + return async (systemUser, url) => { + // We know there is no auth required for these registries + if (url === openupmRegistryUrl || url === unityRegistryUrl) + return { url, auth: null }; + + // Only load config if we have dont have it in the cache + if (cachedConfig === null) { + const configPath = await getUpmConfigPath(systemUser); + cachedConfig = await loadUpmConfig(configPath); + if (cachedConfig === null) { + debugLog( + `No .upmconfig.toml file found. Will use no auth for registry "${url}".` ); + return { url, auth: null }; + } } - const content = await loadUpmConfig(upmConfigPath); - return content !== null ? importUpmConfig(content) : emptyUpmConfig; + const entry = + cachedConfig.npmAuth?.[url] ?? cachedConfig?.npmAuth?.[url + "/"] ?? null; + if (entry === null) { + debugLog( + `.upmconfig.toml had no entry for registry "${url}". Will not use auth for that registry.` + ); + return { url, auth: null }; + } + + const auth = importNpmAuth(entry); + return { url, auth }; }; } /** * Default {@link GetRegistryAuth} function. Uses {@link LoadRegistryAuthFromUpmConfig}. */ -export const getRegistryAuth = LoadRegistryAuthFromUpmConfig(loadUpmConfig); +export const getRegistryAuth = LoadRegistryAuthFromUpmConfig( + getUpmConfigPath, + loadUpmConfig, + npmDebugLog +); diff --git a/src/services/parse-env.ts b/src/services/parse-env.ts index 6a7974db..c9f3b662 100644 --- a/src/services/parse-env.ts +++ b/src/services/parse-env.ts @@ -3,15 +3,13 @@ import { Logger } from "npmlog"; import path from "path"; import { CustomError } from "ts-custom-error"; import { CmdOptions } from "../cli/options"; -import { Registry } from "../domain/registry"; -import { coerceRegistryUrl, RegistryUrl } from "../domain/registry-url"; -import { tryGetAuthForRegistry, UpmConfig } from "../domain/upm-config"; +import { + coerceRegistryUrl, + openupmRegistryUrl, + RegistryUrl, +} from "../domain/registry-url"; import { GetCwd } from "../io/special-paths"; -import { GetUpmConfigPath } from "../io/upm-config-io"; -import { DebugLog } from "../logging"; import { tryGetEnv } from "../utils/env-util"; -import { assertIsError } from "../utils/error-type-guards"; -import { GetRegistryAuth } from "./get-registry-auth"; /** * Error for when auth information for a registry could not be loaded. @@ -33,17 +31,13 @@ export type Env = Readonly<{ */ systemUser: boolean; /** - * Whether to fall back to the upstream registry. + * Whether to fall back to the Unity registry. */ upstream: boolean; /** - * The upstream registry. + * The primary registry url. */ - upstreamRegistry: Registry; - /** - * The primary registry. - */ - registry: Registry; + primaryRegistryUrl: RegistryUrl; }>; /** @@ -57,48 +51,11 @@ export type ParseEnv = (options: CmdOptions) => Promise; /** * Creates a {@link ParseEnv} function. */ -export function makeParseEnv( - log: Logger, - getUpmConfigPath: GetUpmConfigPath, - getRegistryAuth: GetRegistryAuth, - getCwd: GetCwd, - debugLog: DebugLog -): ParseEnv { +export function makeParseEnv(log: Logger, getCwd: GetCwd): ParseEnv { function determineCwd(options: CmdOptions): string { return options.chdir !== undefined ? path.resolve(options.chdir) : getCwd(); } - function determinePrimaryRegistry( - options: CmdOptions, - upmConfig: UpmConfig - ): Registry { - if (options.registry === undefined) { - return { - url: RegistryUrl.parse("https://package.openupm.com"), - auth: null, - }; - } - - const url = coerceRegistryUrl(options.registry); - - const auth = tryGetAuthForRegistry(upmConfig, url); - - if (auth === null) { - log.verbose( - "", - `upm config did not contain an entry for "${url}". Will use this registry without authentication.` - ); - } - - return { url, auth }; - } - - function determineUpstreamRegistry(): Registry { - const url = RegistryUrl.parse("https://packages.unity.com"); - - return { url, auth: null }; - } - function determineLogLevel(options: CmdOptions): "verbose" | "notice" { return options.verbose ? "verbose" : "notice"; } @@ -115,6 +72,11 @@ export function makeParseEnv( return options.systemUser === true; } + function determinePrimaryRegistryUrl(options: CmdOptions): RegistryUrl { + if (options.registry === undefined) return openupmRegistryUrl; + return coerceRegistryUrl(options.registry); + } + return async (options) => { // log level log.level = determineLogLevel(options); @@ -133,29 +95,16 @@ export function makeParseEnv( const systemUser = determineIsSystemUser(options); // registries - const upmConfigPath = await getUpmConfigPath(systemUser); - - let registry: Registry; - let upstreamRegistry: Registry; - try { - const upmConfig = await getRegistryAuth(upmConfigPath); - registry = determinePrimaryRegistry(options, upmConfig); - upstreamRegistry = determineUpstreamRegistry(); - } catch (error) { - assertIsError(error); - debugLog("Upmconfig load or parsing failed.", error); - throw new RegistryAuthLoadError(); - } + const primaryRegistryUrl = determinePrimaryRegistryUrl(options); // cwd const cwd = determineCwd(options); return { cwd, - registry, + primaryRegistryUrl, systemUser, upstream, - upstreamRegistry, }; }; } diff --git a/test/cli/cmd-add.test.ts b/test/cli/cmd-add.test.ts index cb655da6..cf179642 100644 --- a/test/cli/cmd-add.test.ts +++ b/test/cli/cmd-add.test.ts @@ -24,6 +24,7 @@ import { import { noopLogger } from "../../src/logging"; import { ResolveDependencies } from "../../src/services/dependency-resolving"; import { DetermineEditorVersion } from "../../src/services/determine-editor-version"; +import { GetRegistryAuth } from "../../src/services/get-registry-auth"; import { GetRegistryPackumentVersion, ResolvedPackumentVersion, @@ -61,8 +62,7 @@ const incompatiblePackument = buildPackument(somePackage, (packument) => const defaultEnv = { cwd: "/users/some-user/projects/SomeProject", upstream: true, - registry: { url: exampleRegistryUrl, auth: null }, - upstreamRegistry: { url: unityRegistryUrl, auth: null }, + primaryRegistryUrl: exampleRegistryUrl, } as Env; const someVersion = SemanticVersion.parse("1.0.0"); @@ -108,6 +108,9 @@ function makeDependencies() { makeEditorVersion(2022, 2, 1, "f", 2) ); + const getRegistryAuth = mockService(); + getRegistryAuth.mockResolvedValue({ url: exampleRegistryUrl, auth: null }); + const log = makeMockLogger(); const addCmd = makeAddCmd( @@ -117,6 +120,7 @@ function makeDependencies() { loadProjectManifest, writeProjectManifest, determineEditorVersion, + getRegistryAuth, log, noopLogger ); diff --git a/test/cli/cmd-deps.test.ts b/test/cli/cmd-deps.test.ts index 8ca7bbd4..21def045 100644 --- a/test/cli/cmd-deps.test.ts +++ b/test/cli/cmd-deps.test.ts @@ -1,30 +1,29 @@ import { makeDepsCmd } from "../../src/cli/cmd-deps"; -import { Env, ParseEnv } from "../../src/services/parse-env"; -import { exampleRegistryUrl } from "../domain/data-registry"; -import { unityRegistryUrl } from "../../src/domain/registry-url"; -import { DomainName } from "../../src/domain/domain-name"; -import { makePackageReference } from "../../src/domain/package-reference"; -import { makeMockLogger } from "./log.mock"; -import { SemanticVersion } from "../../src/domain/semantic-version"; -import { PackumentNotFoundError } from "../../src/common-errors"; -import { ResolveDependencies } from "../../src/services/dependency-resolving"; -import { mockService } from "../services/service.mock"; -import { noopLogger } from "../../src/logging"; import { ResultCodes } from "../../src/cli/result-codes"; -import { GetLatestVersion } from "../../src/services/get-latest-version"; +import { PackumentNotFoundError } from "../../src/common-errors"; import { makeGraphFromSeed, markBuiltInResolved, markRemoteResolved, } from "../../src/domain/dependency-graph"; +import { DomainName } from "../../src/domain/domain-name"; +import { makePackageReference } from "../../src/domain/package-reference"; +import { SemanticVersion } from "../../src/domain/semantic-version"; +import { noopLogger } from "../../src/logging"; +import { ResolveDependencies } from "../../src/services/dependency-resolving"; +import { GetLatestVersion } from "../../src/services/get-latest-version"; +import { GetRegistryAuth } from "../../src/services/get-registry-auth"; +import { Env, ParseEnv } from "../../src/services/parse-env"; +import { exampleRegistryUrl } from "../domain/data-registry"; +import { mockService } from "../services/service.mock"; +import { makeMockLogger } from "./log.mock"; const somePackage = DomainName.parse("com.some.package"); const otherPackage = DomainName.parse("com.other.package"); const anotherPackage = DomainName.parse("com.another.package"); const defaultEnv = { - registry: { url: exampleRegistryUrl, auth: null }, - upstreamRegistry: { url: unityRegistryUrl, auth: null }, + primaryRegistryUrl: exampleRegistryUrl, } as Env; const someVersion = SemanticVersion.parse("1.2.3"); @@ -54,12 +53,16 @@ function makeDependencies() { const resolveLatestVersion = mockService(); resolveLatestVersion.mockResolvedValue(someVersion); + const getRegistryAuth = mockService(); + getRegistryAuth.mockResolvedValue({ url: exampleRegistryUrl, auth: null }); + const log = makeMockLogger(); const depsCmd = makeDepsCmd( parseEnv, resolveDependencies, resolveLatestVersion, + getRegistryAuth, log, noopLogger ); diff --git a/test/cli/cmd-login.test.ts b/test/cli/cmd-login.test.ts index 5de0b551..1299dfe1 100644 --- a/test/cli/cmd-login.test.ts +++ b/test/cli/cmd-login.test.ts @@ -1,18 +1,18 @@ import { makeLoginCmd } from "../../src/cli/cmd-login"; -import { mockService } from "../services/service.mock"; -import { Env, ParseEnv } from "../../src/services/parse-env"; import { GetUpmConfigPath } from "../../src/io/upm-config-io"; +import { GetRegistryAuth } from "../../src/services/get-registry-auth"; import { Login } from "../../src/services/login"; -import { makeMockLogger } from "./log.mock"; +import { Env, ParseEnv } from "../../src/services/parse-env"; import { exampleRegistryUrl } from "../domain/data-registry"; -import { unityRegistryUrl } from "../../src/domain/registry-url"; +import { mockService } from "../services/service.mock"; +import { makeMockLogger } from "./log.mock"; const defaultEnv = { cwd: "/users/some-user/projects/SomeProject", upstream: true, - registry: { url: exampleRegistryUrl, auth: null }, - upstreamRegistry: { url: unityRegistryUrl, auth: null }, + primaryRegistryUrl: exampleRegistryUrl, } as Env; + const exampleUser = "user"; const examplePassword = "pass"; const exampleEmail = "user@email.com"; @@ -29,10 +29,20 @@ describe("cmd-login", () => { const login = mockService(); login.mockResolvedValue(undefined); + const getRegistryAuth = mockService(); + getRegistryAuth.mockResolvedValue({ url: exampleRegistryUrl, auth: null }); + const log = makeMockLogger(); const loginCmd = makeLoginCmd(parseEnv, getUpmConfigPath, login, log); - return { loginCmd, parseEnv, getUpmConfigPath, login, log } as const; + return { + loginCmd, + parseEnv, + getUpmConfigPath, + login, + getRegistryAuth, + log, + } as const; } // TODO: Add tests for prompting logic diff --git a/test/cli/cmd-remove.test.ts b/test/cli/cmd-remove.test.ts index d8ab85c2..0c868ed3 100644 --- a/test/cli/cmd-remove.test.ts +++ b/test/cli/cmd-remove.test.ts @@ -1,17 +1,18 @@ -import { exampleRegistryUrl } from "../domain/data-registry"; -import { Env, ParseEnv } from "../../src/services/parse-env"; import { makeRemoveCmd } from "../../src/cli/cmd-remove"; import { DomainName } from "../../src/domain/domain-name"; import { SemanticVersion } from "../../src/domain/semantic-version"; -import { makeMockLogger } from "./log.mock"; -import { mockService } from "../services/service.mock"; +import { GetRegistryAuth } from "../../src/services/get-registry-auth"; +import { Env, ParseEnv } from "../../src/services/parse-env"; import { RemovePackages } from "../../src/services/remove-packages"; import { AsyncOk } from "../../src/utils/result-utils"; +import { exampleRegistryUrl } from "../domain/data-registry"; +import { mockService } from "../services/service.mock"; +import { makeMockLogger } from "./log.mock"; const somePackage = DomainName.parse("com.some.package"); const defaultEnv = { cwd: "/users/some-user/projects/SomeProject", - registry: { url: exampleRegistryUrl, auth: null }, + primaryRegistryUrl: exampleRegistryUrl, } as Env; function makeDependencies() { @@ -23,6 +24,12 @@ function makeDependencies() { AsyncOk([{ name: somePackage, version: "1.0.0" as SemanticVersion }]) ); + const getRegistryAuth = mockService(); + getRegistryAuth.mockResolvedValue({ + url: defaultEnv.primaryRegistryUrl, + auth: null, + }); + const log = makeMockLogger(); const removeCmd = makeRemoveCmd(parseEnv, removePackages, log); @@ -30,6 +37,7 @@ function makeDependencies() { removeCmd, parseEnv, removePackages, + getRegistryAuth, log, } as const; } diff --git a/test/cli/cmd-search.test.ts b/test/cli/cmd-search.test.ts index fc456ad0..380b1cd9 100644 --- a/test/cli/cmd-search.test.ts +++ b/test/cli/cmd-search.test.ts @@ -1,14 +1,15 @@ import { makeSearchCmd, SearchOptions } from "../../src/cli/cmd-search"; +import { ResultCodes } from "../../src/cli/result-codes"; import { DomainName } from "../../src/domain/domain-name"; import { SemanticVersion } from "../../src/domain/semantic-version"; -import { makeMockLogger } from "./log.mock"; import { SearchedPackument } from "../../src/io/npm-search"; -import { exampleRegistryUrl } from "../domain/data-registry"; +import { noopLogger } from "../../src/logging"; +import { GetRegistryAuth } from "../../src/services/get-registry-auth"; import { Env, ParseEnv } from "../../src/services/parse-env"; -import { mockService } from "../services/service.mock"; import { SearchPackages } from "../../src/services/search-packages"; -import { noopLogger } from "../../src/logging"; -import { ResultCodes } from "../../src/cli/result-codes"; +import { exampleRegistryUrl } from "../domain/data-registry"; +import { mockService } from "../services/service.mock"; +import { makeMockLogger } from "./log.mock"; const exampleSearchResult: SearchedPackument = { name: DomainName.parse("com.example.package-a"), @@ -21,15 +22,24 @@ const exampleSearchResult: SearchedPackument = { function makeDependencies() { const parseEnv = mockService(); parseEnv.mockResolvedValue({ - registry: { url: exampleRegistryUrl, auth: null }, + primaryRegistryUrl: exampleRegistryUrl, } as Env); const searchPackages = mockService(); searchPackages.mockResolvedValue([exampleSearchResult]); + const getRegistryAuth = mockService(); + getRegistryAuth.mockResolvedValue({ url: exampleRegistryUrl, auth: null }); + const log = makeMockLogger(); - const searchCmd = makeSearchCmd(parseEnv, searchPackages, log, noopLogger); + const searchCmd = makeSearchCmd( + parseEnv, + searchPackages, + getRegistryAuth, + log, + noopLogger + ); return { searchCmd, parseEnv, diff --git a/test/cli/cmd-view.test.ts b/test/cli/cmd-view.test.ts index 66061b6f..e0ca12a6 100644 --- a/test/cli/cmd-view.test.ts +++ b/test/cli/cmd-view.test.ts @@ -1,16 +1,16 @@ import { makeViewCmd } from "../../src/cli/cmd-view"; -import { Env, ParseEnv } from "../../src/services/parse-env"; -import { exampleRegistryUrl } from "../domain/data-registry"; -import { unityRegistryUrl } from "../../src/domain/registry-url"; +import { ResultCodes } from "../../src/cli/result-codes"; +import { PackumentNotFoundError } from "../../src/common-errors"; import { DomainName } from "../../src/domain/domain-name"; import { makePackageReference } from "../../src/domain/package-reference"; import { SemanticVersion } from "../../src/domain/semantic-version"; -import { makeMockLogger } from "./log.mock"; +import { GetRegistryPackument } from "../../src/io/packument-io"; +import { GetRegistryAuth } from "../../src/services/get-registry-auth"; +import { Env, ParseEnv } from "../../src/services/parse-env"; import { buildPackument } from "../domain/data-packument"; +import { exampleRegistryUrl } from "../domain/data-registry"; import { mockService } from "../services/service.mock"; -import { ResultCodes } from "../../src/cli/result-codes"; -import { GetRegistryPackument } from "../../src/io/packument-io"; -import { PackumentNotFoundError } from "../../src/common-errors"; +import { makeMockLogger } from "./log.mock"; const somePackage = DomainName.parse("com.some.package"); const somePackument = buildPackument(somePackage, (packument) => @@ -38,8 +38,7 @@ const somePackument = buildPackument(somePackage, (packument) => ); const defaultEnv = { upstream: false, - registry: { url: exampleRegistryUrl, auth: null }, - upstreamRegistry: { url: unityRegistryUrl, auth: null }, + primaryRegistryUrl: exampleRegistryUrl, } as Env; function makeDependencies() { @@ -49,9 +48,20 @@ function makeDependencies() { const getRegistryPackument = mockService(); getRegistryPackument.mockResolvedValue(somePackument); + const getRegistryAuth = mockService(); + getRegistryAuth.mockResolvedValue({ + url: defaultEnv.primaryRegistryUrl, + auth: null, + }); + const log = makeMockLogger(); - const viewCmd = makeViewCmd(parseEnv, getRegistryPackument, log); + const viewCmd = makeViewCmd( + parseEnv, + getRegistryPackument, + getRegistryAuth, + log + ); return { viewCmd, parseEnv, diff --git a/test/domain/upm-config.test.ts b/test/domain/upm-config.test.ts deleted file mode 100644 index 661294cc..00000000 --- a/test/domain/upm-config.test.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { tryGetAuthForRegistry, UpmConfig } from "../../src/domain/upm-config"; -import { NpmAuth } from "another-npm-registry-client"; - -import { exampleRegistryUrl } from "./data-registry"; - -describe("upm-config", () => { - const someAuth: NpmAuth = { - username: "user", - password: "pass", - email: "email@wow.com", - }; - - describe("get auth for registry", () => { - it("should find auth that was added", () => { - const config: UpmConfig = { - [exampleRegistryUrl]: someAuth, - }; - - const actual = tryGetAuthForRegistry(config, exampleRegistryUrl); - - expect(actual).toEqual(someAuth); - }); - - it("should not find auth for url that does not exist", () => { - const config: UpmConfig = {}; - - const actual = tryGetAuthForRegistry(config, exampleRegistryUrl); - - expect(actual).toBeNull(); - }); - }); -}); diff --git a/test/services/get-registry-auth.test.ts b/test/services/get-registry-auth.test.ts index 1834bef0..48da8ff8 100644 --- a/test/services/get-registry-auth.test.ts +++ b/test/services/get-registry-auth.test.ts @@ -1,46 +1,56 @@ import { Base64 } from "../../src/domain/base64"; -import { emptyUpmConfig } from "../../src/domain/upm-config"; -import { LoadUpmConfig } from "../../src/io/upm-config-io"; +import { + openupmRegistryUrl, + unityRegistryUrl, +} from "../../src/domain/registry-url"; +import { GetUpmConfigPath, LoadUpmConfig } from "../../src/io/upm-config-io"; +import { noopLogger } from "../../src/logging"; import { LoadRegistryAuthFromUpmConfig } from "../../src/services/get-registry-auth"; import { exampleRegistryUrl } from "../domain/data-registry"; import { mockService } from "./service.mock"; describe("get registry auth from upm config", () => { - const someConfigPath = "/home/user/.upmconfig.toml"; const someEmail = "user@mail.com"; const someToken = "isehusehgusheguszg8gshg"; function makeDependencies() { + const getUpmConfigPath = mockService(); + getUpmConfigPath.mockResolvedValue("/home/user/.upmconfig.toml"); + const loadUpmConfig = mockService(); + loadUpmConfig.mockResolvedValue({}); - const loadRegistryAuthFromUpmConfig = - LoadRegistryAuthFromUpmConfig(loadUpmConfig); - return { loadRegistryAuthFromUpmConfig, loadUpmConfig } as const; + const getRegistryAuth = LoadRegistryAuthFromUpmConfig( + getUpmConfigPath, + loadUpmConfig, + noopLogger + ); + return { getRegistryAuth, loadUpmConfig } as const; } - it("should be empty if there is no upm config", async () => { - const { loadRegistryAuthFromUpmConfig, loadUpmConfig } = makeDependencies(); + it("should have no auth if no .upmconfig.toml file", async () => { + const { getRegistryAuth, loadUpmConfig } = makeDependencies(); loadUpmConfig.mockResolvedValue(null); - const actual = await loadRegistryAuthFromUpmConfig(someConfigPath); + const registry = await getRegistryAuth(false, exampleRegistryUrl); - expect(actual).toEqual(emptyUpmConfig); + expect(registry.auth).toBeNull(); }); - it("should import empty", async () => { - const { loadRegistryAuthFromUpmConfig, loadUpmConfig } = makeDependencies(); + it("should have no auth if there is no entry for registry", async () => { + const { getRegistryAuth, loadUpmConfig } = makeDependencies(); loadUpmConfig.mockResolvedValue({}); - const actual = await loadRegistryAuthFromUpmConfig(someConfigPath); + const registry = await getRegistryAuth(false, exampleRegistryUrl); - expect(actual).toEqual(emptyUpmConfig); + expect(registry.auth).toBeNull(); }); - it("should remove trailing slash on registry urls", async () => { - const { loadRegistryAuthFromUpmConfig, loadUpmConfig } = makeDependencies(); + it("should get valid basic auth", async () => { + const { getRegistryAuth, loadUpmConfig } = makeDependencies(); loadUpmConfig.mockResolvedValue({ npmAuth: { - [exampleRegistryUrl + "/"]: { + [exampleRegistryUrl]: { _auth: "dXNlcjpwYXNz" as Base64, // user:pass email: someEmail, alwaysAuth: true, @@ -48,75 +58,83 @@ describe("get registry auth from upm config", () => { }, }); - const actual = await loadRegistryAuthFromUpmConfig(someConfigPath); + const registry = await getRegistryAuth(false, exampleRegistryUrl); - expect(actual).toEqual({ - [exampleRegistryUrl]: { - username: "user", - password: "pass", - email: someEmail, - alwaysAuth: true, - }, + expect(registry.auth).toEqual({ + username: "user", + password: "pass", + email: someEmail, + alwaysAuth: true, + }); + }); + + it("should get valid token auth", async () => { + const { getRegistryAuth, loadUpmConfig } = makeDependencies(); + loadUpmConfig.mockResolvedValue({ + npmAuth: { [exampleRegistryUrl]: { token: someToken, alwaysAuth: true } }, + }); + + const registry = await getRegistryAuth(false, exampleRegistryUrl); + + expect(registry.auth).toEqual({ + token: someToken, + alwaysAuth: true, }); }); - it("should import valid basic auth", async () => { - const { loadRegistryAuthFromUpmConfig, loadUpmConfig } = makeDependencies(); + it("should ignore email when getting token auth", async () => { + const { getRegistryAuth, loadUpmConfig } = makeDependencies(); loadUpmConfig.mockResolvedValue({ npmAuth: { [exampleRegistryUrl]: { - _auth: "dXNlcjpwYXNz" as Base64, // user:pass + token: someToken, email: someEmail, - alwaysAuth: true, }, }, }); - const actual = await loadRegistryAuthFromUpmConfig(someConfigPath); + const registry = await getRegistryAuth(false, exampleRegistryUrl); - expect(actual).toEqual({ - [exampleRegistryUrl]: { - username: "user", - password: "pass", - email: someEmail, - alwaysAuth: true, - }, + expect(registry.auth).toEqual({ + token: someToken, }); }); - it("should import valid token auth", async () => { - const { loadRegistryAuthFromUpmConfig, loadUpmConfig } = makeDependencies(); + it("should get auth for url with trailing slash", async () => { + const { getRegistryAuth, loadUpmConfig } = makeDependencies(); loadUpmConfig.mockResolvedValue({ - npmAuth: { [exampleRegistryUrl]: { token: someToken, alwaysAuth: true } }, + npmAuth: { [exampleRegistryUrl + "/"]: { token: someToken } }, }); - const actual = await loadRegistryAuthFromUpmConfig(someConfigPath); + const registry = await getRegistryAuth(false, exampleRegistryUrl); - expect(actual).toEqual({ - [exampleRegistryUrl]: { - token: someToken, - alwaysAuth: true, - }, + expect(registry.auth).toEqual({ + token: someToken, }); }); - it("should ignore email when importing token auth", async () => { - const { loadRegistryAuthFromUpmConfig, loadUpmConfig } = makeDependencies(); - loadUpmConfig.mockResolvedValue({ - npmAuth: { - [exampleRegistryUrl]: { - token: someToken, - email: someEmail, - }, - }, - }); + it("should not load upmconfig for openupm registry url", async () => { + const { getRegistryAuth, loadUpmConfig } = makeDependencies(); - const actual = await loadRegistryAuthFromUpmConfig(someConfigPath); + await getRegistryAuth(false, openupmRegistryUrl); - expect(actual).toEqual({ - [exampleRegistryUrl]: { - token: someToken, - }, - }); + expect(loadUpmConfig).not.toHaveBeenCalled(); + }); + + it("should not load upmconfig for unity registry url", async () => { + const { getRegistryAuth, loadUpmConfig } = makeDependencies(); + + await getRegistryAuth(false, unityRegistryUrl); + + expect(loadUpmConfig).not.toHaveBeenCalled(); + }); + + it("should cache .upmconfig.toml content", async () => { + const { getRegistryAuth, loadUpmConfig } = makeDependencies(); + + await getRegistryAuth(false, exampleRegistryUrl); + await getRegistryAuth(false, exampleRegistryUrl); + + expect(loadUpmConfig).toHaveBeenCalledTimes(1); }); }); diff --git a/test/services/parse-env.test.ts b/test/services/parse-env.test.ts index 756a6171..a7430c61 100644 --- a/test/services/parse-env.test.ts +++ b/test/services/parse-env.test.ts @@ -1,10 +1,6 @@ -import { NpmAuth } from "another-npm-registry-client"; import path from "path"; -import { emptyUpmConfig, UpmConfig } from "../../src/domain/upm-config"; +import { openupmRegistryUrl } from "../../src/domain/registry-url"; import { GetCwd } from "../../src/io/special-paths"; -import { GetUpmConfigPath } from "../../src/io/upm-config-io"; -import { noopLogger } from "../../src/logging"; -import { GetRegistryAuth } from "../../src/services/get-registry-auth"; import { makeParseEnv } from "../../src/services/parse-env"; import { makeMockLogger } from "../cli/log.mock"; import { exampleRegistryUrl } from "../domain/data-registry"; @@ -12,42 +8,15 @@ import { mockService } from "./service.mock"; const testRootPath = "/users/some-user/projects/MyUnityProject"; -const testNpmAuth: NpmAuth = { - token: "ThisIsNotAValidToken", - alwaysAuth: false, -}; - -const testUpmConfig: UpmConfig = { - [exampleRegistryUrl]: testNpmAuth, -}; - function makeDependencies() { const log = makeMockLogger(); - const getUpmConfigPath = mockService(); - // The root directory does not contain an upm-config - getUpmConfigPath.mockResolvedValue(testRootPath); - - const loadRegistryAuth = mockService(); - loadRegistryAuth.mockResolvedValue(emptyUpmConfig); - // process.cwd is in the root directory. const getCwd = mockService(); getCwd.mockReturnValue(testRootPath); - const parseEnv = makeParseEnv( - log, - getUpmConfigPath, - loadRegistryAuth, - getCwd, - noopLogger - ); - return { - parseEnv, - log, - getUpmConfigPath, - loadRegistryAuth, - } as const; + const parseEnv = makeParseEnv(log, getCwd); + return { parseEnv, log } as const; } describe("env", () => { @@ -178,86 +147,22 @@ describe("env", () => { }); describe("registry", () => { - it("should be global openupm by default", async () => { + it("should be openupm by default", async () => { const { parseEnv } = makeDependencies(); const env = await parseEnv({}); - expect(env.registry.url).toEqual("https://package.openupm.com"); - }); - - it("should be custom registry if overridden", async () => { - const { parseEnv } = makeDependencies(); - - const env = await parseEnv({ - registry: exampleRegistryUrl, - }); - - expect(env.registry.url).toEqual(exampleRegistryUrl); + expect(env.primaryRegistryUrl).toEqual(openupmRegistryUrl); }); - it("should have no auth if no upm-config was found", async () => { + it("should be custom registry url if overridden", async () => { const { parseEnv } = makeDependencies(); const env = await parseEnv({ registry: exampleRegistryUrl, }); - expect(env.registry.auth).toEqual(null); - }); - - it("should have no auth if upm-config had no entry for the url", async () => { - const { parseEnv, loadRegistryAuth } = makeDependencies(); - loadRegistryAuth.mockResolvedValue({}); - - const env = await parseEnv({ - registry: exampleRegistryUrl, - }); - - expect(env.registry.auth).toEqual(null); - }); - - it("should notify if upm-config did not have auth", async () => { - const { parseEnv, log, loadRegistryAuth } = makeDependencies(); - loadRegistryAuth.mockResolvedValue({}); - - await parseEnv({ - registry: exampleRegistryUrl, - }); - - expect(log.verbose).toHaveBeenCalledWith( - "", - expect.stringContaining("did not contain an entry") - ); - }); - - it("should have auth if upm-config had entry for the url", async () => { - const { parseEnv, loadRegistryAuth } = makeDependencies(); - loadRegistryAuth.mockResolvedValue(testUpmConfig); - - const env = await parseEnv({ - registry: exampleRegistryUrl, - }); - - expect(env.registry.auth).toEqual(testNpmAuth); - }); - }); - - describe("upstream registry", () => { - it("should be global unity by default", async () => { - const { parseEnv } = makeDependencies(); - - const env = await parseEnv({}); - - expect(env.upstreamRegistry.url).toEqual("https://packages.unity.com"); - }); - - it("should have no auth", async () => { - const { parseEnv } = makeDependencies(); - - const env = await parseEnv({}); - - expect(env.upstreamRegistry.auth).toEqual(null); + expect(env.primaryRegistryUrl).toEqual(exampleRegistryUrl); }); }); From 0ecdb686e9e6b461f2d27a80f4091de56aa91e94 Mon Sep 17 00:00:00 2001 From: Ramon Brullo Date: Wed, 14 Aug 2024 08:40:22 +0200 Subject: [PATCH 02/26] misc: add jest to vscode plugins --- .vscode/extensions.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 9d9323cc..ec8fd912 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,6 +1,7 @@ { "recommendations": [ "esbenp.prettier-vscode", - "dbaeumer.vscode-eslint" + "dbaeumer.vscode-eslint", + "orta.vscode-jest" ] } \ No newline at end of file From f2a0bfcf1c56ff03cc10d1abf54ac153c3a63cb5 Mon Sep 17 00:00:00 2001 From: Ramon Brullo Date: Wed, 14 Aug 2024 08:43:22 +0200 Subject: [PATCH 03/26] refactor: drop unused code --- src/io/child-process.ts | 33 --------------------------------- 1 file changed, 33 deletions(-) delete mode 100644 src/io/child-process.ts diff --git a/src/io/child-process.ts b/src/io/child-process.ts deleted file mode 100644 index 02ddc258..00000000 --- a/src/io/child-process.ts +++ /dev/null @@ -1,33 +0,0 @@ -import childProcess from "child_process"; -import { DebugLog, npmDebugLog } from "../logging"; - -/** - * Function that run a child process. - * @param command The command to run. - * @returns The commands standard output. - */ -export type RunChildProcess = (command: string) => Promise; - -/** - * Makes a {@link RunChildProcess} function which uses a promisified version of - * the built-in {@link childProcess.exec} function. - */ -function ExecChildProcess(debugLog: DebugLog): RunChildProcess { - return (command) => - new Promise(function (resolve, reject) { - childProcess.exec(command, function (error, stdout) { - if (error) { - debugLog("A child process failed.", error); - reject(error); - return; - } - - resolve(stdout); - }); - }); -} - -/** - * Default {@link RunChildProcess} function. Uses {@link ExecChildProcess}. - */ -export const runChildProcess = ExecChildProcess(npmDebugLog); From c99ca90aa2efd1f0b14cb745d1de8f68f83440a8 Mon Sep 17 00:00:00 2001 From: Ramon Brullo Date: Wed, 14 Aug 2024 08:47:36 +0200 Subject: [PATCH 04/26] refactor: un-async function Function was async for historic reasons, but did not need to be. Made into a sync function. --- src/cli/cmd-login.ts | 2 +- src/io/upm-config-io.ts | 8 ++++---- src/services/get-registry-auth.ts | 2 +- test/cli/cmd-login.test.ts | 2 +- test/services/get-registry-auth.test.ts | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/cli/cmd-login.ts b/src/cli/cmd-login.ts index 03e06625..9132ac82 100644 --- a/src/cli/cmd-login.ts +++ b/src/cli/cmd-login.ts @@ -76,7 +76,7 @@ export function makeLoginCmd( const alwaysAuth = options.alwaysAuth || false; - const configPath = await getUpmConfigPath(env.systemUser); + const configPath = getUpmConfigPath(env.systemUser); await login( username, diff --git a/src/io/upm-config-io.ts b/src/io/upm-config-io.ts index 1c06ddd2..3d53a710 100644 --- a/src/io/upm-config-io.ts +++ b/src/io/upm-config-io.ts @@ -25,7 +25,7 @@ export class NoSystemUserProfilePath extends CustomError {} * @param systemUser Whether to authenticate as a Windows system-user. * @returns The file path. */ -export type GetUpmConfigPath = (systemUser: boolean) => Promise; +export type GetUpmConfigPath = (systemUser: boolean) => string; /** * Makes a {@link GetUpmConfigPath} function which resolves to the default @@ -35,7 +35,7 @@ export type GetUpmConfigPath = (systemUser: boolean) => Promise; export function ResolveDefaultUpmConfigPath( getHomePath: GetHomePath ): GetUpmConfigPath { - async function getConfigDirectory(systemUser: boolean) { + function getConfigDirectory(systemUser: boolean) { const systemUserSubPath = "Unity/config/ServiceAccounts"; if (systemUser) { const profilePath = tryGetEnv("ALLUSERSPROFILE"); @@ -46,11 +46,11 @@ export function ResolveDefaultUpmConfigPath( return getHomePath(); } - return async (systemUser) => { + return (systemUser) => { const customDir = tryGetEnv("UPM_USER_CONFIG_FILE"); if (customDir !== null) return path.resolve(customDir); - const directory = await getConfigDirectory(systemUser); + const directory = getConfigDirectory(systemUser); return path.join(directory, configFileName); }; } diff --git a/src/services/get-registry-auth.ts b/src/services/get-registry-auth.ts index 9657a173..575d3ff6 100644 --- a/src/services/get-registry-auth.ts +++ b/src/services/get-registry-auth.ts @@ -67,7 +67,7 @@ export function LoadRegistryAuthFromUpmConfig( // Only load config if we have dont have it in the cache if (cachedConfig === null) { - const configPath = await getUpmConfigPath(systemUser); + const configPath = getUpmConfigPath(systemUser); cachedConfig = await loadUpmConfig(configPath); if (cachedConfig === null) { debugLog( diff --git a/test/cli/cmd-login.test.ts b/test/cli/cmd-login.test.ts index 1299dfe1..5af82638 100644 --- a/test/cli/cmd-login.test.ts +++ b/test/cli/cmd-login.test.ts @@ -24,7 +24,7 @@ describe("cmd-login", () => { parseEnv.mockResolvedValue(defaultEnv); const getUpmConfigPath = mockService(); - getUpmConfigPath.mockResolvedValue(exampleUpmConfigPath); + getUpmConfigPath.mockReturnValue(exampleUpmConfigPath); const login = mockService(); login.mockResolvedValue(undefined); diff --git a/test/services/get-registry-auth.test.ts b/test/services/get-registry-auth.test.ts index 48da8ff8..b67cae4f 100644 --- a/test/services/get-registry-auth.test.ts +++ b/test/services/get-registry-auth.test.ts @@ -15,7 +15,7 @@ describe("get registry auth from upm config", () => { function makeDependencies() { const getUpmConfigPath = mockService(); - getUpmConfigPath.mockResolvedValue("/home/user/.upmconfig.toml"); + getUpmConfigPath.mockReturnValue("/home/user/.upmconfig.toml"); const loadUpmConfig = mockService(); loadUpmConfig.mockResolvedValue({}); From d6cb833c82fabeff07d99ae655180a5bbae3bed6 Mon Sep 17 00:00:00 2001 From: Ramon Brullo Date: Thu, 15 Aug 2024 10:19:40 +0200 Subject: [PATCH 05/26] docs: fix incorrect jsdoc syntax Exception types should be wrapped in curly braces. --- src/domain/package-manifest.ts | 2 +- src/domain/packument.ts | 4 ++-- src/io/special-paths.ts | 2 +- src/utils/error-type-guards.ts | 2 +- src/utils/zod-utils.ts | 4 ++-- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/domain/package-manifest.ts b/src/domain/package-manifest.ts index fc104096..b92e6e81 100644 --- a/src/domain/package-manifest.ts +++ b/src/domain/package-manifest.ts @@ -91,7 +91,7 @@ export function dependenciesOf( * @param packageManifest The manifest for which to get the editor. * @returns The editor-version or null if the package is compatible * with all Unity version. - * @throws MalformedPackumentError if the packument contains invalid data that + * @throws {MalformedPackumentError} If the packument contains invalid data that * can not be parsed. */ export function tryGetTargetEditorVersionFor( diff --git a/src/domain/packument.ts b/src/domain/packument.ts index 5a33cd49..d6304921 100644 --- a/src/domain/packument.ts +++ b/src/domain/packument.ts @@ -145,7 +145,7 @@ export type ResolvePackumentVersionError = * @param requestedVersion The version to resolve. In this case indicates that * the latest version is requested. * @returns Result containing the resolved version. Will never be error. - * @throws NoVersionsError if the packument had no versions at all. + * @throws {NoVersionsError} If the packument had no versions at all. */ export function tryResolvePackumentVersion( packument: UnityPackument, @@ -168,7 +168,7 @@ export function tryResolvePackumentVersion( * @param requestedVersion The version to resolve. * @returns A result containing the version or an error if it could not be * resolved. - * @throws NoVersionsError if the packument had no versions at all. + * @throws {NoVersionsError} If the packument had no versions at all. */ export function tryResolvePackumentVersion( packument: UnityPackument, diff --git a/src/io/special-paths.ts b/src/io/special-paths.ts index 4e4a5c23..10168296 100644 --- a/src/io/special-paths.ts +++ b/src/io/special-paths.ts @@ -54,7 +54,7 @@ export class NoHomePathError extends CustomError {} /** * {@link GetHomePath} function which resolved the home path using the * `USERPROFILE` and `HOME` environment variables. - * @throws NoHomePathError if none of the required env variables are set. + * @throws {NoHomePathError} If none of the required env variables are set. */ export const getHomePathFromEnv: GetHomePath = () => { const homePath = tryGetEnv("USERPROFILE") ?? tryGetEnv("HOME"); diff --git a/src/utils/error-type-guards.ts b/src/utils/error-type-guards.ts index 5ccf7081..660667f2 100644 --- a/src/utils/error-type-guards.ts +++ b/src/utils/error-type-guards.ts @@ -64,7 +64,7 @@ export function isHttpError(x: unknown): x is HttpErrorBase { /** * Asserts that a value is a {@link HttpErrorBase}. * @param x The value to assert. - * @throws AssertionError if the value is not a {@link HttpErrorBase}. + * @throws {AssertionError} If the value is not a {@link HttpErrorBase}. */ export function assertIsHttpError(x: unknown): asserts x is HttpErrorBase { if (!isHttpError(x)) diff --git a/src/utils/zod-utils.ts b/src/utils/zod-utils.ts index ce5d5d04..52d98716 100644 --- a/src/utils/zod-utils.ts +++ b/src/utils/zod-utils.ts @@ -19,7 +19,7 @@ export function isZod( * zod schemas type. * @param value The value to check. * @param schema The zod schema to check against. - * @throws AssertionError if value does not match schema. + * @throws {AssertionError} If value does not match schema. */ export function assertZod( value: TZod["_input"], @@ -55,7 +55,7 @@ export function removeExplicitUndefined( * * You can use this function to circumvent [issue #365](https://github.com/colinhacks/zod/issues/635). * @param value The value. - * @throws Error if the passed value is undefined. + * @throws {Error} If the passed value is undefined. */ export function removeExplicitUndefined(value: unknown) { if (value === undefined) From 1a5022e02e5da7eac7dd22745c3e11d9d2fa5e70 Mon Sep 17 00:00:00 2001 From: Ramon Brullo Date: Thu, 15 Aug 2024 10:58:19 +0200 Subject: [PATCH 06/26] test: fix test not being run Forgot the .test in file name --- test/services/{get-auth-token.ts => get-auth-token.test.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/services/{get-auth-token.ts => get-auth-token.test.ts} (100%) diff --git a/test/services/get-auth-token.ts b/test/services/get-auth-token.test.ts similarity index 100% rename from test/services/get-auth-token.ts rename to test/services/get-auth-token.test.ts From 9fabedb98939e8c2032ca15480015462a76f3b6f Mon Sep 17 00:00:00 2001 From: Ramon Brullo Date: Thu, 15 Aug 2024 11:07:48 +0200 Subject: [PATCH 07/26] refactor: simplify error type Currently we use the `HttpErrorBase` from the `npm-registry-client` library for http errors. We needed to create the type definitions for this type ourself since the original is JS only. But really we only ever need the statusCode property off this type. So it is far easier to create an interface type with exactly that requirement. This change adds that type and adjusts the code everywhere accordingly --- src/io/common-errors.ts | 10 ++++++++++ src/types/npm-registry-fetch/lib/errors.d.ts | 8 -------- src/utils/error-type-guards.ts | 12 +++++------ test/io/all-packuments-io.test.ts | 14 +++++++------ test/io/npm-search.test.ts | 21 ++++++++++---------- test/io/packument-io.test.ts | 13 ++++++------ test/services/get-auth-token.test.ts | 3 +-- test/services/registry-client.mock.ts | 8 ++++---- 8 files changed, 46 insertions(+), 43 deletions(-) delete mode 100644 src/types/npm-registry-fetch/lib/errors.d.ts diff --git a/src/io/common-errors.ts b/src/io/common-errors.ts index e1bb524f..75bd3a45 100644 --- a/src/io/common-errors.ts +++ b/src/io/common-errors.ts @@ -9,3 +9,13 @@ export class RegistryAuthenticationError extends CustomError { super(); } } + +/** + * Type for {@link Error}s with a HTTP status code property. + */ +export interface HttpErrorLike extends Error { + /** + * The HTTP status code. + */ + statusCode: number; +} diff --git a/src/types/npm-registry-fetch/lib/errors.d.ts b/src/types/npm-registry-fetch/lib/errors.d.ts deleted file mode 100644 index d0b16f1b..00000000 --- a/src/types/npm-registry-fetch/lib/errors.d.ts +++ /dev/null @@ -1,8 +0,0 @@ -import {Response} from "node-fetch"; - -declare module "npm-registry-fetch/lib/errors" { - export class HttpErrorBase extends Error { - statusCode: Response["status"]; - code: `E${Response["status"]}}` | `E${string}`; - } -} diff --git a/src/utils/error-type-guards.ts b/src/utils/error-type-guards.ts index 660667f2..e0483e82 100644 --- a/src/utils/error-type-guards.ts +++ b/src/utils/error-type-guards.ts @@ -1,5 +1,5 @@ import { AssertionError } from "assert"; -import { HttpErrorBase } from "npm-registry-fetch/lib/errors"; +import { HttpErrorLike } from "../io/common-errors"; /** * Type guard for checking if a value is an {@link Error}. @@ -54,19 +54,19 @@ export function assertIsNodeError( } /** - * Type guard for checking whether a value is a {@link HttpErrorBase}. + * Type guard for checking whether a value is a {@link HttpErrorLike}. * @param x The value to check. */ -export function isHttpError(x: unknown): x is HttpErrorBase { +export function isHttpError(x: unknown): x is HttpErrorLike { return isError(x) && "statusCode" in x; } /** - * Asserts that a value is a {@link HttpErrorBase}. + * Asserts that a value is a {@link HttpErrorLike}. * @param x The value to assert. - * @throws {AssertionError} If the value is not a {@link HttpErrorBase}. + * @throws {AssertionError} If the value is not a {@link HttpErrorLike}. */ -export function assertIsHttpError(x: unknown): asserts x is HttpErrorBase { +export function assertIsHttpError(x: unknown): asserts x is HttpErrorLike { if (!isHttpError(x)) throw new AssertionError({ message: "Value is not an http error.", diff --git a/test/io/all-packuments-io.test.ts b/test/io/all-packuments-io.test.ts index 3c0d9f98..bede4a37 100644 --- a/test/io/all-packuments-io.test.ts +++ b/test/io/all-packuments-io.test.ts @@ -1,8 +1,10 @@ import npmFetch from "npm-registry-fetch"; -import { HttpErrorBase } from "npm-registry-fetch/lib/errors"; import { Registry } from "../../src/domain/registry"; import { FetchAllRegistryPackuments } from "../../src/io/all-packuments-io"; -import { RegistryAuthenticationError } from "../../src/io/common-errors"; +import { + HttpErrorLike, + RegistryAuthenticationError, +} from "../../src/io/common-errors"; import { noopLogger } from "../../src/logging"; import { exampleRegistryUrl } from "../domain/data-registry"; @@ -20,11 +22,11 @@ function makeDependencies() { describe("fetch all packuments", () => { it("should fail on non-auth error response", async () => { - const expected = { + const expected: HttpErrorLike = { message: "Idk, it failed", name: "FakeError", statusCode: 500, - } as HttpErrorBase; + }; jest.mocked(npmFetch.json).mockRejectedValue(expected); const { fetchAllRegistryPackuments } = makeDependencies(); @@ -34,11 +36,11 @@ describe("fetch all packuments", () => { }); it("should fail on auth error response", async () => { - const expected = { + const expected: HttpErrorLike = { message: "Idk, it failed", name: "FakeError", statusCode: 401, - } as HttpErrorBase; + }; jest.mocked(npmFetch.json).mockRejectedValue(expected); const { fetchAllRegistryPackuments } = makeDependencies(); diff --git a/test/io/npm-search.test.ts b/test/io/npm-search.test.ts index b8248a3d..a7aa5e12 100644 --- a/test/io/npm-search.test.ts +++ b/test/io/npm-search.test.ts @@ -1,11 +1,12 @@ -import npmSearch from "libnpmsearch"; -import search from "libnpmsearch"; -import { HttpErrorBase } from "npm-registry-fetch/lib/errors"; +import { default as npmSearch, default as search } from "libnpmsearch"; import { Registry } from "../../src/domain/registry"; -import { exampleRegistryUrl } from "../domain/data-registry"; -import { noopLogger } from "../../src/logging"; -import { RegistryAuthenticationError } from "../../src/io/common-errors"; +import { + HttpErrorLike, + RegistryAuthenticationError, +} from "../../src/io/common-errors"; import { NpmApiSearch } from "../../src/io/npm-search"; +import { noopLogger } from "../../src/logging"; +import { exampleRegistryUrl } from "../domain/data-registry"; jest.mock("libnpmsearch"); @@ -21,11 +22,11 @@ describe("npm api search", () => { } it("should fail for non-auth error response", async () => { - const expected = { + const expected: HttpErrorLike = { message: "Idk, it failed", name: "FakeError", statusCode: 500, - } as HttpErrorBase; + }; jest.mocked(npmSearch).mockRejectedValue(expected); const { npmApiSearch } = makeDependencies(); @@ -35,11 +36,11 @@ describe("npm api search", () => { }); it("should fail for auth error response", async () => { - const expected = { + const expected: HttpErrorLike = { message: "Idk, it failed", name: "FakeError", statusCode: 401, - } as HttpErrorBase; + }; jest.mocked(npmSearch).mockRejectedValue(expected); const { npmApiSearch } = makeDependencies(); diff --git a/test/io/packument-io.test.ts b/test/io/packument-io.test.ts index 804d1300..1005c066 100644 --- a/test/io/packument-io.test.ts +++ b/test/io/packument-io.test.ts @@ -1,12 +1,11 @@ -import { buildPackument } from "../domain/data-packument"; -import { HttpErrorBase } from "npm-registry-fetch/lib/errors"; -import { DomainName } from "../../src/domain/domain-name"; import RegClient from "another-npm-registry-client"; +import { DomainName } from "../../src/domain/domain-name"; import { Registry } from "../../src/domain/registry"; -import { exampleRegistryUrl } from "../domain/data-registry"; -import { mockRegClientGetResult } from "../services/registry-client.mock"; import { FetchRegistryPackument } from "../../src/io/packument-io"; import { noopLogger } from "../../src/logging"; +import { buildPackument } from "../domain/data-packument"; +import { exampleRegistryUrl } from "../domain/data-registry"; +import { mockRegClientGetResult } from "../services/registry-client.mock"; describe("packument io", () => { describe("fetch", () => { @@ -47,7 +46,7 @@ describe("packument io", () => { message: "not found", name: "FakeError", statusCode: 404, - } as HttpErrorBase, + }, null ); @@ -64,7 +63,7 @@ describe("packument io", () => { message: "Unauthorized", name: "FakeError", statusCode: 401, - } as HttpErrorBase, + }, null ); diff --git a/test/services/get-auth-token.test.ts b/test/services/get-auth-token.test.ts index abac4ccd..b51f32af 100644 --- a/test/services/get-auth-token.test.ts +++ b/test/services/get-auth-token.test.ts @@ -1,6 +1,5 @@ import "assert"; import RegClient from "another-npm-registry-client"; -import { HttpErrorBase } from "npm-registry-fetch/lib/errors"; import { exampleRegistryUrl } from "../domain/data-registry"; import { mockRegClientAddUserResult } from "./registry-client.mock"; import { RegistryAuthenticationError } from "../../src/io/common-errors"; @@ -74,7 +73,7 @@ describe("authenticate user with npm registry", () => { makeDependencies(); mockRegClientAddUserResult( registryClient, - {} as HttpErrorBase, + new Error(), { ok: false }, { statusMessage: "bad user", diff --git a/test/services/registry-client.mock.ts b/test/services/registry-client.mock.ts index 1912ce22..a8426409 100644 --- a/test/services/registry-client.mock.ts +++ b/test/services/registry-client.mock.ts @@ -1,7 +1,7 @@ import RegClient, { AddUserResponse } from "another-npm-registry-client"; -import { UnityPackument } from "../../src/domain/packument"; -import { HttpErrorBase } from "npm-registry-fetch/lib/errors"; import { Response } from "request"; +import { UnityPackument } from "../../src/domain/packument"; +import { HttpErrorLike } from "../../src/io/common-errors"; /** * Mocks the result of getting a package using a {@link RegClient.Instance}. @@ -11,7 +11,7 @@ import { Response } from "request"; */ export function mockRegClientGetResult( regClient: jest.Mocked, - error: HttpErrorBase | null, + error: Error | HttpErrorLike | null, packument: UnityPackument | null ) { regClient.get.mockImplementation((_1, _2, cb) => { @@ -28,7 +28,7 @@ export function mockRegClientGetResult( */ export function mockRegClientAddUserResult( registryClient: jest.Mocked, - error: HttpErrorBase | null, + error: Error | null, responseData: AddUserResponse | null, response: Pick | null ) { From 44f7e9d9901438ac028587a51b621a9c08789d61 Mon Sep 17 00:00:00 2001 From: Ramon Brullo Date: Thu, 15 Aug 2024 11:08:25 +0200 Subject: [PATCH 08/26] refactor: extract utility function When interacting with npm registries we handle the case where the response code is 401 specially because we throw a different error in that case. We could consider this business logic that is duplicated in these io functions. This change extracts that common logic into a separate function that can be reused and tested. --- src/io/all-packuments-io.ts | 20 +++++++------- src/io/common-errors.ts | 22 +++++++++++++++ src/io/npm-search.ts | 10 +++---- src/io/packument-io.ts | 13 +++------ test/io/common-errors.test.ts | 51 +++++++++++++++++++++++++++++++++++ 5 files changed, 91 insertions(+), 25 deletions(-) create mode 100644 test/io/common-errors.test.ts diff --git a/src/io/all-packuments-io.ts b/src/io/all-packuments-io.ts index 723274e4..43382e6f 100644 --- a/src/io/all-packuments-io.ts +++ b/src/io/all-packuments-io.ts @@ -2,8 +2,8 @@ import npmFetch from "npm-registry-fetch"; import { DomainName } from "../domain/domain-name"; import { Registry } from "../domain/registry"; import { DebugLog, npmDebugLog } from "../logging"; -import { assertIsError, isHttpError } from "../utils/error-type-guards"; -import { RegistryAuthenticationError } from "./common-errors"; +import { assertIsError } from "../utils/error-type-guards"; +import { makeRegistryInteractionError } from "./common-errors"; import { getNpmFetchOptions, SearchedPackument } from "./npm-search"; /** @@ -19,12 +19,16 @@ export type AllPackuments = Readonly<{ * Function for getting all packuments from a npm registry. * @param registry The registry to get packuments for. */ -export type GetAllRegistryPackuments = (registry: Registry) => Promise; +export type GetAllRegistryPackuments = ( + registry: Registry +) => Promise; /** * Makes a {@link GetAllRegistryPackuments} function. */ -export function FetchAllRegistryPackuments(debugLog: DebugLog): GetAllRegistryPackuments { +export function FetchAllRegistryPackuments( + debugLog: DebugLog +): GetAllRegistryPackuments { return async (registry) => { debugLog(`Getting all packages from ${registry.url}.`); try { @@ -37,11 +41,7 @@ export function FetchAllRegistryPackuments(debugLog: DebugLog): GetAllRegistryPa assertIsError(error); debugLog(`Failed to get all packages from ${registry.url}.`, error); - if (isHttpError(error)) - throw error.statusCode === 401 - ? new RegistryAuthenticationError(registry.url) - : error; - throw error; + throw makeRegistryInteractionError(error, registry.url); } }; } @@ -49,4 +49,4 @@ export function FetchAllRegistryPackuments(debugLog: DebugLog): GetAllRegistryPa /** * Default {@link GetAllRegistryPackuments} function. Uses {@link FetchAllRegistryPackuments}. */ -export const getAllRegistryPackuments = FetchAllRegistryPackuments(npmDebugLog) \ No newline at end of file +export const getAllRegistryPackuments = FetchAllRegistryPackuments(npmDebugLog); diff --git a/src/io/common-errors.ts b/src/io/common-errors.ts index 75bd3a45..811e9c7b 100644 --- a/src/io/common-errors.ts +++ b/src/io/common-errors.ts @@ -1,5 +1,6 @@ import { CustomError } from "ts-custom-error"; import { RegistryUrl } from "../domain/registry-url"; +import { isHttpError } from "../utils/error-type-guards"; /** * Error for when authentication with a registry failed. @@ -19,3 +20,24 @@ export interface HttpErrorLike extends Error { */ statusCode: number; } + +/** + * Categorizes an error that occurred when interacting with a remote + * npm registry into the relevant domain errors. + * + * The logic goes like this. + * + * - Non-http {@link Error}s -> get returned as is. + * - Non-auth {@link HttpErrorLike}s -> get returned as is. + * - Auth {@link HttpErrorLike}s -> get converted to a {@link RegistryAuthenticationError}. + * @param error The error that occurred. + * @param registryUrl The url of the registry that was interacted with. + * @returns The categorized error. + */ +export function makeRegistryInteractionError( + error: Error, + registryUrl: RegistryUrl +): Error | HttpErrorLike | RegistryAuthenticationError { + if (!isHttpError(error) || error.statusCode !== 401) return error; + return new RegistryAuthenticationError(registryUrl); +} diff --git a/src/io/npm-search.ts b/src/io/npm-search.ts index 03b0a563..b320e478 100644 --- a/src/io/npm-search.ts +++ b/src/io/npm-search.ts @@ -4,8 +4,8 @@ import { UnityPackument } from "../domain/packument"; import { Registry } from "../domain/registry"; import { SemanticVersion } from "../domain/semantic-version"; import { DebugLog, npmDebugLog } from "../logging"; -import { assertIsHttpError } from "../utils/error-type-guards"; -import { RegistryAuthenticationError } from "./common-errors"; +import { assertIsError } from "../utils/error-type-guards"; +import { makeRegistryInteractionError } from "./common-errors"; /** * A type representing a searched packument. Instead of having all versions @@ -52,11 +52,9 @@ export function NpmApiSearch(debugLog: DebugLog): SearchRegistry { // NOTE: The results of the search will be packument objects, so we can change the type .then((results) => results as SearchedPackument[]) .catch((error) => { - assertIsHttpError(error); + assertIsError(error); debugLog("A http request failed.", error); - throw error.statusCode === 401 - ? new RegistryAuthenticationError(registry.url) - : error; + throw makeRegistryInteractionError(error, registry.url); }); } diff --git a/src/io/packument-io.ts b/src/io/packument-io.ts index a83011a7..a993dfe2 100644 --- a/src/io/packument-io.ts +++ b/src/io/packument-io.ts @@ -1,10 +1,10 @@ import RegClient from "another-npm-registry-client"; -import { assertIsHttpError } from "../utils/error-type-guards"; -import { Registry } from "../domain/registry"; import { DomainName } from "../domain/domain-name"; import { UnityPackument } from "../domain/packument"; -import { RegistryAuthenticationError } from "./common-errors"; +import { Registry } from "../domain/registry"; import { DebugLog, npmDebugLog } from "../logging"; +import { assertIsHttpError } from "../utils/error-type-guards"; +import { makeRegistryInteractionError } from "./common-errors"; import { npmRegistryClient } from "./reg-client"; /** @@ -37,12 +37,7 @@ export function FetchRegistryPackument( debugLog("Fetching a packument failed.", error); assertIsHttpError(error); if (error.statusCode === 404) resolve(null); - else - reject( - error.statusCode === 401 - ? new RegistryAuthenticationError(registry.url) - : error - ); + else reject(makeRegistryInteractionError(error, registry.url)); } else resolve(packument); } ); diff --git a/test/io/common-errors.test.ts b/test/io/common-errors.test.ts new file mode 100644 index 00000000..5bd6f349 --- /dev/null +++ b/test/io/common-errors.test.ts @@ -0,0 +1,51 @@ +import { + makeRegistryInteractionError, + HttpErrorLike, + RegistryAuthenticationError, +} from "../../src/io/common-errors"; +import { exampleRegistryUrl } from "../domain/data-registry"; + +describe("common error utilities", () => { + describe("make registry interaction errors", () => { + it("should be original error if not a http error", () => { + const error = new Error("Not http"); + + const actual = makeRegistryInteractionError( + error, + exampleRegistryUrl + ); + + expect(actual).toEqual(error); + }); + + it("should be original error if not a 401 error", () => { + const error: HttpErrorLike = { + name: "Some error", + message: "Http error", + statusCode: 404, + }; + + const actual = makeRegistryInteractionError( + error, + exampleRegistryUrl + ); + + expect(actual).toEqual(error); + }); + + it("should be auth error if 401 error", () => { + const error: HttpErrorLike = { + name: "Some error", + message: "Http error", + statusCode: 401, + }; + + const actual = makeRegistryInteractionError( + error, + exampleRegistryUrl + ); + + expect(actual).toBeInstanceOf(RegistryAuthenticationError); + }); + }); +}); From 8bd9897fd16b92463d68ab982189851494f1e14e Mon Sep 17 00:00:00 2001 From: Ramon Brullo Date: Fri, 16 Aug 2024 10:34:03 +0200 Subject: [PATCH 09/26] test: add test for pure function Also extract it to its own file --- src/io/all-packuments-io.ts | 5 ++- src/io/npm-registry.ts | 17 +++++++++ src/io/npm-search.ts | 18 +-------- test/io/npm-registry.test.ts | 73 ++++++++++++++++++++++++++++++++++++ 4 files changed, 95 insertions(+), 18 deletions(-) create mode 100644 src/io/npm-registry.ts create mode 100644 test/io/npm-registry.test.ts diff --git a/src/io/all-packuments-io.ts b/src/io/all-packuments-io.ts index 43382e6f..0a2ffa83 100644 --- a/src/io/all-packuments-io.ts +++ b/src/io/all-packuments-io.ts @@ -4,7 +4,8 @@ import { Registry } from "../domain/registry"; import { DebugLog, npmDebugLog } from "../logging"; import { assertIsError } from "../utils/error-type-guards"; import { makeRegistryInteractionError } from "./common-errors"; -import { getNpmFetchOptions, SearchedPackument } from "./npm-search"; +import { makeNpmFetchOptions } from "./npm-registry"; +import { SearchedPackument } from "./npm-search"; /** * The result of querying the /-/all endpoint. @@ -34,7 +35,7 @@ export function FetchAllRegistryPackuments( try { const result = await npmFetch.json( "/-/all", - getNpmFetchOptions(registry) + makeNpmFetchOptions(registry) ); return result as AllPackuments; } catch (error) { diff --git a/src/io/npm-registry.ts b/src/io/npm-registry.ts new file mode 100644 index 00000000..e7436749 --- /dev/null +++ b/src/io/npm-registry.ts @@ -0,0 +1,17 @@ +import { Registry } from "../domain/registry"; +import npmFetch from "npm-registry-fetch"; + +/** + * Converts a {@link Registry} object into a {@link npmFetch.Options} object for + * use in npm registry interactions. + * @param registry The registry object to convert. + * @returns The created options object. + */ +export function makeNpmFetchOptions(registry: Registry): npmFetch.Options { + const opts: npmFetch.Options = { + registry: registry.url, + }; + const auth = registry.auth; + if (auth !== null) Object.assign(opts, auth); + return opts; +} diff --git a/src/io/npm-search.ts b/src/io/npm-search.ts index b320e478..e54370de 100644 --- a/src/io/npm-search.ts +++ b/src/io/npm-search.ts @@ -1,11 +1,11 @@ import npmSearch from "libnpmsearch"; -import npmFetch from "npm-registry-fetch"; import { UnityPackument } from "../domain/packument"; import { Registry } from "../domain/registry"; import { SemanticVersion } from "../domain/semantic-version"; import { DebugLog, npmDebugLog } from "../logging"; import { assertIsError } from "../utils/error-type-guards"; import { makeRegistryInteractionError } from "./common-errors"; +import { makeNpmFetchOptions } from "./npm-registry"; /** * A type representing a searched packument. Instead of having all versions @@ -28,27 +28,13 @@ export type SearchRegistry = ( keyword: string ) => Promise>; -/** - * Get npm fetch options. - */ -export const getNpmFetchOptions = function ( - registry: Registry -): npmFetch.Options { - const opts: npmSearch.Options = { - registry: registry.url, - }; - const auth = registry.auth; - if (auth !== null) Object.assign(opts, auth); - return opts; -}; - /** * Makes a {@link SearchRegistry} function which uses the npm search api to * find packages in a remote registry. */ export function NpmApiSearch(debugLog: DebugLog): SearchRegistry { return (registry, keyword) => - npmSearch(keyword, getNpmFetchOptions(registry)) + npmSearch(keyword, makeNpmFetchOptions(registry)) // NOTE: The results of the search will be packument objects, so we can change the type .then((results) => results as SearchedPackument[]) .catch((error) => { diff --git a/test/io/npm-registry.test.ts b/test/io/npm-registry.test.ts new file mode 100644 index 00000000..e73a7c15 --- /dev/null +++ b/test/io/npm-registry.test.ts @@ -0,0 +1,73 @@ +import { userInfo } from "os"; +import { Registry } from "../../src/domain/registry"; +import { makeNpmFetchOptions } from "../../src/io/npm-registry"; +import { exampleRegistryUrl } from "../domain/data-registry"; +import { Options as FetchOptions } from "npm-registry-fetch"; +import { NpmAuth } from "another-npm-registry-client"; + +describe("npm registry interactions", () => { + describe("make fetch options from registry object", () => { + it("should use input registry url", () => { + const registry: Registry = { + url: exampleRegistryUrl, + auth: null, + }; + + const options = makeNpmFetchOptions(registry); + + expect(options.registry).toEqual(exampleRegistryUrl); + }); + + it("should have no auth data if input does not have one", () => { + const registry: Registry = { + url: exampleRegistryUrl, + auth: null, + }; + + const options = makeNpmFetchOptions(registry); + + expect(options.username).not.toBeDefined(); + expect(options.password).not.toBeDefined(); + expect(options.email).not.toBeDefined(); + expect(options.token).not.toBeDefined(); + expect(options.alwaysAuth).not.toBeDefined(); + }); + + it("should use token auth info", () => { + const auth: NpmAuth = { token: "Some token", alwaysAuth: true }; + const registry: Registry = { + url: exampleRegistryUrl, + auth, + }; + + const options = makeNpmFetchOptions(registry); + + expect(options.username).not.toBeDefined(); + expect(options.password).not.toBeDefined(); + expect(options.email).not.toBeDefined(); + expect(options.token).toEqual(auth.token); + expect(options.alwaysAuth).toEqual(auth.alwaysAuth); + }); + + it("should use basic auth info", () => { + const auth: NpmAuth = { + username: "user", + password: "pass", + email: "user@mail.com", + alwaysAuth: true, + }; + const registry: Registry = { + url: exampleRegistryUrl, + auth, + }; + + const options = makeNpmFetchOptions(registry); + + expect(options.username).toEqual(auth.username); + expect(options.password).toEqual(auth.password); + expect(options.email).toEqual(auth.email); + expect(options.token).not.toBeDefined(); + expect(options.alwaysAuth).toEqual(auth.alwaysAuth); + }); + }); +}); From 036c79490cc211ef7f3e570a7491885f2bcb9dab Mon Sep 17 00:00:00 2001 From: Ramon Brullo Date: Fri, 16 Aug 2024 10:40:57 +0200 Subject: [PATCH 10/26] refactor: use zod Use zod for ProjectVersion.txt validation --- src/io/project-version-io.ts | 13 +++++-------- src/utils/zod-utils.ts | 4 ++-- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/io/project-version-io.ts b/src/io/project-version-io.ts index d50b13bc..452d9a6b 100644 --- a/src/io/project-version-io.ts +++ b/src/io/project-version-io.ts @@ -5,6 +5,8 @@ import * as YAML from "yaml"; import { DebugLog, npmDebugLog } from "../logging"; import { assertIsError } from "../utils/error-type-guards"; import { readTextFile, ReadTextFile } from "./text-file-io"; +import { z } from "zod"; +import { isZod } from "../utils/zod-utils"; export class ProjectVersionMissingError extends CustomError { public constructor(public readonly expectedPath: string) { @@ -31,6 +33,8 @@ export function projectVersionTxtPathFor(projectDirPath: string) { */ export type GetProjectVersion = (projectDirPath: string) => Promise; +const projectVersionTxtSchema = z.object({ m_EditorVersion: z.string() }); + /** * Makes a {@link GetProjectVersion} function which gets the project-version * from the `ProjectSettings/ProjectVersion.txt` file. @@ -54,14 +58,7 @@ export function ReadProjectVersionFile( throw new ProjectVersionMalformedError(); } - if ( - !( - typeof yaml === "object" && - yaml !== null && - "m_EditorVersion" in yaml && - typeof yaml.m_EditorVersion === "string" - ) - ) + if (!isZod(yaml, projectVersionTxtSchema)) throw new ProjectVersionMalformedError(); return yaml.m_EditorVersion; diff --git a/src/utils/zod-utils.ts b/src/utils/zod-utils.ts index 52d98716..79e7af60 100644 --- a/src/utils/zod-utils.ts +++ b/src/utils/zod-utils.ts @@ -8,7 +8,7 @@ import { AssertionError } from "assert"; * @param schema The zod schema to check against. */ export function isZod( - value: TZod["_input"], + value: unknown, schema: TZod ): value is TZod["_output"] { return schema.safeParse(value).success; @@ -22,7 +22,7 @@ export function isZod( * @throws {AssertionError} If value does not match schema. */ export function assertZod( - value: TZod["_input"], + value: unknown, schema: TZod ): asserts value is TZod["_output"] { const result = schema.safeParse(value); From 12499be6d2711e0374e4bb31e38f8897fe14342d Mon Sep 17 00:00:00 2001 From: Ramon Brullo Date: Fri, 16 Aug 2024 10:48:08 +0200 Subject: [PATCH 11/26] refactor: use zod schema --- src/io/project-manifest-io.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/io/project-manifest-io.ts b/src/io/project-manifest-io.ts index 8c8099da..d35536fe 100644 --- a/src/io/project-manifest-io.ts +++ b/src/io/project-manifest-io.ts @@ -13,6 +13,8 @@ import { writeTextFile, WriteTextFile, } from "./text-file-io"; +import { z } from "zod"; +import { isZod } from "../utils/zod-utils"; export class ManifestMissingError extends CustomError { public constructor(public expectedPath: string) { @@ -40,6 +42,9 @@ export type LoadProjectManifest = ( projectPath: string ) => Promise; +// TODO: Add a better schema +const projectManifestSchema = z.object({}); + /** * Makes a {@link LoadProjectManifest} function which reads the content * of a `manifest.json` file. @@ -63,8 +68,7 @@ export function ReadProjectManifestFile( throw new ManifestMalformedError(); } - // TODO: Actually validate the json structure - if (typeof json !== "object") throw new ManifestMalformedError(); + if (!isZod(json, projectManifestSchema)) throw new ManifestMalformedError(); return json as unknown as UnityProjectManifest; }; From 8df36b694484f37f184868b0f65c6c3f17dbf505 Mon Sep 17 00:00:00 2001 From: Ramon Brullo Date: Fri, 16 Aug 2024 10:59:08 +0200 Subject: [PATCH 12/26] refactor: extract pure logic Extract pure logic for serializing project manifests into its own function. Also inline the prune manifest function into it since it is not used anywhere else. --- src/domain/project-manifest.ts | 17 ------- src/io/project-manifest-io.ts | 35 ++++++++++---- test/io/project-manifest-io.test.ts | 73 +++++++---------------------- 3 files changed, 42 insertions(+), 83 deletions(-) diff --git a/src/domain/project-manifest.ts b/src/domain/project-manifest.ts index 9bd96986..aab48c18 100644 --- a/src/domain/project-manifest.ts +++ b/src/domain/project-manifest.ts @@ -148,23 +148,6 @@ export function addTestable( }; } -/** - * Prunes the manifest by performing the following operations: - * - Remove scoped-registries without scopes. - * @param manifest The manifest to prune. - */ -export function pruneManifest( - manifest: UnityProjectManifest -): UnityProjectManifest { - if (manifest.scopedRegistries === undefined) return manifest; - return { - ...manifest, - scopedRegistries: manifest.scopedRegistries.filter( - (it) => it.scopes.length > 0 - ), - }; -} - /** * Checks if a manifest has a dependency on a specific package. */ diff --git a/src/io/project-manifest-io.ts b/src/io/project-manifest-io.ts index d35536fe..354072d6 100644 --- a/src/io/project-manifest-io.ts +++ b/src/io/project-manifest-io.ts @@ -1,20 +1,17 @@ import { AnyJson } from "@iarna/toml"; import path from "path"; import { CustomError } from "ts-custom-error"; -import { - pruneManifest, - UnityProjectManifest, -} from "../domain/project-manifest"; +import { z } from "zod"; +import { UnityProjectManifest } from "../domain/project-manifest"; import { DebugLog, npmDebugLog } from "../logging"; import { assertIsError } from "../utils/error-type-guards"; +import { isZod } from "../utils/zod-utils"; import { readTextFile, ReadTextFile, writeTextFile, WriteTextFile, } from "./text-file-io"; -import { z } from "zod"; -import { isZod } from "../utils/zod-utils"; export class ManifestMissingError extends CustomError { public constructor(public expectedPath: string) { @@ -92,6 +89,26 @@ export type SaveProjectManifest = ( manifest: UnityProjectManifest ) => Promise; +/** + * Serializes a {@link UnityProjectManifest} object into json format. + * @param manifest The manifest to serialize. + * @returns The serialized manifest. + */ +export function serializeProjectManifest( + manifest: UnityProjectManifest +): string { + // Remove empty scoped registries + if (manifest.scopedRegistries !== undefined) + manifest = { + ...manifest, + scopedRegistries: manifest.scopedRegistries.filter( + (it) => it.scopes.length > 0 + ), + }; + + return JSON.stringify(manifest, null, 2); +} + /** * Makes a {@link SaveProjectManifest} function which overwrites the contents * of a `manifest.json` file. @@ -101,10 +118,8 @@ export function WriteProjectManifestFile( ): SaveProjectManifest { return async (projectPath, manifest) => { const manifestPath = manifestPathFor(projectPath); - manifest = pruneManifest(manifest); - const json = JSON.stringify(manifest, null, 2); - - return await writeFile(manifestPath, json); + const content = serializeProjectManifest(manifest); + return await writeFile(manifestPath, content); }; } diff --git a/test/io/project-manifest-io.test.ts b/test/io/project-manifest-io.test.ts index cedbcd8c..abb74e53 100644 --- a/test/io/project-manifest-io.test.ts +++ b/test/io/project-manifest-io.test.ts @@ -4,8 +4,12 @@ import { ManifestMalformedError, ManifestMissingError, manifestPathFor, + serializeProjectManifest, } from "../../src/io/project-manifest-io"; -import { mapScopedRegistry } from "../../src/domain/project-manifest"; +import { + mapScopedRegistry, + UnityProjectManifest, +} from "../../src/domain/project-manifest"; import path from "path"; import { ReadTextFile, WriteTextFile } from "../../src/io/text-file-io"; import { buildProjectManifest } from "../domain/data-project-manifest"; @@ -83,63 +87,20 @@ describe("project-manifest io", () => { }); }); - describe("write file", () => { - function makeDependencies() { - const writeFile = mockService(); - writeFile.mockResolvedValue(undefined); - - const writeProjectManifest = WriteProjectManifestFile(writeFile); - return { writeProjectManifest, writeFile } as const; - } - - it("should write manifest json", async () => { - const { writeProjectManifest, writeFile } = makeDependencies(); - - const manifest = buildProjectManifest((manifest) => - manifest.addDependency("com.package.a", "1.0.0", true, true) - ); + describe("serialize manifest", () => { + it("should prune empty scoped registries", () => { + const manifest: UnityProjectManifest = { + dependencies: {}, + scopedRegistries: [ + // Scoped registry with no scopes + { name: "some registry", url: exampleRegistryUrl, scopes: [] }, + ], + }; - await writeProjectManifest(exampleProjectPath, manifest); + const json = serializeProjectManifest(manifest); - expect(writeFile).toHaveBeenCalledWith( - expect.any(String), - JSON.stringify( - { - dependencies: { - "com.package.a": "1.0.0", - }, - scopedRegistries: [ - { - name: "example.com", - url: exampleRegistryUrl, - scopes: ["com.package.a"], - }, - ], - testables: ["com.package.a"], - }, - null, - 2 - ) - ); - }); - - it("should prune manifest before writing", async () => { - const { writeProjectManifest, writeFile } = makeDependencies(); - // Add and then remove a scope to force an empty scoped-registry - const testDomain = "test" as DomainName; - let manifest = buildProjectManifest((manifest) => - manifest.addScope(testDomain) - ); - manifest = mapScopedRegistry(manifest, exampleRegistryUrl, (registry) => { - return removeScope(registry!, testDomain); - }); - - await writeProjectManifest(exampleProjectPath, manifest); - - expect(writeFile).toHaveBeenCalledWith( - expect.any(String), - JSON.stringify({ dependencies: {}, scopedRegistries: [] }, null, 2) - ); + // The registry should not be in the output json + expect(json).not.toContain("some registry"); }); }); }); From 8661fddd2308bccc02fed6b489fe8db6eee97a04 Mon Sep 17 00:00:00 2001 From: Ramon Brullo Date: Fri, 16 Aug 2024 14:29:20 +0200 Subject: [PATCH 13/26] refactor: upmconfig path logic Extract pure part into domain folder. Also add more tests for it. --- src/cli/error-logging.ts | 28 ++++++++--------- src/domain/upm-config.ts | 41 +++++++++++++++++++++++++ src/io/upm-config-io.ts | 28 ++--------------- test/domain/upmconfig.test.ts | 57 +++++++++++++++++++++++++++++++++++ test/io/upm-config-io.test.ts | 48 +---------------------------- 5 files changed, 116 insertions(+), 86 deletions(-) create mode 100644 src/domain/upm-config.ts create mode 100644 test/domain/upmconfig.test.ts diff --git a/src/cli/error-logging.ts b/src/cli/error-logging.ts index 4bed548e..d603d0b3 100644 --- a/src/cli/error-logging.ts +++ b/src/cli/error-logging.ts @@ -1,25 +1,14 @@ +import { EOL } from "node:os"; import { Logger } from "npmlog"; -import { ResultCodes } from "./result-codes"; -import { RegistryAuthLoadError } from "../services/parse-env"; import { EditorVersionNotSupportedError, PackumentNotFoundError, } from "../common-errors"; import { stringifyEditorVersion } from "../domain/editor-version"; -import { - CompatibilityCheckFailedError, - PackageIncompatibleError, - UnresolvedDependenciesError, -} from "./cmd-add"; import { NoVersionsError, VersionNotFoundError } from "../domain/packument"; -import { EOL } from "node:os"; +import { NoSystemUserProfilePath } from "../domain/upm-config"; import { EditorNotInstalledError } from "../io/builtin-packages"; import { RegistryAuthenticationError } from "../io/common-errors"; -import { - NoHomePathError, - OSNotSupportedError, - VersionNotSupportedOnOsError, -} from "../io/special-paths"; import { ManifestMalformedError, ManifestMissingError, @@ -28,7 +17,18 @@ import { ProjectVersionMalformedError, ProjectVersionMissingError, } from "../io/project-version-io"; -import { NoSystemUserProfilePath } from "../io/upm-config-io"; +import { + NoHomePathError, + OSNotSupportedError, + VersionNotSupportedOnOsError, +} from "../io/special-paths"; +import { RegistryAuthLoadError } from "../services/parse-env"; +import { + CompatibilityCheckFailedError, + PackageIncompatibleError, + UnresolvedDependenciesError, +} from "./cmd-add"; +import { ResultCodes } from "./result-codes"; function makeErrorMessageFor(error: unknown): string { if (error instanceof RegistryAuthLoadError) diff --git a/src/domain/upm-config.ts b/src/domain/upm-config.ts new file mode 100644 index 00000000..5b1a73a6 --- /dev/null +++ b/src/domain/upm-config.ts @@ -0,0 +1,41 @@ +import path from "path"; +import { CustomError } from "ts-custom-error"; + +const configFileName = ".upmconfig.toml"; + +export class NoSystemUserProfilePath extends CustomError {} + +/** + * Determines the path to the users `.upmconfig.toml` file, based on the + * given parameters. + * @param envVars The current environment variables. + * @param homePath The users home path. + * @param forSystemUser Whether to get the path for the system user instead + * of the current one. + * @returns The resolved path. + * @throws {NoSystemUserProfilePath} When trying to get the path for the + * system-user but the `ALLUSERSPROFILE` env var is not set. + * @see https://docs.unity3d.com/Manual/upm-config.html + */ +export function getUserUpmConfigPathFor( + envVars: Record, + homePath: string, + forSystemUser: boolean +): string { + function getConfigDirectory() { + const systemUserSubPath = "Unity/config/ServiceAccounts"; + if (forSystemUser) { + const profilePath = envVars["ALLUSERSPROFILE"]; + if (profilePath === undefined) throw new NoSystemUserProfilePath(); + return path.join(profilePath, systemUserSubPath); + } + + return homePath; + } + + const customDir = envVars["UPM_USER_CONFIG_FILE"]; + if (customDir !== undefined) return path.resolve(customDir); + + const directory = getConfigDirectory(); + return path.join(directory, configFileName); +} diff --git a/src/io/upm-config-io.ts b/src/io/upm-config-io.ts index 3d53a710..bb4e62dd 100644 --- a/src/io/upm-config-io.ts +++ b/src/io/upm-config-io.ts @@ -1,9 +1,7 @@ import TOML from "@iarna/toml"; -import path from "path"; -import { CustomError } from "ts-custom-error"; import { z } from "zod"; import { Base64 } from "../domain/base64"; -import { tryGetEnv } from "../utils/env-util"; +import { getUserUpmConfigPathFor } from "../domain/upm-config"; import { removeExplicitUndefined, RemoveExplicitUndefined, @@ -16,10 +14,6 @@ import { WriteTextFile, } from "./text-file-io"; -const configFileName = ".upmconfig.toml"; - -export class NoSystemUserProfilePath extends CustomError {} - /** * Function which gets the path to the upmconfig file. * @param systemUser Whether to authenticate as a Windows system-user. @@ -35,24 +29,8 @@ export type GetUpmConfigPath = (systemUser: boolean) => string; export function ResolveDefaultUpmConfigPath( getHomePath: GetHomePath ): GetUpmConfigPath { - function getConfigDirectory(systemUser: boolean) { - const systemUserSubPath = "Unity/config/ServiceAccounts"; - if (systemUser) { - const profilePath = tryGetEnv("ALLUSERSPROFILE"); - if (profilePath === null) throw new NoSystemUserProfilePath(); - return path.join(profilePath, systemUserSubPath); - } - - return getHomePath(); - } - - return (systemUser) => { - const customDir = tryGetEnv("UPM_USER_CONFIG_FILE"); - if (customDir !== null) return path.resolve(customDir); - - const directory = getConfigDirectory(systemUser); - return path.join(directory, configFileName); - }; + return (systemUser) => + getUserUpmConfigPathFor(process.env, getHomePath(), systemUser); } /** diff --git a/test/domain/upmconfig.test.ts b/test/domain/upmconfig.test.ts new file mode 100644 index 00000000..e1093470 --- /dev/null +++ b/test/domain/upmconfig.test.ts @@ -0,0 +1,57 @@ +import path from "path"; +import { + getUserUpmConfigPathFor, + NoSystemUserProfilePath, +} from "../../src/domain/upm-config"; + +describe("upm config", () => { + describe("get user config path", () => { + const someHomeDirectory = path.resolve("/home/user/"); + const someAllUsersDirectory = path.resolve("/all-users/"); + + it("should be UPM_USER_CONFIG_FILE env if set", () => { + const expected = path.resolve("/home/user/.config/.upmconfig.toml"); + + const upmConfigPath = getUserUpmConfigPathFor( + { UPM_USER_CONFIG_FILE: expected }, + someHomeDirectory, + false + ); + + expect(upmConfigPath).toEqual(expected); + }); + + it("should be home path for regular users", () => { + const expected = path.join(someHomeDirectory, ".upmconfig.toml"); + + const upmConfigPath = getUserUpmConfigPathFor( + {}, + someHomeDirectory, + false + ); + + expect(upmConfigPath).toEqual(expected); + }); + + it("should be service account path for system user", () => { + const expected = path.join( + someAllUsersDirectory, + "/Unity/config/ServiceAccounts/.upmconfig.toml" + ); + + const upmConfigPath = getUserUpmConfigPathFor( + { ALLUSERSPROFILE: someAllUsersDirectory }, + someHomeDirectory, + true + ); + + expect(upmConfigPath).toEqual(expected); + }); + + it("should be fail for system user if required env is not set", () => { + expect(() => + getUserUpmConfigPathFor({}, someHomeDirectory, true) + ).toThrow(NoSystemUserProfilePath); + }); + }); +}); diff --git a/test/io/upm-config-io.test.ts b/test/io/upm-config-io.test.ts index 1a30a301..10ec907c 100644 --- a/test/io/upm-config-io.test.ts +++ b/test/io/upm-config-io.test.ts @@ -1,58 +1,12 @@ import { EOL } from "node:os"; -import path from "path"; -import { GetHomePath } from "../../src/io/special-paths"; import { ReadTextFile } from "../../src/io/text-file-io"; -import { - ReadUpmConfigFile, - ResolveDefaultUpmConfigPath, -} from "../../src/io/upm-config-io"; +import { ReadUpmConfigFile } from "../../src/io/upm-config-io"; import { exampleRegistryUrl } from "../domain/data-registry"; import { mockService } from "../services/service.mock"; -import { tryGetEnv } from "../../src/utils/env-util"; jest.mock("../../src/utils/env-util"); describe("upm-config-io", () => { - describe("resolve default path", () => { - const homeConfigPath = path.resolve("/some/home/dir/.upmconfig.toml"); - const customConfigPath = path.resolve("/some/other/dir/.upmconfig.toml"); - - function makeDependencies() { - const getHomePath = mockService(); - getHomePath.mockReturnValue(path.dirname(homeConfigPath)); - - jest.mocked(tryGetEnv).mockReturnValue(null); - - const resolveDefaultUpmConfigPath = - ResolveDefaultUpmConfigPath(getHomePath); - - return { resolveDefaultUpmConfigPath, getHomePath } as const; - } - - it("should be UPM_USER_CONFIG_FILE if set", async () => { - const { resolveDefaultUpmConfigPath } = makeDependencies(); - jest - .mocked(tryGetEnv) - .mockImplementation((key) => - key === "UPM_USER_CONFIG_FILE" ? customConfigPath : null - ); - - const actual = await resolveDefaultUpmConfigPath(false); - - expect(actual).toEqual(customConfigPath); - }); - - describe("no system-user", () => { - it("should be in home path", async () => { - const { resolveDefaultUpmConfigPath } = makeDependencies(); - - const actual = await resolveDefaultUpmConfigPath(false); - - expect(actual).toEqual(homeConfigPath); - }); - }); - }); - describe("read file", () => { const someConfigPath = "/home/user/.upmconfig.toml"; const someEmail = "user@mail.com"; From be907f83e7d6bfb002cf093cef1c0250b57c2119 Mon Sep 17 00:00:00 2001 From: Ramon Brullo Date: Fri, 16 Aug 2024 14:32:37 +0200 Subject: [PATCH 14/26] refactor: process cwd logic Instead of injecting a function for getting the process directory, we can just inject the path directly. This reduces boilerplate code --- src/cli/index.ts | 3 +-- src/io/special-paths.ts | 15 ++------------- src/services/parse-env.ts | 7 ++++--- test/services/parse-env.test.ts | 8 +------- 4 files changed, 8 insertions(+), 25 deletions(-) diff --git a/src/cli/index.ts b/src/cli/index.ts index b9a2b601..b631af5a 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -8,7 +8,6 @@ import { loadProjectManifest, saveProjectManifest, } from "../io/project-manifest-io"; -import { getProcessCwd } from "../io/special-paths"; import { getUpmConfigPath } from "../io/upm-config-io"; import { npmDebugLog } from "../logging"; import { resolveDependencies } from "../services/dependency-resolving"; @@ -39,7 +38,7 @@ import { const log = npmlog; -const parseEnv = makeParseEnv(log, getProcessCwd); +const parseEnv = makeParseEnv(log, process.cwd()); const addCmd = makeAddCmd( parseEnv, diff --git a/src/io/special-paths.ts b/src/io/special-paths.ts index 10168296..8aa4f8f4 100644 --- a/src/io/special-paths.ts +++ b/src/io/special-paths.ts @@ -1,6 +1,7 @@ -import { Err, Ok, Result } from "ts-results-es"; import os from "os"; import { CustomError } from "ts-custom-error"; +import { Err, Ok, Result } from "ts-results-es"; +import { EditorVersionNotSupportedError } from "../common-errors"; import { compareEditorVersion, EditorVersion, @@ -8,7 +9,6 @@ import { ReleaseVersion, stringifyEditorVersion, } from "../domain/editor-version"; -import { EditorVersionNotSupportedError } from "../common-errors"; import { tryGetEnv } from "../utils/env-util"; /** @@ -108,14 +108,3 @@ export function tryGetEditorInstallPath( return Err(new OSNotSupportedError(platform)); } - -/** - * Function that gets the current working directories path. - * @returns The path. - */ -export type GetCwd = () => string; - -/** - * {@link GetCwd} function which uses {@link process.cwd}. - */ -export const getProcessCwd: GetCwd = process.cwd; diff --git a/src/services/parse-env.ts b/src/services/parse-env.ts index c9f3b662..27ee4e0c 100644 --- a/src/services/parse-env.ts +++ b/src/services/parse-env.ts @@ -8,7 +8,6 @@ import { openupmRegistryUrl, RegistryUrl, } from "../domain/registry-url"; -import { GetCwd } from "../io/special-paths"; import { tryGetEnv } from "../utils/env-util"; /** @@ -51,9 +50,11 @@ export type ParseEnv = (options: CmdOptions) => Promise; /** * Creates a {@link ParseEnv} function. */ -export function makeParseEnv(log: Logger, getCwd: GetCwd): ParseEnv { +export function makeParseEnv(log: Logger, processCwd: string): ParseEnv { function determineCwd(options: CmdOptions): string { - return options.chdir !== undefined ? path.resolve(options.chdir) : getCwd(); + return options.chdir !== undefined + ? path.resolve(options.chdir) + : processCwd; } function determineLogLevel(options: CmdOptions): "verbose" | "notice" { diff --git a/test/services/parse-env.test.ts b/test/services/parse-env.test.ts index a7430c61..ca0078e3 100644 --- a/test/services/parse-env.test.ts +++ b/test/services/parse-env.test.ts @@ -1,21 +1,15 @@ import path from "path"; import { openupmRegistryUrl } from "../../src/domain/registry-url"; -import { GetCwd } from "../../src/io/special-paths"; import { makeParseEnv } from "../../src/services/parse-env"; import { makeMockLogger } from "../cli/log.mock"; import { exampleRegistryUrl } from "../domain/data-registry"; -import { mockService } from "./service.mock"; const testRootPath = "/users/some-user/projects/MyUnityProject"; function makeDependencies() { const log = makeMockLogger(); - // process.cwd is in the root directory. - const getCwd = mockService(); - getCwd.mockReturnValue(testRootPath); - - const parseEnv = makeParseEnv(log, getCwd); + const parseEnv = makeParseEnv(log, testRootPath); return { parseEnv, log } as const; } From b4b77356b97740b13bb53d9b4c866d35a8856231 Mon Sep 17 00:00:00 2001 From: Ramon Brullo Date: Fri, 16 Aug 2024 14:47:40 +0200 Subject: [PATCH 15/26] refactor: home path logic Make function for resolving home path pure, by injecting env variables into it directly. --- src/cli/index.ts | 17 ++++++++++++----- src/io/npmrc-io.ts | 8 ++++---- src/io/special-paths.ts | 25 +++++++++++-------------- src/io/upm-config-io.ts | 9 ++++----- src/services/get-registry-auth.ts | 11 ++++++----- src/services/login.ts | 13 +++++++------ src/services/put-npm-auth-token.ts | 7 ++----- test/io/npmrc-io.test.ts | 15 ++++++--------- test/io/special-paths.test.ts | 17 +++-------------- 9 files changed, 55 insertions(+), 67 deletions(-) diff --git a/src/cli/index.ts b/src/cli/index.ts index b631af5a..533b8f61 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -8,6 +8,7 @@ import { loadProjectManifest, saveProjectManifest, } from "../io/project-manifest-io"; +import { getHomePathFromEnv } from "../io/special-paths"; import { getUpmConfigPath } from "../io/upm-config-io"; import { npmDebugLog } from "../logging"; import { resolveDependencies } from "../services/dependency-resolving"; @@ -39,6 +40,7 @@ import { const log = npmlog; const parseEnv = makeParseEnv(log, process.cwd()); +const homePath = getHomePathFromEnv(process.env); const addCmd = makeAddCmd( parseEnv, @@ -47,15 +49,20 @@ const addCmd = makeAddCmd( loadProjectManifest, saveProjectManifest, determineEditorVersion, - getRegistryAuth, + getRegistryAuth(homePath), log, npmDebugLog ); -const loginCmd = makeLoginCmd(parseEnv, getUpmConfigPath, login, log); +const loginCmd = makeLoginCmd( + parseEnv, + getUpmConfigPath(homePath), + login(homePath), + log +); const searchCmd = makeSearchCmd( parseEnv, searchPackages, - getRegistryAuth, + getRegistryAuth(homePath), log, npmDebugLog ); @@ -63,7 +70,7 @@ const depsCmd = makeDepsCmd( parseEnv, resolveDependencies, getLatestVersion, - getRegistryAuth, + getRegistryAuth(homePath), log, npmDebugLog ); @@ -71,7 +78,7 @@ const removeCmd = makeRemoveCmd(parseEnv, removePackages, log); const viewCmd = makeViewCmd( parseEnv, getRegistryPackument, - getRegistryAuth, + getRegistryAuth(homePath), log ); diff --git a/src/io/npmrc-io.ts b/src/io/npmrc-io.ts index 079bd221..da3a875f 100644 --- a/src/io/npmrc-io.ts +++ b/src/io/npmrc-io.ts @@ -1,7 +1,6 @@ import { EOL } from "node:os"; import path from "path"; import { Npmrc } from "../domain/npmrc"; -import { GetHomePath, getHomePathFromEnv } from "./special-paths"; import { readTextFile, ReadTextFile, @@ -19,9 +18,9 @@ export type FindNpmrcPath = () => string; * Makes a {@link FindNpmrcPath} function which resolves the path to the * `.npmrc` file that is stored in the users home directory. */ -export function FindNpmrcInHome(getHomePath: GetHomePath): FindNpmrcPath { +export function FindNpmrcInHome(homePath: string): FindNpmrcPath { + // TODO: Unsevice! This function is pure and does not benefit from being a service style function. return () => { - const homePath = getHomePath(); return path.join(homePath, ".npmrc"); }; } @@ -29,7 +28,8 @@ export function FindNpmrcInHome(getHomePath: GetHomePath): FindNpmrcPath { /** * Default {@link FindNpmrcPath} function. Uses {@link FindNpmrcInHome}. */ -export const findNpmrcPath: FindNpmrcPath = FindNpmrcInHome(getHomePathFromEnv); +export const findNpmrcPath = (homePath: string): FindNpmrcPath => + FindNpmrcInHome(homePath); /** * Function for loading npmrc. diff --git a/src/io/special-paths.ts b/src/io/special-paths.ts index 8aa4f8f4..9d2598f6 100644 --- a/src/io/special-paths.ts +++ b/src/io/special-paths.ts @@ -9,7 +9,6 @@ import { ReleaseVersion, stringifyEditorVersion, } from "../domain/editor-version"; -import { tryGetEnv } from "../utils/env-util"; /** * Error for when a specific OS does not support a specific editor-version. @@ -43,24 +42,22 @@ export class OSNotSupportedError extends CustomError { } } -/** - * Function for getting the path of the users home directory. - * @returns The path to the directory. - */ -export type GetHomePath = () => string; - export class NoHomePathError extends CustomError {} /** - * {@link GetHomePath} function which resolved the home path using the - * `USERPROFILE` and `HOME` environment variables. - * @throws {NoHomePathError} If none of the required env variables are set. + * Attempts to resolve the users home path from environment variables, + * specifically the `USERPROFILE` and `HOME` vars. + * @param envVars The current environment variables. + * @returns The resolved path. + * @throws {NoHomePathError} If none of the required env vars were set. */ -export const getHomePathFromEnv: GetHomePath = () => { - const homePath = tryGetEnv("USERPROFILE") ?? tryGetEnv("HOME"); - if (homePath === null) throw new NoHomePathError(); +export function getHomePathFromEnv( + envVars: Record +) { + const homePath = envVars["USERPROFILE"] ?? envVars["HOME"]; + if (homePath === undefined) throw new NoHomePathError(); return homePath; -}; +} /** * Errors which may occur when trying to resolve an editor-version. diff --git a/src/io/upm-config-io.ts b/src/io/upm-config-io.ts index bb4e62dd..54eec4bd 100644 --- a/src/io/upm-config-io.ts +++ b/src/io/upm-config-io.ts @@ -6,7 +6,6 @@ import { removeExplicitUndefined, RemoveExplicitUndefined, } from "../utils/zod-utils"; -import { GetHomePath, getHomePathFromEnv } from "./special-paths"; import { readTextFile, ReadTextFile, @@ -27,17 +26,17 @@ export type GetUpmConfigPath = (systemUser: boolean) => string; * @see https://docs.unity3d.com/Manual/upm-config.html#upmconfig */ export function ResolveDefaultUpmConfigPath( - getHomePath: GetHomePath + homePath: string ): GetUpmConfigPath { return (systemUser) => - getUserUpmConfigPathFor(process.env, getHomePath(), systemUser); + getUserUpmConfigPathFor(process.env, homePath, systemUser); } /** * Default {@link GetUpmConfigPath} function. Uses {@link ResolveDefaultUpmConfigPath}. */ -export const getUpmConfigPath: GetUpmConfigPath = - ResolveDefaultUpmConfigPath(getHomePathFromEnv); +export const getUpmConfigPath = (homePath: string): GetUpmConfigPath => + ResolveDefaultUpmConfigPath(homePath); const authBaseSchema = z.object({ alwaysAuth: z.optional(z.boolean()), diff --git a/src/services/get-registry-auth.ts b/src/services/get-registry-auth.ts index 575d3ff6..7aa55f30 100644 --- a/src/services/get-registry-auth.ts +++ b/src/services/get-registry-auth.ts @@ -94,8 +94,9 @@ export function LoadRegistryAuthFromUpmConfig( /** * Default {@link GetRegistryAuth} function. Uses {@link LoadRegistryAuthFromUpmConfig}. */ -export const getRegistryAuth = LoadRegistryAuthFromUpmConfig( - getUpmConfigPath, - loadUpmConfig, - npmDebugLog -); +export const getRegistryAuth = (homePath: string) => + LoadRegistryAuthFromUpmConfig( + getUpmConfigPath(homePath), + loadUpmConfig, + npmDebugLog + ); diff --git a/src/services/login.ts b/src/services/login.ts index e54a28bd..a9d10c68 100644 --- a/src/services/login.ts +++ b/src/services/login.ts @@ -66,9 +66,10 @@ export function UpmConfigLogin( /** * Default {@link Login} function. Uses {@link UpmConfigLogin}. */ -export const login = UpmConfigLogin( - putRegistryAuth, - getAuthToken, - putNpmAuthToken, - npmDebugLog -); +export const login = (homePath: string) => + UpmConfigLogin( + putRegistryAuth, + getAuthToken, + putNpmAuthToken(homePath), + npmDebugLog + ); diff --git a/src/services/put-npm-auth-token.ts b/src/services/put-npm-auth-token.ts index dc65472f..77ae4d49 100644 --- a/src/services/put-npm-auth-token.ts +++ b/src/services/put-npm-auth-token.ts @@ -41,8 +41,5 @@ export function StoreNpmAuthTokenInNpmrc( /** * Default {@link StoreNpmAuthToken} function. Uses {@link StoreNpmAuthTokenInNpmrc}. */ -export const putNpmAuthToken = StoreNpmAuthTokenInNpmrc( - findNpmrcPath, - loadNpmrc, - saveNpmrc -); +export const putNpmAuthToken = (homePath: string) => + StoreNpmAuthTokenInNpmrc(findNpmrcPath(homePath), loadNpmrc, saveNpmrc); diff --git a/test/io/npmrc-io.test.ts b/test/io/npmrc-io.test.ts index dbe58279..ff786d8a 100644 --- a/test/io/npmrc-io.test.ts +++ b/test/io/npmrc-io.test.ts @@ -5,25 +5,22 @@ import { ReadNpmrcFile, WriteNpmrcPath as WriteNpmrcFile, } from "../../src/io/npmrc-io"; -import { GetHomePath } from "../../src/io/special-paths"; import { ReadTextFile, WriteTextFile } from "../../src/io/text-file-io"; import { mockService } from "../services/service.mock"; describe("npmrc-io", () => { describe("find path in home", () => { - function makeDependencies() { - const getHomePath = mockService(); + const someHomePath = path.join(path.sep, "user", "dir"); - const findNpmrcInHome = FindNpmrcInHome(getHomePath); + function makeDependencies() { + const findNpmrcInHome = FindNpmrcInHome(someHomePath); - return { findNpmrcInHome, getHomePath } as const; + return { findNpmrcInHome } as const; } it("should be [Home]/.npmrc", () => { - const { findNpmrcInHome, getHomePath } = makeDependencies(); - const home = path.join(path.sep, "user", "dir"); - const expected = path.join(home, ".npmrc"); - getHomePath.mockReturnValue(home); + const { findNpmrcInHome } = makeDependencies(); + const expected = path.join(someHomePath, ".npmrc"); const actual = findNpmrcInHome(); diff --git a/test/io/special-paths.test.ts b/test/io/special-paths.test.ts index ee3149ad..cffc990e 100644 --- a/test/io/special-paths.test.ts +++ b/test/io/special-paths.test.ts @@ -9,38 +9,27 @@ import { tryGetEditorInstallPath, VersionNotSupportedOnOsError, } from "../../src/io/special-paths"; -import { tryGetEnv } from "../../src/utils/env-util"; - -jest.mock("../../src/utils/env-util"); describe("special-paths", () => { describe("home from env", () => { it("should be USERPROFILE if defined", () => { const expected = path.join(path.sep, "user", "dir"); - jest - .mocked(tryGetEnv) - .mockImplementation((key) => (key === "USERPROFILE" ? expected : null)); - const actual = getHomePathFromEnv(); + const actual = getHomePathFromEnv({ USERPROFILE: expected }); expect(actual).toEqual(expected); }); it("should be HOME if USERPROFILE is not defined", () => { const expected = path.join(path.sep, "user", "dir"); - jest - .mocked(tryGetEnv) - .mockImplementation((key) => (key === "HOME" ? expected : null)); - const actual = getHomePathFromEnv(); + const actual = getHomePathFromEnv({ HOME: expected }); expect(actual).toEqual(expected); }); it("should fail if HOME and USERPROFILE are not defined", () => { - jest.mocked(tryGetEnv).mockReturnValue(null); - - expect(() => getHomePathFromEnv()).toThrow(NoHomePathError); + expect(() => getHomePathFromEnv({})).toThrow(NoHomePathError); }); }); From 5362ff72ec1e8f2cd903684ca22cb6fa7ce7f7bb Mon Sep 17 00:00:00 2001 From: Ramon Brullo Date: Wed, 21 Aug 2024 16:20:08 +0200 Subject: [PATCH 16/26] refactor: inject registry client Inject registry client instead of singleton. We do this because it is a dependency which depends on app context (because of what type of logging it should use) so it should be created in the app layer --- src/cli/index.ts | 39 ++++++++++--------- src/io/packument-io.ts | 7 +--- src/io/reg-client.ts | 4 +- src/services/built-in-package-check.ts | 10 +++-- src/services/dependency-resolving.ts | 10 +++-- src/services/get-auth-token.ts | 7 +--- src/services/get-latest-version.ts | 5 ++- .../get-registry-packument-version.ts | 5 ++- src/services/login.ts | 6 +-- src/types/another-npm-registry-client.d.ts | 4 +- 10 files changed, 52 insertions(+), 45 deletions(-) diff --git a/src/cli/index.ts b/src/cli/index.ts index 533b8f61..44668ba8 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -3,20 +3,21 @@ import npmlog from "npmlog"; import pkginfo from "pkginfo"; import updateNotifier from "update-notifier"; import pkg from "../../package.json"; -import { getRegistryPackument } from "../io/packument-io"; +import { getRegistryPackument as getRegistryPackumentUsing } from "../io/packument-io"; import { loadProjectManifest, saveProjectManifest, } from "../io/project-manifest-io"; +import { makeNpmRegistryClient } from "../io/reg-client"; import { getHomePathFromEnv } from "../io/special-paths"; -import { getUpmConfigPath } from "../io/upm-config-io"; +import { getUpmConfigPath as getUpmConfigPathUsing } from "../io/upm-config-io"; import { npmDebugLog } from "../logging"; -import { resolveDependencies } from "../services/dependency-resolving"; +import { resolveDependencies as resolveDependenciesUsing } from "../services/dependency-resolving"; import { determineEditorVersion } from "../services/determine-editor-version"; -import { getLatestVersion } from "../services/get-latest-version"; -import { getRegistryAuth } from "../services/get-registry-auth"; -import { getRegistryPackumentVersion } from "../services/get-registry-packument-version"; -import { login } from "../services/login"; +import { getLatestVersion as getLatestVersionUsing } from "../services/get-latest-version"; +import { getRegistryAuth as getRegistryAuthUsing } from "../services/get-registry-auth"; +import { getRegistryPackumentVersion as getRegistryPackumentVersionUsing } from "../services/get-registry-packument-version"; +import { login as loginUsing } from "../services/login"; import { makeParseEnv } from "../services/parse-env"; import { removePackages } from "../services/remove-packages"; import { searchPackages } from "../services/search-packages"; @@ -42,43 +43,45 @@ const log = npmlog; const parseEnv = makeParseEnv(log, process.cwd()); const homePath = getHomePathFromEnv(process.env); +const registryClient = makeNpmRegistryClient(log); + const addCmd = makeAddCmd( parseEnv, - getRegistryPackumentVersion, - resolveDependencies, + getRegistryPackumentVersionUsing(registryClient), + resolveDependenciesUsing(registryClient), loadProjectManifest, saveProjectManifest, determineEditorVersion, - getRegistryAuth(homePath), + getRegistryAuthUsing(homePath), log, npmDebugLog ); const loginCmd = makeLoginCmd( parseEnv, - getUpmConfigPath(homePath), - login(homePath), + getUpmConfigPathUsing(homePath), + loginUsing(homePath, registryClient), log ); const searchCmd = makeSearchCmd( parseEnv, searchPackages, - getRegistryAuth(homePath), + getRegistryAuthUsing(homePath), log, npmDebugLog ); const depsCmd = makeDepsCmd( parseEnv, - resolveDependencies, - getLatestVersion, - getRegistryAuth(homePath), + resolveDependenciesUsing(registryClient), + getLatestVersionUsing(registryClient), + getRegistryAuthUsing(homePath), log, npmDebugLog ); const removeCmd = makeRemoveCmd(parseEnv, removePackages, log); const viewCmd = makeViewCmd( parseEnv, - getRegistryPackument, - getRegistryAuth(homePath), + getRegistryPackumentUsing(registryClient), + getRegistryAuthUsing(homePath), log ); diff --git a/src/io/packument-io.ts b/src/io/packument-io.ts index a993dfe2..5b701c4e 100644 --- a/src/io/packument-io.ts +++ b/src/io/packument-io.ts @@ -5,7 +5,6 @@ import { Registry } from "../domain/registry"; import { DebugLog, npmDebugLog } from "../logging"; import { assertIsHttpError } from "../utils/error-type-guards"; import { makeRegistryInteractionError } from "./common-errors"; -import { npmRegistryClient } from "./reg-client"; /** * Function for getting a packument from a registry. @@ -48,7 +47,5 @@ export function FetchRegistryPackument( /** * Default {@link GetRegistryPackument} function. Uses {@link FetchRegistryPackument}. */ -export const getRegistryPackument = FetchRegistryPackument( - npmRegistryClient, - npmDebugLog -); +export const getRegistryPackument = (registryClient: RegClient.Instance) => + FetchRegistryPackument(registryClient, npmDebugLog); diff --git a/src/io/reg-client.ts b/src/io/reg-client.ts index 92e26d01..5d143626 100644 --- a/src/io/reg-client.ts +++ b/src/io/reg-client.ts @@ -1,6 +1,8 @@ import RegClient from "another-npm-registry-client"; +import { Logger } from "npmlog"; /** * Client for communicating with the npm registry. */ -export const npmRegistryClient = new RegClient(); +export const makeNpmRegistryClient = (debugLogger: Logger) => + new RegClient({ log: debugLogger }); diff --git a/src/services/built-in-package-check.ts b/src/services/built-in-package-check.ts index 237ed656..eb0c8da5 100644 --- a/src/services/built-in-package-check.ts +++ b/src/services/built-in-package-check.ts @@ -1,3 +1,4 @@ +import RegClient from "another-npm-registry-client"; import { DomainName } from "../domain/domain-name"; import { unityRegistryUrl } from "../domain/registry-url"; import { SemanticVersion } from "../domain/semantic-version"; @@ -51,7 +52,8 @@ export function CheckIsNonRegistryUnityPackage( /** * Default {@link CheckIsBuiltInPackage}. Uses {@link CheckIsNonRegistryUnityPackage}. */ -export const checkIsBuiltInPackage = CheckIsNonRegistryUnityPackage( - checkIsUnityPackage, - getRegistryPackument -); +export const checkIsBuiltInPackage = (registryClient: RegClient.Instance) => + CheckIsNonRegistryUnityPackage( + checkIsUnityPackage, + getRegistryPackument(registryClient) + ); diff --git a/src/services/dependency-resolving.ts b/src/services/dependency-resolving.ts index 58f66595..6b10c1df 100644 --- a/src/services/dependency-resolving.ts +++ b/src/services/dependency-resolving.ts @@ -1,3 +1,4 @@ +import RegClient from "another-npm-registry-client"; import { PackumentNotFoundError } from "../common-errors"; import { DependencyGraph, @@ -100,7 +101,8 @@ export function ResolveDependenciesFromRegistries( /** * Default {@link ResolveDependencies} function. Uses {@link ResolveDependenciesFromRegistries}. */ -export const resolveDependencies = ResolveDependenciesFromRegistries( - getRegistryPackument, - checkIsBuiltInPackage -); +export const resolveDependencies = (registryClient: RegClient.Instance) => + ResolveDependenciesFromRegistries( + getRegistryPackument(registryClient), + checkIsBuiltInPackage(registryClient) + ); diff --git a/src/services/get-auth-token.ts b/src/services/get-auth-token.ts index ca0bbf11..76d6a9ab 100644 --- a/src/services/get-auth-token.ts +++ b/src/services/get-auth-token.ts @@ -1,7 +1,6 @@ import RegClient from "another-npm-registry-client"; import { RegistryUrl } from "../domain/registry-url"; import { RegistryAuthenticationError } from "../io/common-errors"; -import { npmRegistryClient } from "../io/reg-client"; import { DebugLog, npmDebugLog } from "../logging"; /** @@ -54,7 +53,5 @@ export function AuthenticateWithNpmRegistry( /** * Default {@link GetAuthToken}. Uses {@link AuthenticateWithNpmRegistry}. */ -export const getAuthToken = AuthenticateWithNpmRegistry( - npmRegistryClient, - npmDebugLog -); +export const getAuthToken = (registryClient: RegClient.Instance) => + AuthenticateWithNpmRegistry(registryClient, npmDebugLog); diff --git a/src/services/get-latest-version.ts b/src/services/get-latest-version.ts index 038e8a31..cff880e4 100644 --- a/src/services/get-latest-version.ts +++ b/src/services/get-latest-version.ts @@ -1,3 +1,4 @@ +import RegClient from "another-npm-registry-client"; import { DomainName } from "../domain/domain-name"; import { tryResolvePackumentVersion } from "../domain/packument"; import { Registry } from "../domain/registry"; @@ -34,5 +35,5 @@ export function GetLatestVersionFromRegistryPackument( /** * Default {@link GetLatestVersion}. Uses {@link GetLatestVersionFromRegistryPackument}. */ -export const getLatestVersion = - GetLatestVersionFromRegistryPackument(getRegistryPackument); +export const getLatestVersion = (registryClient: RegClient.Instance) => + GetLatestVersionFromRegistryPackument(getRegistryPackument(registryClient)); diff --git a/src/services/get-registry-packument-version.ts b/src/services/get-registry-packument-version.ts index df8ec50a..ea1e1e6d 100644 --- a/src/services/get-registry-packument-version.ts +++ b/src/services/get-registry-packument-version.ts @@ -1,3 +1,4 @@ +import RegClient from "another-npm-registry-client"; import { AsyncResult, Err } from "ts-results-es"; import { PackumentNotFoundError } from "../common-errors"; import { DomainName } from "../domain/domain-name"; @@ -69,5 +70,5 @@ export function FetchRegistryPackumentVersion( /** * Default {@link GetRegistryPackumentVersion} function. Uses {@link FetchRegistryPackumentVersion}. */ -export const getRegistryPackumentVersion = - FetchRegistryPackumentVersion(getRegistryPackument); +export const getRegistryPackumentVersion = (regClient: RegClient.Instance) => + FetchRegistryPackumentVersion(getRegistryPackument(regClient)); diff --git a/src/services/login.ts b/src/services/login.ts index a9d10c68..41c12407 100644 --- a/src/services/login.ts +++ b/src/services/login.ts @@ -1,4 +1,4 @@ -import { NpmAuth } from "another-npm-registry-client"; +import RegClient, { NpmAuth } from "another-npm-registry-client"; import { RegistryUrl } from "../domain/registry-url"; import { DebugLog, npmDebugLog } from "../logging"; import { getAuthToken, GetAuthToken } from "./get-auth-token"; @@ -66,10 +66,10 @@ export function UpmConfigLogin( /** * Default {@link Login} function. Uses {@link UpmConfigLogin}. */ -export const login = (homePath: string) => +export const login = (homePath: string, registryClient: RegClient.Instance) => UpmConfigLogin( putRegistryAuth, - getAuthToken, + getAuthToken(registryClient), putNpmAuthToken(homePath), npmDebugLog ); diff --git a/src/types/another-npm-registry-client.d.ts b/src/types/another-npm-registry-client.d.ts index 9441fa2e..f8822993 100644 --- a/src/types/another-npm-registry-client.d.ts +++ b/src/types/another-npm-registry-client.d.ts @@ -1,3 +1,5 @@ +import { Logger } from "npmlog"; + declare module "another-npm-registry-client" { import { type Response } from "request"; @@ -44,6 +46,6 @@ declare module "another-npm-registry-client" { } declare module "another-npm-registry-client" { - const RegClient: new (this: Instance, config?: { log: unknown }) => Instance; + const RegClient: new (this: Instance, config?: { log?: Logger }) => Instance; export = RegClient; } From 308253f59a6de4f99964f28e9815fb58c5acbd55 Mon Sep 17 00:00:00 2001 From: Ramon Brullo Date: Wed, 21 Aug 2024 16:29:33 +0200 Subject: [PATCH 17/26] refactor: inject debug log Inject debug log from app layer since this is something that is dependent on app context and should be defined in the top layer --- src/cli/index.ts | 70 ++++++++++++------- src/io/all-packuments-io.ts | 9 +-- src/io/npm-search.ts | 9 +-- src/io/packument-io.ts | 10 +-- src/io/project-manifest-io.ts | 8 +-- src/io/project-version-io.ts | 12 ++-- src/logging.ts | 18 ----- src/services/built-in-package-check.ts | 13 +++- src/services/dependency-resolving.ts | 15 ++-- src/services/determine-editor-version.ts | 10 ++- src/services/get-auth-token.ts | 8 ++- src/services/get-latest-version.ts | 15 +++- src/services/get-registry-auth.ts | 6 +- .../get-registry-packument-version.ts | 13 +++- src/services/login.ts | 12 ++-- src/services/remove-packages.ts | 12 ++-- src/services/search-packages.ts | 17 ++--- test/io/all-packuments-io.test.ts | 4 +- test/io/npm-search.test.ts | 4 +- test/io/packument-io.test.ts | 4 +- 20 files changed, 147 insertions(+), 122 deletions(-) diff --git a/src/cli/index.ts b/src/cli/index.ts index 44668ba8..7733e47b 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -3,24 +3,24 @@ import npmlog from "npmlog"; import pkginfo from "pkginfo"; import updateNotifier from "update-notifier"; import pkg from "../../package.json"; -import { getRegistryPackument as getRegistryPackumentUsing } from "../io/packument-io"; +import { getRegistryPackumentUsing } from "../io/packument-io"; import { - loadProjectManifest, + loadProjectManifestUsing, saveProjectManifest, } from "../io/project-manifest-io"; import { makeNpmRegistryClient } from "../io/reg-client"; import { getHomePathFromEnv } from "../io/special-paths"; import { getUpmConfigPath as getUpmConfigPathUsing } from "../io/upm-config-io"; -import { npmDebugLog } from "../logging"; +import { DebugLog } from "../logging"; import { resolveDependencies as resolveDependenciesUsing } from "../services/dependency-resolving"; -import { determineEditorVersion } from "../services/determine-editor-version"; +import { determineEditorVersionUsing } from "../services/determine-editor-version"; import { getLatestVersion as getLatestVersionUsing } from "../services/get-latest-version"; -import { getRegistryAuth as getRegistryAuthUsing } from "../services/get-registry-auth"; -import { getRegistryPackumentVersion as getRegistryPackumentVersionUsing } from "../services/get-registry-packument-version"; +import { getRegistryAuthUsing } from "../services/get-registry-auth"; +import { getRegistryPackumentVersionUsing } from "../services/get-registry-packument-version"; import { login as loginUsing } from "../services/login"; import { makeParseEnv } from "../services/parse-env"; -import { removePackages } from "../services/remove-packages"; -import { searchPackages } from "../services/search-packages"; +import { removePackages as removePackagesUsing } from "../services/remove-packages"; +import { searchPackagesUsing } from "../services/search-packages"; import { eachValue } from "./cli-parsing"; import { makeAddCmd } from "./cmd-add"; import { makeDepsCmd } from "./cmd-deps"; @@ -40,6 +40,22 @@ import { const log = npmlog; +/** + * {@link DebugLog} function which uses {@link npmlog} to print logs to + * the console. + */ +const debugLogToConsole: DebugLog = function (message, context) { + const contextMessage = + context !== undefined + ? `\n${ + context instanceof Error + ? context.toString() + : JSON.stringify(context, null, 2) + }` + : ""; + return log.verbose("", `${message}${contextMessage}`); +}; + const parseEnv = makeParseEnv(log, process.cwd()); const homePath = getHomePathFromEnv(process.env); @@ -47,41 +63,45 @@ const registryClient = makeNpmRegistryClient(log); const addCmd = makeAddCmd( parseEnv, - getRegistryPackumentVersionUsing(registryClient), - resolveDependenciesUsing(registryClient), - loadProjectManifest, + getRegistryPackumentVersionUsing(registryClient, debugLogToConsole), + resolveDependenciesUsing(registryClient, debugLogToConsole), + loadProjectManifestUsing(debugLogToConsole), saveProjectManifest, - determineEditorVersion, - getRegistryAuthUsing(homePath), + determineEditorVersionUsing(debugLogToConsole), + getRegistryAuthUsing(homePath, debugLogToConsole), log, - npmDebugLog + debugLogToConsole ); const loginCmd = makeLoginCmd( parseEnv, getUpmConfigPathUsing(homePath), - loginUsing(homePath, registryClient), + loginUsing(homePath, registryClient, debugLogToConsole), log ); const searchCmd = makeSearchCmd( parseEnv, - searchPackages, - getRegistryAuthUsing(homePath), + searchPackagesUsing(debugLogToConsole), + getRegistryAuthUsing(homePath, debugLogToConsole), log, - npmDebugLog + debugLogToConsole ); const depsCmd = makeDepsCmd( parseEnv, - resolveDependenciesUsing(registryClient), - getLatestVersionUsing(registryClient), - getRegistryAuthUsing(homePath), + resolveDependenciesUsing(registryClient, debugLogToConsole), + getLatestVersionUsing(registryClient, debugLogToConsole), + getRegistryAuthUsing(homePath, debugLogToConsole), log, - npmDebugLog + debugLogToConsole +); +const removeCmd = makeRemoveCmd( + parseEnv, + removePackagesUsing(debugLogToConsole), + log ); -const removeCmd = makeRemoveCmd(parseEnv, removePackages, log); const viewCmd = makeViewCmd( parseEnv, - getRegistryPackumentUsing(registryClient), - getRegistryAuthUsing(homePath), + getRegistryPackumentUsing(registryClient, debugLogToConsole), + getRegistryAuthUsing(homePath, debugLogToConsole), log ); diff --git a/src/io/all-packuments-io.ts b/src/io/all-packuments-io.ts index 0a2ffa83..08036b79 100644 --- a/src/io/all-packuments-io.ts +++ b/src/io/all-packuments-io.ts @@ -1,7 +1,7 @@ import npmFetch from "npm-registry-fetch"; import { DomainName } from "../domain/domain-name"; import { Registry } from "../domain/registry"; -import { DebugLog, npmDebugLog } from "../logging"; +import { DebugLog } from "../logging"; import { assertIsError } from "../utils/error-type-guards"; import { makeRegistryInteractionError } from "./common-errors"; import { makeNpmFetchOptions } from "./npm-registry"; @@ -27,7 +27,7 @@ export type GetAllRegistryPackuments = ( /** * Makes a {@link GetAllRegistryPackuments} function. */ -export function FetchAllRegistryPackuments( +export function getAllRegistryPackumentsUsing( debugLog: DebugLog ): GetAllRegistryPackuments { return async (registry) => { @@ -46,8 +46,3 @@ export function FetchAllRegistryPackuments( } }; } - -/** - * Default {@link GetAllRegistryPackuments} function. Uses {@link FetchAllRegistryPackuments}. - */ -export const getAllRegistryPackuments = FetchAllRegistryPackuments(npmDebugLog); diff --git a/src/io/npm-search.ts b/src/io/npm-search.ts index e54370de..8d0ab1aa 100644 --- a/src/io/npm-search.ts +++ b/src/io/npm-search.ts @@ -2,7 +2,7 @@ import npmSearch from "libnpmsearch"; import { UnityPackument } from "../domain/packument"; import { Registry } from "../domain/registry"; import { SemanticVersion } from "../domain/semantic-version"; -import { DebugLog, npmDebugLog } from "../logging"; +import { DebugLog } from "../logging"; import { assertIsError } from "../utils/error-type-guards"; import { makeRegistryInteractionError } from "./common-errors"; import { makeNpmFetchOptions } from "./npm-registry"; @@ -32,7 +32,7 @@ export type SearchRegistry = ( * Makes a {@link SearchRegistry} function which uses the npm search api to * find packages in a remote registry. */ -export function NpmApiSearch(debugLog: DebugLog): SearchRegistry { +export function searchRegistryUsing(debugLog: DebugLog): SearchRegistry { return (registry, keyword) => npmSearch(keyword, makeNpmFetchOptions(registry)) // NOTE: The results of the search will be packument objects, so we can change the type @@ -43,8 +43,3 @@ export function NpmApiSearch(debugLog: DebugLog): SearchRegistry { throw makeRegistryInteractionError(error, registry.url); }); } - -/** - * Default {@link SearchRegistry} function. Uses {@link NpmApiSearch}. - */ -export const searchRegistry = NpmApiSearch(npmDebugLog); diff --git a/src/io/packument-io.ts b/src/io/packument-io.ts index 5b701c4e..4d235902 100644 --- a/src/io/packument-io.ts +++ b/src/io/packument-io.ts @@ -2,7 +2,7 @@ import RegClient from "another-npm-registry-client"; import { DomainName } from "../domain/domain-name"; import { UnityPackument } from "../domain/packument"; import { Registry } from "../domain/registry"; -import { DebugLog, npmDebugLog } from "../logging"; +import { DebugLog } from "../logging"; import { assertIsHttpError } from "../utils/error-type-guards"; import { makeRegistryInteractionError } from "./common-errors"; @@ -21,7 +21,7 @@ export type GetRegistryPackument = ( * Makes a {@link GetRegistryPackument} function which fetches the packument * from a remote npm registry. */ -export function FetchRegistryPackument( +export function getRegistryPackumentUsing( registryClient: RegClient.Instance, debugLog: DebugLog ): GetRegistryPackument { @@ -43,9 +43,3 @@ export function FetchRegistryPackument( }); }; } - -/** - * Default {@link GetRegistryPackument} function. Uses {@link FetchRegistryPackument}. - */ -export const getRegistryPackument = (registryClient: RegClient.Instance) => - FetchRegistryPackument(registryClient, npmDebugLog); diff --git a/src/io/project-manifest-io.ts b/src/io/project-manifest-io.ts index 354072d6..9c48afcb 100644 --- a/src/io/project-manifest-io.ts +++ b/src/io/project-manifest-io.ts @@ -3,7 +3,7 @@ import path from "path"; import { CustomError } from "ts-custom-error"; import { z } from "zod"; import { UnityProjectManifest } from "../domain/project-manifest"; -import { DebugLog, npmDebugLog } from "../logging"; +import { DebugLog } from "../logging"; import { assertIsError } from "../utils/error-type-guards"; import { isZod } from "../utils/zod-utils"; import { @@ -74,10 +74,8 @@ export function ReadProjectManifestFile( /** * Default {@link LoadProjectManifest} function. Uses {@link ReadProjectManifestFile}. */ -export const loadProjectManifest: LoadProjectManifest = ReadProjectManifestFile( - readTextFile, - npmDebugLog -); +export const loadProjectManifestUsing = (debugLog: DebugLog) => + ReadProjectManifestFile(readTextFile, debugLog); /** * Function for replacing the project manifest for a Unity project. diff --git a/src/io/project-version-io.ts b/src/io/project-version-io.ts index 452d9a6b..0e08940b 100644 --- a/src/io/project-version-io.ts +++ b/src/io/project-version-io.ts @@ -2,11 +2,11 @@ import { AnyJson } from "@iarna/toml"; import path from "path"; import { CustomError } from "ts-custom-error"; import * as YAML from "yaml"; -import { DebugLog, npmDebugLog } from "../logging"; -import { assertIsError } from "../utils/error-type-guards"; -import { readTextFile, ReadTextFile } from "./text-file-io"; import { z } from "zod"; +import { DebugLog } from "../logging"; +import { assertIsError } from "../utils/error-type-guards"; import { isZod } from "../utils/zod-utils"; +import { readTextFile, ReadTextFile } from "./text-file-io"; export class ProjectVersionMissingError extends CustomError { public constructor(public readonly expectedPath: string) { @@ -68,7 +68,5 @@ export function ReadProjectVersionFile( /** * Default {@link GetProjectVersion} function. Uses {@link ReadProjectVersionFile}. */ -export const getProjectVersion = ReadProjectVersionFile( - readTextFile, - npmDebugLog -); +export const getProjectVersionUsing = (debugLog: DebugLog) => + ReadProjectVersionFile(readTextFile, debugLog); diff --git a/src/logging.ts b/src/logging.ts index 7956f107..d15cfc06 100644 --- a/src/logging.ts +++ b/src/logging.ts @@ -1,5 +1,3 @@ -import npmlog from "npmlog"; - /** * Function that logs a message to some external system. This interface is * output-agnostic meaning that the logs could go to a console, file or other. @@ -16,19 +14,3 @@ export type DebugLog = ( ) => void | Promise; export const noopLogger: DebugLog = () => {}; - -/** - * {@link DebugLog} function which uses {@link npmlog} to print logs to - * the console. - */ -export const npmDebugLog: DebugLog = function (message, context) { - const contextMessage = - context !== undefined - ? `\n${ - context instanceof Error - ? context.toString() - : JSON.stringify(context, null, 2) - }` - : ""; - return npmlog.verbose("", `${message}${contextMessage}`); -}; diff --git a/src/services/built-in-package-check.ts b/src/services/built-in-package-check.ts index eb0c8da5..b838eae8 100644 --- a/src/services/built-in-package-check.ts +++ b/src/services/built-in-package-check.ts @@ -2,7 +2,11 @@ import RegClient from "another-npm-registry-client"; import { DomainName } from "../domain/domain-name"; import { unityRegistryUrl } from "../domain/registry-url"; import { SemanticVersion } from "../domain/semantic-version"; -import { getRegistryPackument, GetRegistryPackument } from "../io/packument-io"; +import { + GetRegistryPackument, + getRegistryPackumentUsing, +} from "../io/packument-io"; +import { DebugLog } from "../logging"; import { recordKeys } from "../utils/record-utils"; import { checkIsUnityPackage, @@ -52,8 +56,11 @@ export function CheckIsNonRegistryUnityPackage( /** * Default {@link CheckIsBuiltInPackage}. Uses {@link CheckIsNonRegistryUnityPackage}. */ -export const checkIsBuiltInPackage = (registryClient: RegClient.Instance) => +export const checkIsBuiltInPackage = ( + registryClient: RegClient.Instance, + debugLog: DebugLog +) => CheckIsNonRegistryUnityPackage( checkIsUnityPackage, - getRegistryPackument(registryClient) + getRegistryPackumentUsing(registryClient, debugLog) ); diff --git a/src/services/dependency-resolving.ts b/src/services/dependency-resolving.ts index 6b10c1df..be8e20f9 100644 --- a/src/services/dependency-resolving.ts +++ b/src/services/dependency-resolving.ts @@ -16,7 +16,11 @@ import { import { Registry } from "../domain/registry"; import { RegistryUrl } from "../domain/registry-url"; import { SemanticVersion } from "../domain/semantic-version"; -import { getRegistryPackument, GetRegistryPackument } from "../io/packument-io"; +import { + GetRegistryPackument, + getRegistryPackumentUsing, +} from "../io/packument-io"; +import { DebugLog } from "../logging"; import { checkIsBuiltInPackage, CheckIsBuiltInPackage, @@ -101,8 +105,11 @@ export function ResolveDependenciesFromRegistries( /** * Default {@link ResolveDependencies} function. Uses {@link ResolveDependenciesFromRegistries}. */ -export const resolveDependencies = (registryClient: RegClient.Instance) => +export const resolveDependencies = ( + registryClient: RegClient.Instance, + debugLog: DebugLog +) => ResolveDependenciesFromRegistries( - getRegistryPackument(registryClient), - checkIsBuiltInPackage(registryClient) + getRegistryPackumentUsing(registryClient, debugLog), + checkIsBuiltInPackage(registryClient, debugLog) ); diff --git a/src/services/determine-editor-version.ts b/src/services/determine-editor-version.ts index 907e3139..140a3c97 100644 --- a/src/services/determine-editor-version.ts +++ b/src/services/determine-editor-version.ts @@ -3,7 +3,11 @@ import { ReleaseVersion, tryParseEditorVersion, } from "../domain/editor-version"; -import { getProjectVersion, GetProjectVersion } from "../io/project-version-io"; +import { + GetProjectVersion, + getProjectVersionUsing, +} from "../io/project-version-io"; +import { DebugLog } from "../logging"; /** * Function for determining the editor-version for a Unity project. @@ -34,5 +38,5 @@ export function DetermineEditorVersionFromFile( /** * Default {@link DetermineEditorVersion} function. Uses {@link DetermineEditorVersionFromFile}. */ -export const determineEditorVersion = - DetermineEditorVersionFromFile(getProjectVersion); +export const determineEditorVersionUsing = (debugLog: DebugLog) => + DetermineEditorVersionFromFile(getProjectVersionUsing(debugLog)); diff --git a/src/services/get-auth-token.ts b/src/services/get-auth-token.ts index 76d6a9ab..05d315b9 100644 --- a/src/services/get-auth-token.ts +++ b/src/services/get-auth-token.ts @@ -1,7 +1,7 @@ import RegClient from "another-npm-registry-client"; import { RegistryUrl } from "../domain/registry-url"; import { RegistryAuthenticationError } from "../io/common-errors"; -import { DebugLog, npmDebugLog } from "../logging"; +import { DebugLog } from "../logging"; /** * A token authenticating a user. @@ -53,5 +53,7 @@ export function AuthenticateWithNpmRegistry( /** * Default {@link GetAuthToken}. Uses {@link AuthenticateWithNpmRegistry}. */ -export const getAuthToken = (registryClient: RegClient.Instance) => - AuthenticateWithNpmRegistry(registryClient, npmDebugLog); +export const getAuthToken = ( + registryClient: RegClient.Instance, + debugLog: DebugLog +) => AuthenticateWithNpmRegistry(registryClient, debugLog); diff --git a/src/services/get-latest-version.ts b/src/services/get-latest-version.ts index cff880e4..1b8c231e 100644 --- a/src/services/get-latest-version.ts +++ b/src/services/get-latest-version.ts @@ -3,7 +3,11 @@ import { DomainName } from "../domain/domain-name"; import { tryResolvePackumentVersion } from "../domain/packument"; import { Registry } from "../domain/registry"; import { SemanticVersion } from "../domain/semantic-version"; -import { getRegistryPackument, GetRegistryPackument } from "../io/packument-io"; +import { + GetRegistryPackument, + getRegistryPackumentUsing, +} from "../io/packument-io"; +import { DebugLog } from "../logging"; /** * Gets the latest published version of a package from a npm registry. @@ -35,5 +39,10 @@ export function GetLatestVersionFromRegistryPackument( /** * Default {@link GetLatestVersion}. Uses {@link GetLatestVersionFromRegistryPackument}. */ -export const getLatestVersion = (registryClient: RegClient.Instance) => - GetLatestVersionFromRegistryPackument(getRegistryPackument(registryClient)); +export const getLatestVersion = ( + registryClient: RegClient.Instance, + debugLog: DebugLog +) => + GetLatestVersionFromRegistryPackument( + getRegistryPackumentUsing(registryClient, debugLog) + ); diff --git a/src/services/get-registry-auth.ts b/src/services/get-registry-auth.ts index 7aa55f30..252c9af6 100644 --- a/src/services/get-registry-auth.ts +++ b/src/services/get-registry-auth.ts @@ -14,7 +14,7 @@ import { UpmAuth, UpmConfigContent, } from "../io/upm-config-io"; -import { DebugLog, npmDebugLog } from "../logging"; +import { DebugLog } from "../logging"; import { trySplitAtFirstOccurrenceOf } from "../utils/string-utils"; import { removeExplicitUndefined } from "../utils/zod-utils"; @@ -94,9 +94,9 @@ export function LoadRegistryAuthFromUpmConfig( /** * Default {@link GetRegistryAuth} function. Uses {@link LoadRegistryAuthFromUpmConfig}. */ -export const getRegistryAuth = (homePath: string) => +export const getRegistryAuthUsing = (homePath: string, debugLog: DebugLog) => LoadRegistryAuthFromUpmConfig( getUpmConfigPath(homePath), loadUpmConfig, - npmDebugLog + debugLog ); diff --git a/src/services/get-registry-packument-version.ts b/src/services/get-registry-packument-version.ts index ea1e1e6d..2fe68bfe 100644 --- a/src/services/get-registry-packument-version.ts +++ b/src/services/get-registry-packument-version.ts @@ -11,7 +11,11 @@ import { } from "../domain/packument"; import { Registry } from "../domain/registry"; import { RegistryUrl } from "../domain/registry-url"; -import { getRegistryPackument, GetRegistryPackument } from "../io/packument-io"; +import { + GetRegistryPackument, + getRegistryPackumentUsing, +} from "../io/packument-io"; +import { DebugLog } from "../logging"; import { resultifyAsyncOp } from "../utils/result-utils"; /** @@ -70,5 +74,8 @@ export function FetchRegistryPackumentVersion( /** * Default {@link GetRegistryPackumentVersion} function. Uses {@link FetchRegistryPackumentVersion}. */ -export const getRegistryPackumentVersion = (regClient: RegClient.Instance) => - FetchRegistryPackumentVersion(getRegistryPackument(regClient)); +export const getRegistryPackumentVersionUsing = ( + regClient: RegClient.Instance, + debugLog: DebugLog +) => + FetchRegistryPackumentVersion(getRegistryPackumentUsing(regClient, debugLog)); diff --git a/src/services/login.ts b/src/services/login.ts index 41c12407..5757aedf 100644 --- a/src/services/login.ts +++ b/src/services/login.ts @@ -1,6 +1,6 @@ import RegClient, { NpmAuth } from "another-npm-registry-client"; import { RegistryUrl } from "../domain/registry-url"; -import { DebugLog, npmDebugLog } from "../logging"; +import { DebugLog } from "../logging"; import { getAuthToken, GetAuthToken } from "./get-auth-token"; import { putNpmAuthToken, StoreNpmAuthToken } from "./put-npm-auth-token"; import { putRegistryAuth, PutRegistryAuth } from "./put-registry-auth"; @@ -66,10 +66,14 @@ export function UpmConfigLogin( /** * Default {@link Login} function. Uses {@link UpmConfigLogin}. */ -export const login = (homePath: string, registryClient: RegClient.Instance) => +export const login = ( + homePath: string, + registryClient: RegClient.Instance, + debugLog: DebugLog +) => UpmConfigLogin( putRegistryAuth, - getAuthToken(registryClient), + getAuthToken(registryClient, debugLog), putNpmAuthToken(homePath), - npmDebugLog + debugLog ); diff --git a/src/services/remove-packages.ts b/src/services/remove-packages.ts index 054d55ee..60707ccd 100644 --- a/src/services/remove-packages.ts +++ b/src/services/remove-packages.ts @@ -8,11 +8,12 @@ import { } from "../domain/project-manifest"; import { SemanticVersion } from "../domain/semantic-version"; import { - loadProjectManifest, LoadProjectManifest, + loadProjectManifestUsing, saveProjectManifest, SaveProjectManifest, } from "../io/project-manifest-io"; +import { DebugLog } from "../logging"; import { resultifyAsyncOp } from "../utils/result-utils"; /** @@ -122,7 +123,8 @@ export function RemovePackagesFromManifest( /** * Default {@link RemovePackages} function. Uses {@link RemovePackagesFromManifest}. */ -export const removePackages = RemovePackagesFromManifest( - loadProjectManifest, - saveProjectManifest -); +export const removePackages = (debugLog: DebugLog) => + RemovePackagesFromManifest( + loadProjectManifestUsing(debugLog), + saveProjectManifest + ); diff --git a/src/services/search-packages.ts b/src/services/search-packages.ts index 44ba87d2..f2e73ae4 100644 --- a/src/services/search-packages.ts +++ b/src/services/search-packages.ts @@ -1,14 +1,14 @@ import { Registry } from "../domain/registry"; import { - getAllRegistryPackuments, GetAllRegistryPackuments, + getAllRegistryPackumentsUsing, } from "../io/all-packuments-io"; import { SearchedPackument, - searchRegistry, SearchRegistry, + searchRegistryUsing, } from "../io/npm-search"; -import { DebugLog, npmDebugLog } from "../logging"; +import { DebugLog } from "../logging"; import { assertIsError } from "../utils/error-type-guards"; /** @@ -65,8 +65,9 @@ export function ApiAndFallbackPackageSearch( /** * Default {@link SearchPackages} function. Uses {@link ApiAndFallbackPackageSearch}. */ -export const searchPackages = ApiAndFallbackPackageSearch( - searchRegistry, - getAllRegistryPackuments, - npmDebugLog -); +export const searchPackagesUsing = (debugLog: DebugLog) => + ApiAndFallbackPackageSearch( + searchRegistryUsing(debugLog), + getAllRegistryPackumentsUsing(debugLog), + debugLog + ); diff --git a/test/io/all-packuments-io.test.ts b/test/io/all-packuments-io.test.ts index bede4a37..4ad6126f 100644 --- a/test/io/all-packuments-io.test.ts +++ b/test/io/all-packuments-io.test.ts @@ -1,6 +1,6 @@ import npmFetch from "npm-registry-fetch"; import { Registry } from "../../src/domain/registry"; -import { FetchAllRegistryPackuments } from "../../src/io/all-packuments-io"; +import { getAllRegistryPackumentsUsing } from "../../src/io/all-packuments-io"; import { HttpErrorLike, RegistryAuthenticationError, @@ -16,7 +16,7 @@ const exampleRegistry: Registry = { }; function makeDependencies() { - const fetchAllRegistryPackuments = FetchAllRegistryPackuments(noopLogger); + const fetchAllRegistryPackuments = getAllRegistryPackumentsUsing(noopLogger); return { fetchAllRegistryPackuments } as const; } diff --git a/test/io/npm-search.test.ts b/test/io/npm-search.test.ts index a7aa5e12..adaabd93 100644 --- a/test/io/npm-search.test.ts +++ b/test/io/npm-search.test.ts @@ -4,7 +4,7 @@ import { HttpErrorLike, RegistryAuthenticationError, } from "../../src/io/common-errors"; -import { NpmApiSearch } from "../../src/io/npm-search"; +import { searchRegistryUsing } from "../../src/io/npm-search"; import { noopLogger } from "../../src/logging"; import { exampleRegistryUrl } from "../domain/data-registry"; @@ -17,7 +17,7 @@ const exampleRegistry: Registry = { describe("npm api search", () => { function makeDependencies() { - const npmApiSearch = NpmApiSearch(noopLogger); + const npmApiSearch = searchRegistryUsing(noopLogger); return { npmApiSearch } as const; } diff --git a/test/io/packument-io.test.ts b/test/io/packument-io.test.ts index 1005c066..b6415cc5 100644 --- a/test/io/packument-io.test.ts +++ b/test/io/packument-io.test.ts @@ -1,7 +1,7 @@ import RegClient from "another-npm-registry-client"; import { DomainName } from "../../src/domain/domain-name"; import { Registry } from "../../src/domain/registry"; -import { FetchRegistryPackument } from "../../src/io/packument-io"; +import { getRegistryPackumentUsing } from "../../src/io/packument-io"; import { noopLogger } from "../../src/logging"; import { buildPackument } from "../domain/data-packument"; import { exampleRegistryUrl } from "../domain/data-registry"; @@ -21,7 +21,7 @@ describe("packument io", () => { get: jest.fn(), }; - const fetchRegistryPackument = FetchRegistryPackument( + const fetchRegistryPackument = getRegistryPackumentUsing( regClient, noopLogger ); From f6601b41e3991c357e72a9dc5ac4c6b885e5b660 Mon Sep 17 00:00:00 2001 From: Ramon Brullo Date: Sun, 25 Aug 2024 12:10:18 +0200 Subject: [PATCH 18/26] test: improve e2e Make cli tests e2e tests. Also run e2e on windows and more node versions. --- .github/workflows/ci.yml | 25 ++- jest.config.js | 2 +- package.json | 3 +- src/cli/cmd-search.ts | 2 +- src/cli/cmd-view.ts | 78 +-------- src/cli/output-formatting.ts | 90 ++++++++++- src/services/remove-packages.ts | 32 +++- test/cli/cmd-deps.test.ts | 149 ------------------ test/cli/cmd-login.test.ts | 65 -------- test/cli/cmd-remove.test.ts | 67 -------- test/cli/cmd-search.test.ts | 108 ------------- test/cli/cmd-view.test.ts | 118 -------------- test/e2e/{add.e2e.ts => add.test.ts} | 0 test/e2e/{deps.e2e.ts => deps.test.ts} | 0 test/e2e/{help.e2e.ts => help.test.ts} | 0 test/e2e/remove.test.ts | 126 +++++++++++++++ test/e2e/search.test.ts | 32 ++++ test/e2e/setup/project.ts | 13 +- test/e2e/{unknown.e2e.ts => unknown.test.ts} | 0 test/e2e/{version.e2e.ts => version.test.ts} | 0 test/e2e/view.test.ts | 55 +++++++ .../project-manifest-assertions.ts | 8 +- test/{ => unit}/cli/cmd-add.test.ts | 36 ++--- .../{ => unit}/cli/dependency-logging.test.ts | 16 +- test/{ => unit}/cli/log.mock.ts | 0 test/{ => unit}/domain/base64.test.ts | 2 +- test/{ => unit}/domain/data-packument.ts | 8 +- .../domain/data-project-manifest.ts | 10 +- test/{ => unit}/domain/data-registry.ts | 2 +- .../domain/dependency-graph.test.ts | 8 +- test/{ => unit}/domain/domain-name.arb.ts | 2 +- test/{ => unit}/domain/domain-name.test.ts | 4 +- test/{ => unit}/domain/editor-version.test.ts | 2 +- test/{ => unit}/domain/npmrc.test.ts | 2 +- test/{ => unit}/domain/package-id.test.ts | 2 +- .../domain/package-manifest.test.ts | 6 +- .../domain/package-reference.test.ts | 2 +- test/{ => unit}/domain/package-url.test.ts | 4 +- test/{ => unit}/domain/packument.test.ts | 6 +- .../domain/project-manifest.test.ts | 10 +- test/{ => unit}/domain/registry-url.test.ts | 4 +- .../{ => unit}/domain/scoped-registry.test.ts | 6 +- .../domain/semantic-version.test.ts | 4 +- test/{ => unit}/domain/upmconfig.test.ts | 2 +- test/{ => unit}/io/all-packuments-io.test.ts | 8 +- test/{ => unit}/io/builtin-packages.test.ts | 14 +- test/{ => unit}/io/check-url.test.ts | 2 +- test/{ => unit}/io/common-errors.test.ts | 2 +- test/{ => unit}/io/directory-io.test.ts | 2 +- test/{ => unit}/io/node-error.mock.ts | 0 test/{ => unit}/io/npm-registry.test.ts | 4 +- test/{ => unit}/io/npm-search.test.ts | 8 +- test/{ => unit}/io/npmrc-io.test.ts | 4 +- test/{ => unit}/io/packument-io.test.ts | 8 +- .../{ => unit}/io/project-manifest-io.test.ts | 12 +- test/{ => unit}/io/project-version-io.mock.ts | 4 +- test/{ => unit}/io/project-version-io.test.ts | 6 +- test/{ => unit}/io/special-paths.test.ts | 6 +- test/{ => unit}/io/text-file-io.test.ts | 2 +- test/{ => unit}/io/upm-config-io.test.ts | 6 +- .../services/built-in-package-check.test.ts | 12 +- .../services/dependency-resolving.test.ts | 20 +-- .../services/determine-editor-version.test.ts | 6 +- .../services/get-auth-token.test.ts | 6 +- .../services/get-latest-version.test.ts | 12 +- .../services/get-registry-auth.test.ts | 10 +- .../get-registry-packument-version.test.ts | 12 +- test/{ => unit}/services/login.test.ts | 10 +- test/{ => unit}/services/parse-env.test.ts | 4 +- .../services/put-npm-auth-token.test.ts | 6 +- .../services/put-registry-auth.test.ts | 6 +- .../services/registry-client.mock.ts | 4 +- .../services/remote-packuments.mock.ts | 10 +- .../services/remove-packages.test.ts | 8 +- .../services/search-packages.test.ts | 14 +- test/{ => unit}/services/service.mock.ts | 0 .../services/unity-package-check.test.ts | 6 +- test/{ => unit}/utils/sources.test.ts | 6 +- test/{ => unit}/utils/zod-utils.ts | 2 +- 79 files changed, 557 insertions(+), 796 deletions(-) delete mode 100644 test/cli/cmd-deps.test.ts delete mode 100644 test/cli/cmd-login.test.ts delete mode 100644 test/cli/cmd-remove.test.ts delete mode 100644 test/cli/cmd-search.test.ts delete mode 100644 test/cli/cmd-view.test.ts rename test/e2e/{add.e2e.ts => add.test.ts} (100%) rename test/e2e/{deps.e2e.ts => deps.test.ts} (100%) rename test/e2e/{help.e2e.ts => help.test.ts} (100%) create mode 100644 test/e2e/remove.test.ts create mode 100644 test/e2e/search.test.ts rename test/e2e/{unknown.e2e.ts => unknown.test.ts} (100%) rename test/e2e/{version.e2e.ts => version.test.ts} (100%) create mode 100644 test/e2e/view.test.ts rename test/{domain => }/project-manifest-assertions.ts (85%) rename test/{ => unit}/cli/cmd-add.test.ts (90%) rename test/{ => unit}/cli/dependency-logging.test.ts (91%) rename test/{ => unit}/cli/log.mock.ts (100%) rename test/{ => unit}/domain/base64.test.ts (81%) rename test/{ => unit}/domain/data-packument.ts (92%) rename test/{ => unit}/domain/data-project-manifest.ts (87%) rename test/{ => unit}/domain/data-registry.ts (54%) rename test/{ => unit}/domain/dependency-graph.test.ts (95%) rename test/{ => unit}/domain/domain-name.arb.ts (94%) rename test/{ => unit}/domain/domain-name.test.ts (84%) rename test/{ => unit}/domain/editor-version.test.ts (98%) rename test/{ => unit}/domain/npmrc.test.ts (95%) rename test/{ => unit}/domain/package-id.test.ts (93%) rename test/{ => unit}/domain/package-manifest.test.ts (91%) rename test/{ => unit}/domain/package-reference.test.ts (98%) rename test/{ => unit}/domain/package-url.test.ts (92%) rename test/{ => unit}/domain/packument.test.ts (95%) rename test/{ => unit}/domain/project-manifest.test.ts (95%) rename test/{ => unit}/domain/registry-url.test.ts (85%) rename test/{ => unit}/domain/scoped-registry.test.ts (94%) rename test/{ => unit}/domain/semantic-version.test.ts (75%) rename test/{ => unit}/domain/upmconfig.test.ts (97%) rename test/{ => unit}/io/all-packuments-io.test.ts (87%) rename test/{ => unit}/io/builtin-packages.test.ts (87%) rename test/{ => unit}/io/check-url.test.ts (93%) rename test/{ => unit}/io/common-errors.test.ts (96%) rename test/{ => unit}/io/directory-io.test.ts (95%) rename test/{ => unit}/io/node-error.mock.ts (100%) rename test/{ => unit}/io/npm-registry.test.ts (94%) rename test/{ => unit}/io/npm-search.test.ts (88%) rename test/{ => unit}/io/npmrc-io.test.ts (95%) rename test/{ => unit}/io/packument-io.test.ts (89%) rename test/{ => unit}/io/project-manifest-io.test.ts (89%) rename test/{ => unit}/io/project-version-io.mock.ts (84%) rename test/{ => unit}/io/project-version-io.test.ts (92%) rename test/{ => unit}/io/special-paths.test.ts (94%) rename test/{ => unit}/io/text-file-io.test.ts (97%) rename test/{ => unit}/io/upm-config-io.test.ts (94%) rename test/{ => unit}/services/built-in-package-check.test.ts (81%) rename test/{ => unit}/services/dependency-resolving.test.ts (89%) rename test/{ => unit}/services/determine-editor-version.test.ts (87%) rename test/{ => unit}/services/get-auth-token.test.ts (91%) rename test/{ => unit}/services/get-latest-version.test.ts (80%) rename test/{ => unit}/services/get-registry-auth.test.ts (92%) rename test/{ => unit}/services/get-registry-packument-version.test.ts (82%) rename test/{ => unit}/services/login.test.ts (86%) rename test/{ => unit}/services/parse-env.test.ts (97%) rename test/{ => unit}/services/put-npm-auth-token.test.ts (90%) rename test/{ => unit}/services/put-registry-auth.test.ts (95%) rename test/{ => unit}/services/registry-client.mock.ts (90%) rename test/{ => unit}/services/remote-packuments.mock.ts (78%) rename test/{ => unit}/services/remove-packages.test.ts (93%) rename test/{ => unit}/services/search-packages.test.ts (86%) rename test/{ => unit}/services/service.mock.ts (100%) rename test/{ => unit}/services/unity-package-check.test.ts (82%) rename test/{ => unit}/utils/sources.test.ts (92%) rename test/{ => unit}/utils/zod-utils.ts (92%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 453151ed..18ff214e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,47 +1,46 @@ name: CI -on: [push, pull_request] +on: push jobs: - unit-test: + unit-test: runs-on: ubuntu-latest - strategy: - matrix: - node-version: [18.x] name: Unit Tests steps: - uses: actions/checkout@v3 - - name: set node.js ${{ matrix.node-version }} + - name: Setup Node.js uses: actions/setup-node@v3 with: - node-version: ${{ matrix.node-version }} + node-version: "22.x" - name: npm ci run: npm ci - name: Unit tests run: npm run test:unit e2e-test: - runs-on: ubuntu-latest - container: ghcr.io/openupm/openupm-cli-e2e-${{ matrix.os }}:latest + runs-on: ${{ matrix.os }}-latest strategy: matrix: os: - ubuntu - # Seems like windows is not supported at the moment (06.2024) - # - windows + - windows + node: + - "18.x" # Oldest supported lts + - "20.x" # Newest lts + - "22.x" # Latest name: E2E Tests steps: - uses: actions/checkout@v3 - name: setup node.js uses: actions/setup-node@v3 with: - node-version: 18 + node-version: ${{ matrix.node }} - name: install deps run: npm ci - name: build run: npm run build - name: run tests - run: npx jest --testMatch "**/*.e2e.ts" --runInBand --silent=false + run: npm run test:e2e release: runs-on: ubuntu-latest diff --git a/jest.config.js b/jest.config.js index b113f698..a3e3ad37 100644 --- a/jest.config.js +++ b/jest.config.js @@ -4,7 +4,7 @@ module.exports = { verbose: true, clearMocks: true, setupFilesAfterEnv: [ - "./test/domain/project-manifest-assertions.ts", + "./test/project-manifest-assertions.ts", "./test/result-assertions.ts", ], }; diff --git a/package.json b/package.json index 364c6a0f..2a73a9e1 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,8 @@ "node": ">=18" }, "scripts": { - "test:unit": "cross-env NODE_ENV=test jest --testMatch \"**/*.test.ts\"", + "test:e2e": "jest --testMatch \"**/e2e/**/*.test.ts\" --runInBand", + "test:unit": "cross-env NODE_ENV=test jest --testMatch \"**/unit/**/*.test.ts\"", "clean": "rimraf lib", "build": "npm run clean && tsc -p tsconfig.build.json", "build:watch": "tsc -w -p tsconfig.build.json", diff --git a/src/cli/cmd-search.ts b/src/cli/cmd-search.ts index bd369ba9..883a11a2 100644 --- a/src/cli/cmd-search.ts +++ b/src/cli/cmd-search.ts @@ -58,7 +58,7 @@ export function makeSearchCmd( } debugLog(`${usedEndpoint}: ${results.map((it) => it.name).join(os.EOL)}`); - console.log(formatAsTable(results)); + log.notice("", formatAsTable(results)); return ResultCodes.Ok; }; } diff --git a/src/cli/cmd-view.ts b/src/cli/cmd-view.ts index 187e67b1..d8938768 100644 --- a/src/cli/cmd-view.ts +++ b/src/cli/cmd-view.ts @@ -1,20 +1,18 @@ -import assert from "assert"; -import chalk from "chalk"; import { Logger } from "npmlog"; +import { EOL } from "os"; import { PackumentNotFoundError } from "../common-errors"; import { hasVersion, PackageReference, splitPackageReference, } from "../domain/package-reference"; -import { tryGetLatestVersion, UnityPackument } from "../domain/packument"; import { unityRegistry } from "../domain/registry"; import { GetRegistryPackument } from "../io/packument-io"; import { GetRegistryAuth } from "../services/get-registry-auth"; import { ParseEnv } from "../services/parse-env"; -import { recordKeys } from "../utils/record-utils"; import { queryAllRegistriesLazy } from "../utils/sources"; import { CmdOptions } from "./options"; +import { formatPackumentInfo } from "./output-formatting"; import { ResultCodes } from "./result-codes"; /** @@ -37,75 +35,6 @@ export type ViewCmd = ( options: ViewOptions ) => Promise; -const printInfo = function (packument: UnityPackument) { - const versionCount = recordKeys(packument.versions).length; - const ver = tryGetLatestVersion(packument); - assert(ver !== undefined); - const verInfo = packument.versions[ver]!; - const license = verInfo.license || "proprietary or unlicensed"; - const displayName = verInfo.displayName; - const description = verInfo.description || packument.description; - const keywords = verInfo.keywords || packument.keywords; - const homepage = verInfo.homepage; - const dist = verInfo.dist; - const dependencies = verInfo.dependencies; - const latest = packument["dist-tags"]?.latest; - let time = packument.time?.modified; - if ( - !time && - latest && - packument.time !== undefined && - latest in packument.time - ) - time = packument.time[latest]; - - console.log(); - console.log( - chalk.greenBright(packument.name) + - "@" + - chalk.greenBright(ver) + - " | " + - chalk.green(license) + - " | versions: " + - chalk.yellow(versionCount) - ); - console.log(); - if (displayName) console.log(chalk.greenBright(displayName)); - if (description) console.log(description); - if (description && description.includes("\n")) console.log(); - if (homepage) console.log(chalk.cyan(homepage)); - if (keywords) console.log(`keywords: ${keywords.join(", ")}`); - - if (dist) { - console.log(); - console.log("dist"); - console.log(".tarball: " + chalk.cyan(dist.tarball)); - if (dist.shasum) console.log(".shasum: " + chalk.yellow(dist.shasum)); - if (dist.integrity) - console.log(".integrity: " + chalk.yellow(dist.integrity)); - } - - if (dependencies && recordKeys(dependencies).length > 0) { - console.log(); - console.log("dependencies"); - recordKeys(dependencies) - .sort() - .forEach((n) => console.log(chalk.yellow(n) + ` ${dependencies[n]}`)); - } - - console.log(); - console.log("latest: " + chalk.greenBright(latest)); - - console.log(); - console.log("published at " + chalk.yellow(time)); - - console.log(); - console.log("versions:"); - for (const version in packument.versions) { - console.log(" " + chalk.greenBright(version)); - } -}; - /** * Makes a {@link ViewCmd} function. */ @@ -139,7 +68,8 @@ export function makeViewCmd( const packument = packumentFromRegistry?.value ?? null; if (packument === null) throw new PackumentNotFoundError(pkg); - printInfo(packument); + const output = formatPackumentInfo(packument, EOL); + log.notice("", output); return ResultCodes.Ok; }; } diff --git a/src/cli/output-formatting.ts b/src/cli/output-formatting.ts index ece5ed1c..6f8866dd 100644 --- a/src/cli/output-formatting.ts +++ b/src/cli/output-formatting.ts @@ -1,10 +1,12 @@ +import assert from "assert"; +import chalk from "chalk"; import Table from "cli-table"; import { tryGetLatestVersion, UnityPackument, VersionedPackument, } from "../domain/packument"; -import assert from "assert"; +import { recordKeys } from "../utils/record-utils"; /** * A type describing the minimum required properties of a packument @@ -45,3 +47,89 @@ export function formatAsTable( rows.forEach((row) => table.push(row)); return table.toString(); } + +/** + * Creates a string from a packument containing relevant information. This can + * be printed to the console. + * + * The string is multiline. + * @param packument The packument to format. + * @param eol The string to use to delimit lines, for example {@link import("os").EOL}. + * @returns The formatted string. + */ +export function formatPackumentInfo( + packument: UnityPackument, + eol: string +): string { + let output = ""; + + const versionCount = recordKeys(packument.versions).length; + const ver = tryGetLatestVersion(packument); + assert(ver !== undefined); + const verInfo = packument.versions[ver]!; + const license = verInfo.license || "proprietary or unlicensed"; + const displayName = verInfo.displayName; + const description = verInfo.description || packument.description; + const keywords = verInfo.keywords || packument.keywords; + const homepage = verInfo.homepage; + const dist = verInfo.dist; + const dependencies = verInfo.dependencies; + const latest = packument["dist-tags"]?.latest; + let time = packument.time?.modified; + if ( + !time && + latest && + packument.time !== undefined && + latest in packument.time + ) + time = packument.time[latest]; + + output += eol; + output += `${chalk.greenBright(packument.name)}@${chalk.greenBright( + ver + )} | ${chalk.green(license)} | versions: ${chalk.yellow( + versionCount + )}${eol}${eol}`; + + if (displayName) output += chalk.greenBright(displayName) + eol; + if (description) output += description + eol; + if (description && description.includes("\n")) output += eol; + if (homepage) output += chalk.cyan(homepage) + eol; + if (keywords) output += `keywords: ${keywords.join(", ")}` + eol; + + if (dist) { + output += eol; + output += `dist${eol}`; + output += `.tarball: ${chalk.cyan(dist.tarball)}${eol}`; + if (dist.shasum) output += `.shasum: ${chalk.yellow(dist.shasum)}${eol}`; + if (dist.integrity) + output += `.integrity: ${chalk.yellow(dist.integrity)}${eol}`; + } + + if (dependencies && recordKeys(dependencies).length > 0) { + output += eol; + output += `dependencies${eol}`; + output += recordKeys(dependencies) + .sort() + .reduce( + (acc, dependencyName) => + `${acc + chalk.yellow(dependencyName)} ${ + dependencies[dependencyName] + }${eol}`, + "" + ); + } + + output += eol; + output += `latest: ${chalk.greenBright(latest)}${eol}`; + + output += eol; + output += `published at ${chalk.yellow(time)}${eol}`; + + output += eol; + output += `versions:${eol}${Object.keys(packument.versions) + .map((version) => " " + chalk.greenBright(version)) + .join(eol)}`; + + return output; +} diff --git a/src/services/remove-packages.ts b/src/services/remove-packages.ts index 60707ccd..39b2ecd2 100644 --- a/src/services/remove-packages.ts +++ b/src/services/remove-packages.ts @@ -62,7 +62,7 @@ export function RemovePackagesFromManifest( manifest = removeDependency(manifest, packageName); - if (manifest.scopedRegistries !== undefined) + if (manifest.scopedRegistries !== undefined) { manifest = { ...manifest, scopedRegistries: manifest.scopedRegistries @@ -75,6 +75,36 @@ export function RemovePackagesFromManifest( })), }; + // Remove scoped registries without scopes + manifest = { + ...manifest, + scopedRegistries: manifest.scopedRegistries!.filter( + (it) => it.scopes.length > 0 + ), + }; + + // Remove scoped registries property if empty + if (manifest.scopedRegistries!.length === 0) { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { scopedRegistries, ...withoutScopedRegistries } = manifest; + manifest = withoutScopedRegistries; + } + } + + if (manifest.testables !== undefined) { + manifest = { + ...manifest, + testables: manifest.testables.filter((it) => it !== packageName), + }; + + // Remove testables property if empty + if (manifest.testables!.length === 0) { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { testables, ...withoutTestables } = manifest; + manifest = withoutTestables; + } + } + return Ok([manifest, { name: packageName, version: versionInManifest }]); }; diff --git a/test/cli/cmd-deps.test.ts b/test/cli/cmd-deps.test.ts deleted file mode 100644 index 21def045..00000000 --- a/test/cli/cmd-deps.test.ts +++ /dev/null @@ -1,149 +0,0 @@ -import { makeDepsCmd } from "../../src/cli/cmd-deps"; -import { ResultCodes } from "../../src/cli/result-codes"; -import { PackumentNotFoundError } from "../../src/common-errors"; -import { - makeGraphFromSeed, - markBuiltInResolved, - markRemoteResolved, -} from "../../src/domain/dependency-graph"; -import { DomainName } from "../../src/domain/domain-name"; -import { makePackageReference } from "../../src/domain/package-reference"; -import { SemanticVersion } from "../../src/domain/semantic-version"; -import { noopLogger } from "../../src/logging"; -import { ResolveDependencies } from "../../src/services/dependency-resolving"; -import { GetLatestVersion } from "../../src/services/get-latest-version"; -import { GetRegistryAuth } from "../../src/services/get-registry-auth"; -import { Env, ParseEnv } from "../../src/services/parse-env"; -import { exampleRegistryUrl } from "../domain/data-registry"; -import { mockService } from "../services/service.mock"; -import { makeMockLogger } from "./log.mock"; - -const somePackage = DomainName.parse("com.some.package"); -const otherPackage = DomainName.parse("com.other.package"); -const anotherPackage = DomainName.parse("com.another.package"); - -const defaultEnv = { - primaryRegistryUrl: exampleRegistryUrl, -} as Env; - -const someVersion = SemanticVersion.parse("1.2.3"); - -function makeDependencies() { - const parseEnv = mockService(); - parseEnv.mockResolvedValue(defaultEnv); - - const resolveDependencies = mockService(); - let defaultGraph = makeGraphFromSeed(somePackage, someVersion); - defaultGraph = markRemoteResolved( - defaultGraph, - somePackage, - someVersion, - exampleRegistryUrl, - { [otherPackage]: someVersion } - ); - defaultGraph = markRemoteResolved( - defaultGraph, - otherPackage, - someVersion, - exampleRegistryUrl, - {} - ); - resolveDependencies.mockResolvedValue(defaultGraph); - - const resolveLatestVersion = mockService(); - resolveLatestVersion.mockResolvedValue(someVersion); - - const getRegistryAuth = mockService(); - getRegistryAuth.mockResolvedValue({ url: exampleRegistryUrl, auth: null }); - - const log = makeMockLogger(); - - const depsCmd = makeDepsCmd( - parseEnv, - resolveDependencies, - resolveLatestVersion, - getRegistryAuth, - log, - noopLogger - ); - return { - depsCmd, - parseEnv, - resolveDependencies, - resolveLatestVersion, - log, - } as const; -} - -describe("cmd-deps", () => { - it("should fail if package-reference has url-version", async () => { - const { depsCmd } = makeDependencies(); - - const resultCode = await depsCmd( - makePackageReference(somePackage, "https://some.registry.com"), - {} - ); - - expect(resultCode).toEqual(ResultCodes.Error); - }); - - it("should fail if latest version could not be r esolved", async () => { - const { depsCmd, resolveLatestVersion } = makeDependencies(); - resolveLatestVersion.mockResolvedValue(null); - - await expect(depsCmd(somePackage, {})).rejects.toBeInstanceOf( - PackumentNotFoundError - ); - }); - - it("should notify if package-reference has url-version", async () => { - const { depsCmd, log } = makeDependencies(); - - await depsCmd( - makePackageReference(somePackage, "https://some.registry.com"), - {} - ); - - expect(log.error).toHaveBeenCalledWith( - expect.any(String), - expect.stringContaining("url-version") - ); - }); - - it("should print dependency graph", async () => { - const { depsCmd, resolveDependencies, log } = makeDependencies(); - let graph = makeGraphFromSeed(somePackage, someVersion); - graph = markRemoteResolved( - graph, - somePackage, - someVersion, - exampleRegistryUrl, - { - [otherPackage]: someVersion, - [anotherPackage]: someVersion, - } - ); - graph = markBuiltInResolved(graph, otherPackage, someVersion); - graph = markBuiltInResolved(graph, anotherPackage, someVersion); - resolveDependencies.mockResolvedValue(graph); - - await depsCmd(somePackage, {}); - - // Here we just do some generic checks to see if something containing - // all relevant information was logged. For more detailed testing - // of the output format see the tests for the dependency graph - // logging logic. - expect(log.notice).toHaveBeenCalledWith( - "", - expect.stringContaining(somePackage) - ); - expect(log.notice).toHaveBeenCalledWith( - "", - expect.stringContaining(otherPackage) - ); - expect(log.notice).toHaveBeenCalledWith( - "", - expect.stringContaining(anotherPackage) - ); - }); -}); diff --git a/test/cli/cmd-login.test.ts b/test/cli/cmd-login.test.ts deleted file mode 100644 index 5af82638..00000000 --- a/test/cli/cmd-login.test.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { makeLoginCmd } from "../../src/cli/cmd-login"; -import { GetUpmConfigPath } from "../../src/io/upm-config-io"; -import { GetRegistryAuth } from "../../src/services/get-registry-auth"; -import { Login } from "../../src/services/login"; -import { Env, ParseEnv } from "../../src/services/parse-env"; -import { exampleRegistryUrl } from "../domain/data-registry"; -import { mockService } from "../services/service.mock"; -import { makeMockLogger } from "./log.mock"; - -const defaultEnv = { - cwd: "/users/some-user/projects/SomeProject", - upstream: true, - primaryRegistryUrl: exampleRegistryUrl, -} as Env; - -const exampleUser = "user"; -const examplePassword = "pass"; -const exampleEmail = "user@email.com"; -const exampleUpmConfigPath = "/user/home/.upmconfig.toml"; - -describe("cmd-login", () => { - function makeDependencies() { - const parseEnv = mockService(); - parseEnv.mockResolvedValue(defaultEnv); - - const getUpmConfigPath = mockService(); - getUpmConfigPath.mockReturnValue(exampleUpmConfigPath); - - const login = mockService(); - login.mockResolvedValue(undefined); - - const getRegistryAuth = mockService(); - getRegistryAuth.mockResolvedValue({ url: exampleRegistryUrl, auth: null }); - - const log = makeMockLogger(); - - const loginCmd = makeLoginCmd(parseEnv, getUpmConfigPath, login, log); - return { - loginCmd, - parseEnv, - getUpmConfigPath, - login, - getRegistryAuth, - log, - } as const; - } - - // TODO: Add tests for prompting logic - - it("should notify of success", async () => { - const { loginCmd, log } = makeDependencies(); - - await loginCmd({ - username: exampleUser, - password: examplePassword, - email: exampleEmail, - registry: exampleRegistryUrl, - }); - - expect(log.notice).toHaveBeenCalledWith( - "config", - expect.stringContaining("saved unity config") - ); - }); -}); diff --git a/test/cli/cmd-remove.test.ts b/test/cli/cmd-remove.test.ts deleted file mode 100644 index 0c868ed3..00000000 --- a/test/cli/cmd-remove.test.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { makeRemoveCmd } from "../../src/cli/cmd-remove"; -import { DomainName } from "../../src/domain/domain-name"; -import { SemanticVersion } from "../../src/domain/semantic-version"; -import { GetRegistryAuth } from "../../src/services/get-registry-auth"; -import { Env, ParseEnv } from "../../src/services/parse-env"; -import { RemovePackages } from "../../src/services/remove-packages"; -import { AsyncOk } from "../../src/utils/result-utils"; -import { exampleRegistryUrl } from "../domain/data-registry"; -import { mockService } from "../services/service.mock"; -import { makeMockLogger } from "./log.mock"; - -const somePackage = DomainName.parse("com.some.package"); -const defaultEnv = { - cwd: "/users/some-user/projects/SomeProject", - primaryRegistryUrl: exampleRegistryUrl, -} as Env; - -function makeDependencies() { - const parseEnv = mockService(); - parseEnv.mockResolvedValue(defaultEnv); - - const removePackages = mockService(); - removePackages.mockReturnValue( - AsyncOk([{ name: somePackage, version: "1.0.0" as SemanticVersion }]) - ); - - const getRegistryAuth = mockService(); - getRegistryAuth.mockResolvedValue({ - url: defaultEnv.primaryRegistryUrl, - auth: null, - }); - - const log = makeMockLogger(); - - const removeCmd = makeRemoveCmd(parseEnv, removePackages, log); - return { - removeCmd, - parseEnv, - removePackages, - getRegistryAuth, - log, - } as const; -} - -describe("cmd-remove", () => { - it("should print removed packages", async () => { - const { removeCmd, log } = makeDependencies(); - - await removeCmd([somePackage], {}); - - expect(log.notice).toHaveBeenCalledWith( - expect.any(String), - expect.stringContaining(`${somePackage}@1.0.0`) - ); - }); - - it("should suggest to open Unity after save", async () => { - const { removeCmd, log } = makeDependencies(); - - await removeCmd([somePackage], {}); - - expect(log.notice).toHaveBeenCalledWith( - "", - expect.stringContaining("open Unity") - ); - }); -}); diff --git a/test/cli/cmd-search.test.ts b/test/cli/cmd-search.test.ts deleted file mode 100644 index 380b1cd9..00000000 --- a/test/cli/cmd-search.test.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { makeSearchCmd, SearchOptions } from "../../src/cli/cmd-search"; -import { ResultCodes } from "../../src/cli/result-codes"; -import { DomainName } from "../../src/domain/domain-name"; -import { SemanticVersion } from "../../src/domain/semantic-version"; -import { SearchedPackument } from "../../src/io/npm-search"; -import { noopLogger } from "../../src/logging"; -import { GetRegistryAuth } from "../../src/services/get-registry-auth"; -import { Env, ParseEnv } from "../../src/services/parse-env"; -import { SearchPackages } from "../../src/services/search-packages"; -import { exampleRegistryUrl } from "../domain/data-registry"; -import { mockService } from "../services/service.mock"; -import { makeMockLogger } from "./log.mock"; - -const exampleSearchResult: SearchedPackument = { - name: DomainName.parse("com.example.package-a"), - versions: { [SemanticVersion.parse("1.0.0")]: "latest" }, - description: "A demo package", - date: new Date(2019, 9, 2, 3, 2, 38), - "dist-tags": { latest: SemanticVersion.parse("1.0.0") }, -}; - -function makeDependencies() { - const parseEnv = mockService(); - parseEnv.mockResolvedValue({ - primaryRegistryUrl: exampleRegistryUrl, - } as Env); - - const searchPackages = mockService(); - searchPackages.mockResolvedValue([exampleSearchResult]); - - const getRegistryAuth = mockService(); - getRegistryAuth.mockResolvedValue({ url: exampleRegistryUrl, auth: null }); - - const log = makeMockLogger(); - - const searchCmd = makeSearchCmd( - parseEnv, - searchPackages, - getRegistryAuth, - log, - noopLogger - ); - return { - searchCmd, - parseEnv, - searchPackages, - log, - } as const; -} - -describe("cmd-search", () => { - const options: SearchOptions = { - registry: exampleRegistryUrl, - upstream: false, - }; - - it("should print packument information", async () => { - const consoleSpy = jest.spyOn(console, "log"); - const { searchCmd } = makeDependencies(); - - await searchCmd("package-a", options); - - expect(consoleSpy).toHaveBeenCalledWith( - expect.stringContaining("package-a") - ); - expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining("1.0.0")); - expect(consoleSpy).toHaveBeenCalledWith( - expect.stringContaining("2019-10-02") - ); - }); - - it("should be ok if no network error occurred", async () => { - const { searchCmd } = makeDependencies(); - - const resultCode = await searchCmd("pkg-not-exist", options); - - expect(resultCode).toEqual(ResultCodes.Ok); - }); - - it("should notify of unknown packument", async () => { - const { searchCmd, searchPackages, log } = makeDependencies(); - searchPackages.mockResolvedValue([]); - - await searchCmd("pkg-not-exist", options); - - expect(log.notice).toHaveBeenCalledWith( - "", - expect.stringContaining("No matches found") - ); - }); - - it("should notify when falling back to old search", async () => { - const { searchCmd, searchPackages, log } = makeDependencies(); - searchPackages.mockImplementation( - async (_registry, _keyword, onUseAllFallback) => { - onUseAllFallback && onUseAllFallback(); - return []; - } - ); - - await searchCmd("package-a", options); - - expect(log.warn).toHaveBeenCalledWith( - "", - expect.stringContaining("using old search") - ); - }); -}); diff --git a/test/cli/cmd-view.test.ts b/test/cli/cmd-view.test.ts deleted file mode 100644 index e0ca12a6..00000000 --- a/test/cli/cmd-view.test.ts +++ /dev/null @@ -1,118 +0,0 @@ -import { makeViewCmd } from "../../src/cli/cmd-view"; -import { ResultCodes } from "../../src/cli/result-codes"; -import { PackumentNotFoundError } from "../../src/common-errors"; -import { DomainName } from "../../src/domain/domain-name"; -import { makePackageReference } from "../../src/domain/package-reference"; -import { SemanticVersion } from "../../src/domain/semantic-version"; -import { GetRegistryPackument } from "../../src/io/packument-io"; -import { GetRegistryAuth } from "../../src/services/get-registry-auth"; -import { Env, ParseEnv } from "../../src/services/parse-env"; -import { buildPackument } from "../domain/data-packument"; -import { exampleRegistryUrl } from "../domain/data-registry"; -import { mockService } from "../services/service.mock"; -import { makeMockLogger } from "./log.mock"; - -const somePackage = DomainName.parse("com.some.package"); -const somePackument = buildPackument(somePackage, (packument) => - packument - .set("time", { - modified: "2019-11-28T18:51:58.123Z", - created: "2019-11-28T18:51:58.123Z", - [SemanticVersion.parse("1.0.0")]: "2019-11-28T18:51:58.123Z", - }) - .addVersion("1.0.0", (version) => - version - .set("displayName", "Package A") - .set("author", { name: "batman" }) - .set("unity", "2018.4") - .set("description", "A demo package") - .set("keywords", [""]) - .set("dist", { - integrity: - "sha512-MAh44bur7HGyfbCXH9WKfaUNS67aRMfO0VAbLkr+jwseb1hJue/I1pKsC7PKksuBYh4oqoo9Jov1cBcvjVgjmA==", - shasum: "516957cac4249f95cafab0290335def7d9703db7", - tarball: - "https://cdn.example.com/com.example.package-a/com.example.package-a-1.0.0.tgz", - }) - ) -); -const defaultEnv = { - upstream: false, - primaryRegistryUrl: exampleRegistryUrl, -} as Env; - -function makeDependencies() { - const parseEnv = mockService(); - parseEnv.mockResolvedValue(defaultEnv); - - const getRegistryPackument = mockService(); - getRegistryPackument.mockResolvedValue(somePackument); - - const getRegistryAuth = mockService(); - getRegistryAuth.mockResolvedValue({ - url: defaultEnv.primaryRegistryUrl, - auth: null, - }); - - const log = makeMockLogger(); - - const viewCmd = makeViewCmd( - parseEnv, - getRegistryPackument, - getRegistryAuth, - log - ); - return { - viewCmd, - parseEnv, - getRegistryPackument: getRegistryPackument, - log, - } as const; -} - -describe("cmd-view", () => { - it("should fail if package version was specified", async () => { - const { viewCmd } = makeDependencies(); - - const resultCode = await viewCmd( - makePackageReference(somePackage, SemanticVersion.parse("1.0.0")), - {} - ); - - expect(resultCode).toEqual(ResultCodes.Error); - }); - - it("should fail if package is not found", async () => { - const { viewCmd, getRegistryPackument } = makeDependencies(); - getRegistryPackument.mockResolvedValue(null); - - await expect(viewCmd(somePackage, {})).rejects.toBeInstanceOf( - PackumentNotFoundError - ); - }); - - it("should notify if package version was specified", async () => { - const { viewCmd, log } = makeDependencies(); - - await viewCmd( - makePackageReference(somePackage, SemanticVersion.parse("1.0.0")), - {} - ); - - expect(log.warn).toHaveBeenCalledWith( - "", - expect.stringContaining("please do not specify") - ); - }); - - it("should print package information", async () => { - const consoleSpy = jest.spyOn(console, "log"); - const { viewCmd } = makeDependencies(); - - await viewCmd(somePackage, {}); - - expect(consoleSpy).toHaveBeenCalledWith( - expect.stringContaining(somePackage) - ); - }); -}); diff --git a/test/e2e/add.e2e.ts b/test/e2e/add.test.ts similarity index 100% rename from test/e2e/add.e2e.ts rename to test/e2e/add.test.ts diff --git a/test/e2e/deps.e2e.ts b/test/e2e/deps.test.ts similarity index 100% rename from test/e2e/deps.e2e.ts rename to test/e2e/deps.test.ts diff --git a/test/e2e/help.e2e.ts b/test/e2e/help.test.ts similarity index 100% rename from test/e2e/help.e2e.ts rename to test/e2e/help.test.ts diff --git a/test/e2e/remove.test.ts b/test/e2e/remove.test.ts new file mode 100644 index 00000000..85497550 --- /dev/null +++ b/test/e2e/remove.test.ts @@ -0,0 +1,126 @@ +import { ResultCodes } from "../../src/cli/result-codes"; +import { emptyProjectManifest } from "../../src/domain/project-manifest"; +import { buildProjectManifest } from "../unit/domain/data-project-manifest"; +import { exampleRegistryUrl } from "../unit/domain/data-registry"; +import { getProjectManifest } from "./check/project-manifest"; +import { runOpenupm } from "./run"; +import { prepareHomeDirectory } from "./setup/directories"; +import { prepareUnityProject } from "./setup/project"; + +describe("remove packages", () => { + it("should not accept package reference with version", async () => { + const homeDirectory = await prepareHomeDirectory(); + const projectDirectory = await prepareUnityProject(homeDirectory); + + const result = await runOpenupm(projectDirectory, [ + "remove", + "dev.comradevanti.opt-unity@2.0.0", + ]); + + expect(result.code).toEqual(ResultCodes.Error); + }); + + it("should remove package from manifest (manifest is empty afterwards)", async () => { + const homeDirectory = await prepareHomeDirectory(); + const projectDirectory = await prepareUnityProject(homeDirectory, { + manifest: buildProjectManifest((manifest) => + manifest.addDependency( + "dev.comradevanti.opt-unity", + "2.0.0", + true, + true + ) + ), + }); + + const result = await runOpenupm(projectDirectory, [ + "remove", + "dev.comradevanti.opt-unity", + ]); + + const actualManifest = await getProjectManifest(projectDirectory); + expect(result.code).toEqual(ResultCodes.Ok); + expect(result.stdErr).toEqual( + expect.arrayContaining([ + expect.stringContaining('Removed "dev.comradevanti.opt-unity@2.0.0"'), + expect.stringContaining("please open Unity"), + ]) + ); + expect(actualManifest).toEqual(emptyProjectManifest); + }); + + it("should remove package from manifest (other packages remain)", async () => { + const homeDirectory = await prepareHomeDirectory(); + const projectDirectory = await prepareUnityProject(homeDirectory, { + manifest: buildProjectManifest((manifest) => + manifest + .addDependency("dev.comradevanti.opt-unity", "2.0.0", true, true) + .addDependency("some.other.package", "1.0.0", true, false) + ), + }); + + const result = await runOpenupm(projectDirectory, [ + "remove", + "dev.comradevanti.opt-unity", + ]); + + const actualManifest = await getProjectManifest(projectDirectory); + expect(result.code).toEqual(ResultCodes.Ok); + expect(result.stdErr).toEqual( + expect.arrayContaining([ + expect.stringContaining('Removed "dev.comradevanti.opt-unity@2.0.0"'), + expect.stringContaining("please open Unity"), + ]) + ); + expect(actualManifest).toEqual({ + dependencies: { "some.other.package": "1.0.0" }, + scopedRegistries: [ + { + name: "example.com", + url: exampleRegistryUrl, + scopes: ["some.other.package"], + }, + ], + }); + }); + + it("should fail if package is not in manifest", async () => { + const homeDirectory = await prepareHomeDirectory(); + const projectDirectory = await prepareUnityProject(homeDirectory); + + const result = await runOpenupm(projectDirectory, [ + "remove", + "dev.comradevanti.opt-unity", + ]); + + expect(result.code).toEqual(ResultCodes.Error); + expect(result.stdErr).toEqual( + expect.arrayContaining([ + expect.stringContaining( + 'Package "dev.comradevanti.opt-unity" could not be found' + ), + expect.stringContaining("Did you make a typo"), + ]) + ); + }); + + it("should be atomic", async () => { + const homeDirectory = await prepareHomeDirectory(); + const initialManifest = buildProjectManifest((manifest) => + manifest.addDependency("dev.comradevanti.opt-unity", "2.0.0", true, true) + ); + const projectDirectory = await prepareUnityProject(homeDirectory, { + manifest: initialManifest, + }); + + const result = await runOpenupm(projectDirectory, [ + "remove", + "dev.comradevanti.opt-unity", + "other.unknown.package", + ]); + + const actualManifest = await getProjectManifest(projectDirectory); + expect(result.code).toEqual(ResultCodes.Error); + expect(actualManifest).toEqual(initialManifest); + }); +}); diff --git a/test/e2e/search.test.ts b/test/e2e/search.test.ts new file mode 100644 index 00000000..09a98ac6 --- /dev/null +++ b/test/e2e/search.test.ts @@ -0,0 +1,32 @@ +import { ResultCodes } from "../../src/cli/result-codes"; +import { runOpenupm } from "./run"; +import { prepareHomeDirectory } from "./setup/directories"; + +describe("cmd-search", () => { + it("should print packument information", async () => { + const homeDir = await prepareHomeDirectory(); + const output = await runOpenupm(homeDir, [ + "search", + "comradevanti.opt-unity", + ]); + + expect(output.code).toEqual(ResultCodes.Ok); + expect(output.stdErr).toEqual( + expect.arrayContaining([ + expect.stringContaining("dev.comradevanti.opt-unity"), + expect.stringContaining("3.5.0"), + expect.stringContaining("2023-02-28"), + ]) + ); + }); + + it("should notify of unknown packument", async () => { + const homeDir = await prepareHomeDirectory(); + const output = await runOpenupm(homeDir, ["search", "pkg-not-exist"]); + + expect(output.code).toEqual(ResultCodes.Ok); + expect(output.stdErr).toEqual( + expect.arrayContaining([expect.stringContaining("No matches found")]) + ); + }); +}); diff --git a/test/e2e/setup/project.ts b/test/e2e/setup/project.ts index cd921864..1cd53e85 100644 --- a/test/e2e/setup/project.ts +++ b/test/e2e/setup/project.ts @@ -1,7 +1,10 @@ import fse from "fs-extra"; import path from "path"; import yaml from "yaml"; -import { emptyProjectManifest } from "../../../src/domain/project-manifest"; +import { + emptyProjectManifest, + UnityProjectManifest, +} from "../../../src/domain/project-manifest"; import { WriteProjectManifestFile } from "../../../src/io/project-manifest-io"; import { projectVersionTxtPathFor } from "../../../src/io/project-version-io"; import { writeTextFile } from "../../../src/io/text-file-io"; @@ -16,7 +19,8 @@ const writeProjectManifest = WriteProjectManifestFile(writeTextFile); * @returns The path to the created project's directory. */ export async function prepareUnityProject( - homeDirectory: string + homeDirectory: string, + options?: { manifest?: UnityProjectManifest } ): Promise { const projectName = "MyProject"; const projectDir = path.join(homeDirectory, projectName); @@ -32,7 +36,10 @@ export async function prepareUnityProject( { encoding: "utf8" } ); - await writeProjectManifest(projectDir, emptyProjectManifest); + await writeProjectManifest( + projectDir, + options?.manifest ?? emptyProjectManifest + ); return projectDir; } diff --git a/test/e2e/unknown.e2e.ts b/test/e2e/unknown.test.ts similarity index 100% rename from test/e2e/unknown.e2e.ts rename to test/e2e/unknown.test.ts diff --git a/test/e2e/version.e2e.ts b/test/e2e/version.test.ts similarity index 100% rename from test/e2e/version.e2e.ts rename to test/e2e/version.test.ts diff --git a/test/e2e/view.test.ts b/test/e2e/view.test.ts new file mode 100644 index 00000000..56d1610b --- /dev/null +++ b/test/e2e/view.test.ts @@ -0,0 +1,55 @@ +import { ResultCodes } from "../../src/cli/result-codes"; +import { runOpenupm } from "./run"; +import { prepareHomeDirectory } from "./setup/directories"; + +describe("view packages", () => { + it("should fail if package version was specified", async () => { + const homeDir = await prepareHomeDirectory(); + + const output = await runOpenupm(homeDir, [ + "view", + "dev.comradevanti.opt-unity@2.0.0", + ]); + + expect(output.code).toEqual(ResultCodes.Error); + expect(output.stdErr).toEqual( + expect.arrayContaining([ + expect.stringContaining("please do not specify a version"), + ]) + ); + }); + + it("should fail if package is not found", async () => { + const homeDir = await prepareHomeDirectory(); + + const output = await runOpenupm(homeDir, [ + "view", + "not.existent.package.123456", + ]); + + expect(output.code).toEqual(ResultCodes.Error); + expect(output.stdErr).toEqual( + expect.arrayContaining([expect.stringContaining("could not be found")]) + ); + }); + + it("should print package information", async () => { + const homeDir = await prepareHomeDirectory(); + + const output = await runOpenupm(homeDir, [ + "view", + "dev.comradevanti.opt-unity", + // We need to disable color, otherwise chalk will mess with the output + "--no-color" + ]); + + expect(output.code).toEqual(ResultCodes.Ok); + expect(output.stdErr).toEqual( + expect.arrayContaining([ + expect.stringContaining( + "dev.comradevanti.opt-unity@3.5.0 | Unlicense | versions: 10" + ), + ]) + ); + }); +}); diff --git a/test/domain/project-manifest-assertions.ts b/test/project-manifest-assertions.ts similarity index 85% rename from test/domain/project-manifest-assertions.ts rename to test/project-manifest-assertions.ts index aca0e1b3..92d113ed 100644 --- a/test/domain/project-manifest-assertions.ts +++ b/test/project-manifest-assertions.ts @@ -1,7 +1,7 @@ -import { DomainName } from "../../src/domain/domain-name"; -import { SemanticVersion } from "../../src/domain/semantic-version"; -import { PackageUrl } from "../../src/domain/package-url"; -import { UnityProjectManifest } from "../../src/domain/project-manifest"; +import { DomainName } from "../src/domain/domain-name"; +import { SemanticVersion } from "../src/domain/semantic-version"; +import { PackageUrl } from "../src/domain/package-url"; +import { UnityProjectManifest } from "../src/domain/project-manifest"; expect.extend({ toHaveDependency( diff --git a/test/cli/cmd-add.test.ts b/test/unit/cli/cmd-add.test.ts similarity index 90% rename from test/cli/cmd-add.test.ts rename to test/unit/cli/cmd-add.test.ts index cf179642..c253b297 100644 --- a/test/cli/cmd-add.test.ts +++ b/test/unit/cli/cmd-add.test.ts @@ -3,34 +3,34 @@ import { makeAddCmd, PackageIncompatibleError, UnresolvedDependenciesError, -} from "../../src/cli/cmd-add"; -import { ResultCodes } from "../../src/cli/result-codes"; -import { PackumentNotFoundError } from "../../src/common-errors"; +} from "../../../src/cli/cmd-add"; +import { ResultCodes } from "../../../src/cli/result-codes"; +import { PackumentNotFoundError } from "../../../src/common-errors"; import { makeGraphFromSeed, markFailed, markRemoteResolved, -} from "../../src/domain/dependency-graph"; -import { DomainName } from "../../src/domain/domain-name"; -import { makeEditorVersion } from "../../src/domain/editor-version"; -import { UnityPackumentVersion } from "../../src/domain/packument"; -import { emptyProjectManifest } from "../../src/domain/project-manifest"; -import { unityRegistryUrl } from "../../src/domain/registry-url"; -import { SemanticVersion } from "../../src/domain/semantic-version"; +} from "../../../src/domain/dependency-graph"; +import { DomainName } from "../../../src/domain/domain-name"; +import { makeEditorVersion } from "../../../src/domain/editor-version"; +import { UnityPackumentVersion } from "../../../src/domain/packument"; +import { emptyProjectManifest } from "../../../src/domain/project-manifest"; +import { unityRegistryUrl } from "../../../src/domain/registry-url"; +import { SemanticVersion } from "../../../src/domain/semantic-version"; import { LoadProjectManifest, SaveProjectManifest, -} from "../../src/io/project-manifest-io"; -import { noopLogger } from "../../src/logging"; -import { ResolveDependencies } from "../../src/services/dependency-resolving"; -import { DetermineEditorVersion } from "../../src/services/determine-editor-version"; -import { GetRegistryAuth } from "../../src/services/get-registry-auth"; +} from "../../../src/io/project-manifest-io"; +import { noopLogger } from "../../../src/logging"; +import { ResolveDependencies } from "../../../src/services/dependency-resolving"; +import { DetermineEditorVersion } from "../../../src/services/determine-editor-version"; +import { GetRegistryAuth } from "../../../src/services/get-registry-auth"; import { GetRegistryPackumentVersion, ResolvedPackumentVersion, -} from "../../src/services/get-registry-packument-version"; -import { Env, ParseEnv } from "../../src/services/parse-env"; -import { AsyncErr, AsyncOk } from "../../src/utils/result-utils"; +} from "../../../src/services/get-registry-packument-version"; +import { Env, ParseEnv } from "../../../src/services/parse-env"; +import { AsyncErr, AsyncOk } from "../../../src/utils/result-utils"; import { buildPackument } from "../domain/data-packument"; import { buildProjectManifest } from "../domain/data-project-manifest"; import { exampleRegistryUrl } from "../domain/data-registry"; diff --git a/test/cli/dependency-logging.test.ts b/test/unit/cli/dependency-logging.test.ts similarity index 91% rename from test/cli/dependency-logging.test.ts rename to test/unit/cli/dependency-logging.test.ts index 23b9bd93..c04b23c4 100644 --- a/test/cli/dependency-logging.test.ts +++ b/test/unit/cli/dependency-logging.test.ts @@ -3,15 +3,15 @@ import { markBuiltInResolved, markFailed, markRemoteResolved, -} from "../../src/domain/dependency-graph"; -import { stringifyDependencyGraph } from "../../src/cli/dependency-logging"; -import { makePackageReference } from "../../src/domain/package-reference"; +} from "../../../src/domain/dependency-graph"; +import { stringifyDependencyGraph } from "../../../src/cli/dependency-logging"; +import { makePackageReference } from "../../../src/domain/package-reference"; import { exampleRegistryUrl } from "../domain/data-registry"; -import { PackumentNotFoundError } from "../../src/common-errors"; -import { unityRegistryUrl } from "../../src/domain/registry-url"; -import { VersionNotFoundError } from "../../src/domain/packument"; -import { DomainName } from "../../src/domain/domain-name"; -import { SemanticVersion } from "../../src/domain/semantic-version"; +import { PackumentNotFoundError } from "../../../src/common-errors"; +import { unityRegistryUrl } from "../../../src/domain/registry-url"; +import { VersionNotFoundError } from "../../../src/domain/packument"; +import { DomainName } from "../../../src/domain/domain-name"; +import { SemanticVersion } from "../../../src/domain/semantic-version"; describe("dependency-logging", () => { describe("graph", () => { diff --git a/test/cli/log.mock.ts b/test/unit/cli/log.mock.ts similarity index 100% rename from test/cli/log.mock.ts rename to test/unit/cli/log.mock.ts diff --git a/test/domain/base64.test.ts b/test/unit/domain/base64.test.ts similarity index 81% rename from test/domain/base64.test.ts rename to test/unit/domain/base64.test.ts index 39f57672..a98d7200 100644 --- a/test/domain/base64.test.ts +++ b/test/unit/domain/base64.test.ts @@ -1,4 +1,4 @@ -import { decodeBase64, encodeBase64 } from "../../src/domain/base64"; +import { decodeBase64, encodeBase64 } from "../../../src/domain/base64"; import fc from "fast-check"; describe("base 64", () => { diff --git a/test/domain/data-packument.ts b/test/unit/domain/data-packument.ts similarity index 92% rename from test/domain/data-packument.ts rename to test/unit/domain/data-packument.ts index 1823b5ed..f402e868 100644 --- a/test/domain/data-packument.ts +++ b/test/unit/domain/data-packument.ts @@ -1,11 +1,11 @@ import assert from "assert"; -import { DomainName } from "../../src/domain/domain-name"; -import { SemanticVersion } from "../../src/domain/semantic-version"; +import { DomainName } from "../../../src/domain/domain-name"; +import { SemanticVersion } from "../../../src/domain/semantic-version"; import { UnityPackument, UnityPackumentVersion, -} from "../../src/domain/packument"; -import { isZod } from "../../src/utils/zod-utils"; +} from "../../../src/domain/packument"; +import { isZod } from "../../../src/utils/zod-utils"; /** * Builder class for {@link UnityPackumentVersion}. diff --git a/test/domain/data-project-manifest.ts b/test/unit/domain/data-project-manifest.ts similarity index 87% rename from test/domain/data-project-manifest.ts rename to test/unit/domain/data-project-manifest.ts index 0e515cc4..1dcdd1e7 100644 --- a/test/domain/data-project-manifest.ts +++ b/test/unit/domain/data-project-manifest.ts @@ -1,15 +1,15 @@ -import { SemanticVersion } from "../../src/domain/semantic-version"; -import { addScope, makeScopedRegistry } from "../../src/domain/scoped-registry"; +import { SemanticVersion } from "../../../src/domain/semantic-version"; +import { addScope, makeScopedRegistry } from "../../../src/domain/scoped-registry"; import { addTestable, emptyProjectManifest, mapScopedRegistry, setDependency, UnityProjectManifest, -} from "../../src/domain/project-manifest"; +} from "../../../src/domain/project-manifest"; import { exampleRegistryUrl } from "./data-registry"; -import { assertZod } from "../../src/utils/zod-utils"; -import { DomainName } from "../../src/domain/domain-name"; +import { assertZod } from "../../../src/utils/zod-utils"; +import { DomainName } from "../../../src/domain/domain-name"; /** * Builder class for {@link UnityProjectManifest}. diff --git a/test/domain/data-registry.ts b/test/unit/domain/data-registry.ts similarity index 54% rename from test/domain/data-registry.ts rename to test/unit/domain/data-registry.ts index a2e0581c..209f9669 100644 --- a/test/domain/data-registry.ts +++ b/test/unit/domain/data-registry.ts @@ -1,3 +1,3 @@ -import { RegistryUrl } from "../../src/domain/registry-url"; +import { RegistryUrl } from "../../../src/domain/registry-url"; export const exampleRegistryUrl = RegistryUrl.parse("https://example.com"); diff --git a/test/domain/dependency-graph.test.ts b/test/unit/domain/dependency-graph.test.ts similarity index 95% rename from test/domain/dependency-graph.test.ts rename to test/unit/domain/dependency-graph.test.ts index ecb8f0fa..e694ef4d 100644 --- a/test/domain/dependency-graph.test.ts +++ b/test/unit/domain/dependency-graph.test.ts @@ -1,5 +1,5 @@ -import { DomainName } from "../../src/domain/domain-name"; -import { SemanticVersion } from "../../src/domain/semantic-version"; +import { DomainName } from "../../../src/domain/domain-name"; +import { SemanticVersion } from "../../../src/domain/semantic-version"; import { graphNodeCount, makeGraphFromSeed, @@ -10,9 +10,9 @@ import { traverseDependencyGraph, tryGetGraphNode, tryGetNextUnresolved, -} from "../../src/domain/dependency-graph"; +} from "../../../src/domain/dependency-graph"; import { exampleRegistryUrl } from "./data-registry"; -import { PackumentNotFoundError } from "../../src/common-errors"; +import { PackumentNotFoundError } from "../../../src/common-errors"; describe("dependency graph", () => { const somePackage = DomainName.parse("com.some.package"); diff --git a/test/domain/domain-name.arb.ts b/test/unit/domain/domain-name.arb.ts similarity index 94% rename from test/domain/domain-name.arb.ts rename to test/unit/domain/domain-name.arb.ts index 154a4d54..51bb8cb5 100644 --- a/test/domain/domain-name.arb.ts +++ b/test/unit/domain/domain-name.arb.ts @@ -1,5 +1,5 @@ import fc from "fast-check"; -import { DomainName } from "../../src/domain/domain-name"; +import { DomainName } from "../../../src/domain/domain-name"; /** * Single char string [A-Z]. diff --git a/test/domain/domain-name.test.ts b/test/unit/domain/domain-name.test.ts similarity index 84% rename from test/domain/domain-name.test.ts rename to test/unit/domain/domain-name.test.ts index 75a44bb8..89c39265 100644 --- a/test/domain/domain-name.test.ts +++ b/test/unit/domain/domain-name.test.ts @@ -1,5 +1,5 @@ -import { isZod } from "../../src/utils/zod-utils"; -import { DomainName } from "../../src/domain/domain-name"; +import { isZod } from "../../../src/utils/zod-utils"; +import { DomainName } from "../../../src/domain/domain-name"; describe("domain-name", () => { describe("validation", () => { diff --git a/test/domain/editor-version.test.ts b/test/unit/domain/editor-version.test.ts similarity index 98% rename from test/domain/editor-version.test.ts rename to test/unit/domain/editor-version.test.ts index d278b989..c1bcbf47 100644 --- a/test/domain/editor-version.test.ts +++ b/test/unit/domain/editor-version.test.ts @@ -1,7 +1,7 @@ import { compareEditorVersion, tryParseEditorVersion, -} from "../../src/domain/editor-version"; +} from "../../../src/domain/editor-version"; import assert from "assert"; diff --git a/test/domain/npmrc.test.ts b/test/unit/domain/npmrc.test.ts similarity index 95% rename from test/domain/npmrc.test.ts rename to test/unit/domain/npmrc.test.ts index 219da751..97015f5e 100644 --- a/test/domain/npmrc.test.ts +++ b/test/unit/domain/npmrc.test.ts @@ -1,4 +1,4 @@ -import { emptyNpmrc, setToken } from "../../src/domain/npmrc"; +import { emptyNpmrc, setToken } from "../../../src/domain/npmrc"; import { exampleRegistryUrl } from "./data-registry"; diff --git a/test/domain/package-id.test.ts b/test/unit/domain/package-id.test.ts similarity index 93% rename from test/domain/package-id.test.ts rename to test/unit/domain/package-id.test.ts index b9012fca..eeb36753 100644 --- a/test/domain/package-id.test.ts +++ b/test/unit/domain/package-id.test.ts @@ -1,4 +1,4 @@ -import { isPackageId } from "../../src/domain/package-id"; +import { isPackageId } from "../../../src/domain/package-id"; import fc from "fast-check"; import { arbDomainName } from "./domain-name.arb"; diff --git a/test/domain/package-manifest.test.ts b/test/unit/domain/package-manifest.test.ts similarity index 91% rename from test/domain/package-manifest.test.ts rename to test/unit/domain/package-manifest.test.ts index b8876c54..a737d16b 100644 --- a/test/domain/package-manifest.test.ts +++ b/test/unit/domain/package-manifest.test.ts @@ -1,9 +1,9 @@ import { dependenciesOf, tryGetTargetEditorVersionFor, -} from "../../src/domain/package-manifest"; -import { makeEditorVersion } from "../../src/domain/editor-version"; -import { MalformedPackumentError } from "../../src/common-errors"; +} from "../../../src/domain/package-manifest"; +import { makeEditorVersion } from "../../../src/domain/editor-version"; +import { MalformedPackumentError } from "../../../src/common-errors"; describe("package manifest", () => { describe("get dependency list", () => { diff --git a/test/domain/package-reference.test.ts b/test/unit/domain/package-reference.test.ts similarity index 98% rename from test/domain/package-reference.test.ts rename to test/unit/domain/package-reference.test.ts index 1c050663..696132ba 100644 --- a/test/domain/package-reference.test.ts +++ b/test/unit/domain/package-reference.test.ts @@ -2,7 +2,7 @@ import { isPackageReference, makePackageReference, splitPackageReference, -} from "../../src/domain/package-reference"; +} from "../../../src/domain/package-reference"; import fc from "fast-check"; import { arbDomainName } from "./domain-name.arb"; diff --git a/test/domain/package-url.test.ts b/test/unit/domain/package-url.test.ts similarity index 92% rename from test/domain/package-url.test.ts rename to test/unit/domain/package-url.test.ts index 125322e5..31e9d033 100644 --- a/test/domain/package-url.test.ts +++ b/test/unit/domain/package-url.test.ts @@ -1,7 +1,7 @@ -import { PackageUrl } from "../../src/domain/package-url"; +import { PackageUrl } from "../../../src/domain/package-url"; import fc from "fast-check"; import { arbDomainName } from "./domain-name.arb"; -import { isZod } from "../../src/utils/zod-utils"; +import { isZod } from "../../../src/utils/zod-utils"; describe("package-url", () => { describe("validation", () => { diff --git a/test/domain/packument.test.ts b/test/unit/domain/packument.test.ts similarity index 95% rename from test/domain/packument.test.ts rename to test/unit/domain/packument.test.ts index e54881e3..1cc4cf63 100644 --- a/test/domain/packument.test.ts +++ b/test/unit/domain/packument.test.ts @@ -4,10 +4,10 @@ import { tryGetPackumentVersion, tryResolvePackumentVersion, VersionNotFoundError, -} from "../../src/domain/packument"; -import { SemanticVersion } from "../../src/domain/semantic-version"; +} from "../../../src/domain/packument"; +import { SemanticVersion } from "../../../src/domain/semantic-version"; import { buildPackument } from "./data-packument"; -import { DomainName } from "../../src/domain/domain-name"; +import { DomainName } from "../../../src/domain/domain-name"; describe("packument", () => { describe("get latest version", () => { diff --git a/test/domain/project-manifest.test.ts b/test/unit/domain/project-manifest.test.ts similarity index 95% rename from test/domain/project-manifest.test.ts rename to test/unit/domain/project-manifest.test.ts index 5c4359cc..ac4f22b3 100644 --- a/test/domain/project-manifest.test.ts +++ b/test/unit/domain/project-manifest.test.ts @@ -7,15 +7,15 @@ import { setDependency, setScopedRegistry, tryGetScopedRegistryByUrl, -} from "../../src/domain/project-manifest"; -import { DomainName } from "../../src/domain/domain-name"; -import { SemanticVersion } from "../../src/domain/semantic-version"; -import { addScope, makeScopedRegistry } from "../../src/domain/scoped-registry"; +} from "../../../src/domain/project-manifest"; +import { DomainName } from "../../../src/domain/domain-name"; +import { SemanticVersion } from "../../../src/domain/semantic-version"; +import { addScope, makeScopedRegistry } from "../../../src/domain/scoped-registry"; import fc from "fast-check"; import { arbDomainName } from "./domain-name.arb"; import { exampleRegistryUrl } from "./data-registry"; import { buildProjectManifest } from "./data-project-manifest"; -import { RegistryUrl } from "../../src/domain/registry-url"; +import { RegistryUrl } from "../../../src/domain/registry-url"; describe("project-manifest", () => { describe("set dependency", () => { diff --git a/test/domain/registry-url.test.ts b/test/unit/domain/registry-url.test.ts similarity index 85% rename from test/domain/registry-url.test.ts rename to test/unit/domain/registry-url.test.ts index 81251739..c6352e0b 100644 --- a/test/domain/registry-url.test.ts +++ b/test/unit/domain/registry-url.test.ts @@ -1,5 +1,5 @@ -import { coerceRegistryUrl, RegistryUrl } from "../../src/domain/registry-url"; -import { isZod } from "../../src/utils/zod-utils"; +import { coerceRegistryUrl, RegistryUrl } from "../../../src/domain/registry-url"; +import { isZod } from "../../../src/utils/zod-utils"; describe("registry-url", () => { describe("validation", () => { diff --git a/test/domain/scoped-registry.test.ts b/test/unit/domain/scoped-registry.test.ts similarity index 94% rename from test/domain/scoped-registry.test.ts rename to test/unit/domain/scoped-registry.test.ts index bf12323b..3e93155b 100644 --- a/test/domain/scoped-registry.test.ts +++ b/test/unit/domain/scoped-registry.test.ts @@ -4,12 +4,12 @@ import { makeEmptyScopedRegistryFor, makeScopedRegistry, removeScope, -} from "../../src/domain/scoped-registry"; -import { DomainName } from "../../src/domain/domain-name"; +} from "../../../src/domain/scoped-registry"; +import { DomainName } from "../../../src/domain/domain-name"; import fc from "fast-check"; import { arbDomainName } from "./domain-name.arb"; import { exampleRegistryUrl } from "./data-registry"; -import { unityRegistryUrl } from "../../src/domain/registry-url"; +import { unityRegistryUrl } from "../../../src/domain/registry-url"; describe("scoped-registry", () => { describe("construction", () => { diff --git a/test/domain/semantic-version.test.ts b/test/unit/domain/semantic-version.test.ts similarity index 75% rename from test/domain/semantic-version.test.ts rename to test/unit/domain/semantic-version.test.ts index 9d847e4e..0dffa320 100644 --- a/test/domain/semantic-version.test.ts +++ b/test/unit/domain/semantic-version.test.ts @@ -1,5 +1,5 @@ -import { SemanticVersion } from "../../src/domain/semantic-version"; -import { isZod } from "../../src/utils/zod-utils"; +import { SemanticVersion } from "../../../src/domain/semantic-version"; +import { isZod } from "../../../src/utils/zod-utils"; describe("semantic-version", () => { describe("validate", () => { diff --git a/test/domain/upmconfig.test.ts b/test/unit/domain/upmconfig.test.ts similarity index 97% rename from test/domain/upmconfig.test.ts rename to test/unit/domain/upmconfig.test.ts index e1093470..d02b661f 100644 --- a/test/domain/upmconfig.test.ts +++ b/test/unit/domain/upmconfig.test.ts @@ -2,7 +2,7 @@ import path from "path"; import { getUserUpmConfigPathFor, NoSystemUserProfilePath, -} from "../../src/domain/upm-config"; +} from "../../../src/domain/upm-config"; describe("upm config", () => { describe("get user config path", () => { diff --git a/test/io/all-packuments-io.test.ts b/test/unit/io/all-packuments-io.test.ts similarity index 87% rename from test/io/all-packuments-io.test.ts rename to test/unit/io/all-packuments-io.test.ts index 4ad6126f..8d284590 100644 --- a/test/io/all-packuments-io.test.ts +++ b/test/unit/io/all-packuments-io.test.ts @@ -1,11 +1,11 @@ import npmFetch from "npm-registry-fetch"; -import { Registry } from "../../src/domain/registry"; -import { getAllRegistryPackumentsUsing } from "../../src/io/all-packuments-io"; +import { Registry } from "../../../src/domain/registry"; +import { getAllRegistryPackumentsUsing } from "../../../src/io/all-packuments-io"; import { HttpErrorLike, RegistryAuthenticationError, -} from "../../src/io/common-errors"; -import { noopLogger } from "../../src/logging"; +} from "../../../src/io/common-errors"; +import { noopLogger } from "../../../src/logging"; import { exampleRegistryUrl } from "../domain/data-registry"; jest.mock("npm-registry-fetch"); diff --git a/test/io/builtin-packages.test.ts b/test/unit/io/builtin-packages.test.ts similarity index 87% rename from test/io/builtin-packages.test.ts rename to test/unit/io/builtin-packages.test.ts index 49e740bc..6e42257b 100644 --- a/test/io/builtin-packages.test.ts +++ b/test/unit/io/builtin-packages.test.ts @@ -1,15 +1,15 @@ -import * as specialPaths from "../../src/io/special-paths"; -import { OSNotSupportedError } from "../../src/io/special-paths"; import { Err, Ok } from "ts-results-es"; +import { makeEditorVersion } from "../../../src/domain/editor-version"; import { EditorNotInstalledError, makeFindBuiltInPackages, -} from "../../src/io/builtin-packages"; -import { makeEditorVersion } from "../../src/domain/editor-version"; -import { noopLogger } from "../../src/logging"; -import { eaccesError, enoentError } from "./node-error.mock"; +} from "../../../src/io/builtin-packages"; +import { GetDirectoriesIn } from "../../../src/io/directory-io"; +import * as specialPaths from "../../../src/io/special-paths"; +import { OSNotSupportedError } from "../../../src/io/special-paths"; +import { noopLogger } from "../../../src/logging"; import { mockService } from "../services/service.mock"; -import { GetDirectoriesIn } from "../../src/io/directory-io"; +import { eaccesError, enoentError } from "./node-error.mock"; function makeDependencies() { const getDirectoriesIn = mockService(); diff --git a/test/io/check-url.test.ts b/test/unit/io/check-url.test.ts similarity index 93% rename from test/io/check-url.test.ts rename to test/unit/io/check-url.test.ts index a8a20abf..7ccf2648 100644 --- a/test/io/check-url.test.ts +++ b/test/unit/io/check-url.test.ts @@ -1,5 +1,5 @@ import fetch, { Response } from "node-fetch"; -import { CheckUrlIsOk } from "../../src/io/check-url"; +import { CheckUrlIsOk } from "../../../src/io/check-url"; jest.mock("node-fetch"); diff --git a/test/io/common-errors.test.ts b/test/unit/io/common-errors.test.ts similarity index 96% rename from test/io/common-errors.test.ts rename to test/unit/io/common-errors.test.ts index 5bd6f349..2de6d4d8 100644 --- a/test/io/common-errors.test.ts +++ b/test/unit/io/common-errors.test.ts @@ -2,7 +2,7 @@ import { makeRegistryInteractionError, HttpErrorLike, RegistryAuthenticationError, -} from "../../src/io/common-errors"; +} from "../../../src/io/common-errors"; import { exampleRegistryUrl } from "../domain/data-registry"; describe("common error utilities", () => { diff --git a/test/io/directory-io.test.ts b/test/unit/io/directory-io.test.ts similarity index 95% rename from test/io/directory-io.test.ts rename to test/unit/io/directory-io.test.ts index b6e16ecb..5f4a6b94 100644 --- a/test/io/directory-io.test.ts +++ b/test/unit/io/directory-io.test.ts @@ -1,7 +1,7 @@ import { makeNodeError } from "./node-error.mock"; import fs from "fs/promises"; import { Dirent } from "node:fs"; -import { makeGetDirectoriesIn } from "../../src/io/directory-io"; +import { makeGetDirectoriesIn } from "../../../src/io/directory-io"; describe("directory io", () => { function makeDependencies() { diff --git a/test/io/node-error.mock.ts b/test/unit/io/node-error.mock.ts similarity index 100% rename from test/io/node-error.mock.ts rename to test/unit/io/node-error.mock.ts diff --git a/test/io/npm-registry.test.ts b/test/unit/io/npm-registry.test.ts similarity index 94% rename from test/io/npm-registry.test.ts rename to test/unit/io/npm-registry.test.ts index e73a7c15..5553fbe2 100644 --- a/test/io/npm-registry.test.ts +++ b/test/unit/io/npm-registry.test.ts @@ -1,6 +1,6 @@ import { userInfo } from "os"; -import { Registry } from "../../src/domain/registry"; -import { makeNpmFetchOptions } from "../../src/io/npm-registry"; +import { Registry } from "../../../src/domain/registry"; +import { makeNpmFetchOptions } from "../../../src/io/npm-registry"; import { exampleRegistryUrl } from "../domain/data-registry"; import { Options as FetchOptions } from "npm-registry-fetch"; import { NpmAuth } from "another-npm-registry-client"; diff --git a/test/io/npm-search.test.ts b/test/unit/io/npm-search.test.ts similarity index 88% rename from test/io/npm-search.test.ts rename to test/unit/io/npm-search.test.ts index adaabd93..e5a98ef6 100644 --- a/test/io/npm-search.test.ts +++ b/test/unit/io/npm-search.test.ts @@ -1,11 +1,11 @@ import { default as npmSearch, default as search } from "libnpmsearch"; -import { Registry } from "../../src/domain/registry"; +import { Registry } from "../../../src/domain/registry"; import { HttpErrorLike, RegistryAuthenticationError, -} from "../../src/io/common-errors"; -import { searchRegistryUsing } from "../../src/io/npm-search"; -import { noopLogger } from "../../src/logging"; +} from "../../../src/io/common-errors"; +import { searchRegistryUsing } from "../../../src/io/npm-search"; +import { noopLogger } from "../../../src/logging"; import { exampleRegistryUrl } from "../domain/data-registry"; jest.mock("libnpmsearch"); diff --git a/test/io/npmrc-io.test.ts b/test/unit/io/npmrc-io.test.ts similarity index 95% rename from test/io/npmrc-io.test.ts rename to test/unit/io/npmrc-io.test.ts index ff786d8a..36ad4fa8 100644 --- a/test/io/npmrc-io.test.ts +++ b/test/unit/io/npmrc-io.test.ts @@ -4,8 +4,8 @@ import { FindNpmrcInHome, ReadNpmrcFile, WriteNpmrcPath as WriteNpmrcFile, -} from "../../src/io/npmrc-io"; -import { ReadTextFile, WriteTextFile } from "../../src/io/text-file-io"; +} from "../../../src/io/npmrc-io"; +import { ReadTextFile, WriteTextFile } from "../../../src/io/text-file-io"; import { mockService } from "../services/service.mock"; describe("npmrc-io", () => { diff --git a/test/io/packument-io.test.ts b/test/unit/io/packument-io.test.ts similarity index 89% rename from test/io/packument-io.test.ts rename to test/unit/io/packument-io.test.ts index b6415cc5..e6656caa 100644 --- a/test/io/packument-io.test.ts +++ b/test/unit/io/packument-io.test.ts @@ -1,8 +1,8 @@ import RegClient from "another-npm-registry-client"; -import { DomainName } from "../../src/domain/domain-name"; -import { Registry } from "../../src/domain/registry"; -import { getRegistryPackumentUsing } from "../../src/io/packument-io"; -import { noopLogger } from "../../src/logging"; +import { DomainName } from "../../../src/domain/domain-name"; +import { Registry } from "../../../src/domain/registry"; +import { getRegistryPackumentUsing } from "../../../src/io/packument-io"; +import { noopLogger } from "../../../src/logging"; import { buildPackument } from "../domain/data-packument"; import { exampleRegistryUrl } from "../domain/data-registry"; import { mockRegClientGetResult } from "../services/registry-client.mock"; diff --git a/test/io/project-manifest-io.test.ts b/test/unit/io/project-manifest-io.test.ts similarity index 89% rename from test/io/project-manifest-io.test.ts rename to test/unit/io/project-manifest-io.test.ts index abb74e53..1edbc6c8 100644 --- a/test/io/project-manifest-io.test.ts +++ b/test/unit/io/project-manifest-io.test.ts @@ -5,19 +5,19 @@ import { ManifestMissingError, manifestPathFor, serializeProjectManifest, -} from "../../src/io/project-manifest-io"; +} from "../../../src/io/project-manifest-io"; import { mapScopedRegistry, UnityProjectManifest, -} from "../../src/domain/project-manifest"; +} from "../../../src/domain/project-manifest"; import path from "path"; -import { ReadTextFile, WriteTextFile } from "../../src/io/text-file-io"; +import { ReadTextFile, WriteTextFile } from "../../../src/io/text-file-io"; import { buildProjectManifest } from "../domain/data-project-manifest"; -import { DomainName } from "../../src/domain/domain-name"; -import { removeScope } from "../../src/domain/scoped-registry"; +import { DomainName } from "../../../src/domain/domain-name"; +import { removeScope } from "../../../src/domain/scoped-registry"; import { exampleRegistryUrl } from "../domain/data-registry"; import { mockService } from "../services/service.mock"; -import { noopLogger } from "../../src/logging"; +import { noopLogger } from "../../../src/logging"; const exampleProjectPath = "/some/path"; describe("project-manifest io", () => { diff --git a/test/io/project-version-io.mock.ts b/test/unit/io/project-version-io.mock.ts similarity index 84% rename from test/io/project-version-io.mock.ts rename to test/unit/io/project-version-io.mock.ts index 6f6054da..6cf28fe4 100644 --- a/test/io/project-version-io.mock.ts +++ b/test/unit/io/project-version-io.mock.ts @@ -1,8 +1,8 @@ import { ReleaseVersion, stringifyEditorVersion, -} from "../../src/domain/editor-version"; -import { GetProjectVersion } from "../../src/io/project-version-io"; +} from "../../../src/domain/editor-version"; +import { GetProjectVersion } from "../../../src/io/project-version-io"; /** * Mocks return values for calls to a {@link GetProjectVersion} function. diff --git a/test/io/project-version-io.test.ts b/test/unit/io/project-version-io.test.ts similarity index 92% rename from test/io/project-version-io.test.ts rename to test/unit/io/project-version-io.test.ts index 60094196..2f417f65 100644 --- a/test/io/project-version-io.test.ts +++ b/test/unit/io/project-version-io.test.ts @@ -2,9 +2,9 @@ import { ProjectVersionMalformedError, ProjectVersionMissingError, ReadProjectVersionFile, -} from "../../src/io/project-version-io"; -import { ReadTextFile } from "../../src/io/text-file-io"; -import { noopLogger } from "../../src/logging"; +} from "../../../src/io/project-version-io"; +import { ReadTextFile } from "../../../src/io/text-file-io"; +import { noopLogger } from "../../../src/logging"; import { mockService } from "../services/service.mock"; describe("project-version-io", () => { diff --git a/test/io/special-paths.test.ts b/test/unit/io/special-paths.test.ts similarity index 94% rename from test/io/special-paths.test.ts rename to test/unit/io/special-paths.test.ts index cffc990e..f0c221ca 100644 --- a/test/io/special-paths.test.ts +++ b/test/unit/io/special-paths.test.ts @@ -1,14 +1,14 @@ import os from "os"; import path from "path"; -import { EditorVersionNotSupportedError } from "../../src/common-errors"; -import { makeEditorVersion } from "../../src/domain/editor-version"; +import { EditorVersionNotSupportedError } from "../../../src/common-errors"; +import { makeEditorVersion } from "../../../src/domain/editor-version"; import { getHomePathFromEnv, NoHomePathError, OSNotSupportedError, tryGetEditorInstallPath, VersionNotSupportedOnOsError, -} from "../../src/io/special-paths"; +} from "../../../src/io/special-paths"; describe("special-paths", () => { describe("home from env", () => { diff --git a/test/io/text-file-io.test.ts b/test/unit/io/text-file-io.test.ts similarity index 97% rename from test/io/text-file-io.test.ts rename to test/unit/io/text-file-io.test.ts index 0a29fb7a..87654d6c 100644 --- a/test/io/text-file-io.test.ts +++ b/test/unit/io/text-file-io.test.ts @@ -1,6 +1,6 @@ import fse from "fs-extra"; import fs from "fs/promises"; -import { readTextFile, writeTextFile } from "../../src/io/text-file-io"; +import { readTextFile, writeTextFile } from "../../../src/io/text-file-io"; import { makeNodeError } from "./node-error.mock"; describe("text file io", () => { diff --git a/test/io/upm-config-io.test.ts b/test/unit/io/upm-config-io.test.ts similarity index 94% rename from test/io/upm-config-io.test.ts rename to test/unit/io/upm-config-io.test.ts index 10ec907c..9cb5a0b0 100644 --- a/test/io/upm-config-io.test.ts +++ b/test/unit/io/upm-config-io.test.ts @@ -1,10 +1,10 @@ import { EOL } from "node:os"; -import { ReadTextFile } from "../../src/io/text-file-io"; -import { ReadUpmConfigFile } from "../../src/io/upm-config-io"; +import { ReadTextFile } from "../../../src/io/text-file-io"; +import { ReadUpmConfigFile } from "../../../src/io/upm-config-io"; import { exampleRegistryUrl } from "../domain/data-registry"; import { mockService } from "../services/service.mock"; -jest.mock("../../src/utils/env-util"); +jest.mock("../../../src/utils/env-util"); describe("upm-config-io", () => { describe("read file", () => { diff --git a/test/services/built-in-package-check.test.ts b/test/unit/services/built-in-package-check.test.ts similarity index 81% rename from test/services/built-in-package-check.test.ts rename to test/unit/services/built-in-package-check.test.ts index 36298c15..96ded157 100644 --- a/test/services/built-in-package-check.test.ts +++ b/test/unit/services/built-in-package-check.test.ts @@ -1,9 +1,9 @@ -import { DomainName } from "../../src/domain/domain-name"; -import { UnityPackument } from "../../src/domain/packument"; -import { SemanticVersion } from "../../src/domain/semantic-version"; -import { GetRegistryPackument } from "../../src/io/packument-io"; -import { CheckIsNonRegistryUnityPackage } from "../../src/services/built-in-package-check"; -import { CheckIsUnityPackage } from "../../src/services/unity-package-check"; +import { DomainName } from "../../../src/domain/domain-name"; +import { UnityPackument } from "../../../src/domain/packument"; +import { SemanticVersion } from "../../../src/domain/semantic-version"; +import { GetRegistryPackument } from "../../../src/io/packument-io"; +import { CheckIsNonRegistryUnityPackage } from "../../../src/services/built-in-package-check"; +import { CheckIsUnityPackage } from "../../../src/services/unity-package-check"; import { mockService } from "./service.mock"; describe("is non-registry unity package", () => { diff --git a/test/services/dependency-resolving.test.ts b/test/unit/services/dependency-resolving.test.ts similarity index 89% rename from test/services/dependency-resolving.test.ts rename to test/unit/services/dependency-resolving.test.ts index 738b9462..2cb95443 100644 --- a/test/services/dependency-resolving.test.ts +++ b/test/unit/services/dependency-resolving.test.ts @@ -1,15 +1,15 @@ import { mockService } from "./service.mock"; -import { GetRegistryPackument } from "../../src/io/packument-io"; -import { CheckIsBuiltInPackage } from "../../src/services/built-in-package-check"; +import { GetRegistryPackument } from "../../../src/io/packument-io"; +import { CheckIsBuiltInPackage } from "../../../src/services/built-in-package-check"; import { exampleRegistryUrl } from "../domain/data-registry"; -import { unityRegistryUrl } from "../../src/domain/registry-url"; -import { DomainName } from "../../src/domain/domain-name"; -import { SemanticVersion } from "../../src/domain/semantic-version"; -import { Registry } from "../../src/domain/registry"; -import { NodeType, tryGetGraphNode } from "../../src/domain/dependency-graph"; -import { PackumentNotFoundError } from "../../src/common-errors"; -import { VersionNotFoundError } from "../../src/domain/packument"; -import { ResolveDependenciesFromRegistries } from "../../src/services/dependency-resolving"; +import { unityRegistryUrl } from "../../../src/domain/registry-url"; +import { DomainName } from "../../../src/domain/domain-name"; +import { SemanticVersion } from "../../../src/domain/semantic-version"; +import { Registry } from "../../../src/domain/registry"; +import { NodeType, tryGetGraphNode } from "../../../src/domain/dependency-graph"; +import { PackumentNotFoundError } from "../../../src/common-errors"; +import { VersionNotFoundError } from "../../../src/domain/packument"; +import { ResolveDependenciesFromRegistries } from "../../../src/services/dependency-resolving"; describe("dependency resolving", () => { const sources: Registry[] = [ diff --git a/test/services/determine-editor-version.test.ts b/test/unit/services/determine-editor-version.test.ts similarity index 87% rename from test/services/determine-editor-version.test.ts rename to test/unit/services/determine-editor-version.test.ts index 122abbb8..1f79cf14 100644 --- a/test/services/determine-editor-version.test.ts +++ b/test/unit/services/determine-editor-version.test.ts @@ -1,8 +1,8 @@ import { mockService } from "./service.mock"; -import { GetProjectVersion } from "../../src/io/project-version-io"; -import { makeEditorVersion } from "../../src/domain/editor-version"; +import { GetProjectVersion } from "../../../src/io/project-version-io"; +import { makeEditorVersion } from "../../../src/domain/editor-version"; import { mockProjectVersion } from "../io/project-version-io.mock"; -import { DetermineEditorVersionFromFile } from "../../src/services/determine-editor-version"; +import { DetermineEditorVersionFromFile } from "../../../src/services/determine-editor-version"; describe("determine editor version from file", () => { const exampleProjectPath = "/home/my-project/"; diff --git a/test/services/get-auth-token.test.ts b/test/unit/services/get-auth-token.test.ts similarity index 91% rename from test/services/get-auth-token.test.ts rename to test/unit/services/get-auth-token.test.ts index b51f32af..54f8895e 100644 --- a/test/services/get-auth-token.test.ts +++ b/test/unit/services/get-auth-token.test.ts @@ -2,9 +2,9 @@ import "assert"; import RegClient from "another-npm-registry-client"; import { exampleRegistryUrl } from "../domain/data-registry"; import { mockRegClientAddUserResult } from "./registry-client.mock"; -import { RegistryAuthenticationError } from "../../src/io/common-errors"; -import { noopLogger } from "../../src/logging"; -import { AuthenticateWithNpmRegistry } from "../../src/services/get-auth-token"; +import { RegistryAuthenticationError } from "../../../src/io/common-errors"; +import { noopLogger } from "../../../src/logging"; +import { AuthenticateWithNpmRegistry } from "../../../src/services/get-auth-token"; describe("authenticate user with npm registry", () => { function makeDependencies() { diff --git a/test/services/get-latest-version.test.ts b/test/unit/services/get-latest-version.test.ts similarity index 80% rename from test/services/get-latest-version.test.ts rename to test/unit/services/get-latest-version.test.ts index 01308aa6..415cfa6d 100644 --- a/test/services/get-latest-version.test.ts +++ b/test/unit/services/get-latest-version.test.ts @@ -1,9 +1,9 @@ -import { DomainName } from "../../src/domain/domain-name"; -import { UnityPackument } from "../../src/domain/packument"; -import { Registry } from "../../src/domain/registry"; -import { SemanticVersion } from "../../src/domain/semantic-version"; -import { GetRegistryPackument } from "../../src/io/packument-io"; -import { GetLatestVersionFromRegistryPackument } from "../../src/services/get-latest-version"; +import { DomainName } from "../../../src/domain/domain-name"; +import { UnityPackument } from "../../../src/domain/packument"; +import { Registry } from "../../../src/domain/registry"; +import { SemanticVersion } from "../../../src/domain/semantic-version"; +import { GetRegistryPackument } from "../../../src/io/packument-io"; +import { GetLatestVersionFromRegistryPackument } from "../../../src/services/get-latest-version"; import { exampleRegistryUrl } from "../domain/data-registry"; import { mockService } from "./service.mock"; diff --git a/test/services/get-registry-auth.test.ts b/test/unit/services/get-registry-auth.test.ts similarity index 92% rename from test/services/get-registry-auth.test.ts rename to test/unit/services/get-registry-auth.test.ts index b67cae4f..6675b231 100644 --- a/test/services/get-registry-auth.test.ts +++ b/test/unit/services/get-registry-auth.test.ts @@ -1,11 +1,11 @@ -import { Base64 } from "../../src/domain/base64"; +import { Base64 } from "../../../src/domain/base64"; import { openupmRegistryUrl, unityRegistryUrl, -} from "../../src/domain/registry-url"; -import { GetUpmConfigPath, LoadUpmConfig } from "../../src/io/upm-config-io"; -import { noopLogger } from "../../src/logging"; -import { LoadRegistryAuthFromUpmConfig } from "../../src/services/get-registry-auth"; +} from "../../../src/domain/registry-url"; +import { GetUpmConfigPath, LoadUpmConfig } from "../../../src/io/upm-config-io"; +import { noopLogger } from "../../../src/logging"; +import { LoadRegistryAuthFromUpmConfig } from "../../../src/services/get-registry-auth"; import { exampleRegistryUrl } from "../domain/data-registry"; import { mockService } from "./service.mock"; diff --git a/test/services/get-registry-packument-version.test.ts b/test/unit/services/get-registry-packument-version.test.ts similarity index 82% rename from test/services/get-registry-packument-version.test.ts rename to test/unit/services/get-registry-packument-version.test.ts index bac53e2b..34ab4a75 100644 --- a/test/services/get-registry-packument-version.test.ts +++ b/test/unit/services/get-registry-packument-version.test.ts @@ -1,9 +1,9 @@ -import { PackumentNotFoundError } from "../../src/common-errors"; -import { DomainName } from "../../src/domain/domain-name"; -import { Registry } from "../../src/domain/registry"; -import { SemanticVersion } from "../../src/domain/semantic-version"; -import { GetRegistryPackument } from "../../src/io/packument-io"; -import { FetchRegistryPackumentVersion } from "../../src/services/get-registry-packument-version"; +import { PackumentNotFoundError } from "../../../src/common-errors"; +import { DomainName } from "../../../src/domain/domain-name"; +import { Registry } from "../../../src/domain/registry"; +import { SemanticVersion } from "../../../src/domain/semantic-version"; +import { GetRegistryPackument } from "../../../src/io/packument-io"; +import { FetchRegistryPackumentVersion } from "../../../src/services/get-registry-packument-version"; import { buildPackument } from "../domain/data-packument"; import { exampleRegistryUrl } from "../domain/data-registry"; import { mockService } from "./service.mock"; diff --git a/test/services/login.test.ts b/test/unit/services/login.test.ts similarity index 86% rename from test/services/login.test.ts rename to test/unit/services/login.test.ts index e34c7edf..95bc3424 100644 --- a/test/services/login.test.ts +++ b/test/unit/services/login.test.ts @@ -1,8 +1,8 @@ -import { noopLogger } from "../../src/logging"; -import { GetAuthToken } from "../../src/services/get-auth-token"; -import { UpmConfigLogin } from "../../src/services/login"; -import { StoreNpmAuthToken } from "../../src/services/put-npm-auth-token"; -import { PutRegistryAuth } from "../../src/services/put-registry-auth"; +import { noopLogger } from "../../../src/logging"; +import { GetAuthToken } from "../../../src/services/get-auth-token"; +import { UpmConfigLogin } from "../../../src/services/login"; +import { StoreNpmAuthToken } from "../../../src/services/put-npm-auth-token"; +import { PutRegistryAuth } from "../../../src/services/put-registry-auth"; import { exampleRegistryUrl } from "../domain/data-registry"; import { mockService } from "./service.mock"; diff --git a/test/services/parse-env.test.ts b/test/unit/services/parse-env.test.ts similarity index 97% rename from test/services/parse-env.test.ts rename to test/unit/services/parse-env.test.ts index ca0078e3..65f44dfc 100644 --- a/test/services/parse-env.test.ts +++ b/test/unit/services/parse-env.test.ts @@ -1,6 +1,6 @@ import path from "path"; -import { openupmRegistryUrl } from "../../src/domain/registry-url"; -import { makeParseEnv } from "../../src/services/parse-env"; +import { openupmRegistryUrl } from "../../../src/domain/registry-url"; +import { makeParseEnv } from "../../../src/services/parse-env"; import { makeMockLogger } from "../cli/log.mock"; import { exampleRegistryUrl } from "../domain/data-registry"; diff --git a/test/services/put-npm-auth-token.test.ts b/test/unit/services/put-npm-auth-token.test.ts similarity index 90% rename from test/services/put-npm-auth-token.test.ts rename to test/unit/services/put-npm-auth-token.test.ts index 4f9d4bd2..28ec828d 100644 --- a/test/services/put-npm-auth-token.test.ts +++ b/test/unit/services/put-npm-auth-token.test.ts @@ -1,6 +1,6 @@ -import { emptyNpmrc, setToken } from "../../src/domain/npmrc"; -import { FindNpmrcPath, LoadNpmrc, SaveNpmrc } from "../../src/io/npmrc-io"; -import { StoreNpmAuthTokenInNpmrc as PutNpmAuthTokenInNpmrc } from "../../src/services/put-npm-auth-token"; +import { emptyNpmrc, setToken } from "../../../src/domain/npmrc"; +import { FindNpmrcPath, LoadNpmrc, SaveNpmrc } from "../../../src/io/npmrc-io"; +import { StoreNpmAuthTokenInNpmrc as PutNpmAuthTokenInNpmrc } from "../../../src/services/put-npm-auth-token"; import { exampleRegistryUrl } from "../domain/data-registry"; import { mockService } from "./service.mock"; diff --git a/test/services/put-registry-auth.test.ts b/test/unit/services/put-registry-auth.test.ts similarity index 95% rename from test/services/put-registry-auth.test.ts rename to test/unit/services/put-registry-auth.test.ts index 458d4dd0..fe58aab1 100644 --- a/test/services/put-registry-auth.test.ts +++ b/test/unit/services/put-registry-auth.test.ts @@ -1,8 +1,8 @@ import { mockService } from "./service.mock"; -import { LoadUpmConfig, SaveUpmConfig } from "../../src/io/upm-config-io"; +import { LoadUpmConfig, SaveUpmConfig } from "../../../src/io/upm-config-io"; import { exampleRegistryUrl } from "../domain/data-registry"; -import { Base64 } from "../../src/domain/base64"; -import { PutRegistryAuthIntoUpmConfig } from "../../src/services/put-registry-auth"; +import { Base64 } from "../../../src/domain/base64"; +import { PutRegistryAuthIntoUpmConfig } from "../../../src/services/put-registry-auth"; describe("put registry auth into upm config", () => { const someConfigPath = "/home/user/.upmconfig.toml"; diff --git a/test/services/registry-client.mock.ts b/test/unit/services/registry-client.mock.ts similarity index 90% rename from test/services/registry-client.mock.ts rename to test/unit/services/registry-client.mock.ts index a8426409..d781126c 100644 --- a/test/services/registry-client.mock.ts +++ b/test/unit/services/registry-client.mock.ts @@ -1,7 +1,7 @@ import RegClient, { AddUserResponse } from "another-npm-registry-client"; import { Response } from "request"; -import { UnityPackument } from "../../src/domain/packument"; -import { HttpErrorLike } from "../../src/io/common-errors"; +import { UnityPackument } from "../../../src/domain/packument"; +import { HttpErrorLike } from "../../../src/io/common-errors"; /** * Mocks the result of getting a package using a {@link RegClient.Instance}. diff --git a/test/services/remote-packuments.mock.ts b/test/unit/services/remote-packuments.mock.ts similarity index 78% rename from test/services/remote-packuments.mock.ts rename to test/unit/services/remote-packuments.mock.ts index 47cb49c3..f2ed7f99 100644 --- a/test/services/remote-packuments.mock.ts +++ b/test/unit/services/remote-packuments.mock.ts @@ -1,11 +1,11 @@ -import { PackumentNotFoundError } from "../../src/common-errors"; +import { PackumentNotFoundError } from "../../../src/common-errors"; import { tryResolvePackumentVersion, UnityPackument, -} from "../../src/domain/packument"; -import { RegistryUrl } from "../../src/domain/registry-url"; -import { GetRegistryPackumentVersion } from "../../src/services/get-registry-packument-version"; -import { AsyncErr } from "../../src/utils/result-utils"; +} from "../../../src/domain/packument"; +import { RegistryUrl } from "../../../src/domain/registry-url"; +import { GetRegistryPackumentVersion } from "../../../src/services/get-registry-packument-version"; +import { AsyncErr } from "../../../src/utils/result-utils"; type MockEntry = [RegistryUrl, UnityPackument]; diff --git a/test/services/remove-packages.test.ts b/test/unit/services/remove-packages.test.ts similarity index 93% rename from test/services/remove-packages.test.ts rename to test/unit/services/remove-packages.test.ts index fb7de970..e90c4a08 100644 --- a/test/services/remove-packages.test.ts +++ b/test/unit/services/remove-packages.test.ts @@ -1,14 +1,14 @@ import path from "path"; -import { PackumentNotFoundError } from "../../src/common-errors"; -import { DomainName } from "../../src/domain/domain-name"; +import { PackumentNotFoundError } from "../../../src/common-errors"; +import { DomainName } from "../../../src/domain/domain-name"; import { LoadProjectManifest, SaveProjectManifest, -} from "../../src/io/project-manifest-io"; +} from "../../../src/io/project-manifest-io"; import { RemovedPackage, RemovePackagesFromManifest, -} from "../../src/services/remove-packages"; +} from "../../../src/services/remove-packages"; import { buildProjectManifest } from "../domain/data-project-manifest"; import { mockService } from "./service.mock"; diff --git a/test/services/search-packages.test.ts b/test/unit/services/search-packages.test.ts similarity index 86% rename from test/services/search-packages.test.ts rename to test/unit/services/search-packages.test.ts index ca982454..293c4c16 100644 --- a/test/services/search-packages.test.ts +++ b/test/unit/services/search-packages.test.ts @@ -1,13 +1,13 @@ -import { DomainName } from "../../src/domain/domain-name"; -import { Registry } from "../../src/domain/registry"; -import { SemanticVersion } from "../../src/domain/semantic-version"; +import { DomainName } from "../../../src/domain/domain-name"; +import { Registry } from "../../../src/domain/registry"; +import { SemanticVersion } from "../../../src/domain/semantic-version"; import { AllPackuments, GetAllRegistryPackuments, -} from "../../src/io/all-packuments-io"; -import { SearchedPackument, SearchRegistry } from "../../src/io/npm-search"; -import { noopLogger } from "../../src/logging"; -import { ApiAndFallbackPackageSearch } from "../../src/services/search-packages"; +} from "../../../src/io/all-packuments-io"; +import { SearchedPackument, SearchRegistry } from "../../../src/io/npm-search"; +import { noopLogger } from "../../../src/logging"; +import { ApiAndFallbackPackageSearch } from "../../../src/services/search-packages"; import { exampleRegistryUrl } from "../domain/data-registry"; import { mockService } from "./service.mock"; diff --git a/test/services/service.mock.ts b/test/unit/services/service.mock.ts similarity index 100% rename from test/services/service.mock.ts rename to test/unit/services/service.mock.ts diff --git a/test/services/unity-package-check.test.ts b/test/unit/services/unity-package-check.test.ts similarity index 82% rename from test/services/unity-package-check.test.ts rename to test/unit/services/unity-package-check.test.ts index 2889d096..407fdeaf 100644 --- a/test/services/unity-package-check.test.ts +++ b/test/unit/services/unity-package-check.test.ts @@ -1,6 +1,6 @@ -import { DomainName } from "../../src/domain/domain-name"; -import { CheckUrlExists } from "../../src/io/check-url"; -import { PackageHasDocPage } from "../../src/services/unity-package-check"; +import { DomainName } from "../../../src/domain/domain-name"; +import { CheckUrlExists } from "../../../src/io/check-url"; +import { PackageHasDocPage } from "../../../src/services/unity-package-check"; import { mockService } from "./service.mock"; describe("unity package has doc page", () => { diff --git a/test/utils/sources.test.ts b/test/unit/utils/sources.test.ts similarity index 92% rename from test/utils/sources.test.ts rename to test/unit/utils/sources.test.ts index 3a02f93d..c3426ae1 100644 --- a/test/utils/sources.test.ts +++ b/test/unit/utils/sources.test.ts @@ -1,7 +1,7 @@ -import { queryAllRegistriesLazy } from "../../src/utils/sources"; -import { Registry } from "../../src/domain/registry"; +import { queryAllRegistriesLazy } from "../../../src/utils/sources"; +import { Registry } from "../../../src/domain/registry"; import { exampleRegistryUrl } from "../domain/data-registry"; -import { unityRegistryUrl } from "../../src/domain/registry-url"; +import { unityRegistryUrl } from "../../../src/domain/registry-url"; describe("sources", () => { describe("query registries lazy", () => { diff --git a/test/utils/zod-utils.ts b/test/unit/utils/zod-utils.ts similarity index 92% rename from test/utils/zod-utils.ts rename to test/unit/utils/zod-utils.ts index 4650cb9c..e5a6710b 100644 --- a/test/utils/zod-utils.ts +++ b/test/unit/utils/zod-utils.ts @@ -1,4 +1,4 @@ -import { removeExplicitUndefined } from "../../src/utils/zod-utils"; +import { removeExplicitUndefined } from "../../../src/utils/zod-utils"; describe("zod utils", () => { describe("remove explicit undefined", () => { From a0c6d4b84213f83aaf114da4e549e01c338ed871 Mon Sep 17 00:00:00 2001 From: Ramon Brullo Date: Sun, 25 Aug 2024 12:34:05 +0200 Subject: [PATCH 19/26] refactor: function mocking Was named service mocking but could really be used to mock functions of any type. --- test/unit/cli/cmd-add.test.ts | 16 ++++++++-------- test/unit/io/builtin-packages.test.ts | 4 ++-- test/unit/io/npmrc-io.test.ts | 6 +++--- test/unit/io/project-manifest-io.test.ts | 4 ++-- test/unit/io/project-version-io.test.ts | 4 ++-- test/unit/io/upm-config-io.test.ts | 4 ++-- .../unit/services/built-in-package-check.test.ts | 6 +++--- test/unit/services/dependency-resolving.test.ts | 6 +++--- .../services/determine-editor-version.test.ts | 4 ++-- .../services/{service.mock.ts => func.mock.ts} | 8 ++++---- test/unit/services/get-latest-version.test.ts | 4 ++-- test/unit/services/get-registry-auth.test.ts | 6 +++--- .../get-registry-packument-version.test.ts | 4 ++-- test/unit/services/login.test.ts | 8 ++++---- test/unit/services/put-npm-auth-token.test.ts | 8 ++++---- test/unit/services/put-registry-auth.test.ts | 6 +++--- test/unit/services/remove-packages.test.ts | 6 +++--- test/unit/services/search-packages.test.ts | 6 +++--- test/unit/services/unity-package-check.test.ts | 4 ++-- 19 files changed, 57 insertions(+), 57 deletions(-) rename test/unit/services/{service.mock.ts => func.mock.ts} (62%) diff --git a/test/unit/cli/cmd-add.test.ts b/test/unit/cli/cmd-add.test.ts index c253b297..bae31b63 100644 --- a/test/unit/cli/cmd-add.test.ts +++ b/test/unit/cli/cmd-add.test.ts @@ -35,7 +35,7 @@ import { buildPackument } from "../domain/data-packument"; import { buildProjectManifest } from "../domain/data-project-manifest"; import { exampleRegistryUrl } from "../domain/data-registry"; import { mockResolvedPackuments } from "../services/remote-packuments.mock"; -import { mockService } from "../services/service.mock"; +import { mockFunctionOfType } from "../services/func.mock"; import { makeMockLogger } from "./log.mock"; const somePackage = DomainName.parse("com.some.package"); @@ -68,18 +68,18 @@ const defaultEnv = { const someVersion = SemanticVersion.parse("1.0.0"); function makeDependencies() { - const parseEnv = mockService(); + const parseEnv = mockFunctionOfType(); parseEnv.mockResolvedValue(defaultEnv); const getRegistryPackumentVersion = - mockService(); + mockFunctionOfType(); mockResolvedPackuments( getRegistryPackumentVersion, [exampleRegistryUrl, somePackument], [exampleRegistryUrl, otherPackument] ); - const resolveDependencies = mockService(); + const resolveDependencies = mockFunctionOfType(); let defaultGraph = makeGraphFromSeed(somePackage, someVersion); defaultGraph = markRemoteResolved( defaultGraph, @@ -97,18 +97,18 @@ function makeDependencies() { ); resolveDependencies.mockResolvedValue(defaultGraph); - const loadProjectManifest = mockService(); + const loadProjectManifest = mockFunctionOfType(); loadProjectManifest.mockResolvedValue(emptyProjectManifest); - const writeProjectManifest = mockService(); + const writeProjectManifest = mockFunctionOfType(); writeProjectManifest.mockResolvedValue(undefined); - const determineEditorVersion = mockService(); + const determineEditorVersion = mockFunctionOfType(); determineEditorVersion.mockResolvedValue( makeEditorVersion(2022, 2, 1, "f", 2) ); - const getRegistryAuth = mockService(); + const getRegistryAuth = mockFunctionOfType(); getRegistryAuth.mockResolvedValue({ url: exampleRegistryUrl, auth: null }); const log = makeMockLogger(); diff --git a/test/unit/io/builtin-packages.test.ts b/test/unit/io/builtin-packages.test.ts index 6e42257b..2038a02f 100644 --- a/test/unit/io/builtin-packages.test.ts +++ b/test/unit/io/builtin-packages.test.ts @@ -8,11 +8,11 @@ import { GetDirectoriesIn } from "../../../src/io/directory-io"; import * as specialPaths from "../../../src/io/special-paths"; import { OSNotSupportedError } from "../../../src/io/special-paths"; import { noopLogger } from "../../../src/logging"; -import { mockService } from "../services/service.mock"; +import { mockFunctionOfType } from "../services/func.mock"; import { eaccesError, enoentError } from "./node-error.mock"; function makeDependencies() { - const getDirectoriesIn = mockService(); + const getDirectoriesIn = mockFunctionOfType(); const getBuiltInPackages = makeFindBuiltInPackages( getDirectoriesIn, diff --git a/test/unit/io/npmrc-io.test.ts b/test/unit/io/npmrc-io.test.ts index 36ad4fa8..e5a4919b 100644 --- a/test/unit/io/npmrc-io.test.ts +++ b/test/unit/io/npmrc-io.test.ts @@ -6,7 +6,7 @@ import { WriteNpmrcPath as WriteNpmrcFile, } from "../../../src/io/npmrc-io"; import { ReadTextFile, WriteTextFile } from "../../../src/io/text-file-io"; -import { mockService } from "../services/service.mock"; +import { mockFunctionOfType } from "../services/func.mock"; describe("npmrc-io", () => { describe("find path in home", () => { @@ -30,7 +30,7 @@ describe("npmrc-io", () => { describe("read file", () => { function makeDependencies() { - const readText = mockService(); + const readText = mockFunctionOfType(); const readNpmrcFile = ReadNpmrcFile(readText); return { readNpmrcFile, readText } as const; @@ -59,7 +59,7 @@ describe("npmrc-io", () => { describe("write file", () => { function makeDependencies() { - const writeFile = mockService(); + const writeFile = mockFunctionOfType(); writeFile.mockResolvedValue(undefined); const writeNpmrcFile = WriteNpmrcFile(writeFile); diff --git a/test/unit/io/project-manifest-io.test.ts b/test/unit/io/project-manifest-io.test.ts index 1edbc6c8..59e81b24 100644 --- a/test/unit/io/project-manifest-io.test.ts +++ b/test/unit/io/project-manifest-io.test.ts @@ -16,7 +16,7 @@ import { buildProjectManifest } from "../domain/data-project-manifest"; import { DomainName } from "../../../src/domain/domain-name"; import { removeScope } from "../../../src/domain/scoped-registry"; import { exampleRegistryUrl } from "../domain/data-registry"; -import { mockService } from "../services/service.mock"; +import { mockFunctionOfType } from "../services/func.mock"; import { noopLogger } from "../../../src/logging"; const exampleProjectPath = "/some/path"; @@ -35,7 +35,7 @@ describe("project-manifest io", () => { describe("read file", () => { function makeDependencies() { - const readFile = mockService(); + const readFile = mockFunctionOfType(); const readProjectManifestFile = ReadProjectManifestFile( readFile, diff --git a/test/unit/io/project-version-io.test.ts b/test/unit/io/project-version-io.test.ts index 2f417f65..affdffe7 100644 --- a/test/unit/io/project-version-io.test.ts +++ b/test/unit/io/project-version-io.test.ts @@ -5,12 +5,12 @@ import { } from "../../../src/io/project-version-io"; import { ReadTextFile } from "../../../src/io/text-file-io"; import { noopLogger } from "../../../src/logging"; -import { mockService } from "../services/service.mock"; +import { mockFunctionOfType } from "../services/func.mock"; describe("project-version-io", () => { describe("read file", () => { function makeDependencies() { - const readFile = mockService(); + const readFile = mockFunctionOfType(); const readProjectVersionFile = ReadProjectVersionFile( readFile, diff --git a/test/unit/io/upm-config-io.test.ts b/test/unit/io/upm-config-io.test.ts index 9cb5a0b0..e4caa86d 100644 --- a/test/unit/io/upm-config-io.test.ts +++ b/test/unit/io/upm-config-io.test.ts @@ -2,7 +2,7 @@ import { EOL } from "node:os"; import { ReadTextFile } from "../../../src/io/text-file-io"; import { ReadUpmConfigFile } from "../../../src/io/upm-config-io"; import { exampleRegistryUrl } from "../domain/data-registry"; -import { mockService } from "../services/service.mock"; +import { mockFunctionOfType } from "../services/func.mock"; jest.mock("../../../src/utils/env-util"); @@ -13,7 +13,7 @@ describe("upm-config-io", () => { const someToken = "isehusehgusheguszg8gshg"; function makeDependencies() { - const readFile = mockService(); + const readFile = mockFunctionOfType(); readFile.mockResolvedValue(""); const readUpmConfigFile = ReadUpmConfigFile(readFile); diff --git a/test/unit/services/built-in-package-check.test.ts b/test/unit/services/built-in-package-check.test.ts index 96ded157..32b10b9f 100644 --- a/test/unit/services/built-in-package-check.test.ts +++ b/test/unit/services/built-in-package-check.test.ts @@ -4,16 +4,16 @@ import { SemanticVersion } from "../../../src/domain/semantic-version"; import { GetRegistryPackument } from "../../../src/io/packument-io"; import { CheckIsNonRegistryUnityPackage } from "../../../src/services/built-in-package-check"; import { CheckIsUnityPackage } from "../../../src/services/unity-package-check"; -import { mockService } from "./service.mock"; +import { mockFunctionOfType } from "./func.mock"; describe("is non-registry unity package", () => { const somePackage = DomainName.parse("com.some.package"); const someVersion = SemanticVersion.parse("1.0.0"); function makeDependencies() { - const checkIsUnityPackage = mockService(); + const checkIsUnityPackage = mockFunctionOfType(); - const getRegistryPackument = mockService(); + const getRegistryPackument = mockFunctionOfType(); const checkIsNonRegistryUnityPackage = CheckIsNonRegistryUnityPackage( checkIsUnityPackage, diff --git a/test/unit/services/dependency-resolving.test.ts b/test/unit/services/dependency-resolving.test.ts index 2cb95443..e25b0378 100644 --- a/test/unit/services/dependency-resolving.test.ts +++ b/test/unit/services/dependency-resolving.test.ts @@ -1,4 +1,4 @@ -import { mockService } from "./service.mock"; +import { mockFunctionOfType } from "./func.mock"; import { GetRegistryPackument } from "../../../src/io/packument-io"; import { CheckIsBuiltInPackage } from "../../../src/services/built-in-package-check"; import { exampleRegistryUrl } from "../domain/data-registry"; @@ -24,9 +24,9 @@ describe("dependency resolving", () => { const otherVersion = SemanticVersion.parse("2.0.0"); function makeDependencies() { - const getRegistryPackument = mockService(); + const getRegistryPackument = mockFunctionOfType(); - const checkIsBuiltInPackage = mockService(); + const checkIsBuiltInPackage = mockFunctionOfType(); checkIsBuiltInPackage.mockResolvedValue(false); const resolveDependencies = ResolveDependenciesFromRegistries( diff --git a/test/unit/services/determine-editor-version.test.ts b/test/unit/services/determine-editor-version.test.ts index 1f79cf14..ab1d26b7 100644 --- a/test/unit/services/determine-editor-version.test.ts +++ b/test/unit/services/determine-editor-version.test.ts @@ -1,4 +1,4 @@ -import { mockService } from "./service.mock"; +import { mockFunctionOfType } from "./func.mock"; import { GetProjectVersion } from "../../../src/io/project-version-io"; import { makeEditorVersion } from "../../../src/domain/editor-version"; import { mockProjectVersion } from "../io/project-version-io.mock"; @@ -8,7 +8,7 @@ describe("determine editor version from file", () => { const exampleProjectPath = "/home/my-project/"; function makeDependencies() { - const getProjectVersion = mockService(); + const getProjectVersion = mockFunctionOfType(); const determineEditorVersionFromFile = DetermineEditorVersionFromFile(getProjectVersion); diff --git a/test/unit/services/service.mock.ts b/test/unit/services/func.mock.ts similarity index 62% rename from test/unit/services/service.mock.ts rename to test/unit/services/func.mock.ts index 8c74c7bb..6e74961f 100644 --- a/test/unit/services/service.mock.ts +++ b/test/unit/services/func.mock.ts @@ -1,12 +1,12 @@ /** - * Creates a mock for a service function. + * Creates a mock for a function of a specific type. * This is just a typing-utility function. Under the hood this just calls * {@link jest.fn}. */ -export function mockService< +export function mockFunctionOfType< // eslint-disable-next-line @typescript-eslint/no-explicit-any - T extends (...args: any[]) => any ->(): jest.MockedFunction { + TFunc extends (...args: any[]) => any +>(): jest.MockedFunction { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore return jest.fn(); diff --git a/test/unit/services/get-latest-version.test.ts b/test/unit/services/get-latest-version.test.ts index 415cfa6d..79a5ce4f 100644 --- a/test/unit/services/get-latest-version.test.ts +++ b/test/unit/services/get-latest-version.test.ts @@ -5,7 +5,7 @@ import { SemanticVersion } from "../../../src/domain/semantic-version"; import { GetRegistryPackument } from "../../../src/io/packument-io"; import { GetLatestVersionFromRegistryPackument } from "../../../src/services/get-latest-version"; import { exampleRegistryUrl } from "../domain/data-registry"; -import { mockService } from "./service.mock"; +import { mockFunctionOfType } from "./func.mock"; describe("get latest version from registry packument", () => { const somePackage = DomainName.parse("com.some.package"); @@ -13,7 +13,7 @@ describe("get latest version from registry packument", () => { const exampleRegistry: Registry = { url: exampleRegistryUrl, auth: null }; function makeDependencies() { - const getRegistryPackument = mockService(); + const getRegistryPackument = mockFunctionOfType(); const getLatestVersionFromRegistryPackument = GetLatestVersionFromRegistryPackument(getRegistryPackument); diff --git a/test/unit/services/get-registry-auth.test.ts b/test/unit/services/get-registry-auth.test.ts index 6675b231..ffd36b73 100644 --- a/test/unit/services/get-registry-auth.test.ts +++ b/test/unit/services/get-registry-auth.test.ts @@ -7,17 +7,17 @@ import { GetUpmConfigPath, LoadUpmConfig } from "../../../src/io/upm-config-io"; import { noopLogger } from "../../../src/logging"; import { LoadRegistryAuthFromUpmConfig } from "../../../src/services/get-registry-auth"; import { exampleRegistryUrl } from "../domain/data-registry"; -import { mockService } from "./service.mock"; +import { mockFunctionOfType } from "./func.mock"; describe("get registry auth from upm config", () => { const someEmail = "user@mail.com"; const someToken = "isehusehgusheguszg8gshg"; function makeDependencies() { - const getUpmConfigPath = mockService(); + const getUpmConfigPath = mockFunctionOfType(); getUpmConfigPath.mockReturnValue("/home/user/.upmconfig.toml"); - const loadUpmConfig = mockService(); + const loadUpmConfig = mockFunctionOfType(); loadUpmConfig.mockResolvedValue({}); const getRegistryAuth = LoadRegistryAuthFromUpmConfig( diff --git a/test/unit/services/get-registry-packument-version.test.ts b/test/unit/services/get-registry-packument-version.test.ts index 34ab4a75..da9ff4af 100644 --- a/test/unit/services/get-registry-packument-version.test.ts +++ b/test/unit/services/get-registry-packument-version.test.ts @@ -6,7 +6,7 @@ import { GetRegistryPackument } from "../../../src/io/packument-io"; import { FetchRegistryPackumentVersion } from "../../../src/services/get-registry-packument-version"; import { buildPackument } from "../domain/data-packument"; import { exampleRegistryUrl } from "../domain/data-registry"; -import { mockService } from "./service.mock"; +import { mockFunctionOfType } from "./func.mock"; describe("fetch registrypackument version", () => { const somePackage = DomainName.parse("com.some.package"); @@ -16,7 +16,7 @@ describe("fetch registrypackument version", () => { const someRegistry: Registry = { url: exampleRegistryUrl, auth: null }; function makeDependencies() { - const getRegistryPackument = mockService(); + const getRegistryPackument = mockFunctionOfType(); const fetchRegistryPackumentVersion = FetchRegistryPackumentVersion(getRegistryPackument); diff --git a/test/unit/services/login.test.ts b/test/unit/services/login.test.ts index 95bc3424..c166eb4a 100644 --- a/test/unit/services/login.test.ts +++ b/test/unit/services/login.test.ts @@ -4,7 +4,7 @@ import { UpmConfigLogin } from "../../../src/services/login"; import { StoreNpmAuthToken } from "../../../src/services/put-npm-auth-token"; import { PutRegistryAuth } from "../../../src/services/put-registry-auth"; import { exampleRegistryUrl } from "../domain/data-registry"; -import { mockService } from "./service.mock"; +import { mockFunctionOfType } from "./func.mock"; const exampleUser = "user"; const examplePassword = "pass"; @@ -15,13 +15,13 @@ const exampleToken = "some token"; describe("login", () => { function makeDependencies() { - const putRegistryAuth = mockService(); + const putRegistryAuth = mockFunctionOfType(); putRegistryAuth.mockResolvedValue(undefined); - const npmLogin = mockService(); + const npmLogin = mockFunctionOfType(); npmLogin.mockResolvedValue(exampleToken); - const putNpmAuthToken = mockService(); + const putNpmAuthToken = mockFunctionOfType(); putNpmAuthToken.mockResolvedValue(exampleNpmrcPath); const login = UpmConfigLogin( diff --git a/test/unit/services/put-npm-auth-token.test.ts b/test/unit/services/put-npm-auth-token.test.ts index 28ec828d..dc5d40ea 100644 --- a/test/unit/services/put-npm-auth-token.test.ts +++ b/test/unit/services/put-npm-auth-token.test.ts @@ -2,19 +2,19 @@ import { emptyNpmrc, setToken } from "../../../src/domain/npmrc"; import { FindNpmrcPath, LoadNpmrc, SaveNpmrc } from "../../../src/io/npmrc-io"; import { StoreNpmAuthTokenInNpmrc as PutNpmAuthTokenInNpmrc } from "../../../src/services/put-npm-auth-token"; import { exampleRegistryUrl } from "../domain/data-registry"; -import { mockService } from "./service.mock"; +import { mockFunctionOfType } from "./func.mock"; const exampleNpmrcPath = "/users/someuser/.npmrc"; describe("put npm auth token in npmrc", () => { function makeDependencies() { - const findPath = mockService(); + const findPath = mockFunctionOfType(); findPath.mockReturnValue(exampleNpmrcPath); - const loadNpmrc = mockService(); + const loadNpmrc = mockFunctionOfType(); loadNpmrc.mockResolvedValue(null); - const saveNpmrc = mockService(); + const saveNpmrc = mockFunctionOfType(); saveNpmrc.mockResolvedValue(undefined); const putNpmAuthTokenInNpmrc = PutNpmAuthTokenInNpmrc( diff --git a/test/unit/services/put-registry-auth.test.ts b/test/unit/services/put-registry-auth.test.ts index fe58aab1..ca1de26d 100644 --- a/test/unit/services/put-registry-auth.test.ts +++ b/test/unit/services/put-registry-auth.test.ts @@ -1,4 +1,4 @@ -import { mockService } from "./service.mock"; +import { mockFunctionOfType } from "./func.mock"; import { LoadUpmConfig, SaveUpmConfig } from "../../../src/io/upm-config-io"; import { exampleRegistryUrl } from "../domain/data-registry"; import { Base64 } from "../../../src/domain/base64"; @@ -11,10 +11,10 @@ describe("put registry auth into upm config", () => { const otherToken = "8zseg974wge4g94whfheghf"; function makeDependencies() { - const loadUpmConfig = mockService(); + const loadUpmConfig = mockFunctionOfType(); loadUpmConfig.mockResolvedValue({}); - const saveUpmConfig = mockService(); + const saveUpmConfig = mockFunctionOfType(); saveUpmConfig.mockResolvedValue(); const putRegistryAuth = PutRegistryAuthIntoUpmConfig( diff --git a/test/unit/services/remove-packages.test.ts b/test/unit/services/remove-packages.test.ts index e90c4a08..cb80bc5d 100644 --- a/test/unit/services/remove-packages.test.ts +++ b/test/unit/services/remove-packages.test.ts @@ -10,7 +10,7 @@ import { RemovePackagesFromManifest, } from "../../../src/services/remove-packages"; import { buildProjectManifest } from "../domain/data-project-manifest"; -import { mockService } from "./service.mock"; +import { mockFunctionOfType } from "./func.mock"; describe("remove packages from manifest", () => { const someProjectPath = path.resolve("/home/projects/MyUnityProject"); @@ -22,10 +22,10 @@ describe("remove packages from manifest", () => { ); function makeDependencies() { - const loadProjectManifest = mockService(); + const loadProjectManifest = mockFunctionOfType(); loadProjectManifest.mockResolvedValue(defaultManifest); - const writeProjectManifest = mockService(); + const writeProjectManifest = mockFunctionOfType(); writeProjectManifest.mockResolvedValue(undefined); const removePackagesFromManifestu = RemovePackagesFromManifest( diff --git a/test/unit/services/search-packages.test.ts b/test/unit/services/search-packages.test.ts index 293c4c16..b8ac1da7 100644 --- a/test/unit/services/search-packages.test.ts +++ b/test/unit/services/search-packages.test.ts @@ -9,7 +9,7 @@ import { SearchedPackument, SearchRegistry } from "../../../src/io/npm-search"; import { noopLogger } from "../../../src/logging"; import { ApiAndFallbackPackageSearch } from "../../../src/services/search-packages"; import { exampleRegistryUrl } from "../domain/data-registry"; -import { mockService } from "./service.mock"; +import { mockFunctionOfType } from "./func.mock"; describe("search packages", () => { const exampleRegistry: Registry = { @@ -33,10 +33,10 @@ describe("search packages", () => { } as AllPackuments; function makeDependencies() { - const searchRegistry = mockService(); + const searchRegistry = mockFunctionOfType(); searchRegistry.mockResolvedValue([exampleSearchResult]); - const fetchAllPackument = mockService(); + const fetchAllPackument = mockFunctionOfType(); fetchAllPackument.mockResolvedValue(exampleAllPackumentsResult); const searchPackages = ApiAndFallbackPackageSearch( diff --git a/test/unit/services/unity-package-check.test.ts b/test/unit/services/unity-package-check.test.ts index 407fdeaf..9835c758 100644 --- a/test/unit/services/unity-package-check.test.ts +++ b/test/unit/services/unity-package-check.test.ts @@ -1,13 +1,13 @@ import { DomainName } from "../../../src/domain/domain-name"; import { CheckUrlExists } from "../../../src/io/check-url"; import { PackageHasDocPage } from "../../../src/services/unity-package-check"; -import { mockService } from "./service.mock"; +import { mockFunctionOfType } from "./func.mock"; describe("unity package has doc page", () => { const somePackage = DomainName.parse("com.some.package"); function makeDependencies() { - const checkUrlExists = mockService(); + const checkUrlExists = mockFunctionOfType(); const packageHasDocPage = PackageHasDocPage(checkUrlExists); return { packageHasDocPage, checkUrlExists } as const; From 897ff877e881d6f0460a1b0fec59dfe17ad64352 Mon Sep 17 00:00:00 2001 From: Ramon Brullo Date: Sun, 25 Aug 2024 13:32:28 +0200 Subject: [PATCH 20/26] refactor: extract pure logic Move pure logic for splitting multiline string into its own function. --- src/io/npmrc-io.ts | 8 +++++++- src/utils/string-utils.ts | 9 +++++++++ test/utils/string-utils.test.ts | 17 +++++++++++++++++ 3 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 test/utils/string-utils.test.ts diff --git a/src/io/npmrc-io.ts b/src/io/npmrc-io.ts index da3a875f..de4012cb 100644 --- a/src/io/npmrc-io.ts +++ b/src/io/npmrc-io.ts @@ -1,6 +1,7 @@ import { EOL } from "node:os"; import path from "path"; import { Npmrc } from "../domain/npmrc"; +import { splitLines } from "../utils/string-utils"; import { readTextFile, ReadTextFile, @@ -44,7 +45,12 @@ export type LoadNpmrc = (path: string) => Promise; */ export function ReadNpmrcFile(readFile: ReadTextFile): LoadNpmrc { return (path) => - readFile(path, true).then((content) => content?.split(EOL) ?? null); + readFile(path, true).then((content) => + content !== null + ? // TODO: Check if lines are valid. + splitLines(content) + : null + ); } /** diff --git a/src/utils/string-utils.ts b/src/utils/string-utils.ts index 8058189c..ae1dce25 100644 --- a/src/utils/string-utils.ts +++ b/src/utils/string-utils.ts @@ -25,3 +25,12 @@ export function removeTrailingSlash(s: T): T { if (s.endsWith("/")) return s.slice(0, -1) as T; return s; } + +/** + * Splits a multiline string into it's separate lines. + * @param s The string to split. + * @returns All non-empty lines in the string. + */ +export function splitLines(s: string): ReadonlyArray { + return s.split(/[\r\n]+/).filter((it) => it.length > 0); +} diff --git a/test/utils/string-utils.test.ts b/test/utils/string-utils.test.ts new file mode 100644 index 00000000..08118153 --- /dev/null +++ b/test/utils/string-utils.test.ts @@ -0,0 +1,17 @@ +import { splitLines } from "../../src/utils/string-utils"; + +describe("string utils", () => { + describe("split into lines", () => { + it("should work for any linebreak string", () => { + const content = "A\nB\r\nC\rD"; + const lines = splitLines(content); + expect(lines).toEqual(["A", "B", "C", "D"]); + }); + + it("should remove empty lines", () => { + const content = "A\n\n\n\nB"; + const lines = splitLines(content); + expect(lines).toEqual(["A", "B"]); + }); + }); +}); From 92e250bc8626a5fec531080eac4d8af68c124d56 Mon Sep 17 00:00:00 2001 From: Ramon Brullo Date: Sun, 25 Aug 2024 14:04:30 +0200 Subject: [PATCH 21/26] refactor: extract pure logic Extract pure function for parsing manifest --- src/io/project-manifest-io.ts | 22 +++++++----- test/io/project-manifest-io.test.ts | 56 +++++++++++++++++------------ 2 files changed, 47 insertions(+), 31 deletions(-) diff --git a/src/io/project-manifest-io.ts b/src/io/project-manifest-io.ts index 354072d6..baa928d8 100644 --- a/src/io/project-manifest-io.ts +++ b/src/io/project-manifest-io.ts @@ -1,11 +1,9 @@ -import { AnyJson } from "@iarna/toml"; import path from "path"; import { CustomError } from "ts-custom-error"; import { z } from "zod"; import { UnityProjectManifest } from "../domain/project-manifest"; import { DebugLog, npmDebugLog } from "../logging"; import { assertIsError } from "../utils/error-type-guards"; -import { isZod } from "../utils/zod-utils"; import { readTextFile, ReadTextFile, @@ -40,7 +38,18 @@ export type LoadProjectManifest = ( ) => Promise; // TODO: Add a better schema -const projectManifestSchema = z.object({}); +const projectManifestSchema = z.object({}).passthrough(); + +/** + * Parses the content of a `manifest.json` file to a {@link UnityProjectManifest}. + * @param content The files content. + * @returns The parsed file. + * @throws {Error} If parsing failed. + */ +export function parseProjectManifest(content: string): UnityProjectManifest { + const json = JSON.parse(content); + return projectManifestSchema.parse(json) as UnityProjectManifest; +} /** * Makes a {@link LoadProjectManifest} function which reads the content @@ -56,18 +65,13 @@ export function ReadProjectManifestFile( const content = await readFile(manifestPath, true); if (content === null) throw new ManifestMissingError(manifestPath); - let json: AnyJson; try { - json = await JSON.parse(content); + return parseProjectManifest(content); } catch (error) { assertIsError(error); debugLog("Manifest parse failed because of invalid json content.", error); throw new ManifestMalformedError(); } - - if (!isZod(json, projectManifestSchema)) throw new ManifestMalformedError(); - - return json as unknown as UnityProjectManifest; }; } diff --git a/test/io/project-manifest-io.test.ts b/test/io/project-manifest-io.test.ts index abb74e53..5e03a741 100644 --- a/test/io/project-manifest-io.test.ts +++ b/test/io/project-manifest-io.test.ts @@ -1,26 +1,47 @@ +import path from "path"; +import { UnityProjectManifest } from "../../src/domain/project-manifest"; import { - ReadProjectManifestFile, - WriteProjectManifestFile, ManifestMalformedError, ManifestMissingError, manifestPathFor, + parseProjectManifest, + ReadProjectManifestFile, serializeProjectManifest, } from "../../src/io/project-manifest-io"; -import { - mapScopedRegistry, - UnityProjectManifest, -} from "../../src/domain/project-manifest"; -import path from "path"; -import { ReadTextFile, WriteTextFile } from "../../src/io/text-file-io"; -import { buildProjectManifest } from "../domain/data-project-manifest"; -import { DomainName } from "../../src/domain/domain-name"; -import { removeScope } from "../../src/domain/scoped-registry"; +import { ReadTextFile } from "../../src/io/text-file-io"; +import { noopLogger } from "../../src/logging"; import { exampleRegistryUrl } from "../domain/data-registry"; import { mockService } from "../services/service.mock"; -import { noopLogger } from "../../src/logging"; const exampleProjectPath = "/some/path"; describe("project-manifest io", () => { + describe("parse", () => { + it("should parse valid manifest", () => { + const content = `{ "dependencies": { "com.package.a": "1.0.0"} }`; + + const parsed = parseProjectManifest(content); + + expect(parsed).toEqual({ + dependencies: { + "com.package.a": "1.0.0", + }, + }); + }); + + it("should fail for bad json", () => { + const content = "not : valid // json"; + + expect(() => parseProjectManifest(content)).toThrow(Error); + }); + + it("should fail incorrect json shape", () => { + // Valid json but not what we want + const content = `123`; + + expect(() => parseProjectManifest(content)).toThrow(Error); + }); + }); + describe("path", () => { it("should determine correct manifest path", () => { const manifestPath = manifestPathFor("test-openupm-cli"); @@ -53,7 +74,7 @@ describe("project-manifest io", () => { ).rejects.toBeInstanceOf(ManifestMissingError); }); - it("should fail if manifest contains invalid json", async () => { + it("should fail if manifest could not be parsed", async () => { const { readProjectManifestFile, readFile } = makeDependencies(); readFile.mockResolvedValue("not {} valid : json"); @@ -62,15 +83,6 @@ describe("project-manifest io", () => { ).rejects.toBeInstanceOf(ManifestMalformedError); }); - it("should fail if manifest contains invalid content", async () => { - const { readProjectManifestFile, readFile } = makeDependencies(); - readFile.mockResolvedValue(`123`); - - await expect( - readProjectManifestFile(exampleProjectPath) - ).rejects.toBeInstanceOf(ManifestMalformedError); - }); - it("should load valid manifest", async () => { const { readProjectManifestFile, readFile } = makeDependencies(); readFile.mockResolvedValue( From 033c4fb5a540bb764d290edbc1487760583d128f Mon Sep 17 00:00:00 2001 From: Ramon Brullo Date: Fri, 30 Aug 2024 09:23:10 +0200 Subject: [PATCH 22/26] refactor: extract pure function --- src/domain/packument.ts | 24 +++++++++++++++----- src/services/built-in-package-check.ts | 5 ++--- test/unit/domain/packument.test.ts | 31 +++++++++++++++++++++++++- 3 files changed, 50 insertions(+), 10 deletions(-) diff --git a/src/domain/packument.ts b/src/domain/packument.ts index d6304921..f87415d7 100644 --- a/src/domain/packument.ts +++ b/src/domain/packument.ts @@ -1,14 +1,13 @@ -import { SemanticVersion } from "./semantic-version"; -import { DomainName } from "./domain-name"; -import { UnityPackageManifest } from "./package-manifest"; -import { PackageId } from "./package-id"; -import { Dist, Maintainer } from "@npm/types"; +import { Dist } from "@npm/types"; import { CustomError } from "ts-custom-error"; import { Err, Ok, Result } from "ts-results-es"; import { recordKeys } from "../utils/record-utils"; +import { DomainName } from "./domain-name"; +import { UnityPackageManifest } from "./package-manifest"; +import { SemanticVersion } from "./semantic-version"; -import { ResolvableVersion } from "./package-reference"; import { PackumentNotFoundError } from "../common-errors"; +import { ResolvableVersion } from "./package-reference"; /** * Contains information about a specific version of a package. This is based on @@ -201,3 +200,16 @@ export function tryResolvePackumentVersion( return Ok(tryGetPackumentVersion(packument, requestedVersion)!); } + +/** + * Checks whether a packument has an entry for a version. + * @param packument The packument to check. + * @param version The version to check for. + */ +export function packumentHasVersion( + packument: UnityPackument, + version: SemanticVersion +): boolean { + const versions = recordKeys(packument.versions); + return versions.includes(version); +} diff --git a/src/services/built-in-package-check.ts b/src/services/built-in-package-check.ts index b838eae8..e10776fb 100644 --- a/src/services/built-in-package-check.ts +++ b/src/services/built-in-package-check.ts @@ -1,5 +1,6 @@ import RegClient from "another-npm-registry-client"; import { DomainName } from "../domain/domain-name"; +import { packumentHasVersion } from "../domain/packument"; import { unityRegistryUrl } from "../domain/registry-url"; import { SemanticVersion } from "../domain/semantic-version"; import { @@ -7,7 +8,6 @@ import { getRegistryPackumentUsing, } from "../io/packument-io"; import { DebugLog } from "../logging"; -import { recordKeys } from "../utils/record-utils"; import { checkIsUnityPackage, CheckIsUnityPackage, @@ -42,8 +42,7 @@ export function CheckIsNonRegistryUnityPackage( packageName ); if (packument === null) return false; - const versions = recordKeys(packument.versions); - return versions.includes(version); + return packumentHasVersion(packument, version); } return async (packageName, version) => { diff --git a/test/unit/domain/packument.test.ts b/test/unit/domain/packument.test.ts index 1cc4cf63..ff1b6346 100644 --- a/test/unit/domain/packument.test.ts +++ b/test/unit/domain/packument.test.ts @@ -1,5 +1,7 @@ +import { DomainName } from "../../../src/domain/domain-name"; import { NoVersionsError, + packumentHasVersion, tryGetLatestVersion, tryGetPackumentVersion, tryResolvePackumentVersion, @@ -7,7 +9,6 @@ import { } from "../../../src/domain/packument"; import { SemanticVersion } from "../../../src/domain/semantic-version"; import { buildPackument } from "./data-packument"; -import { DomainName } from "../../../src/domain/domain-name"; describe("packument", () => { describe("get latest version", () => { @@ -122,4 +123,32 @@ describe("packument", () => { ); }); }); + + describe("has version", () => { + it("should have version if there is entry for it", () => { + const packument = buildPackument("com.some.package", (packument) => + packument.addVersion("1.0.0") + ); + + const hasVersion = packumentHasVersion( + packument, + SemanticVersion.parse("1.0.0") + ); + + expect(hasVersion).toBeTruthy(); + }); + + it("should not have version if there is no entry for it", () => { + const packument = buildPackument("com.some.package", (packument) => + packument.addVersion("1.0.0") + ); + + const hasVersion = packumentHasVersion( + packument, + SemanticVersion.parse("2.0.0") + ); + + expect(hasVersion).toBeFalsy(); + }); + }); }); From 0b0b2a1bb6ebc1e1d59baa10a427d59acac1a9fc Mon Sep 17 00:00:00 2001 From: Ramon Brullo Date: Fri, 30 Aug 2024 10:11:47 +0200 Subject: [PATCH 23/26] refactor: inline service Reduce boilerplate by inlining unity-package-check service --- src/io/check-url.ts | 19 ++++------ src/services/built-in-package-check.ts | 19 ++++++---- src/services/unity-package-check.ts | 28 --------------- test/unit/io/check-url.test.ts | 36 ------------------- .../services/built-in-package-check.test.ts | 20 +++++------ .../unit/services/unity-package-check.test.ts | 33 ----------------- 6 files changed, 29 insertions(+), 126 deletions(-) delete mode 100644 src/services/unity-package-check.ts delete mode 100644 test/unit/io/check-url.test.ts delete mode 100644 test/unit/services/unity-package-check.test.ts diff --git a/src/io/check-url.ts b/src/io/check-url.ts index 06b5f3c6..ce38a9cb 100644 --- a/src/io/check-url.ts +++ b/src/io/check-url.ts @@ -8,17 +8,10 @@ import fetch from "node-fetch"; export type CheckUrlExists = (url: string) => Promise; /** - * Makes a {@link CheckUrlExists} function that determines whether a url - * exists by checking whether it responds to a HEAD request with 200. + * {@link CheckUrlExists} function which uses {@link fetch} to send a + * `HEAD` request to the url and checks whether it is `ok`. */ -export function CheckUrlIsOk(): CheckUrlExists { - return async (url) => { - const response = await fetch(url, { method: "HEAD" }); - return response.status === 200; - }; -} - -/** - * Default {@link CheckUrlExists} function. Uses {@link CheckUrlIsOk}. - */ -export const checkUrlExists: CheckUrlExists = CheckUrlIsOk(); +export const fetchCheckUrlExists: CheckUrlExists = async (url) => { + const response = await fetch(url, { method: "HEAD" }); + return response.ok; +}; diff --git a/src/services/built-in-package-check.ts b/src/services/built-in-package-check.ts index e10776fb..b41d9f82 100644 --- a/src/services/built-in-package-check.ts +++ b/src/services/built-in-package-check.ts @@ -3,15 +3,12 @@ import { DomainName } from "../domain/domain-name"; import { packumentHasVersion } from "../domain/packument"; import { unityRegistryUrl } from "../domain/registry-url"; import { SemanticVersion } from "../domain/semantic-version"; +import { CheckUrlExists, fetchCheckUrlExists } from "../io/check-url"; import { GetRegistryPackument, getRegistryPackumentUsing, } from "../io/packument-io"; import { DebugLog } from "../logging"; -import { - checkIsUnityPackage, - CheckIsUnityPackage, -} from "./unity-package-check"; /** * Function for checking whether a specific package version is built-in. @@ -24,15 +21,25 @@ export type CheckIsBuiltInPackage = ( version: SemanticVersion ) => Promise; +function docUrlForPackage(packageName: DomainName) { + return `https://docs.unity3d.com/Manual/${packageName}.html`; +} + /** * Makes a {@link CheckIsBuiltInPackage} function which checks if package is * built-in by establishing if the package is an official Unity package which * does not exist on the Unity package registry. */ export function CheckIsNonRegistryUnityPackage( - checkIsUnityPackage: CheckIsUnityPackage, + checkUrlExists: CheckUrlExists, getRegistryPackument: GetRegistryPackument ): CheckIsBuiltInPackage { + function checkIsUnityPackage(packageName: DomainName) { + // A package is an official Unity package if it has a documentation page + const url = docUrlForPackage(packageName); + return checkUrlExists(url); + } + async function checkExistsOnUnityRegistry( packageName: DomainName, version: SemanticVersion @@ -60,6 +67,6 @@ export const checkIsBuiltInPackage = ( debugLog: DebugLog ) => CheckIsNonRegistryUnityPackage( - checkIsUnityPackage, + fetchCheckUrlExists, getRegistryPackumentUsing(registryClient, debugLog) ); diff --git a/src/services/unity-package-check.ts b/src/services/unity-package-check.ts deleted file mode 100644 index ce74365d..00000000 --- a/src/services/unity-package-check.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { DomainName } from "../domain/domain-name"; -import { checkUrlExists, CheckUrlExists } from "../io/check-url"; - -/** - * Function for checking whether a package is an official Unity package. - * @param packageName The name of the package. - * @returns A boolean indicating whether the package is a Unity package. - */ -export type CheckIsUnityPackage = (packageName: DomainName) => Promise; - -/** - * Makes a {@link CheckIsUnityPackage} function which checks whether the package - * exists by checking whether it has a doc page. - */ -export function PackageHasDocPage( - checkUrlExists: CheckUrlExists -): CheckIsUnityPackage { - return (packageName) => { - // A package is an official Unity package if it has a documentation page - const url = `https://docs.unity3d.com/Manual/${packageName}.html`; - return checkUrlExists(url); - }; -} - -/** - * Default {@link CheckIsUnityPackage}. Uses {@link PackageHasDocPage}. - */ -export const checkIsUnityPackage = PackageHasDocPage(checkUrlExists); diff --git a/test/unit/io/check-url.test.ts b/test/unit/io/check-url.test.ts deleted file mode 100644 index 7ccf2648..00000000 --- a/test/unit/io/check-url.test.ts +++ /dev/null @@ -1,36 +0,0 @@ -import fetch, { Response } from "node-fetch"; -import { CheckUrlIsOk } from "../../../src/io/check-url"; - -jest.mock("node-fetch"); - -describe("check url is ok", () => { - function makeDependencies() { - const checkUrlIsOk = CheckUrlIsOk(); - return { checkUrlIsOk } as const; - } - - it("should be true if url responds with 200", async () => { - jest.mocked(fetch).mockResolvedValue({ - status: 200, - } as Response); - const { checkUrlIsOk } = makeDependencies(); - - const actual = await checkUrlIsOk("https://some.url.com"); - - expect(actual).toBeTruthy(); - }); - - it.each([100, 201, 301, 401, 404, 500])( - "should be false other status codes (%d)", - async (statusCode) => { - jest.mocked(fetch).mockResolvedValue({ - status: statusCode, - } as Response); - const { checkUrlIsOk } = makeDependencies(); - - const actual = await checkUrlIsOk("https://some.url.com"); - - expect(actual).toBeFalsy(); - } - ); -}); diff --git a/test/unit/services/built-in-package-check.test.ts b/test/unit/services/built-in-package-check.test.ts index 32b10b9f..a62d0566 100644 --- a/test/unit/services/built-in-package-check.test.ts +++ b/test/unit/services/built-in-package-check.test.ts @@ -1,9 +1,9 @@ import { DomainName } from "../../../src/domain/domain-name"; import { UnityPackument } from "../../../src/domain/packument"; import { SemanticVersion } from "../../../src/domain/semantic-version"; +import { CheckUrlExists } from "../../../src/io/check-url"; import { GetRegistryPackument } from "../../../src/io/packument-io"; import { CheckIsNonRegistryUnityPackage } from "../../../src/services/built-in-package-check"; -import { CheckIsUnityPackage } from "../../../src/services/unity-package-check"; import { mockFunctionOfType } from "./func.mock"; describe("is non-registry unity package", () => { @@ -11,25 +11,25 @@ describe("is non-registry unity package", () => { const someVersion = SemanticVersion.parse("1.0.0"); function makeDependencies() { - const checkIsUnityPackage = mockFunctionOfType(); + const checkUrlExists = mockFunctionOfType(); const getRegistryPackument = mockFunctionOfType(); const checkIsNonRegistryUnityPackage = CheckIsNonRegistryUnityPackage( - checkIsUnityPackage, + checkUrlExists, getRegistryPackument ); return { checkIsNonRegistryUnityPackage, - checkIsUnityPackage, + checkUrlExists, getRegistryPackument, }; } it("should be false if package is not a Unity package", async () => { - const { checkIsNonRegistryUnityPackage, checkIsUnityPackage } = + const { checkIsNonRegistryUnityPackage, checkUrlExists } = makeDependencies(); - checkIsUnityPackage.mockResolvedValue(false); + checkUrlExists.mockResolvedValue(false); const actual = await checkIsNonRegistryUnityPackage( somePackage, @@ -42,10 +42,10 @@ describe("is non-registry unity package", () => { it("should be false if package is Unity package and exists on Unity registry", async () => { const { checkIsNonRegistryUnityPackage, - checkIsUnityPackage, + checkUrlExists, getRegistryPackument, } = makeDependencies(); - checkIsUnityPackage.mockResolvedValue(true); + checkUrlExists.mockResolvedValue(true); getRegistryPackument.mockResolvedValue({ versions: { [someVersion]: {} }, } as UnityPackument); @@ -61,10 +61,10 @@ describe("is non-registry unity package", () => { it("should be true if package is Unity package, but does not exist on Unity registry", async () => { const { checkIsNonRegistryUnityPackage, - checkIsUnityPackage, + checkUrlExists, getRegistryPackument, } = makeDependencies(); - checkIsUnityPackage.mockResolvedValue(true); + checkUrlExists.mockResolvedValue(true); getRegistryPackument.mockResolvedValue(null); const actual = await checkIsNonRegistryUnityPackage( diff --git a/test/unit/services/unity-package-check.test.ts b/test/unit/services/unity-package-check.test.ts deleted file mode 100644 index 9835c758..00000000 --- a/test/unit/services/unity-package-check.test.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { DomainName } from "../../../src/domain/domain-name"; -import { CheckUrlExists } from "../../../src/io/check-url"; -import { PackageHasDocPage } from "../../../src/services/unity-package-check"; -import { mockFunctionOfType } from "./func.mock"; - -describe("unity package has doc page", () => { - const somePackage = DomainName.parse("com.some.package"); - - function makeDependencies() { - const checkUrlExists = mockFunctionOfType(); - - const packageHasDocPage = PackageHasDocPage(checkUrlExists); - return { packageHasDocPage, checkUrlExists } as const; - } - - it("should be true if manual page exists", async () => { - const { packageHasDocPage, checkUrlExists } = makeDependencies(); - checkUrlExists.mockResolvedValue(true); - - const actual = await packageHasDocPage(somePackage); - - expect(actual).toBeTruthy(); - }); - - it("should be false if manual page does not exist", async () => { - const { packageHasDocPage, checkUrlExists } = makeDependencies(); - checkUrlExists.mockResolvedValue(false); - - const actual = await packageHasDocPage(somePackage); - - expect(actual).toBeFalsy(); - }); -}); From 8b66ab692a225bf8f0041337de7f4b70da75850a Mon Sep 17 00:00:00 2001 From: Ramon Brullo Date: Fri, 30 Aug 2024 10:12:15 +0200 Subject: [PATCH 24/26] test: add mocking utility functions --- test/unit/services/func.mock.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/test/unit/services/func.mock.ts b/test/unit/services/func.mock.ts index 6e74961f..35e8c858 100644 --- a/test/unit/services/func.mock.ts +++ b/test/unit/services/func.mock.ts @@ -11,3 +11,18 @@ export function mockFunctionOfType< // @ts-ignore return jest.fn(); } + +/** + * Creates a mock for a function with a specific return type. + */ +export function mockFunctionWithReturnType() { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return mockFunctionOfType<(...args: any[]) => T>(); +} + +/** + * Creates a mock for a function with a specific resolved type. + */ +export function mockFunctionWithResolvedType() { + return mockFunctionWithReturnType>(); +} From 44fe7ea0e5400a5d7403a56bf632b173b8245beb Mon Sep 17 00:00:00 2001 From: Ramon Brullo Date: Sat, 31 Aug 2024 08:46:50 +0200 Subject: [PATCH 25/26] fix: incorrect import path --- test/unit/utils/string-utils.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/utils/string-utils.test.ts b/test/unit/utils/string-utils.test.ts index 08118153..d80ad4f3 100644 --- a/test/unit/utils/string-utils.test.ts +++ b/test/unit/utils/string-utils.test.ts @@ -1,4 +1,4 @@ -import { splitLines } from "../../src/utils/string-utils"; +import { splitLines } from "../../../src/utils/string-utils"; describe("string utils", () => { describe("split into lines", () => { From a96db47c74e24b7575639a1d7265a4172172fb07 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sat, 31 Aug 2024 06:53:07 +0000 Subject: [PATCH 26/26] chore(release): 4.1.1 [skip ci] ## [4.1.1](https://github.com/openupm/openupm-cli/compare/4.1.0...4.1.1) (2024-08-31) ### Bug Fixes * incorrect import path ([44fe7ea](https://github.com/openupm/openupm-cli/commit/44fe7ea0e5400a5d7403a56bf632b173b8245beb)) --- CHANGELOG.md | 7 +++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fb5147ac..6cab8326 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [4.1.1](https://github.com/openupm/openupm-cli/compare/4.1.0...4.1.1) (2024-08-31) + + +### Bug Fixes + +* incorrect import path ([44fe7ea](https://github.com/openupm/openupm-cli/commit/44fe7ea0e5400a5d7403a56bf632b173b8245beb)) + # [4.1.0](https://github.com/openupm/openupm-cli/compare/4.0.0...4.1.0) (2024-08-13) diff --git a/package-lock.json b/package-lock.json index 5d6d5326..156d0633 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "openupm-cli", - "version": "4.1.0", + "version": "4.1.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "openupm-cli", - "version": "4.1.0", + "version": "4.1.1", "license": "BSD-3-Clause", "dependencies": { "@commander-js/extra-typings": "^9.5.0", diff --git a/package.json b/package.json index 2a73a9e1..25d7a8b5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "openupm-cli", - "version": "4.1.0", + "version": "4.1.1", "preferGlobal": true, "description": "openupm command line interface", "engines": {