Updated dependencies + A few other things (!150)
Initially this was going to be an update to dependencies but it seems i got a little carried away!
Anyways this PR removes 2 unused dependencies (`jshint` and `utf-8-validate`), and 2 other, `del` and `fs-extra` that were replaced by the built-in `fs/promises`.
It also renames all `tsconfig` and `Dockerfile` files, in a way that when viewed in a file tree sorted alphabetically they will be next to each other.
It also updates the typescript target to `ES2022`, and changes moduleResolution from `Node` to `Node10` (this isn't an update, they are the same thing but `Node` is now deprecated).
It also adds the `node:` discriminator to every import from built-in modules.
It also has major changes to the build script, `del` and `fs-extra` were only being used in the build script, it's now using `fs/promises` instead, cleaned up the code from some functions, adds better documentation to a few functions, and renames some gulp tasks and npm scripts to better represent what they actually do.
And finally it updates dependencies, except for `atomically` which can't be updated unless the project switches to ESM.
Reviewed-on: https://dev.sp-tarkov.com/SPT-AKI/Server/pulls/150
Co-authored-by: TheSparta <thesparta@noreply.dev.sp-tarkov.com>
Co-committed-by: TheSparta <thesparta@noreply.dev.sp-tarkov.com>
2023-10-14 09:01:41 +00:00
import { execSync } from "node:child_process" ;
import os from "node:os" ;
import path from "node:path" ;
2023-03-03 15:23:46 +00:00
import semver from "semver" ;
import { DependencyContainer , inject , injectable } from "tsyringe" ;
2023-10-10 17:01:29 +01:00
import { ModDetails } from "../models/eft/profile/IAkiProfile" ;
2023-03-03 15:23:46 +00:00
import { ConfigTypes } from "../models/enums/ConfigTypes" ;
import { IPreAkiLoadMod } from "../models/external/IPreAkiLoadMod" ;
import { IPreAkiLoadModAsync } from "../models/external/IPreAkiLoadModAsync" ;
import { ICoreConfig } from "../models/spt/config/ICoreConfig" ;
import { IModLoader } from "../models/spt/mod/IModLoader" ;
import { IPackageJsonData } from "../models/spt/mod/IPackageJsonData" ;
import { ILogger } from "../models/spt/utils/ILogger" ;
import { ConfigServer } from "../servers/ConfigServer" ;
import { LocalisationService } from "../services/LocalisationService" ;
import { ModCompilerService } from "../services/ModCompilerService" ;
import { JsonUtil } from "../utils/JsonUtil" ;
import { VFS } from "../utils/VFS" ;
import { BundleLoader } from "./BundleLoader" ;
2023-10-18 14:44:29 +00:00
import { ModLoadOrder } from "./ModLoadOrder" ;
2023-03-03 15:23:46 +00:00
import { ModTypeCheck } from "./ModTypeCheck" ;
@injectable ( )
export class PreAkiModLoader implements IModLoader
{
protected static container : DependencyContainer ;
protected readonly basepath = "user/mods/" ;
protected readonly modOrderPath = "user/mods/order.json" ;
protected order : Record < string , number > = { } ;
protected imported : Record < string , IPackageJsonData > = { } ;
protected akiConfig : ICoreConfig ;
2023-10-10 11:03:20 +00:00
protected serverDependencies : Record < string , string > ;
protected skippedMods : string [ ] = [ ] ;
2023-03-03 15:23:46 +00:00
constructor (
@inject ( "WinstonLogger" ) protected logger : ILogger ,
@inject ( "VFS" ) protected vfs : VFS ,
@inject ( "JsonUtil" ) protected jsonUtil : JsonUtil ,
@inject ( "ModCompilerService" ) protected modCompilerService : ModCompilerService ,
@inject ( "BundleLoader" ) protected bundleLoader : BundleLoader ,
@inject ( "LocalisationService" ) protected localisationService : LocalisationService ,
@inject ( "ConfigServer" ) protected configServer : ConfigServer ,
2023-10-18 14:44:29 +00:00
@inject ( "ModLoadOrder" ) protected modLoadOrder : ModLoadOrder ,
2023-03-03 15:23:46 +00:00
@inject ( "ModTypeCheck" ) protected modTypeCheck : ModTypeCheck
)
{
this . akiConfig = this . configServer . getConfig < ICoreConfig > ( ConfigTypes . CORE ) ;
2023-10-10 11:03:20 +00:00
const packageJsonPath : string = path . join ( __dirname , "../../package.json" ) ;
this . serverDependencies = JSON . parse ( this . vfs . readFile ( packageJsonPath ) ) . dependencies ;
2023-03-03 15:23:46 +00:00
}
public async load ( container : DependencyContainer ) : Promise < void >
{
if ( globalThis . G_MODS_ENABLED )
{
PreAkiModLoader . container = container ;
await this . importMods ( ) ;
await this . executeMods ( container ) ;
}
}
/ * *
* Returns a list of mods with preserved load order
* @returns Array of mod names in load order
* /
public getImportedModsNames ( ) : string [ ]
{
return Object . keys ( this . imported ) ;
}
public getImportedModDetails ( ) : Record < string , IPackageJsonData >
{
return this . imported ;
}
2023-10-10 17:01:29 +01:00
public getProfileModsGroupedByModName ( profileMods : ModDetails [ ] ) : ModDetails [ ]
{
// Group all mods used by profile by name
const modsGroupedByName : Record < string , ModDetails [ ] > = { } ;
for ( const mod of profileMods )
{
if ( ! modsGroupedByName [ mod . name ] )
{
modsGroupedByName [ mod . name ] = [ ] ;
}
modsGroupedByName [ mod . name ] . push ( mod ) ;
}
// Find the highest versioned mod and add to results array
const result = [ ] ;
for ( const modName in modsGroupedByName )
{
const modDatas = modsGroupedByName [ modName ] ;
const modVersions = modDatas . map ( x = > x . version ) ;
const highestVersion = semver . maxSatisfying ( modVersions , "*" ) ;
const chosenVersion = modDatas . find ( x = > x . name === modName && x . version === highestVersion ) ;
if ( ! chosenVersion )
{
continue ;
}
result . push ( chosenVersion ) ;
}
return result ;
}
2023-03-03 15:23:46 +00:00
public getModPath ( mod : string ) : string
{
return ` ${ this . basepath } ${ mod } / ` ;
}
protected async importMods ( ) : Promise < void >
{
if ( ! this . vfs . exists ( this . basepath ) )
{
// no mods folder found
this . logger . info ( this . localisationService . getText ( "modloader-user_mod_folder_missing" ) ) ;
this . vfs . createDir ( this . basepath ) ;
return ;
}
let mods : string [ ] = this . vfs . getDirs ( this . basepath ) ;
this . logger . info ( this . localisationService . getText ( "modloader-loading_mods" , mods . length ) ) ;
// Mod order
if ( ! this . vfs . exists ( this . modOrderPath ) )
{
this . logger . info ( this . localisationService . getText ( "modloader-mod_order_missing" ) ) ;
2023-08-09 10:49:45 +00:00
// Write file with empty order array to disk
this . vfs . writeFile ( this . modOrderPath , this . jsonUtil . serializeAdvanced ( { order : [ ] } , null , 4 ) ) ;
2023-03-03 15:23:46 +00:00
}
else
{
const modOrder = this . vfs . readFile ( this . modOrderPath , { encoding : "utf8" } ) ;
try
{
2023-08-09 10:49:45 +00:00
this . jsonUtil . deserialize < any > ( modOrder ) . order . forEach ( ( mod : string , index : number ) = >
2023-03-03 15:23:46 +00:00
{
this . order [ mod ] = index ;
} ) ;
}
catch ( error )
{
this . logger . error ( this . localisationService . getText ( "modloader-mod_order_error" ) ) ;
}
}
// Used to check all errors before stopping the load execution
let errorsFound = false ;
// Validate and remove broken mods from mod list
const brokenMods : string [ ] = this . getBrokenMods ( mods ) ;
mods = mods . filter ( ( mod ) = > ! brokenMods . includes ( mod ) ) ;
const modPackageData = this . getModsPackageData ( mods ) ;
this . checkForDuplicateMods ( modPackageData ) ;
for ( const modFolderName in modPackageData )
{
const modToValidate = modPackageData [ modFolderName ] ;
2023-10-10 11:03:20 +00:00
// if the mod has library dependencies check if these dependencies are bundled in the server, if not install them
if ( modToValidate . dependencies && Object . keys ( modToValidate . dependencies ) . length > 0 && ! this . vfs . exists ( ` ${ this . basepath } ${ modFolderName } /node_modules ` ) )
{
this . autoInstallDependencies ( ` ${ this . basepath } ${ modFolderName } ` , modToValidate ) ;
}
2023-03-03 15:23:46 +00:00
// Returns if any mod dependency is not satisfied
if ( ! this . areModDependenciesFulfilled ( modToValidate , modPackageData ) )
{
errorsFound = true ;
}
// Returns if at least two incompatible mods are found
if ( ! this . isModCompatible ( modToValidate , modPackageData ) )
{
errorsFound = true ;
}
// Returns if mod isnt compatible with this verison of aki
if ( ! this . isModCombatibleWithAki ( modToValidate ) )
{
errorsFound = true ;
}
}
if ( errorsFound )
{
this . logger . error ( this . localisationService . getText ( "modloader-no_mods_loaded" ) ) ;
return ;
}
// sort mod order
const missingFromOrderJSON = { } ;
2023-08-02 08:50:04 +01:00
const sortedMods = mods . sort ( ( prev , next ) = > this . sortMods ( prev , next , missingFromOrderJSON ) ) ;
2023-03-03 15:23:46 +00:00
// log the missing mods from order.json
Object . keys ( missingFromOrderJSON ) . forEach ( ( missingMod ) = > ( this . logger . debug ( this . localisationService . getText ( "modloader-mod_order_missing_from_json" , missingMod ) ) ) ) ;
// add mods
for ( const mod of sortedMods )
{
await this . addMod ( mod ) ;
}
2023-10-18 14:44:29 +00:00
this . modLoadOrder . setModList ( this . imported ) ;
2023-03-03 15:23:46 +00:00
}
2023-08-02 08:50:04 +01:00
protected sortMods ( prev : string , next : string , missingFromOrderJSON : Record < string , boolean > ) : number
{
const previndex = this . order [ prev ] ;
const nextindex = this . order [ next ] ;
// mod is not on the list, move the mod to last
if ( previndex === undefined )
{
missingFromOrderJSON [ prev ] = true ;
return 1 ;
}
else if ( nextindex === undefined )
{
missingFromOrderJSON [ next ] = true ;
return - 1 ;
}
return previndex - nextindex ;
}
2023-03-03 15:23:46 +00:00
/ * *
2023-07-22 11:46:38 +01:00
* Check for duplicate mods loaded , show error if any
* @param modPackageData Dictionary of mod package . json data
2023-03-03 15:23:46 +00:00
* /
protected checkForDuplicateMods ( modPackageData : Record < string , IPackageJsonData > ) : void
{
const modNames = [ ] ;
for ( const modKey in modPackageData )
{
const mod = modPackageData [ modKey ] ;
modNames . push ( ` ${ mod . author } - ${ mod . name } ` ) ;
}
const dupes = this . getDuplicates ( modNames ) ;
2023-06-26 07:39:22 +01:00
if ( dupes ? . length > 0 )
2023-03-03 15:23:46 +00:00
{
2023-06-26 07:39:22 +01:00
this . logger . error ( this . localisationService . getText ( "modloader-x_duplicates_found" , dupes . join ( "," ) ) ) ;
2023-03-03 15:23:46 +00:00
}
}
/ * *
* Check for and return duplicate strings inside an array
* @param stringArray Array to check for duplicates
* @returns string array of duplicates , empty if none found
* /
protected getDuplicates ( stringArray : string [ ] ) : string [ ]
{
return stringArray . filter ( ( s = > v = > s . has ( v ) || ! s . add ( v ) ) ( new Set ) ) ;
}
/ * *
* Get an array of mods with errors that prevent them from working with SPT
* @param mods mods to validate
* @returns Mod names as array
* /
protected getBrokenMods ( mods : string [ ] ) : string [ ]
{
const brokenMods : string [ ] = [ ] ;
for ( const mod of mods )
{
if ( ! this . validMod ( mod ) )
{
brokenMods . push ( mod ) ;
}
}
return brokenMods ;
}
/ * *
* Get packageJson data for mods
* @param mods mods to get packageJson for
* @returns dictionary < modName - package.json >
* /
protected getModsPackageData ( mods : string [ ] ) : Record < string , IPackageJsonData >
{
const loadedMods : Record < string , IPackageJsonData > = { } ;
for ( const mod of mods )
{
loadedMods [ mod ] = this . jsonUtil . deserialize ( this . vfs . readFile ( ` ${ this . getModPath ( mod ) } /package.json ` ) ) ;
}
return loadedMods ;
}
protected isModCombatibleWithAki ( mod : IPackageJsonData ) : boolean
{
const akiVersion = this . akiConfig . akiVersion ;
const modName = ` ${ mod . author } - ${ mod . name } ` ;
// Error and prevent loading If no akiVersion property exists
if ( ! mod . akiVersion )
{
this . logger . error ( this . localisationService . getText ( "modloader-missing_akiversion_field" , modName ) ) ;
return false ;
}
// Error and prevent loading if akiVersion property is not a valid semver string
if ( ! ( semver . valid ( mod . akiVersion ) || semver . validRange ( mod . akiVersion ) ) )
{
this . logger . error ( this . localisationService . getText ( "modloader-invalid_akiversion_field" , modName ) ) ;
return false ;
}
// Warn and allow loading if semver is not satisfied
if ( ! semver . satisfies ( akiVersion , mod . akiVersion ) )
{
this . logger . warning ( this . localisationService . getText ( "modloader-outdated_akiversion_field" , modName ) ) ;
return true ;
}
return true ;
}
protected async executeMods ( container : DependencyContainer ) : Promise < void >
{
// sort mods load order
const source = this . sortModsLoadOrder ( ) ;
// import mod classes
for ( const mod of source )
{
if ( "main" in this . imported [ mod ] )
{
const filepath = ` ${ this . getModPath ( mod ) } ${ this . imported [ mod ] . main } ` ;
// import class
const modFilePath = ` ${ process . cwd ( ) } / ${ filepath } ` ;
// eslint-disable-next-line @typescript-eslint/no-var-requires
const requiredMod = require ( modFilePath ) ;
if ( ! this . modTypeCheck . isPostV3Compatible ( requiredMod . mod ) )
{
this . logger . error ( this . localisationService . getText ( "modloader-mod_incompatible" , mod ) ) ;
delete this . imported [ mod ] ;
return ;
}
2023-10-10 11:03:20 +00:00
if ( this . modTypeCheck . isPreAkiLoadAsync ( requiredMod . mod ) )
{
try
{
await ( requiredMod . mod as IPreAkiLoadModAsync ) . preAkiLoadAsync ( container ) ;
globalThis [ mod ] = requiredMod ;
}
catch ( err )
{
this . logger . error ( this . localisationService . getText ( "modloader-async_mod_error" , ` ${ err ? . message ? ? "" } \ n ${ err . stack ? ? "" } ` ) ) ;
}
}
2023-03-03 15:23:46 +00:00
if ( this . modTypeCheck . isPreAkiLoad ( requiredMod . mod ) )
{
( requiredMod . mod as IPreAkiLoadMod ) . preAkiLoad ( container ) ;
globalThis [ mod ] = requiredMod ;
}
}
}
}
public sortModsLoadOrder ( ) : string [ ]
{
// if loadorder.json exists: load it, otherwise generate load order
if ( this . vfs . exists ( ` ${ this . basepath } loadorder.json ` ) )
{
return this . jsonUtil . deserialize ( this . vfs . readFile ( ` ${ this . basepath } loadorder.json ` ) ) ;
}
else
{
2023-10-18 14:44:29 +00:00
return this . modLoadOrder . getLoadOrder ( ) ;
2023-03-03 15:23:46 +00:00
}
}
2023-10-14 09:48:24 +01:00
/ * *
* Compile mod and add into class property "imported"
* @param mod Name of mod to compile / add
* /
2023-03-03 15:23:46 +00:00
protected async addMod ( mod : string ) : Promise < void >
{
const modPath = this . getModPath ( mod ) ;
const packageData = this . jsonUtil . deserialize < IPackageJsonData > ( this . vfs . readFile ( ` ${ modPath } /package.json ` ) ) ;
2023-10-10 11:03:20 +00:00
if ( this . skippedMods . includes ( packageData . name ) )
{
this . logger . warning ( this . localisationService . getText ( "modloader-skipped_mod" , { name : packageData.name , author : packageData.author } ) ) ;
return ;
}
2023-03-03 15:23:46 +00:00
const isBundleMod = packageData . isBundleMod ? ? false ;
if ( isBundleMod )
{
this . bundleLoader . addBundles ( modPath ) ;
}
const typeScriptFiles = this . vfs . getFilesOfType ( ` ${ modPath } src ` , ".ts" ) ;
if ( typeScriptFiles . length > 0 )
{
if ( globalThis . G_MODS_TRANSPILE_TS )
{
// compile ts into js if ts files exist and globalThis.G_MODS_TRANSPILE_TS is set to true
await this . modCompilerService . compileMod ( mod , modPath , typeScriptFiles ) ;
}
else
{
// rename the mod entry point to .ts if it's set to .js because G_MODS_TRANSPILE_TS is set to false
2023-10-14 09:48:24 +01:00
packageData . main = ( packageData . main ) . replace ( ".js" , ".ts" ) ;
2023-03-03 15:23:46 +00:00
}
}
2023-10-14 09:48:24 +01:00
// Purge scripts data from package object
packageData . scripts = { } ;
// Add mod to imported list
2023-03-03 15:23:46 +00:00
this . imported [ mod ] = { . . . packageData , dependencies : packageData.modDependencies } ;
this . logger . info ( this . localisationService . getText ( "modloader-loaded_mod" , { name : packageData.name , version : packageData.version , author : packageData.author } ) ) ;
}
2023-10-10 11:03:20 +00:00
protected autoInstallDependencies ( modPath : string , pkg : IPackageJsonData ) : void
{
const dependenciesToInstall : [ string , string ] [ ] = Object . entries ( pkg . dependencies ) ;
let depIdx = 0 ;
for ( const [ depName , depVersion ] of dependenciesToInstall )
{
// currently not checking for version mismatches, but we could check it, just don't know what we would do afterwards, some options would be:
// 1 - throw an error
// 2 - use the server's version (which is what's currently happening by not checking the version)
// 3 - use the mod's version (don't know the reprecursions this would have, or if it would even work)
// if a dependency from the mod exists in the server dependencies we can safely remove it from the list of dependencies to install since it already comes bundled in the server.
if ( this . serverDependencies [ depName ] )
{
dependenciesToInstall . splice ( depIdx , 1 ) ;
}
depIdx ++ ;
}
//if the mod has no extra dependencies return as there's nothing that needs to be done.
if ( dependenciesToInstall . length === 0 )
{
return ;
}
//if this feature flag is set to false, we warn the user he has a mod that requires extra dependencies and might not work, point them in the right direction on how to enable this feature.
if ( ! this . akiConfig . features . autoInstallModDependencies )
{
this . logger . warning ( this . localisationService . getText ( "modloader-installing_external_dependencies_disabled" , {
name : pkg.name ,
author : pkg.author ,
configPath : path.join ( globalThis . G_RELEASE_CONFIGURATION ? "Aki_Data/Server/configs" : "assets/configs" , "core.json" ) ,
configOption : "autoInstallModDependencies"
} ) ) ;
this . skippedMods . push ( pkg . name ) ;
return ;
}
//temporarily rename package.json because otherwise npm, pnpm and any other package manager will forcefully download all packages in dependencies without any way of disabling this behavior
this . vfs . rename ( ` ${ modPath } /package.json ` , ` ${ modPath } /package.json.bak ` ) ;
this . vfs . writeFile ( ` ${ modPath } /package.json ` , "{}" ) ;
this . logger . info ( this . localisationService . getText ( "modloader-installing_external_dependencies" , { name : pkg.name , author : pkg.author } ) ) ;
const pnpmPath = path . join ( process . cwd ( ) , ( globalThis . G_RELEASE_CONFIGURATION ? "Aki_Data/Server/@pnpm/exe" : "node_modules/@pnpm/exe" ) , ( os . platform ( ) === "win32" ? "pnpm.exe" : "pnpm" ) ) ;
let command : string = ` ${ pnpmPath } install ` ;
command += dependenciesToInstall . map ( ( [ depName , depVersion ] ) = > ` ${ depName } @ ${ depVersion } ` ) . join ( " " ) ;
execSync ( command , { cwd : modPath } ) ;
// delete the new blank package.json then rename the backup back to the original name
this . vfs . removeFile ( ` ${ modPath } /package.json ` ) ;
this . vfs . rename ( ` ${ modPath } /package.json.bak ` , ` ${ modPath } /package.json ` ) ;
}
2023-03-03 15:23:46 +00:00
protected areModDependenciesFulfilled ( pkg : IPackageJsonData , loadedMods : Record < string , IPackageJsonData > ) : boolean
{
if ( ! pkg . modDependencies )
{
return true ;
}
const modName = ` ${ pkg . author } - ${ pkg . name } ` ;
for ( const [ modDependency , requiredVersion ] of Object . entries ( pkg . modDependencies ) )
{
// Raise dependency version incompatible if the dependency is not found in the mod list
if ( ! ( modDependency in loadedMods ) )
{
this . logger . error ( this . localisationService . getText ( "modloader-missing_dependency" , { mod : modName , modDependency : modDependency } ) ) ;
return false ;
}
if ( ! semver . satisfies ( loadedMods [ modDependency ] . version , requiredVersion ) )
{
this . logger . error ( this . localisationService . getText ( "modloader-outdated_dependency" , { mod : modName , modDependency : modDependency , currentVersion : loadedMods [ modDependency ] . version , requiredVersion : requiredVersion } ) ) ;
return false ;
}
}
return true ;
}
protected isModCompatible ( mod : IPackageJsonData , loadedMods : Record < string , IPackageJsonData > ) : boolean
{
const incompatbileModsList = mod . incompatibilities ;
if ( ! incompatbileModsList )
{
return true ;
}
for ( const incompatibleModName of incompatbileModsList )
{
// Raise dependency version incompatible if any incompatible mod is found
if ( incompatibleModName in loadedMods )
{
this . logger . error ( this . localisationService . getText ( "modloader-incompatible_mod_found" , { author : mod.author , modName : mod.name , incompatibleModName : incompatibleModName } ) ) ;
return false ;
}
}
return true ;
}
/ * *
* Validate a mod passes a number of checks
* @param modName name of mod in /mods/ to validate
* @returns true if valid
* /
protected validMod ( modName : string ) : boolean
{
const modPath = this . getModPath ( modName ) ;
const modIsCalledBepinEx = modName . toLowerCase ( ) === "bepinex" ;
2023-10-10 11:03:20 +00:00
const modIsCalledUser = modName . toLowerCase ( ) === "user" ;
const modIsCalledSrc = modName . toLowerCase ( ) === "src" ;
const modIsCalledDb = modName . toLowerCase ( ) === "db" ;
2023-03-03 15:23:46 +00:00
const hasBepinExFolderStructure = this . vfs . exists ( ` ${ modPath } /plugins ` ) ;
const containsDll = this . vfs . getFiles ( ` ${ modPath } ` ) . find ( x = > x . includes ( ".dll" ) ) ;
2023-10-10 11:03:20 +00:00
if ( modIsCalledSrc || modIsCalledDb || modIsCalledUser )
{
this . logger . error ( this . localisationService . getText ( "modloader-not_correct_mod_folder" , modName ) ) ;
return false ;
}
2023-03-03 15:23:46 +00:00
if ( modIsCalledBepinEx || hasBepinExFolderStructure || containsDll )
{
this . logger . error ( this . localisationService . getText ( "modloader-is_client_mod" , modName ) ) ;
return false ;
}
2023-10-10 11:03:20 +00:00
// Check if config exists
2023-03-03 15:23:46 +00:00
if ( ! this . vfs . exists ( ` ${ modPath } /package.json ` ) )
{
this . logger . error ( this . localisationService . getText ( "modloader-missing_package_json" , modName ) ) ;
return false ;
}
2023-10-10 11:03:20 +00:00
// Validate mod
2023-03-03 15:23:46 +00:00
const config = this . jsonUtil . deserialize < IPackageJsonData > ( this . vfs . readFile ( ` ${ modPath } /package.json ` ) ) ;
const checks = [ "name" , "author" , "version" , "license" ] ;
let issue = false ;
for ( const check of checks )
{
if ( ! ( check in config ) )
{
this . logger . error ( this . localisationService . getText ( "modloader-missing_package_json_property" , { modName : modName , prop : check } ) ) ;
issue = true ;
}
}
if ( ! semver . valid ( config . version ) )
{
this . logger . error ( this . localisationService . getText ( "modloader-invalid_version_property" , modName ) ) ;
issue = true ;
}
if ( "main" in config )
{
if ( config . main . split ( "." ) . pop ( ) !== "js" ) // expects js file as entry
{
this . logger . error ( this . localisationService . getText ( "modloader-main_property_not_js" , modName ) ) ;
issue = true ;
}
if ( ! this . vfs . exists ( ` ${ modPath } / ${ config . main } ` ) )
{
// If TS file exists with same name, dont perform check as we'll generate JS from TS file
const tsFileName = config . main . replace ( ".js" , ".ts" ) ;
const tsFileExists = this . vfs . exists ( ` ${ modPath } / ${ tsFileName } ` ) ;
if ( ! tsFileExists )
{
this . logger . error ( this . localisationService . getText ( "modloader-main_property_points_to_nothing" , modName ) ) ;
issue = true ;
}
}
}
if ( config . incompatibilities && ! Array . isArray ( config . incompatibilities ) )
{
this . logger . error ( this . localisationService . getText ( "modloader-incompatibilities_not_string_array" , modName ) ) ;
issue = true ;
}
return ! issue ;
}
public getContainer ( ) : DependencyContainer
{
if ( PreAkiModLoader . container )
{
return PreAkiModLoader . container ;
}
else
{
throw new Error ( this . localisationService . getText ( "modloader-dependency_container_not_initalized" ) ) ;
}
}
2023-10-18 14:44:29 +00:00
}