diff --git a/src/cli/cmd-add.ts b/src/cli/cmd-add.ts index 85824068..a0cb27b7 100644 --- a/src/cli/cmd-add.ts +++ b/src/cli/cmd-add.ts @@ -37,6 +37,7 @@ import { areArraysEqual } from "../utils/array-utils"; import { Err, Ok, Result } from "ts-results-es"; import { CustomError } from "ts-custom-error"; import { + logDetermineEditorError, logEnvParseError, logManifestLoadError, logManifestSaveError, @@ -54,6 +55,7 @@ import { tryGetTargetEditorVersionFor } from "../domain/package-manifest"; import { VersionNotFoundError } from "../domain/packument"; import { FetchPackumentError } from "../io/packument-io"; import { DebugLog } from "../logging"; +import { DetermineEditorVersion } from "../services/determine-editor-version"; export class InvalidPackumentDataError extends CustomError { private readonly _class = "InvalidPackumentDataError"; @@ -113,6 +115,7 @@ export function makeAddCmd( resolveDependencies: ResolveDependenciesService, loadProjectManifest: LoadProjectManifest, writeProjectManifest: WriteProjectManifest, + determineEditorVersion: DetermineEditorVersion, log: Logger, debugLog: DebugLog ): AddCmd { @@ -126,10 +129,17 @@ export function makeAddCmd( } const env = envResult.value; - if (typeof env.editorVersion === "string") + const editorVersionResult = await determineEditorVersion(env.cwd).promise; + if (editorVersionResult.isErr()) { + logDetermineEditorError(log, editorVersionResult.error); + return editorVersionResult; + } + const editorVersion = editorVersionResult.value; + + if (typeof editorVersion === "string") log.warn( "editor.version", - `${env.editorVersion} is unknown, the editor version check is disabled` + `${editorVersion} is unknown, the editor version check is disabled` ); const tryAddToManifest = async function ( @@ -194,13 +204,13 @@ export function makeAddCmd( // verify editor version if ( targetEditorVersion !== null && - typeof env.editorVersion !== "string" && - compareEditorVersion(env.editorVersion, targetEditorVersion) < 0 + typeof editorVersion !== "string" && + compareEditorVersion(editorVersion, targetEditorVersion) < 0 ) { log.warn( "editor.version", `requires ${targetEditorVersion} but found ${stringifyEditorVersion( - env.editorVersion + editorVersion )}` ); if (!options.force) { diff --git a/src/cli/error-logging.ts b/src/cli/error-logging.ts index 81fd6152..ae8dff4b 100644 --- a/src/cli/error-logging.ts +++ b/src/cli/error-logging.ts @@ -13,6 +13,7 @@ import { ChildProcessError } from "../utils/process"; import { RequiredEnvMissingError } from "../io/upm-config-io"; import { FileMissingError, GenericIOError } from "../io/common-errors"; import { StringFormatError } from "../utils/string-parsing"; +import { DetermineEditorVersionError } from "../services/determine-editor-version"; /** * Logs a {@link ManifestLoadError} to the console. @@ -70,14 +71,10 @@ export function logEnvParseError(log: Logger, error: EnvParseError) { const reason = error instanceof NoWslError ? "you attempted to use wsl even though you are not running openupm inside wsl" - : error instanceof FileMissingError - ? `the projects version file (ProjectVersion.txt) could not be found at "${error.path}"` : error instanceof GenericIOError ? `a file-system interaction failed` : error instanceof ChildProcessError ? "a required child process failed" - : error instanceof FileParseError - ? `the project version file (ProjectVersion.txt) has an invalid structure` : error instanceof RequiredEnvMissingError ? `none of the following environment variables were set: ${error.keyNames.join( ", " @@ -88,3 +85,25 @@ export function logEnvParseError(log: Logger, error: EnvParseError) { // TODO: Suggest actions user might take in order to fix the problem. } + +/** + * Logs a {@link DetermineEditorVersionError} to a logger. + */ +export function logDetermineEditorError( + log: Logger, + error: DetermineEditorVersionError +) { + const reason = + error instanceof FileMissingError + ? `the projects version file (ProjectVersion.txt) could not be found at "${error.path}"` + : error instanceof GenericIOError + ? `a file-system interaction failed` + : error instanceof FileParseError + ? `the project version file (ProjectVersion.txt) has an invalid structure` + : `the project versions file (ProjectVersion.txt) did not contain valid yaml`; + + const errorMessage = `editor version could be determined because ${reason}.`; + log.error("", errorMessage); + + // TODO: Suggest actions user might take in order to fix the problem. +} diff --git a/src/cli/index.ts b/src/cli/index.ts index 49b6075b..7704a2c5 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -47,6 +47,7 @@ import { makePackagesSearcher } from "../services/search-packages"; import { makeRemotePackumentResolver } from "../services/resolve-remote-packument"; import { makeLoginService } from "../services/login"; import { DebugLog } from "../logging"; +import { makeEditorVersionDeterminer } from "../services/determine-editor-version"; // Composition root @@ -80,9 +81,9 @@ const parseEnv = makeParseEnvService( log, getUpmConfigPath, loadUpmConfig, - getCwd, - loadProjectVersion + getCwd ); +const determineEditorVersion = makeEditorVersionDeterminer(loadProjectVersion); const authNpmrc = makeAuthNpmrcService(findNpmrcPath, loadNpmrc, saveNpmrc); const npmLogin = makeNpmLoginService(regClient); const resolveRemovePackumentVersion = @@ -110,6 +111,7 @@ const addCmd = makeAddCmd( resolveDependencies, loadProjectManifest, writeProjectManifest, + determineEditorVersion, log, debugLog ); diff --git a/src/services/determine-editor-version.ts b/src/services/determine-editor-version.ts new file mode 100644 index 00000000..cec78c1c --- /dev/null +++ b/src/services/determine-editor-version.ts @@ -0,0 +1,41 @@ +import { AsyncResult } from "ts-results-es"; +import { + isRelease, + ReleaseVersion, + tryParseEditorVersion, +} from "../domain/editor-version"; +import { + LoadProjectVersion, + ProjectVersionLoadError, +} from "../io/project-version-io"; + +/** + * Error which may occur when determining the editor-version. + */ +export type DetermineEditorVersionError = ProjectVersionLoadError; + +/** + * Function for determining the editor-version for a Unity project. + * @param projectPath The path to the projects root directory. + * @returns The editor-version. Either a parsed version object or the raw + * version string if it could not be parsed. + */ +export type DetermineEditorVersion = ( + projectPath: string +) => AsyncResult; + +/** + * Makes a {@link DetermineEditorVersion} function. + */ +export function makeEditorVersionDeterminer( + loadProjectVersion: LoadProjectVersion +): DetermineEditorVersion { + return (projectPath) => { + return loadProjectVersion(projectPath).map((unparsedEditorVersion) => { + const parsedEditorVersion = tryParseEditorVersion(unparsedEditorVersion); + return parsedEditorVersion !== null && isRelease(parsedEditorVersion) + ? parsedEditorVersion + : unparsedEditorVersion; + }); + }; +} diff --git a/src/services/parse-env.ts b/src/services/parse-env.ts index 2f8f1e03..568c4a7d 100644 --- a/src/services/parse-env.ts +++ b/src/services/parse-env.ts @@ -9,18 +9,8 @@ import path from "path"; import { coerceRegistryUrl, makeRegistryUrl } from "../domain/registry-url"; import { tryGetAuthForRegistry, UPMConfig } from "../domain/upm-config"; import { CmdOptions } from "../cli/options"; -import { FileParseError } from "../common-errors"; import { Ok, Result } from "ts-results-es"; -import { - LoadProjectVersion, - ProjectVersionLoadError, -} from "../io/project-version-io"; import { tryGetEnv } from "../utils/env-util"; -import { - isRelease, - ReleaseVersion, - tryParseEditorVersion, -} from "../domain/editor-version"; import { Registry } from "../domain/registry"; import { Logger } from "npmlog"; import { GetCwd } from "../io/special-paths"; @@ -32,17 +22,9 @@ export type Env = Readonly<{ upstream: boolean; upstreamRegistry: Registry; registry: Registry; - /** - * The current project's editor version. Either a parsed {@link EditorVersion} - * object if parsing was successful or the unparsed string. - */ - editorVersion: ReleaseVersion | string; }>; -export type EnvParseError = - | GetUpmConfigPathError - | UpmConfigLoadError - | ProjectVersionLoadError; +export type EnvParseError = GetUpmConfigPathError | UpmConfigLoadError; /** * Service function for parsing environment information and global @@ -59,8 +41,7 @@ export function makeParseEnvService( log: Logger, getUpmConfigPath: GetUpmConfigPath, loadUpmConfig: LoadUpmConfig, - getCwd: GetCwd, - loadProjectVersion: LoadProjectVersion + getCwd: GetCwd ): ParseEnvService { function determineCwd(options: CmdOptions): string { return options._global.chdir !== undefined @@ -156,20 +137,8 @@ export function makeParseEnvService( // cwd const cwd = determineCwd(options); - // editor version - const projectVersionLoadResult = await loadProjectVersion(cwd).promise; - if (projectVersionLoadResult.isErr()) return projectVersionLoadResult; - - const unparsedEditorVersion = projectVersionLoadResult.value; - const parsedEditorVersion = tryParseEditorVersion(unparsedEditorVersion); - const editorVersion = - parsedEditorVersion !== null && isRelease(parsedEditorVersion) - ? parsedEditorVersion - : unparsedEditorVersion; - return Ok({ cwd, - editorVersion, registry, systemUser, upstream, diff --git a/test/cli/cmd-add.test.ts b/test/cli/cmd-add.test.ts index fbe4e573..24a9b368 100644 --- a/test/cli/cmd-add.test.ts +++ b/test/cli/cmd-add.test.ts @@ -32,6 +32,7 @@ import { makePackageReference } from "../../src/domain/package-reference"; import { VersionNotFoundError } from "../../src/domain/packument"; import { noopLogger } from "../../src/logging"; import { FileMissingError, GenericIOError } from "../../src/io/common-errors"; +import { DetermineEditorVersion } from "../../src/services/determine-editor-version"; const somePackage = makeDomainName("com.some.package"); const otherPackage = makeDomainName("com.other.package"); @@ -59,7 +60,6 @@ const defaultEnv = { upstream: true, registry: { url: exampleRegistryUrl, auth: null }, upstreamRegistry: { url: unityRegistryUrl, auth: null }, - editorVersion: makeEditorVersion(2022, 2, 1, "f", 2), } as Env; function makeDependencies() { @@ -101,6 +101,11 @@ function makeDependencies() { const writeProjectManifest = mockService(); mockProjectManifestWriteResult(writeProjectManifest); + const determineEditorVersion = mockService(); + determineEditorVersion.mockReturnValue( + Ok(makeEditorVersion(2022, 2, 1, "f", 2)).toAsyncResult() + ); + const log = makeMockLogger(); const addCmd = makeAddCmd( @@ -109,6 +114,7 @@ function makeDependencies() { resolveDependencies, loadProjectManifest, writeProjectManifest, + determineEditorVersion, log, noopLogger ); @@ -119,6 +125,7 @@ function makeDependencies() { resolveDependencies, loadProjectManifest, writeProjectManifest, + determineEditorVersion, log, } as const; } @@ -134,6 +141,19 @@ describe("cmd-add", () => { expect(result).toBeError((actual) => expect(actual).toEqual(expected)); }); + it("should fail if editor-version could not be determined", async () => { + const { addCmd, determineEditorVersion } = makeDependencies(); + determineEditorVersion.mockReturnValue( + Err(new GenericIOError()).toAsyncResult() + ); + + const result = await addCmd(somePackage, { _global: {} }); + + expect(result).toBeError((actual) => + expect(actual).toBeInstanceOf(GenericIOError) + ); + }); + it("should fail if manifest could not be loaded", async () => { const { addCmd, loadProjectManifest } = makeDependencies(); mockProjectManifest(loadProjectManifest, null); @@ -203,10 +223,8 @@ describe("cmd-add", () => { }); it("should notify if editor-version is unknown", async () => { - const { addCmd, parseEnv, log } = makeDependencies(); - parseEnv.mockResolvedValue( - Ok({ ...defaultEnv, editorVersion: "bad version" }) - ); + const { addCmd, determineEditorVersion, log } = makeDependencies(); + determineEditorVersion.mockReturnValue(Ok("bad version").toAsyncResult()); await addCmd(somePackage, { _global: {}, diff --git a/test/cli/cmd-login.test.ts b/test/cli/cmd-login.test.ts index 124da938..fabf6606 100644 --- a/test/cli/cmd-login.test.ts +++ b/test/cli/cmd-login.test.ts @@ -10,7 +10,6 @@ import { LoginService } from "../../src/services/login"; import { makeMockLogger } from "./log.mock"; import { exampleRegistryUrl } from "../domain/data-registry"; import { unityRegistryUrl } from "../../src/domain/registry-url"; -import { makeEditorVersion } from "../../src/domain/editor-version"; import { AuthenticationError } from "../../src/services/npm-login"; import { GenericIOError } from "../../src/io/common-errors"; @@ -19,7 +18,6 @@ const defaultEnv = { upstream: true, registry: { url: exampleRegistryUrl, auth: null }, upstreamRegistry: { url: unityRegistryUrl, auth: null }, - editorVersion: makeEditorVersion(2022, 2, 1, "f", 2), } as Env; const exampleUser = "user"; const examplePassword = "pass"; diff --git a/test/services/determine-editor-version.test.ts b/test/services/determine-editor-version.test.ts new file mode 100644 index 00000000..123b3f9a --- /dev/null +++ b/test/services/determine-editor-version.test.ts @@ -0,0 +1,60 @@ +import { makeEditorVersionDeterminer } from "../../src/services/determine-editor-version"; +import { mockService } from "./service.mock"; +import { LoadProjectVersion } from "../../src/io/project-version-io"; +import { makeEditorVersion } from "../../src/domain/editor-version"; +import { mockProjectVersion } from "../io/project-version-io.mock"; +import { GenericIOError } from "../../src/io/common-errors"; +import { Err } from "ts-results-es"; + +describe("determine editor version", () => { + const exampleProjectPath = "/home/my-project/"; + + function makeDependencies() { + const loadProjectVersion = mockService(); + + const determineEditorVersion = + makeEditorVersionDeterminer(loadProjectVersion); + return { determineEditorVersion, loadProjectVersion } as const; + } + + it("should be parsed object for valid release versions", async () => { + const { determineEditorVersion, loadProjectVersion } = makeDependencies(); + mockProjectVersion(loadProjectVersion, "2021.3.1f1"); + + const result = await determineEditorVersion(exampleProjectPath).promise; + + expect(result).toBeOk((version) => + expect(version).toEqual(makeEditorVersion(2021, 3, 1, "f", 1)) + ); + }); + + it("should be original string for non-release versions", async () => { + const { determineEditorVersion, loadProjectVersion } = makeDependencies(); + const expected = "2022.3"; + mockProjectVersion(loadProjectVersion, expected); + + const result = await determineEditorVersion(exampleProjectPath).promise; + + expect(result).toBeOk((version) => expect(version).toEqual(expected)); + }); + + it("should be original string for non-version string", async () => { + const { determineEditorVersion, loadProjectVersion } = makeDependencies(); + const expected = "Bad version"; + mockProjectVersion(loadProjectVersion, expected); + + const result = await determineEditorVersion(exampleProjectPath).promise; + + expect(result).toBeOk((version) => expect(version).toEqual(expected)); + }); + + it("should fail if ProjectVersion.txt could not be loaded", async () => { + const { determineEditorVersion, loadProjectVersion } = makeDependencies(); + const expected = new GenericIOError(); + loadProjectVersion.mockReturnValue(Err(expected).toAsyncResult()); + + const result = await determineEditorVersion(exampleProjectPath).promise; + + expect(result).toBeError((error) => expect(error).toEqual(expected)); + }); +}); diff --git a/test/services/parse-env.test.ts b/test/services/parse-env.test.ts index 7b7a0181..c7ffa7de 100644 --- a/test/services/parse-env.test.ts +++ b/test/services/parse-env.test.ts @@ -3,16 +3,12 @@ import { NpmAuth } from "another-npm-registry-client"; import { Env, makeParseEnvService } from "../../src/services/parse-env"; import { Err, Ok } from "ts-results-es"; import { GetUpmConfigPath, LoadUpmConfig } from "../../src/io/upm-config-io"; -import { makeEditorVersion } from "../../src/domain/editor-version"; import { NoWslError } from "../../src/io/wsl"; import { mockUpmConfig } from "../io/upm-config-io.mock"; -import { mockProjectVersion } from "../io/project-version-io.mock"; import { exampleRegistryUrl } from "../domain/data-registry"; import { makeMockLogger } from "../cli/log.mock"; import { mockService } from "./service.mock"; import { GetCwd } from "../../src/io/special-paths"; -import { LoadProjectVersion } from "../../src/io/project-version-io"; -import { GenericIOError } from "../../src/io/common-errors"; import path from "path"; const testRootPath = "/users/some-user/projects/MyUnityProject"; @@ -31,8 +27,6 @@ const testUpmConfig: UPMConfig = { npmAuth: { [exampleRegistryUrl]: testUpmAuth }, }; -const testProjectVersion = "2021.3.1f1"; - function makeDependencies() { const log = makeMockLogger(); @@ -47,22 +41,17 @@ function makeDependencies() { const getCwd = mockService(); getCwd.mockReturnValue(testRootPath); - const loadProjectVersion = mockService(); - mockProjectVersion(loadProjectVersion, testProjectVersion); - const parseEnv = makeParseEnvService( log, getUpmConfigPath, loadUpmConfig, - getCwd, - loadProjectVersion + getCwd ); return { parseEnv, log, getUpmConfigPath, loadUpmConfig, - loadProjectVersion, } as const; } @@ -468,58 +457,4 @@ describe("env", () => { expect(result).toBeOk((env: Env) => expect(env.cwd).toEqual(expected)); }); }); - - describe("editor-version", () => { - it("should be parsed object for valid release versions", async () => { - const { parseEnv } = makeDependencies(); - - const result = await parseEnv({ - _global: {}, - }); - - expect(result).toBeOk((env: Env) => - expect(env.editorVersion).toEqual(makeEditorVersion(2021, 3, 1, "f", 1)) - ); - }); - - it("should be original string for non-release versions", async () => { - const { parseEnv, loadProjectVersion } = makeDependencies(); - const expected = "2022.3"; - mockProjectVersion(loadProjectVersion, expected); - - const result = await parseEnv({ - _global: {}, - }); - - expect(result).toBeOk((env: Env) => - expect(env.editorVersion).toEqual(expected) - ); - }); - - it("should be original string for non-version string", async () => { - const { parseEnv, loadProjectVersion } = makeDependencies(); - const expected = "Bad version"; - mockProjectVersion(loadProjectVersion, expected); - - const result = await parseEnv({ - _global: {}, - }); - - expect(result).toBeOk((env: Env) => - expect(env.editorVersion).toEqual(expected) - ); - }); - - it("should fail if ProjectVersion.txt could not be loaded", async () => { - const { parseEnv, loadProjectVersion } = makeDependencies(); - const expected = new GenericIOError(); - loadProjectVersion.mockReturnValue(Err(expected).toAsyncResult()); - - const result = await parseEnv({ - _global: {}, - }); - - expect(result).toBeError((error) => expect(error).toEqual(expected)); - }); - }); });