From f42ceb4ff1c88fd7debb655aa7e4010966f67aed Mon Sep 17 00:00:00 2001 From: Meza Date: Thu, 21 Dec 2023 18:33:22 +0000 Subject: [PATCH] fix(#62): a relative mods folder will now be calculated from the config file's location and not the current run dir as before --- src/actions/add.test.ts | 3 ++- src/actions/add.ts | 4 ++-- src/actions/change.test.ts | 17 ++++++++++++++-- src/actions/change.ts | 4 ++-- src/actions/install.test.ts | 19 +++++++++++++++++- src/actions/install.ts | 18 ++++++++++++----- src/actions/prune.test.ts | 3 ++- src/actions/prune.ts | 6 +++--- src/actions/remove.test.ts | 6 +++--- src/actions/remove.ts | 6 +++--- src/actions/scan.test.ts | 3 ++- src/actions/scan.ts | 8 ++++---- src/actions/update.test.ts | 11 ++++++++++- src/actions/update.ts | 7 ++++--- src/interactions/initializeConfig.test.ts | 1 + src/interactions/initializeConfig.ts | 7 ++++++- src/lib/config.test.ts | 24 ++++++++++++++++++++++- src/lib/config.ts | 13 ++++++++++++ src/lib/configurationHelper.test.ts | 21 +------------------- src/lib/configurationHelper.ts | 5 ----- src/lib/fileHelper.test.ts | 12 ++++++++---- src/lib/fileHelper.ts | 7 ++++--- src/lib/scan.test.ts | 2 +- src/lib/scan.ts | 2 +- 24 files changed, 141 insertions(+), 68 deletions(-) diff --git a/src/actions/add.test.ts b/src/actions/add.test.ts index df09579c..7f23dc67 100644 --- a/src/actions/add.test.ts +++ b/src/actions/add.test.ts @@ -1,7 +1,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { add } from './add.js'; import { - ensureConfiguration, + ensureConfiguration, getModsFolder, readLockFile, writeConfigFile, writeLockFile @@ -72,6 +72,7 @@ describe('The add module', async () => { // the main configuration to work with vi.mocked(ensureConfiguration).mockResolvedValue(context.randomConfiguration.generated); + vi.mocked(getModsFolder).mockReturnValue(context.randomConfiguration.generated.modsFolder); vi.mocked(readLockFile).mockResolvedValue([]); // the mod details returned from the repository diff --git a/src/actions/add.ts b/src/actions/add.ts index 4f213485..bc1b80ed 100644 --- a/src/actions/add.ts +++ b/src/actions/add.ts @@ -2,7 +2,7 @@ import path from 'path'; import { fetchModDetails } from '../repositories/index.js'; import { Mod, Platform } from '../lib/modlist.types.js'; import { - ensureConfiguration, + ensureConfiguration, getModsFolder, readLockFile, writeConfigFile, writeLockFile @@ -73,7 +73,7 @@ export const add = async (platform: Platform, id: string, options: AddOptions, l options.version ); - await downloadFile(modData.downloadUrl, path.resolve(configuration.modsFolder, modData.fileName)); + await downloadFile(modData.downloadUrl, path.resolve(getModsFolder(options.config, configuration), modData.fileName)); const installations = await readLockFile(options, logger); diff --git a/src/actions/change.test.ts b/src/actions/change.test.ts index eedd3d03..167e82cb 100644 --- a/src/actions/change.test.ts +++ b/src/actions/change.test.ts @@ -4,7 +4,14 @@ import { chance } from 'jest-chance'; import { DefaultOptions } from '../mmm.js'; import { changeGameVersion } from './change.js'; import { generateModsJson } from '../../test/modlistGenerator.js'; -import { fileExists, ensureConfiguration, readLockFile, writeConfigFile, writeLockFile } from '../lib/config.js'; +import { + fileExists, + ensureConfiguration, + readLockFile, + writeConfigFile, + writeLockFile, + getModsFolder +} from '../lib/config.js'; import { install } from './install.js'; import { testGameVersion } from './testGameVersion.js'; import { generateModInstall } from '../../test/modInstallGenerator.js'; @@ -48,6 +55,7 @@ describe('The change action', () => { options.quiet = quietFlag; vi.mocked(ensureConfiguration).mockResolvedValueOnce(config); + vi.mocked(getModsFolder).mockReturnValue(config.modsFolder); vi.mocked(readLockFile).mockResolvedValue([]); await changeGameVersion(version, options, logger); @@ -95,8 +103,10 @@ describe('The change action', () => { generateModConfig({ id: install1.id, type: install1.type }).generated, generateModConfig({ id: install2.id, type: install2.type }).generated, generateModConfig({ id: install3.id, type: install3.type }).generated - ] + ], + modsFolder: '/mods' }).generated); + vi.mocked(getModsFolder).mockReturnValue('/mods'); await changeGameVersion(version, options, logger); @@ -117,13 +127,16 @@ describe('The change action', () => { const install3 = generateModInstall({ fileName: 'mymod3' }).generated; vi.mocked(readLockFile).mockResolvedValueOnce([install1, install3]); + vi.mocked(ensureConfiguration).mockResolvedValue(generateModsJson({ + modsFolder: '/mods', mods: [ generateModConfig({ id: install1.id, type: install1.type }).generated, generateModConfig({ id: install2.id, type: install2.type }).generated, generateModConfig({ id: install3.id, type: install3.type }).generated ] }).generated); + vi.mocked(getModsFolder).mockReturnValue('/mods'); await changeGameVersion(version, options, logger); diff --git a/src/actions/change.ts b/src/actions/change.ts index f176e80d..43c9f085 100644 --- a/src/actions/change.ts +++ b/src/actions/change.ts @@ -4,7 +4,7 @@ import { testGameVersion } from './testGameVersion.js'; import { Mod } from '../lib/modlist.types.js'; import { ensureConfiguration, - fileExists, readLockFile, + fileExists, getModsFolder, readLockFile, writeConfigFile, writeLockFile } from '../lib/config.js'; @@ -25,7 +25,7 @@ export const changeGameVersion = async (gameVersion: string, options: VerifyUpgr if (hasInstallation(mod, installations)) { const installedModIndex = getInstallation(mod, installedMods); - const oldModPath = path.resolve(configuration.modsFolder, installedMods[installedModIndex].fileName); + const oldModPath = path.resolve(getModsFolder(options.config, configuration), installedMods[installedModIndex].fileName); if (await fileExists(oldModPath)) { await fs.rm(oldModPath); } diff --git a/src/actions/install.test.ts b/src/actions/install.test.ts index 99edcf8e..de75d499 100644 --- a/src/actions/install.test.ts +++ b/src/actions/install.test.ts @@ -1,7 +1,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; import { install } from './install.js'; import { fetchModDetails } from '../repositories/index.js'; -import { ensureConfiguration, readLockFile, writeConfigFile, writeLockFile } from '../lib/config.js'; +import { ensureConfiguration, getModsFolder, readLockFile, writeConfigFile, writeLockFile } from '../lib/config.js'; import { generateRemoteModDetails } from '../../test/generateRemoteDetails.js'; import { downloadFile } from '../lib/downloader.js'; import { updateMod } from '../lib/updater.js'; @@ -74,6 +74,7 @@ describe('The install module', () => { // Prepare the configuration file state vi.mocked(ensureConfiguration).mockResolvedValueOnce(randomConfiguration); + vi.mocked(getModsFolder).mockReturnValue(randomConfiguration.modsFolder); vi.mocked(readLockFile).mockResolvedValueOnce(emptyLockFile); // Prepare the details the mod details fetcher should return @@ -116,6 +117,7 @@ describe('The install module', () => { // Prepare the configuration file state vi.mocked(ensureConfiguration).mockResolvedValueOnce(randomConfiguration); + vi.mocked(getModsFolder).mockReturnValue(randomConfiguration.modsFolder); vi.mocked(readLockFile).mockResolvedValueOnce(emptyLockFile); // Prepare the details the mod details fetcher should return @@ -140,6 +142,7 @@ describe('The install module', () => { // Prepare the configuration file state vi.mocked(ensureConfiguration).mockResolvedValueOnce(randomConfiguration); + vi.mocked(getModsFolder).mockReturnValue(randomConfiguration.modsFolder); vi.mocked(readLockFile).mockResolvedValueOnce([ randomInstallation ]); @@ -178,6 +181,7 @@ describe('The install module', () => { // Prepare the configuration file state vi.mocked(ensureConfiguration).mockResolvedValueOnce(randomConfiguration); + vi.mocked(getModsFolder).mockReturnValue(randomConfiguration.modsFolder); vi.mocked(readLockFile).mockResolvedValueOnce([ randomInstallation ]); @@ -216,6 +220,7 @@ describe('The install module', () => { const { randomInstalledMod, randomInstallation, randomConfiguration } = setupOneInstalledMod(); vi.mocked(ensureConfiguration).mockResolvedValueOnce(randomConfiguration); + vi.mocked(getModsFolder).mockReturnValue(randomConfiguration.modsFolder); vi.mocked(readLockFile).mockResolvedValueOnce([randomInstallation]); vi.mocked(getHash).mockResolvedValueOnce(randomInstallation.hash); @@ -233,6 +238,7 @@ describe('The install module', () => { randomInstalledMod.version = '1.1.0'; vi.mocked(ensureConfiguration).mockResolvedValueOnce(randomConfiguration); + vi.mocked(getModsFolder).mockReturnValue(randomConfiguration.modsFolder); vi.mocked(readLockFile).mockResolvedValueOnce([randomInstallation]); vi.mocked(getHash).mockResolvedValueOnce(randomInstallation.hash); @@ -248,6 +254,7 @@ describe('The install module', () => { // Prepare the configuration file state vi.mocked(ensureConfiguration).mockResolvedValueOnce(randomConfiguration); + vi.mocked(getModsFolder).mockReturnValue(randomConfiguration.modsFolder); vi.mocked(readLockFile).mockResolvedValueOnce([ randomInstallation ]); @@ -281,6 +288,7 @@ describe('The install module', () => { const emptyConfiguration = generateModsJson().generated; const emptyInstallations: ModInstall[] = []; vi.mocked(ensureConfiguration).mockResolvedValueOnce(emptyConfiguration); + vi.mocked(getModsFolder).mockReturnValue(emptyConfiguration.modsFolder); vi.mocked(readLockFile).mockResolvedValueOnce(emptyInstallations); const file1 = chance.word(); @@ -303,6 +311,7 @@ describe('The install module', () => { const emptyConfiguration = generateModsJson().generated; const emptyInstallations: ModInstall[] = []; vi.mocked(ensureConfiguration).mockResolvedValueOnce(emptyConfiguration); + vi.mocked(getModsFolder).mockReturnValue(emptyConfiguration.modsFolder); vi.mocked(readLockFile).mockResolvedValueOnce(emptyInstallations); const file1 = chance.word(); @@ -341,6 +350,7 @@ describe('The install module', () => { const emptyConfiguration = generateModsJson().generated; const emptyInstallations: ModInstall[] = []; vi.mocked(ensureConfiguration).mockResolvedValueOnce(emptyConfiguration); + vi.mocked(getModsFolder).mockReturnValue(emptyConfiguration.modsFolder); vi.mocked(readLockFile).mockResolvedValueOnce(emptyInstallations); vi.mocked(hasInstallation).mockReturnValueOnce(false); @@ -379,6 +389,7 @@ describe('The install module', () => { // Prepare the configuration file state vi.mocked(ensureConfiguration).mockResolvedValueOnce(randomConfiguration); + vi.mocked(getModsFolder).mockReturnValue(randomConfiguration.modsFolder); vi.mocked(readLockFile).mockResolvedValueOnce([ randomInstallation ]); @@ -403,6 +414,7 @@ describe('The install module', () => { // Prepare the configuration file state vi.mocked(ensureConfiguration).mockResolvedValueOnce(randomConfiguration); + vi.mocked(getModsFolder).mockReturnValue(randomConfiguration.modsFolder); vi.mocked(readLockFile).mockResolvedValueOnce([ randomInstallation ]); @@ -430,6 +442,8 @@ describe('The install module', () => { // Prepare the configuration file state vi.mocked(ensureConfiguration).mockResolvedValueOnce(randomConfiguration); + vi.mocked(getModsFolder).mockReturnValue(randomConfiguration.modsFolder); + vi.mocked(getModsFolder).mockReturnValue(randomConfiguration.modsFolder); vi.mocked(readLockFile).mockResolvedValueOnce(emptyLockFile); // Prepare the details the mod details fetcher should return @@ -456,6 +470,7 @@ describe('The install module', () => { // Prepare the configuration file state vi.mocked(ensureConfiguration).mockResolvedValueOnce(randomConfiguration); + vi.mocked(getModsFolder).mockReturnValue(randomConfiguration.modsFolder); vi.mocked(readLockFile).mockResolvedValueOnce(emptyLockFile); const error = new CouldNotFindModException('id', Platform.MODRINTH); @@ -478,6 +493,7 @@ describe('The install module', () => { // Prepare the configuration file state vi.mocked(ensureConfiguration).mockResolvedValueOnce(randomConfiguration); + vi.mocked(getModsFolder).mockReturnValue(randomConfiguration.modsFolder); vi.mocked(readLockFile).mockResolvedValueOnce(emptyLockFile); const error = new NoRemoteFileFound(aModName, Platform.CURSEFORGE); vi.mocked(fetchModDetails).mockRejectedValueOnce(error); @@ -500,6 +516,7 @@ describe('The install module', () => { // Prepare the configuration file state vi.mocked(ensureConfiguration).mockResolvedValueOnce(randomConfiguration); + vi.mocked(getModsFolder).mockReturnValue(randomConfiguration.modsFolder); vi.mocked(readLockFile).mockResolvedValueOnce(emptyLockFile); vi.mocked(fetchModDetails).mockRejectedValueOnce(error); diff --git a/src/actions/install.ts b/src/actions/install.ts index 1cb358a8..663a7879 100644 --- a/src/actions/install.ts +++ b/src/actions/install.ts @@ -1,5 +1,12 @@ import chalk from 'chalk'; -import { ensureConfiguration, fileExists, readLockFile, writeConfigFile, writeLockFile } from '../lib/config.js'; +import { + ensureConfiguration, + fileExists, + getModsFolder, + readLockFile, + writeConfigFile, + writeLockFile +} from '../lib/config.js'; import path from 'path'; import { fetchModDetails } from '../repositories/index.js'; import { downloadFile } from '../lib/downloader.js'; @@ -26,7 +33,7 @@ const getMod = async (moddata: RemoteModDetails, modsFolder: string) => { const handleUnknownFiles = async (options: DefaultOptions, configuration: ModsJson, installations: ModInstall[], logger: Logger) => { //const modsFolder = getModsDir(options.config, configuration.modsFolder); - const allFiles = await getModFiles(options.config, configuration.modsFolder); + const allFiles = await getModFiles(options.config, configuration); const nonManagedFiles = allFiles.filter((filePath) => { return !fileIsManaged(filePath, installations); }); @@ -50,6 +57,7 @@ export const install = async (options: DefaultOptions, logger: Logger) => { await handleUnknownFiles(options, configuration, installations, logger); const installedMods = installations; const mods = configuration.mods; + const modsFolder = getModsFolder(options.config, configuration); const processMod = async (mod: Mod, index: number) => { const canonVersion = mod.version || 'latest'; @@ -59,7 +67,7 @@ export const install = async (options: DefaultOptions, logger: Logger) => { if (hasInstallation(mod, installations)) { const installedModIndex = getInstallation(mod, installedMods); - const modPath = path.resolve(configuration.modsFolder, installedMods[installedModIndex].fileName); + const modPath = path.resolve(getModsFolder(options.config, configuration), installedMods[installedModIndex].fileName); if (!await fileExists(modPath)) { logger.log(`${mod.name} doesn't exist, downloading from ${installedMods[installedModIndex].type}`); @@ -70,7 +78,7 @@ export const install = async (options: DefaultOptions, logger: Logger) => { const installedHash = await getHash(modPath); if (installedMods[installedModIndex].hash !== installedHash) { logger.log(`${mod.name} has hash mismatch, downloading from source`); - await updateMod(installedMods[installedModIndex], modPath, configuration.modsFolder); + await updateMod(installedMods[installedModIndex], modPath, modsFolder); return; } return; @@ -90,7 +98,7 @@ export const install = async (options: DefaultOptions, logger: Logger) => { // no installation exists logger.log(`${mod.name} doesn't exist, downloading from ${mod.type}`); - const dlData = await getMod(modData, configuration.modsFolder); + const dlData = await getMod(modData, modsFolder); installedMods.push({ name: modData.name, diff --git a/src/actions/prune.test.ts b/src/actions/prune.test.ts index 9cd91cdc..798ce1e3 100644 --- a/src/actions/prune.test.ts +++ b/src/actions/prune.test.ts @@ -3,7 +3,7 @@ import { Logger } from '../lib/Logger.js'; import { prune, PruneOptions } from './prune.js'; import { ModInstall, ModsJson } from '../lib/modlist.types.js'; import { generateModsJson } from '../../test/modlistGenerator.js'; -import { ensureConfiguration, readLockFile } from '../lib/config.js'; +import { ensureConfiguration, getModsFolder, readLockFile } from '../lib/config.js'; import { chance } from 'jest-chance'; import { fileIsManaged } from '../lib/configurationHelper.js'; import { shouldPruneFiles } from '../interactions/shouldPruneFiles.js'; @@ -40,6 +40,7 @@ describe('The prune action', () => { vi.mocked(ensureConfiguration).mockResolvedValueOnce(context.configuration); vi.mocked(readLockFile).mockResolvedValueOnce(context.installations); + vi.mocked(getModsFolder).mockReturnValue(context.configuration.modsFolder); }); diff --git a/src/actions/prune.ts b/src/actions/prune.ts index 4013ee4a..64697e29 100644 --- a/src/actions/prune.ts +++ b/src/actions/prune.ts @@ -1,6 +1,6 @@ import { DefaultOptions } from '../mmm.js'; import { Logger } from '../lib/Logger.js'; -import { ensureConfiguration, readLockFile } from '../lib/config.js'; +import { ensureConfiguration, getModsFolder, readLockFile } from '../lib/config.js'; import path from 'node:path'; import fs from 'fs/promises'; import { fileIsManaged } from '../lib/configurationHelper.js'; @@ -14,9 +14,9 @@ export interface PruneOptions extends DefaultOptions { export const prune = async (options: PruneOptions, logger: Logger) => { const configuration = await ensureConfiguration(options.config, logger); const installations = await readLockFile(options, logger); - const modsFolder = path.resolve(configuration.modsFolder); + const modsFolder = getModsFolder(options.config, configuration); - const files = await getModFiles(options.config, configuration.modsFolder); + const files = await getModFiles(options.config, configuration); if (files.length === 0) { logger.log('You have no files in your mods folder.'); diff --git a/src/actions/remove.test.ts b/src/actions/remove.test.ts index 979dee67..c20b0534 100644 --- a/src/actions/remove.test.ts +++ b/src/actions/remove.test.ts @@ -4,9 +4,9 @@ import { Mod, ModInstall, ModsJson } from '../lib/modlist.types.js'; import { generateModsJson } from '../../test/modlistGenerator.js'; import { removeAction, RemoveOptions } from './remove.js'; import { Logger } from '../lib/Logger.js'; -import { ensureConfiguration, readLockFile, writeConfigFile, writeLockFile } from '../lib/config.js'; +import { ensureConfiguration, getModsFolder, readLockFile, writeConfigFile, writeLockFile } from '../lib/config.js'; import { generateModConfig } from '../../test/modConfigGenerator.js'; -import { findLocalMods, getInstallation, getModsDir, hasInstallation } from '../lib/configurationHelper.js'; +import { findLocalMods, getInstallation, hasInstallation } from '../lib/configurationHelper.js'; import { chance } from 'jest-chance'; import { generateModInstall } from '../../test/modInstallGenerator.js'; import fs from 'fs/promises'; @@ -151,7 +151,7 @@ describe('The remove action', () => { const config = generateModsJson({ mods: [mod1, mod2, mod3] }).generated; - vi.mocked(getModsDir).mockReturnValue(path.resolve('/mods')); + vi.mocked(getModsFolder).mockReturnValue('/mods'); vi.mocked(ensureConfiguration).mockResolvedValueOnce(config); vi.mocked(readLockFile).mockResolvedValueOnce([ mod1Install, diff --git a/src/actions/remove.ts b/src/actions/remove.ts index 468b3542..9492e482 100644 --- a/src/actions/remove.ts +++ b/src/actions/remove.ts @@ -1,7 +1,7 @@ import { DefaultOptions } from '../mmm.js'; import { Logger } from '../lib/Logger.js'; -import { ensureConfiguration, readLockFile, writeConfigFile, writeLockFile } from '../lib/config.js'; -import { findLocalMods, getInstallation, getModsDir, hasInstallation } from '../lib/configurationHelper.js'; +import { ensureConfiguration, getModsFolder, readLockFile, writeConfigFile, writeLockFile } from '../lib/config.js'; +import { findLocalMods, getInstallation, hasInstallation } from '../lib/configurationHelper.js'; import fs from 'fs/promises'; import path from 'path'; import chalk from 'chalk'; @@ -14,7 +14,7 @@ export const removeAction = async (mods: string[], options: RemoveOptions, logge const configuration = await ensureConfiguration(options.config, logger); const installations = await readLockFile(options, logger); const matches = findLocalMods(mods, configuration); - const modsDir = getModsDir(options.config, configuration.modsFolder); + const modsDir = getModsFolder(options.config, configuration); if (options.dryRun) { logger.log(chalk.yellow('Running in dry-run mode. Nothing will actually be removed.')); diff --git a/src/actions/scan.test.ts b/src/actions/scan.test.ts index 6ba9f902..1946f319 100644 --- a/src/actions/scan.test.ts +++ b/src/actions/scan.test.ts @@ -4,7 +4,7 @@ import { scan, ScanOptions } from './scan.js'; import { chance } from 'jest-chance'; import { ModInstall, ModsJson, Platform } from '../lib/modlist.types.js'; import { scan as scanLib } from '../lib/scan.js'; -import { ensureConfiguration, readLockFile, writeConfigFile, writeLockFile } from '../lib/config.js'; +import { ensureConfiguration, getModsFolder, readLockFile, writeConfigFile, writeLockFile } from '../lib/config.js'; import { generateModsJson } from '../../test/modlistGenerator.js'; import { generateScanResult, ScanResultGeneratorOverrides } from '../../test/generateScanResult.js'; import { shouldAddScanResults } from '../interactions/shouldAddScanResults.js'; @@ -55,6 +55,7 @@ describe('The Scan action', () => { throw new Error('process.exit'); }); vi.mocked(getModFiles).mockResolvedValueOnce([]); + vi.mocked(getModsFolder).mockReturnValue(context.randomConfiguration.modsFolder); }); describe('when there are unexpected errors', () => { it('logs them correctly', async ({ options, logger }) => { diff --git a/src/actions/scan.ts b/src/actions/scan.ts index 8fefd08d..ff90e86e 100644 --- a/src/actions/scan.ts +++ b/src/actions/scan.ts @@ -1,13 +1,13 @@ import { DefaultOptions } from '../mmm.js'; import { Logger } from '../lib/Logger.js'; -import { ensureConfiguration, readLockFile, writeConfigFile, writeLockFile } from '../lib/config.js'; +import { ensureConfiguration, getModsFolder, readLockFile, writeConfigFile, writeLockFile } from '../lib/config.js'; import { PlatformLookupResult } from '../repositories/index.js'; import { Mod, ModInstall, ModsJson, Platform, RemoteModDetails } from '../lib/modlist.types.js'; import { scan as scanLib } from '../lib/scan.js'; import chalk from 'chalk'; import { shouldAddScanResults } from '../interactions/shouldAddScanResults.js'; -import { getInstallation, getModsDir } from '../lib/configurationHelper.js'; +import { getInstallation } from '../lib/configurationHelper.js'; import { getModFiles } from '../lib/fileHelper.js'; import path from 'path'; @@ -47,8 +47,8 @@ const processForeignFiles = async ( hasResults: boolean, logger: Logger ) => { - const modsFolder = getModsDir(options.config, configuration.modsFolder); - const allFiles = await getModFiles(options.config, configuration.modsFolder); + const modsFolder = getModsFolder(options.config, configuration); + const allFiles = await getModFiles(options.config, configuration); const nonMatchedFiles = allFiles.filter((filePath) => { const foundIndex = dealtWith.findIndex((dealtWithFile) => { diff --git a/src/actions/update.test.ts b/src/actions/update.test.ts index b83fcaf4..6b48ef98 100644 --- a/src/actions/update.test.ts +++ b/src/actions/update.test.ts @@ -8,7 +8,7 @@ import { } from '../../test/setupHelpers.js'; import { update } from './update.js'; import { getHash } from '../lib/hash.js'; -import { ensureConfiguration, readLockFile, writeConfigFile, writeLockFile } from '../lib/config.js'; +import { ensureConfiguration, getModsFolder, readLockFile, writeConfigFile, writeLockFile } from '../lib/config.js'; import { downloadFile } from '../lib/downloader.js'; import { fetchModDetails } from '../repositories/index.js'; import { generateRemoteModDetails } from '../../test/generateRemoteDetails.js'; @@ -63,6 +63,7 @@ describe('The update action', () => { vi.mocked(fetchModDetails).mockResolvedValueOnce(remoteDetails.generated); vi.mocked(ensureConfiguration).mockResolvedValueOnce(randomConfiguration); + vi.mocked(getModsFolder).mockReturnValue(randomConfiguration.modsFolder); vi.mocked(readLockFile).mockResolvedValueOnce([randomInstallation]); assumeModFileExists(randomInstallation.fileName); @@ -94,6 +95,7 @@ describe('The update action', () => { vi.mocked(fetchModDetails).mockResolvedValueOnce(remoteDetails.generated); vi.mocked(ensureConfiguration).mockResolvedValueOnce(randomConfiguration); + vi.mocked(getModsFolder).mockReturnValue(randomConfiguration.modsFolder); vi.mocked(readLockFile).mockResolvedValueOnce([randomInstallation]); assumeModFileExists(randomInstallation.fileName); @@ -133,6 +135,7 @@ describe('The update action', () => { vi.mocked(ensureConfiguration).mockResolvedValueOnce(randomConfiguration); vi.mocked(readLockFile).mockResolvedValueOnce([randomInstallation]); vi.mocked(updateMod).mockResolvedValueOnce(remoteDetails.generated); + vi.mocked(getModsFolder).mockReturnValue(randomConfiguration.modsFolder); assumeModFileExists(randomInstallation.fileName); @@ -187,6 +190,7 @@ describe('The update action', () => { vi.mocked(ensureConfiguration).mockResolvedValueOnce(randomConfiguration); vi.mocked(readLockFile).mockResolvedValueOnce([randomInstallation]); vi.mocked(updateMod).mockResolvedValueOnce(remoteDetails.generated); + vi.mocked(getModsFolder).mockReturnValue(randomConfiguration.modsFolder); assumeModFileExists(randomInstallation.fileName); @@ -222,6 +226,7 @@ describe('The update action', () => { vi.mocked(fetchModDetails).mockResolvedValueOnce(remoteDetails.generated); vi.mocked(ensureConfiguration).mockResolvedValueOnce(randomConfiguration); + vi.mocked(getModsFolder).mockReturnValue(randomConfiguration.modsFolder); vi.mocked(readLockFile).mockResolvedValueOnce([randomInstallation]); assumeModFileExists(randomInstallation.fileName); @@ -248,6 +253,7 @@ describe('The update action', () => { vi.mocked(fetchModDetails).mockResolvedValueOnce(remoteDetails.generated); vi.mocked(ensureConfiguration).mockResolvedValueOnce(randomConfiguration); + vi.mocked(getModsFolder).mockReturnValue(randomConfiguration.modsFolder); vi.mocked(readLockFile).mockResolvedValueOnce([]); await update(options, logger); @@ -272,7 +278,9 @@ describe('The update action', () => { vi.mocked(fetchModDetails).mockResolvedValueOnce(remoteDetails.generated); vi.mocked(ensureConfiguration).mockResolvedValueOnce(randomConfiguration); + vi.mocked(getModsFolder).mockReturnValue(randomConfiguration.modsFolder); vi.mocked(readLockFile).mockResolvedValueOnce([randomInstallation]); + vi.mocked(getModsFolder).mockReturnValue(randomConfiguration.modsFolder); assumeModFileIsMissing(randomInstallation); const expectedPath = path.resolve(randomConfiguration.modsFolder, randomInstallation.fileName); @@ -293,6 +301,7 @@ describe('The update action', () => { }); vi.mocked(ensureConfiguration).mockResolvedValueOnce(randomConfiguration); + vi.mocked(getModsFolder).mockReturnValue(randomConfiguration.modsFolder); await expect(update(options, logger)).rejects.toThrow(randomErrorMessage); }); diff --git a/src/actions/update.ts b/src/actions/update.ts index f659929f..cb4891fb 100644 --- a/src/actions/update.ts +++ b/src/actions/update.ts @@ -1,7 +1,7 @@ import { DefaultOptions } from '../mmm.js'; import { ensureConfiguration, - fileExists, + fileExists, getModsFolder, readLockFile, writeConfigFile, writeLockFile @@ -25,6 +25,7 @@ export const update = async (options: DefaultOptions, logger: Logger) => { const installedMods = installations; const mods = configuration.mods; + const modsFolder = getModsFolder(options.config, configuration); const processMod = async (mod: Mod, index: number) => { try { @@ -46,7 +47,7 @@ export const update = async (options: DefaultOptions, logger: Logger) => { } const installedModIndex = getInstallation(mod, installedMods); - const oldModPath = path.resolve(configuration.modsFolder, installedMods[installedModIndex].fileName); + const oldModPath = path.resolve(modsFolder, installedMods[installedModIndex].fileName); if (!await fileExists(oldModPath)) { logger.error(`${mod.name} (${oldModPath}) doesn't exist. Please delete the lock file and the mods folder and try again.`, 1); @@ -55,7 +56,7 @@ export const update = async (options: DefaultOptions, logger: Logger) => { const installedHash = await getHash(oldModPath); if (modData.hash !== installedHash || modData.releaseDate > installedMods[installedModIndex].releasedOn) { logger.log(`${mod.name} has an update, downloading...`); - await updateMod(modData, oldModPath, configuration.modsFolder); + await updateMod(modData, oldModPath, modsFolder); installedMods[installedModIndex].hash = modData.hash; installedMods[installedModIndex].downloadUrl = modData.downloadUrl; diff --git a/src/interactions/initializeConfig.test.ts b/src/interactions/initializeConfig.test.ts index 799d92f8..267a1bb7 100644 --- a/src/interactions/initializeConfig.test.ts +++ b/src/interactions/initializeConfig.test.ts @@ -350,6 +350,7 @@ describe('The Initialization Interaction', () => { it('skips the mods folder question when it is supplied', async () => { const input = generateInitializeOptions().generated; + input.modsFolder = `/${input.modsFolder}`; vi.mocked(inquirer.prompt).mockResolvedValueOnce({}); await initializeConfig(input, chance.word(), logger); diff --git a/src/interactions/initializeConfig.ts b/src/interactions/initializeConfig.ts index a6d914fb..fd8fd0ab 100644 --- a/src/interactions/initializeConfig.ts +++ b/src/interactions/initializeConfig.ts @@ -39,7 +39,12 @@ const mergeOptions = (options: InitializeOptions, iq: IQInternal) => { }; const validateModsFolder = async (input: string, cwd: string) => { - const dir = path.resolve(cwd, input); + let dir = path.resolve(cwd, input); + + if (path.isAbsolute(input)) { + dir = input; + } + if (!await fileExists(dir)) { return `The folder: ${dir} does not exist. Please enter a valid one and try again.`; } diff --git a/src/lib/config.test.ts b/src/lib/config.test.ts index b21fe359..dd3cd592 100644 --- a/src/lib/config.test.ts +++ b/src/lib/config.test.ts @@ -3,7 +3,7 @@ import fs from 'node:fs/promises'; import { chance } from 'jest-chance'; import { ensureConfiguration, - fileExists, + fileExists, getModsFolder, initializeConfigFile, readConfigFile, readLockFile, @@ -36,6 +36,7 @@ describe('The config library', () => { let logger: Logger; beforeEach((context) => { vi.resetAllMocks(); + vi.unstubAllGlobals(); context.logger = new Logger({} as never); context.options = { config: 'config.json', @@ -234,4 +235,25 @@ describe('The config library', () => { expect(actualOutput).toEqual(randomModsJson.expected); }); }); + + it('can resolve a relative mod folder', () => { + const randomModsJson = generateModsJson().generated; + const configPath = path.resolve('/some-path/config.json'); + const expected = path.resolve('/some-path/mods'); + randomModsJson.modsFolder = 'mods'; + const actual = getModsFolder(configPath, randomModsJson); + + expect(actual).toEqual(expected); + }); + + it('can resolve an absolute mod folder', () => { + const randomModsJson = generateModsJson().generated; + const configPath = '/some-path/config.json'; + const modsFolder = '/my-ultimate-mods'; + randomModsJson.modsFolder = modsFolder; + const expected = '/my-ultimate-mods'; + const actual = getModsFolder(configPath, randomModsJson); + + expect(actual).toEqual(expected); + }); }); diff --git a/src/lib/config.ts b/src/lib/config.ts index 7615699d..0c43c6fe 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -84,3 +84,16 @@ export const ensureConfiguration = async (configPath: string, logger: Logger, qu throw error; } }; + +export const getModsFolder = (configLocation: string, config: ModsJson): string => { + const realConfigLocation = path.resolve(configLocation); + const configFolder = path.dirname(realConfigLocation); + const configuredModsFolder = config.modsFolder; + + if (path.isAbsolute(configuredModsFolder)) { + return configuredModsFolder; + } + + return path.resolve(configFolder, configuredModsFolder); + +}; diff --git a/src/lib/configurationHelper.test.ts b/src/lib/configurationHelper.test.ts index 477ebc34..a072f7ae 100644 --- a/src/lib/configurationHelper.test.ts +++ b/src/lib/configurationHelper.test.ts @@ -2,10 +2,9 @@ import { describe, it, expect, beforeEach } from 'vitest'; import { generateModInstall } from '../../test/modInstallGenerator.js'; import { Mod, ModInstall, Platform } from './modlist.types.js'; import { generateModConfig } from '../../test/modConfigGenerator.js'; -import { fileIsManaged, findLocalMods, getInstallation, getModsDir, hasInstallation } from './configurationHelper.js'; +import { fileIsManaged, findLocalMods, getInstallation, hasInstallation } from './configurationHelper.js'; import { chance } from 'jest-chance'; import { generateModsJson } from '../../test/modlistGenerator.js'; -import path from 'node:path'; interface LocalTestContext { installations: ModInstall[]; @@ -41,24 +40,6 @@ describe('The configuration helper', () => { expect(fileIsManaged('does-not-exist.jar', installations)).toBeFalsy(); }); - it('can resolve a relative mod folder', () => { - const configPath = path.resolve('/some-path/config.json'); - const modsFolder = 'mods'; - const expected = path.resolve('/some-path/mods'); - const actual = getModsDir(configPath, modsFolder); - - expect(actual).toEqual(expected); - }); - - it('can resolve an absolute mod folder', () => { - const configPath = '/some-path/config.json'; - const modsFolder = '/my-ultimate-mods'; - const expected = '/my-ultimate-mods'; - const actual = getModsDir(configPath, modsFolder); - - expect(actual).toEqual(expected); - }); - describe('when looking up mods', () => { it('can find a mod by ID', () => { const modId = chance.word(); diff --git a/src/lib/configurationHelper.ts b/src/lib/configurationHelper.ts index 77313c38..f272bf96 100644 --- a/src/lib/configurationHelper.ts +++ b/src/lib/configurationHelper.ts @@ -35,11 +35,6 @@ export const fileIsManaged = (file: string, installations: ModInstall[]) => { return result !== undefined; }; -export const getModsDir = (configPath: string, modsFolder: string) => { - const dir = path.resolve(path.dirname(configPath)); - return path.isAbsolute(modsFolder) ? modsFolder : path.resolve(dir, modsFolder); -}; - export const getInstallation = (mod: Mod, installations: ModInstall[]) => { return installations.findIndex((i) => i.id === mod.id && i.type === mod.type); }; diff --git a/src/lib/fileHelper.test.ts b/src/lib/fileHelper.test.ts index e6116aed..6c9998e1 100644 --- a/src/lib/fileHelper.test.ts +++ b/src/lib/fileHelper.test.ts @@ -1,9 +1,11 @@ import { beforeEach, describe, vi, it, expect } from 'vitest'; import * as fs from 'fs/promises'; +import { generateModsJson } from '../../test/modlistGenerator.js'; import { notIgnored } from './ignore.js'; import { chance } from 'jest-chance'; import path from 'path'; import { getModFiles } from './fileHelper.js'; +import { ModsJson } from './modlist.types.js'; vi.mock('./ignore.js'); vi.mock('fs/promises'); @@ -11,24 +13,26 @@ vi.mock('fs/promises'); interface LocalTestContext { configLocation: string; rootDir: string; + configuration: ModsJson; } describe('The file helper module', () => { beforeEach((context) => { context.rootDir = path.resolve('/', chance.word()); context.configLocation = path.resolve(context.rootDir, chance.word()); + context.configuration = generateModsJson({ modsFolder: 'mods' }).generated; }); - it('can handle a relative mods folder', async ({ configLocation, rootDir }) => { + it('can handle a relative mods folder', async ({ configLocation, rootDir, configuration }) => { vi.mocked(fs.readdir).mockResolvedValueOnce([]); - await getModFiles(configLocation, 'mods'); + await getModFiles(configLocation, configuration); expect(fs.readdir).toHaveBeenCalledWith(path.resolve(rootDir, 'mods')); }); - it('applies the ignore filter', async ({ configLocation, rootDir }) => { + it('applies the ignore filter', async ({ configLocation, rootDir, configuration }) => { const foundFiles = chance.n(() => { return path.resolve(rootDir, 'mods', chance.word()); }, chance.integer({ min: 2, max: 20 })); @@ -36,7 +40,7 @@ describe('The file helper module', () => { vi.mocked(fs.readdir).mockResolvedValueOnce(foundFiles); vi.mocked(notIgnored).mockResolvedValueOnce(notIgnoredFiles); - const actual = await getModFiles(configLocation, 'mods'); + const actual = await getModFiles(configLocation, configuration); expect(notIgnored).toHaveBeenCalledWith(rootDir, foundFiles); expect(actual).toEqual(notIgnoredFiles); diff --git a/src/lib/fileHelper.ts b/src/lib/fileHelper.ts index 93b56852..923fe1f6 100644 --- a/src/lib/fileHelper.ts +++ b/src/lib/fileHelper.ts @@ -1,11 +1,12 @@ import path from 'path'; import * as fs from 'fs/promises'; +import { getModsFolder } from './config.js'; import { notIgnored } from './ignore.js'; -import { getModsDir } from './configurationHelper.js'; +import { ModsJson } from './modlist.types.js'; -export const getModFiles = async (configLocation: string, modsFolder: string) => { +export const getModFiles = async (configLocation: string, configuration: ModsJson) => { const dir = path.resolve(path.dirname(configLocation)); - const modsDir = getModsDir(configLocation, modsFolder); + const modsDir = getModsFolder(configLocation, configuration); const modFileNames = await fs.readdir(modsDir); const files = modFileNames.map((file) => { return path.resolve(modsDir, file); diff --git a/src/lib/scan.test.ts b/src/lib/scan.test.ts index 57991782..9b6245ad 100644 --- a/src/lib/scan.test.ts +++ b/src/lib/scan.test.ts @@ -45,7 +45,7 @@ describe('The scan library', () => { const actual = await scan(context.config, context.randomPlatform, context.randomConfiguration, context.randomInstallations); expect(actual).toEqual([]); - expect(vi.mocked(getModFiles)).toHaveBeenCalledWith(context.config, randomModsFolder); + expect(vi.mocked(getModFiles)).toHaveBeenCalledWith(context.config, context.randomConfiguration); }); }); diff --git a/src/lib/scan.ts b/src/lib/scan.ts index f9cdb017..c0bcc00b 100644 --- a/src/lib/scan.ts +++ b/src/lib/scan.ts @@ -92,6 +92,6 @@ export const scanFiles = async (files: string[], installations: ModInstall[], pr }; export const scan = async (configLocation: string, prefer: Platform, configuration: ModsJson, installations: ModInstall[]) => { - const files = await getModFiles(configLocation, configuration.modsFolder); + const files = await getModFiles(configLocation, configuration); return scanFiles(files, installations, prefer, configuration); };