diff --git a/package-lock.json b/package-lock.json index 5a56a78..8fa8ad6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,16 +24,25 @@ "js-tokens": "^4.0.0" } }, + "@types/fs-extra": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-8.0.0.tgz", + "integrity": "sha512-bCtL5v9zdbQW86yexOlXWTEGvLNqWxMFyi7gQA7Gcthbezr2cPSOb8SkESVKA937QD5cIwOFLDFt0MQoXOEr9Q==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/node": { - "version": "12.6.8", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.6.8.tgz", - "integrity": "sha512-aX+gFgA5GHcDi89KG5keey2zf0WfZk/HAQotEamsK2kbey+8yGKcson0hbK8E+v0NArlCJQCqMP161YhV6ZXLg==", + "version": "12.7.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.7.2.tgz", + "integrity": "sha512-dyYO+f6ihZEtNPDcWNR1fkoTDf3zAK3lAABDze3mz6POyIercH0lEUawUFXlG8xaQZmm1yEBON/4TsYv/laDYg==", "dev": true }, "@types/yargs": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.0.tgz", - "integrity": "sha512-hY0o+kcz9M6kH32NUeb6VURghqMuCVkiUx+8Btsqhj4Hhov/hVGUx9DmBJeIkzlp1uAQK4wngQBCjqWdUUkFyA==", + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.2.tgz", + "integrity": "sha512-lwwgizwk/bIIU+3ELORkyuOgDjCh7zuWDFqRtPPhhVgq9N1F7CvLNKg1TX4f2duwtKQ0p044Au9r1PLIXHrIzQ==", "dev": true, "requires": { "@types/yargs-parser": "*" @@ -174,9 +183,9 @@ "dev": true }, "esutils": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", - "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true }, "find-up": { @@ -321,9 +330,9 @@ } }, "p-limit": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", - "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.1.tgz", + "integrity": "sha512-85Tk+90UCVWvbDavCLKPOLC9vvY8OwEX/RtKF+/1OADJMVlFfEHOiMTPVyxg7mk/dKa+ipdHm0OUkTvCpMTuwg==", "requires": { "p-try": "^2.0.0" } @@ -369,27 +378,27 @@ "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" }, "resolve": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.11.1.tgz", - "integrity": "sha512-vIpgF6wfuJOZI7KKKSP+HmiKggadPQAdsp5HiC1mvqnfp0gF1vdwgBWZIdrVft9pgqoMFQN+R7BSWZiBxx+BBw==", + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz", + "integrity": "sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==", "dev": true, "requires": { "path-parse": "^1.0.6" } }, "rimraf": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.0.tgz", + "integrity": "sha512-NDGVxTsjqfunkds7CqsOiEnxln4Bo7Nddl3XhS4pXg5OzwkLqJ971ZVAAnB+DDLnF76N+VnDEiBHaVV8I06SUg==", "dev": true, "requires": { "glob": "^7.1.3" } }, "semver": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", - "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", "dev": true }, "set-blocking": { @@ -437,9 +446,9 @@ "dev": true }, "tslint": { - "version": "5.18.0", - "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.18.0.tgz", - "integrity": "sha512-Q3kXkuDEijQ37nXZZLKErssQVnwCV/+23gFEMROi8IlbaBG6tXqLPQJ5Wjcyt/yHPKBC+hD5SzuGaMora+ZS6w==", + "version": "5.19.0", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.19.0.tgz", + "integrity": "sha512-1LwwtBxfRJZnUvoS9c0uj8XQtAnyhWr9KlNvDIdB+oXyT+VpsOAaEhEgKi1HrZ8rq0ki/AAnbGSv4KM6/AfVZw==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", @@ -504,11 +513,12 @@ "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==" }, "yargs": { - "version": "13.3.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.0.tgz", - "integrity": "sha512-2eehun/8ALW8TLoIl7MVaRUrg+yCnenu8B4kBlRxj3GJGDKU1Og7sMXPNm1BYyM1DOJmTZ4YeN/Nwxv+8XJsUA==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-14.0.0.tgz", + "integrity": "sha512-ssa5JuRjMeZEUjg7bEL99AwpitxU/zWGAGpdj0di41pOEmJti8NR6kyUIJBkR78DTYNPZOU08luUo0GTHuB+ow==", "requires": { "cliui": "^5.0.0", + "decamelize": "^1.2.0", "find-up": "^3.0.0", "get-caller-file": "^2.0.1", "require-directory": "^2.1.1", diff --git a/package.json b/package.json index 893a2ea..72705bf 100644 --- a/package.json +++ b/package.json @@ -26,15 +26,16 @@ }, "homepage": "https://github.com/dscalzi/Nebula#readme", "devDependencies": { - "@types/node": "^12.6.8", - "@types/yargs": "^13.0.0", - "rimraf": "^2.6.3", - "tslint": "^5.18.0", + "@types/fs-extra": "^8.0.0", + "@types/node": "^12.7.2", + "@types/yargs": "^13.0.2", + "rimraf": "^3.0.0", + "tslint": "^5.19.0", "typescript": "^3.5.3" }, "dependencies": { "adm-zip": "^0.4.13", "fs-extra": "^8.1.0", - "yargs": "^13.3.0" + "yargs": "^14.0.0" } } diff --git a/src/index.ts b/src/index.ts index f566c88..6f3f875 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,8 @@ /* tslint:disable:no-shadowed-variable */ -import { resolve } from 'path' +import { writeFile } from 'fs-extra' +import { resolve as resolvePath } from 'path' import yargs from 'yargs' +import { DistributionStructure } from './model/struct/distribution.struct' function rootOption(yargs: yargs.Argv) { return yargs.option('root', { @@ -10,7 +12,29 @@ function rootOption(yargs: yargs.Argv) { global: true }) .coerce({ - root: resolve + root: resolvePath + }) +} + +function baseUrlOption(yargs: yargs.Argv) { + return yargs.option('baseUrl', { + describe: 'Base url of your file host.', + type: 'string', + demandOption: true, + global: true + }) + .coerce({ + baseUrl: (arg: string) => { + // Users must provide protocol in all other instances. + if (arg.indexOf('//') === -1) { + if (arg.toLowerCase().startsWith('localhost')) { + arg = 'http://' + arg + } else { + throw new TypeError('Please provide a URL protocol (ex. http:// or https://)') + } + } + return (new URL(arg)).toString() + } }) } @@ -32,9 +56,15 @@ const initRootCommand: yargs.CommandModule = { yargs = rootOption(yargs) return yargs }, - handler: (argv) => { - console.log(`Root set to ${argv.root}`) - console.log('Invoked init root.') + handler: async (argv) => { + console.debug(`Root set to ${argv.root}`) + console.debug('Invoked init root.') + try { + await new DistributionStructure(argv.root as string, '').init() + console.log(`Successfully created new root at ${argv.root}`) + } catch (error) { + console.error(`Failed to init new root at ${argv.root}`, error) + } } } @@ -80,8 +110,8 @@ const generateServerCommand: yargs.CommandModule = { }) }, handler: (argv) => { - console.log(`Root set to ${argv.root}`) - console.log(`Generating server ${argv.id} for Minecraft ${argv.version}.`, + console.debug(`Root set to ${argv.root}`) + console.debug(`Generating server ${argv.id} for Minecraft ${argv.version}.`, `\n\t├ Include forge: ${argv.forge}`, `\n\t└ Include liteloader: ${argv.liteloader}`) } @@ -92,12 +122,22 @@ const generateDistroCommand: yargs.CommandModule = { describe: 'Generate a distribution index from the root file structure.', builder: (yargs) => { yargs = rootOption(yargs) + yargs = baseUrlOption(yargs) yargs = namePositional(yargs) return yargs }, - handler: (argv) => { - console.log(`Root set to ${argv.root}`) - console.log(`Invoked generate distro name ${argv.name}.json.`) + handler: async (argv) => { + console.debug(`Root set to ${argv.root}`) + console.debug(`Base Url set to ${argv.baseUrl}`) + console.debug(`Invoked generate distro name ${argv.name}.json.`) + try { + const distributionStruct = new DistributionStructure(argv.root as string, argv.baseUrl as string) + const distro = await distributionStruct.getSpecModel() + writeFile(resolvePath(argv.root as string, `${argv.name}.json`), JSON.stringify(distro, null, 2)) + console.log(distro) + } catch (error) { + console.error(`Failed to generate distribution with root ${argv.root}.`, error) + } } } @@ -122,7 +162,7 @@ const validateCommand: yargs.CommandModule = { return namePositional(yargs) }, handler: (argv) => { - console.log(`Invoked validate with name ${argv.name}.json`) + console.debug(`Invoked validate with name ${argv.name}.json`) } } diff --git a/src/model/spec/type.ts b/src/model/spec/type.ts index 91bac11..56a3c2f 100644 --- a/src/model/spec/type.ts +++ b/src/model/spec/type.ts @@ -1,45 +1,58 @@ -export const Types: {[property: string]: Type} = { +export enum Type { + + Library = 'Library', + ForgeHosted = 'ForgeHosted', + Forge = 'Forge', + LiteLoader = 'LiteLoader', + ForgeMod = 'ForgeMod', + LiteMod = 'LiteMod', + File = 'File', + VersionManifest = 'VersionManifest' + +} + +export interface TypeMetadata { + + id: string + defaultExtension?: string + +} + +export const TypeMetadata: {[property: string]: TypeMetadata} = { Library: { - id: 'Library', + id: Type.Library, defaultExtension: 'jar' }, /** * @deprecated Will be replaced by Types.Forge. */ ForgeHosted: { - id: 'ForgeHosted', + id: Type.ForgeHosted, defaultExtension: 'jar' }, Forge: { - id: 'Forge', + id: Type.Forge, defaultExtension: 'jar' }, LiteLoader: { - id: 'LiteLoader', + id: Type.LiteLoader, defaultExtension: 'jar' }, ForgeMod: { - id: 'ForgeMod', + id: Type.ForgeMod, defaultExtension: 'jar' }, LiteMod: { - id: 'LiteMod', + id: Type.LiteMod, defaultExtension: 'litemod' }, File: { - id: 'File' + id: Type.File }, VersionManifest: { - id: 'VersionManifest', + id: Type.VersionManifest, defaultExtension: 'json' } } - -export interface Type { - - id: string - defaultExtension?: string - -} diff --git a/src/model/struct/basemodel.struct.ts b/src/model/struct/basemodel.struct.ts new file mode 100644 index 0000000..81b0874 --- /dev/null +++ b/src/model/struct/basemodel.struct.ts @@ -0,0 +1,26 @@ +import { mkdirs } from 'fs-extra' +import { join, resolve } from 'path' +import { ModelStructure } from './model.struct' + +export abstract class BaseModelStructure implements ModelStructure { + + protected resolvedModels: T[] | undefined + protected containerDirectory: string + + constructor( + protected absoluteRoot: string, + protected relativeRoot: string, + protected structRoot: string, + protected baseUrl: string + ) { + this.relativeRoot = join(relativeRoot, structRoot) + this.containerDirectory = resolve(absoluteRoot, structRoot) + } + + public async init() { + mkdirs(this.containerDirectory) + } + + public abstract async getSpecModel(): Promise + +} diff --git a/src/model/struct/distribution.struct.ts b/src/model/struct/distribution.struct.ts index 8f217ed..fdd135c 100644 --- a/src/model/struct/distribution.struct.ts +++ b/src/model/struct/distribution.struct.ts @@ -1,25 +1,32 @@ +import { mkdirs } from 'fs-extra' import { Distribution } from '../spec/distribution' import { ModelStructure } from './model.struct' import { ServerStructure } from './server.struct' export class DistributionStructure implements ModelStructure { - private servers: ServerStructure[] | undefined + private serverStruct: ServerStructure constructor( - private root: string - ) {} - - public getServers() { - return new ServerStructure(this.root).getSpecModel() + private absoluteRoot: string, + private baseUrl: string + ) { + this.serverStruct = new ServerStructure(this.absoluteRoot, this.baseUrl) } - public getSpecModel(): Distribution { - return { - version: '1.0.0', - rss: 'TODO', - servers: this.getServers() - } + public async init() { + await mkdirs(this.absoluteRoot) + await this.serverStruct.init() + } + + public async getSpecModel() { + return new Promise(async (resolve) => { + resolve({ + version: '1.0.0', + rss: '', + servers: await this.serverStruct.getSpecModel() + }) + }) as Promise } } diff --git a/src/model/struct/model.struct.ts b/src/model/struct/model.struct.ts index 179f531..b86b446 100644 --- a/src/model/struct/model.struct.ts +++ b/src/model/struct/model.struct.ts @@ -1,5 +1,7 @@ export interface ModelStructure { - getSpecModel(): T + init(): void + + getSpecModel(): Promise } diff --git a/src/model/struct/module/file.struct.ts b/src/model/struct/module/file.struct.ts new file mode 100644 index 0000000..5bf8b53 --- /dev/null +++ b/src/model/struct/module/file.struct.ts @@ -0,0 +1,28 @@ +import { Stats } from 'fs' +import { Type } from '../../spec/type' +import { ModuleStructure } from './module.struct' + +export class FileStructure extends ModuleStructure { + + constructor( + absoluteRoot: string, + relativeRoot: string, + baseUrl: string + ) { + super(absoluteRoot, relativeRoot, 'files', baseUrl, Type.File) + } + + protected async getModuleId(name: string, path: string, stats: Stats, buf: Buffer): Promise { + return name + } + protected async getModuleName(name: string, path: string, stats: Stats, buf: Buffer): Promise { + return name + } + protected async getModuleUrl(name: string, path: string, stats: Stats, buf: Buffer): Promise { + return 'TODO' + } + protected async getModulePath(name: string, path: string, stats: Stats, buf: Buffer): Promise { + return 'TODO' + } + +} diff --git a/src/model/struct/module/forgemod.struct.ts b/src/model/struct/module/forgemod.struct.ts new file mode 100644 index 0000000..2c34279 --- /dev/null +++ b/src/model/struct/module/forgemod.struct.ts @@ -0,0 +1,28 @@ +import { Stats } from 'fs-extra' +import { Type } from '../../spec/type' +import { ModuleStructure } from './module.struct' + +export class ForgeModStructure extends ModuleStructure { + + constructor( + absoluteRoot: string, + relativeRoot: string, + baseUrl: string + ) { + super(absoluteRoot, relativeRoot, 'forgemods', baseUrl, Type.ForgeMod) + } + + protected async getModuleId(name: string, path: string, stats: Stats, buf: Buffer): Promise { + return name + } + protected async getModuleName(name: string, path: string, stats: Stats, buf: Buffer): Promise { + return name + } + protected async getModuleUrl(name: string, path: string, stats: Stats, buf: Buffer): Promise { + return 'TODO' + } + protected async getModulePath(name: string, path: string, stats: Stats, buf: Buffer): Promise { + return 'TODO' + } + +} diff --git a/src/model/struct/module/litemod.struct.ts b/src/model/struct/module/litemod.struct.ts new file mode 100644 index 0000000..a273d64 --- /dev/null +++ b/src/model/struct/module/litemod.struct.ts @@ -0,0 +1,29 @@ +import { Stats } from 'fs-extra' +import { join } from 'path' +import { Type } from '../../spec/type' +import { ModuleStructure } from './module.struct' + +export class LiteModStructure extends ModuleStructure { + + constructor( + absoluteRoot: string, + relativeRoot: string, + baseUrl: string + ) { + super(absoluteRoot, relativeRoot, 'litemods', baseUrl, Type.LiteMod) + } + + protected async getModuleId(name: string, path: string, stats: Stats, buf: Buffer): Promise { + return name + } + protected async getModuleName(name: string, path: string, stats: Stats, buf: Buffer): Promise { + return name + } + protected async getModuleUrl(name: string, path: string, stats: Stats, buf: Buffer): Promise { + return 'TODO' + } + protected async getModulePath(name: string, path: string, stats: Stats, buf: Buffer): Promise { + return 'TODO' + } + +} diff --git a/src/model/struct/module/module.struct.ts b/src/model/struct/module/module.struct.ts new file mode 100644 index 0000000..5de62c5 --- /dev/null +++ b/src/model/struct/module/module.struct.ts @@ -0,0 +1,67 @@ +import { createHash } from 'crypto' +import { lstat, pathExists, readdir, readFile, Stats } from 'fs-extra' +import { resolve } from 'path' +import { Module } from '../../spec/module' +import { Type } from '../../spec/type' +import { BaseModelStructure } from '../basemodel.struct' + +export abstract class ModuleStructure extends BaseModelStructure { + + constructor( + absoluteRoot: string, + relativeRoot: string, + structRoot: string, + baseUrl: string, + protected type: Type + ) { + super(absoluteRoot, relativeRoot, structRoot, baseUrl) + } + + public async getSpecModel(): Promise { + if (this.resolvedModels == null) { + this.resolvedModels = await this._doModuleRetrieval() + } + + return this.resolvedModels + } + + protected async abstract getModuleId(name: string, path: string, stats: Stats, buf: Buffer): Promise + protected async abstract getModuleName(name: string, path: string, stats: Stats, buf: Buffer): Promise + protected async abstract getModuleUrl(name: string, path: string, stats: Stats, buf: Buffer): Promise + protected async abstract getModulePath(name: string, path: string, stats: Stats, buf: Buffer): Promise + + private async _doModuleRetrieval(): Promise { + + const accumulator: Module[] = [] + + if (await pathExists(this.containerDirectory)) { + const files = await readdir(this.containerDirectory) + for (const file of files) { + const filePath = resolve(this.containerDirectory, file) + const stats = await lstat(filePath) + const buf = await readFile(filePath) + if (stats.isFile()) { + accumulator.push({ + id: await this.getModuleId(file, filePath, stats, buf), + name: await this.getModuleName(file, filePath, stats, buf), + type: this.type, + required: { + value: false, + def: false + }, + artifact: { + size: stats.size, + MD5: createHash('md5').update(buf).digest('hex'), + url: await this.getModuleUrl(file, filePath, stats, buf), + path: await this.getModulePath(file, filePath, stats, buf) + } + }) + } + } + } + + return accumulator + + } + +} diff --git a/src/model/struct/server.struct.ts b/src/model/struct/server.struct.ts index 614bbf7..eec5fa6 100644 --- a/src/model/struct/server.struct.ts +++ b/src/model/struct/server.struct.ts @@ -1,25 +1,100 @@ -import { resolve } from 'path' +import { lstat, readdir } from 'fs-extra' +import { join, resolve as resolvePath } from 'path' +import { resolve as resolveUrl } from 'url' import { Server } from '../spec/server' -import { ModelStructure } from './model.struct' +import { BaseModelStructure } from './basemodel.struct' +import { FileStructure } from './module/file.struct' +import { ForgeModStructure } from './module/forgemod.struct' +import { LiteModStructure } from './module/litemod.struct' -export class ServerStructure implements ModelStructure { +export class ServerStructure extends BaseModelStructure { - private servers: Server[] | undefined + private readonly ID_REGEX = /(.+-(.+)$)/ constructor( - private root: string - ) {} - - public getSpecModel(): Server[] { - if (this.servers == null) { - this.servers = this._doSeverRetrieval() - } - return this.servers + absoluteRoot: string, + baseUrl: string + ) { + super(absoluteRoot, '', 'servers', baseUrl) } - private _doSeverRetrieval(): Server[] { - const base = resolve(this.root, 'servers') - return [] // TODO + public async getSpecModel() { + if (this.resolvedModels == null) { + this.resolvedModels = await this._doSeverRetrieval() + } + return this.resolvedModels + } + + private async _doSeverRetrieval(): Promise { + + const accumulator: Server[] = [] + const files = await readdir(this.containerDirectory) + for (const file of files) { + const absoluteServerRoot = resolvePath(this.containerDirectory, file) + const relativeServerRoot = join(this.relativeRoot, file) + if ((await lstat(absoluteServerRoot)).isDirectory()) { + + const match = this.ID_REGEX.exec(file) + if (match == null) { + console.warn(`Server directory ${file} does not match the defined standard.`) + console.warn(`All server ids must end with - (ex. -1.12.2)`) + continue + } + + let iconUrl + + // Resolve server icon + const subFiles = await readdir(absoluteServerRoot) + for (const subFile of subFiles) { + const caseInsensitive = subFile.toLowerCase() + if (caseInsensitive.endsWith('.jpg') || caseInsensitive.endsWith('.png')) { + iconUrl = resolveUrl(this.baseUrl, join(relativeServerRoot, subFile)) + } + } + + if (!iconUrl) { + console.warn(`No icon file found for server ${file}.`) + iconUrl = '' + } + + const forgeModStruct = new ForgeModStructure(absoluteServerRoot, relativeServerRoot, this.baseUrl) + const forgeModModules = await forgeModStruct.getSpecModel() + + const liteModStruct = new LiteModStructure(absoluteServerRoot, relativeServerRoot, this.baseUrl) + const liteModModules = await liteModStruct.getSpecModel() + + const fileStruct = new FileStructure(absoluteServerRoot, relativeServerRoot, this.baseUrl) + const fileModules = await fileStruct.getSpecModel() + + const modules = [ + ...forgeModModules, + ...liteModModules, + ...fileModules + ] + + accumulator.push({ + id: match[1], + name: '', + description: '', + icon: iconUrl, + version: '1.0.0', + address: '', + minecraftVersion: match[2], + discord: { + shortId: '', + largeImageText: '', + largeImageKey: '' + }, + mainServer: false, + autoconnect: false, + modules + }) + + } else { + console.warn(`Path ${file} in server directory is not a directory!`) + } + } + return accumulator } }