diff --git a/src/commands/configure.ts b/src/commands/configure.ts index 554360e6..29cd615c 100644 --- a/src/commands/configure.ts +++ b/src/commands/configure.ts @@ -18,6 +18,7 @@ export default class ConfigureCommand extends Command { 'magma configure -n -u UserName', ]; public static flags = { + batchScript: setFlag.batchScript, linuxGsmInstanceConfig: setFlag.linuxGsmInstanceConfig, nonInteractive: setFlag.nonInteractive, password: setFlag.password, @@ -62,6 +63,13 @@ export default class ConfigureCommand extends Command { let value; switch (key) { + case 'batchScript': + configKey = 'batchScript'; + value = await this.validateEntry( + this.convertFlagToEntry('batchScript'), flags.batchScript + ); + break; + case 'linuxGsmInstanceConfig': configKey = 'linuxGsm'; value = await this.validateEntry( @@ -154,6 +162,10 @@ export default class ConfigureCommand extends Command { break; + case 'batchScript': + output = await this.insurer.ensureValidBatchScript(...values) as K; + break; + case 'linuxGsm': output = await this.insurer.ensureValidLinuxGsm(...values) as K; break; diff --git a/src/commands/initialize.ts b/src/commands/initialize.ts index b91b4ded..886f13a4 100644 --- a/src/commands/initialize.ts +++ b/src/commands/initialize.ts @@ -17,6 +17,7 @@ export default class InitializeCommand extends Command { description: 'Skip the check for the magma.json file. If it exists, it will be overwritten.', }), linuxGsmInstanceConfig: setFlag.linuxGsmInstanceConfig, + batchScript: setFlag.batchScript, nonInteractive: setFlag.nonInteractive, password: setFlag.password, server: setFlag.server, @@ -57,6 +58,7 @@ export default class InitializeCommand extends Command { spinner.succeed('Logged in'); const linuxGsm = await insurer.ensureValidLinuxGsm(flags.linuxGsmInstanceConfig); + const batchScript = await insurer.ensureValidBatchScript(flags.batchScript); const webhookUrl = await insurer.ensureValidWebhookUrl(flags.webhookUrl); @@ -69,6 +71,7 @@ export default class InitializeCommand extends Command { }, key, linuxGsm, + batchScript, mods: [], serverPath, steamCmdPath, diff --git a/src/commands/install.ts b/src/commands/install.ts index 066130c7..cdf61280 100644 --- a/src/commands/install.ts +++ b/src/commands/install.ts @@ -34,7 +34,6 @@ export default class InstallCommand extends Command { this.updateConfigFile(mods); - // Update LinuxGSM config Processor.updateServerConfigFile(Config.get('mods')); } diff --git a/src/constants/configEntries.ts b/src/constants/configEntries.ts index 67079efa..da7588de 100644 --- a/src/constants/configEntries.ts +++ b/src/constants/configEntries.ts @@ -1,10 +1,10 @@ -import IConfigEntry from '../interfaces/iConfigEntry'; - import * as os from 'os'; +import IConfigEntry from '../interfaces/iConfigEntry'; const configEntries: IConfigEntry[] = [ { config: 'credentials', displayName: 'Steam credentials' }, { config: 'linuxGsm', displayName: 'LinuxGSM instance configuration file', condition: () => os.platform() === 'linux' }, + { config: 'batchScript', displayName: 'Batch file (*.bat) where your mods are listed in', condition: () => os.platform() === 'win32' }, { config: 'serverPath', displayName: 'Server path' }, { config: 'steamCmdPath', displayName: 'SteamCMD path' }, { config: 'webhookUrl', displayName: 'Webhook URL' }, diff --git a/src/filesystem.ts b/src/filesystem.ts index 5dace3ee..df655038 100644 --- a/src/filesystem.ts +++ b/src/filesystem.ts @@ -11,6 +11,14 @@ export default class Filesystem { return false; } + public static hasExtension(filepath: string, extension: string): boolean { + if (fs.existsSync(filepath)) { + return path.parse(filepath).ext === extension; + } + + return false; + } + public static isDirectory(filepath: string, includeSymLinks?: boolean): boolean { if (fs.existsSync(filepath)) { const stats = fs.lstatSync(filepath); diff --git a/src/flags.ts b/src/flags.ts index 655c90d7..dc05c4e6 100644 --- a/src/flags.ts +++ b/src/flags.ts @@ -11,6 +11,11 @@ export const linuxGsmInstanceConfig = flags.string({ description: 'Absolute path to the LinuxGSM instance configuration file (where it handles mods/servermods) (only supported on Linux)', }); +export const batchScript = flags.string({ + char: 'b', + description: 'Absolute path to the Batch script starting your server, where it has your mods (only supported on Windows)', +}); + export const password = flags.string({ char: 'p', description: 'Steam user password.', diff --git a/src/insurer.ts b/src/insurer.ts index 64b61cda..0ec769c1 100644 --- a/src/insurer.ts +++ b/src/insurer.ts @@ -1,13 +1,12 @@ import { prompt } from 'inquirer'; - -import ora = require('ora'); - import Config from './config'; import Filesystem from './filesystem'; -import Prompt from './prompt'; -import Validate from './validator'; import IMod from './interfaces/iMod'; import ISteamCredentials from './interfaces/iSteamCredentials'; +import Prompt from './prompt'; +import Validate from './validator'; + +import ora = require('ora'); export default class Insurer { public validate: Validate; @@ -168,6 +167,34 @@ export default class Insurer { } } + public async ensureValidBatchScript(path?: string): Promise { + if (process.platform === 'linux') { return; } + + if (path) { + if (Filesystem.isFile(path)) { + return path; + } else { + if (this.nonInteractive) { + throw new Error('The Batch script file path is invalid. Did you include the file?'); + } + + return await this.prompt.forBatchScript(); + } + } else { + if (!this.nonInteractive) { + const response: { uses: boolean } = await prompt({ + message: 'Are you using a Batch script to start your server?', + name: 'uses', + type: 'confirm', + }); + + if (response.uses) { + return await this.prompt.forBatchScript(); + } + } + } + } + public async ensureValidWebhookUrl(url?: string): Promise { if (url) { return url; diff --git a/src/interfaces/iConfig.ts b/src/interfaces/iConfig.ts index 4a671ba9..300ac9a7 100644 --- a/src/interfaces/iConfig.ts +++ b/src/interfaces/iConfig.ts @@ -2,6 +2,7 @@ import IMod from './iMod'; import ISteamCredentials from './iSteamCredentials'; export default interface IConfig { + batchScript?: string; version: number; lastId: number; mods: IMod[]; diff --git a/src/processor.ts b/src/processor.ts index f4fce12e..1ee64f17 100644 --- a/src/processor.ts +++ b/src/processor.ts @@ -103,8 +103,68 @@ export default class Processor { } public static updateServerConfigFile(mods: IMod[]): void { - if (process.platform === 'win32') { return; } + if (process.platform === 'win32') { + this.handleConfigForWindows(mods); + } else { + this.handleConfigForLinux(mods); + } + } + + private static handleConfigForWindows(mods: IMod[]): void { + const requiredMods = mods.filter(mod => mod.type === EModType.all && mod.isActive === true); + const serverMods = mods.filter(mod => mod.type === EModType.server && mod.isActive === true); + + const serverConfigPath = Config.get('batchScript'); + + if (!serverConfigPath) { return; } + + let requiredModString = '-mod='; + let serverModString = '-serverMod='; + + for (const mod of requiredMods) { + requiredModString += `mods\\@${_.snakeCase(mod.name)}`; + + if (requiredMods.indexOf(mod) !== requiredMods.length - 1) { + requiredModString += ';'; + } + } + + for (const mod of serverMods) { + serverModString += `servermods\\@${_.snakeCase(mod.name)}`; + + if (serverMods.indexOf(mod) !== serverMods.length - 1) { + serverModString += ';'; + } + } + + // Read config file and transform it into a line array + const configText = fs.readFileSync(serverConfigPath).toString().replace(/[\r]/g, '').trim().split('\n'); + + let requiredModStringAdded = false; + let serverModStringAdded = false; + + const regexMod = /-mod=(([a-zA-Z]\w+\\@[a-zA-Z0-9_-]\w+[;]?)*|(@[a-zA-Z0-9_-]\w+[;]?))*/g; + const regexServerMod = /-serverMod=(([a-zA-Z]\w+\\@[a-zA-Z0-9_-]\w+[;]?)*|(@[a-zA-Z0-9_-]\w+[;]?))*/g; + + for (const [index, line] of configText.entries()) { + // If the line is commented out or empty, we ignore it + if (line.charAt(0) === 'rem' || line === '') { continue; } + + if (line.match(regexMod)) { + configText[index] = configText[index].replace(new RegExp(regexMod), requiredModString); + requiredModStringAdded = true; + } + + if (line.match(regexServerMod)) { + configText[index] = configText[index].replace(regexServerMod, serverModString); + serverModStringAdded = true; + } + } + + fs.writeFileSync(serverConfigPath, configText.join('\n')); + } + private static handleConfigForLinux(mods: IMod[]): void { const requiredMods = mods.filter(mod => mod.type === EModType.all && mod.isActive === true); const serverMods = mods.filter(mod => mod.type === EModType.server && mod.isActive === true); diff --git a/src/prompt.ts b/src/prompt.ts index 11fd4526..3787caf2 100644 --- a/src/prompt.ts +++ b/src/prompt.ts @@ -1,12 +1,12 @@ import { prompt } from 'inquirer'; +import ConfigEntries from './constants/configEntries'; +import Filesystem from './filesystem'; +import IConfigEntry from './interfaces/iConfigEntry'; +import ISteamCredentials from './interfaces/iSteamCredentials'; +import Validate from './validator'; import ora = require('ora'); -import Filesystem from './filesystem'; -import Validate from './validator'; -import ConfigEntries from './constants/configEntries'; -import ISteamCredentials from './interfaces/iSteamCredentials'; -import IConfigEntry from './interfaces/iConfigEntry'; export default class Prompt { private nonInteractive: boolean; @@ -88,6 +88,17 @@ export default class Prompt { return response.path; } + public async forBatchScript(): Promise { + const response: { path: string } = await prompt({ + message: 'Absolute path to the Batch script starting your server, where it has your mods', + name: 'path', + type: 'input', + validate: path => { return Filesystem.hasExtension(path, '.bat') } + }); + + return response.path; + } + public async forWebhookUrl(): Promise { const response: { url: string } = await prompt({ message: 'A webhook URL for the cron command to use?',