diff --git a/src/commands/tiles.ts b/src/commands/tiles.ts index d55f905..d7431d5 100644 --- a/src/commands/tiles.ts +++ b/src/commands/tiles.ts @@ -1,15 +1,15 @@ -import path from 'path' import fs from 'fs' import sharp from 'sharp' import { commandLogger } from '../utilities/logger' import { Server } from 'socket.io' +import { getPublicPath } from '../utilities/utilities' export default class TilesCommand { constructor(private readonly io: Server) {} public async execute(): Promise { // Get all tiles - const tilesDir = path.join(process.cwd(), 'public', 'tiles') + const tilesDir = getPublicPath('tiles') const tiles = fs.readdirSync(tilesDir).filter((file) => file.endsWith('.png')) // Create output directory if it doesn't exist @@ -19,14 +19,14 @@ export default class TilesCommand { for (const tile of tiles) { // Check if tile is already 66x34 - const metadata = await sharp(path.join(tilesDir, tile)).metadata() + const metadata = await sharp(getPublicPath('tiles', tile)).metadata() if (metadata.width === 66 && metadata.height === 34) { commandLogger.info(`Tile ${tile} already processed`) continue } - const inputPath = path.join(tilesDir, tile) - const outputPath = path.join(tilesDir, tile) + const inputPath = getPublicPath('tiles', tile) + const outputPath = getPublicPath('tiles', tile) try { await sharp(inputPath) diff --git a/src/managers/commandManager.ts b/src/managers/commandManager.ts index 1699197..e56d194 100644 --- a/src/managers/commandManager.ts +++ b/src/managers/commandManager.ts @@ -3,7 +3,7 @@ import * as fs from 'fs' import * as path from 'path' import { Server } from 'socket.io' import { commandLogger } from '../utilities/logger' -import config from '../utilities/config' +import { getAppPath } from '../utilities/utilities' class CommandManager { private commands: Map = new Map() @@ -20,8 +20,6 @@ class CommandManager { this.rl.on('close', () => { this.rlClosed = true }) - - console.log(process.cwd()) } public async boot(io: Server) { @@ -63,34 +61,34 @@ class CommandManager { } private async loadCommands() { - const commandsDir = path.join(process.cwd(), 'commands'); - commandLogger.info(`Loading commands from: ${commandsDir}`); + const directory = getAppPath('commands') + commandLogger.info(`Loading commands from: ${directory}`) try { - const files = await fs.promises.readdir(commandsDir, { withFileTypes: true }); + const files = await fs.promises.readdir(directory, { withFileTypes: true }) for (const file of files) { if (!file.isFile() || (!file.name.endsWith('.ts') && !file.name.endsWith('.js'))) { - continue; + continue } - const fullPath = path.join(commandsDir, file.name); - const commandName = path.basename(file.name, path.extname(file.name)); + const fullPath = getAppPath('commands', file.name) + const commandName = path.basename(file.name, path.extname(file.name)) try { - const module = await import(fullPath); + const module = await import(fullPath) if (typeof module.default !== 'function') { - commandLogger.warn(`Unrecognized export in ${file.name}`); - continue; + commandLogger.warn(`Unrecognized export in ${file.name}`) + continue } - this.registerCommand(commandName, module.default); + this.registerCommand(commandName, module.default) } catch (error) { - commandLogger.error(`Error loading command ${file.name}: ${error instanceof Error ? error.message : String(error)}`); + commandLogger.error(`Error loading command ${file.name}: ${error instanceof Error ? error.message : String(error)}`) } } } catch (error) { - commandLogger.error(`Failed to read commands directory: ${error instanceof Error ? error.message : String(error)}`); + commandLogger.error(`Failed to read commands directory: ${error instanceof Error ? error.message : String(error)}`) } } diff --git a/src/managers/queueManager.ts b/src/managers/queueManager.ts index a44bb4a..9856619 100644 --- a/src/managers/queueManager.ts +++ b/src/managers/queueManager.ts @@ -5,7 +5,7 @@ import { Server as SocketServer } from 'socket.io' import { TSocket } from '../utilities/types' import { queueLogger } from '../utilities/logger' import fs from 'fs' -import path from 'path' +import { getAppPath } from '../utilities/utilities' class QueueManager { private connection!: IORedis @@ -52,9 +52,9 @@ class QueueManager { const { jobName, params, socketId } = job.data try { - const jobsDir = path.join(process.cwd(), 'jobs') + const jobsDir = getAppPath('jobs') const extension = config.ENV === 'development' ? '.ts' : '.js' - const jobPath = path.join(jobsDir, `${jobName}${extension}`) + const jobPath = getAppPath('jobs', `${jobName}${extension}`) if (!fs.existsSync(jobPath)) { queueLogger.warn(`Job file not found: ${jobPath}`) diff --git a/src/models/loadedZone.ts b/src/models/loadedZone.ts index 574462d..a28dd8c 100644 --- a/src/models/loadedZone.ts +++ b/src/models/loadedZone.ts @@ -1,7 +1,5 @@ import { Zone } from '@prisma/client' import zoneRepository from '../repositories/zoneRepository' -import characterManager from '../managers/characterManager' -import { ExtendedCharacter } from '../utilities/types' class LoadedZone { private readonly zone: Zone diff --git a/src/repositories/spriteRepository.ts b/src/repositories/spriteRepository.ts index f773e85..e72ea0a 100644 --- a/src/repositories/spriteRepository.ts +++ b/src/repositories/spriteRepository.ts @@ -1,5 +1,5 @@ import prisma from '../utilities/prisma' // Import the global Prisma instance -import { Sprite, SpriteAction } from '@prisma/client' +import { SpriteAction } from '@prisma/client' class SpriteRepository { async getById(id: string) { diff --git a/src/server.ts b/src/server.ts index 000d2c2..959bc7e 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,5 +1,4 @@ import fs from 'fs' -import path from 'path' import express, { Application } from 'express' import config from './utilities/config' import { createServer as httpServer, Server as HTTPServer } from 'http' @@ -9,13 +8,13 @@ import { Server as SocketServer } from 'socket.io' import { Authentication } from './middleware/authentication' import { TSocket } from './utilities/types' import prisma from './utilities/prisma' -import { Dirent } from 'node:fs' import { appLogger, watchLogs } from './utilities/logger' import ZoneManager from './managers/zoneManager' import UserManager from './managers/userManager' import CommandManager from './managers/commandManager' import CharacterManager from './managers/characterManager' import QueueManager from './managers/queueManager' +import { getAppPath } from './utilities/utilities' export class Server { private readonly app: Application @@ -86,46 +85,46 @@ export class Server { * @private */ private async handleConnection(socket: TSocket) { - const eventsPath = path.join(__dirname, 'socketEvents') try { - await this.loadEventHandlers(eventsPath, socket) + await this.loadEventHandlers('socketEvents', '', socket) } catch (error: any) { appLogger.error(`Failed to load event handlers: ${error.message}`) } } - private async loadEventHandlers(dir: string, socket: TSocket) { + private async loadEventHandlers(baseDir: string, subDir: string, socket: TSocket) { try { - const files = await fs.promises.readdir(dir, { withFileTypes: true }); + const fullDir = getAppPath(baseDir, subDir) + const files = await fs.promises.readdir(fullDir, { withFileTypes: true }) for (const file of files) { - const fullPath = path.join(dir, file.name); + const filePath = getAppPath(baseDir, subDir, file.name) if (file.isDirectory()) { - await this.loadEventHandlers(fullPath, socket); - continue; + await this.loadEventHandlers(baseDir, `${subDir}/${file.name}`, socket) + continue } if (!file.isFile() || (!file.name.endsWith('.ts') && !file.name.endsWith('.js'))) { - continue; + continue } try { - const module = await import(fullPath); + const module = await import(filePath) if (typeof module.default !== 'function') { - appLogger.warn(`Unrecognized export in ${file.name}`); - continue; + appLogger.warn(`Unrecognized export in ${file.name}`) + continue } - const EventClass = module.default; - const eventInstance = new EventClass(this.io, socket); - eventInstance.listen(); + const EventClass = module.default + const eventInstance = new EventClass(this.io, socket) + eventInstance.listen() } catch (error) { - appLogger.error(`Error loading event handler ${file.name}: ${error instanceof Error ? error.message : String(error)}`); + appLogger.error(`Error loading event handler ${file.name}: ${error instanceof Error ? error.message : String(error)}`) } } } catch (error) { - appLogger.error(`Error reading directory ${dir}: ${error instanceof Error ? error.message : String(error)}`); + appLogger.error(`Error reading directory: ${error instanceof Error ? error.message : String(error)}`) } } } diff --git a/src/services/assetService.ts b/src/services/assetService.ts deleted file mode 100644 index fab2229..0000000 --- a/src/services/assetService.ts +++ /dev/null @@ -1,3 +0,0 @@ -class AssetService {} - -export default AssetService diff --git a/src/services/character/characterService.ts b/src/services/character/characterService.ts deleted file mode 100644 index b62016c..0000000 --- a/src/services/character/characterService.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { Character } from '@prisma/client' - -class CharacterService {} - -export default CharacterService diff --git a/src/socketEvents/character/connect.ts b/src/socketEvents/character/connect.ts index a14a92b..4a2e142 100644 --- a/src/socketEvents/character/connect.ts +++ b/src/socketEvents/character/connect.ts @@ -1,7 +1,6 @@ import { Server } from 'socket.io' -import { TSocket, ExtendedCharacter } from '../../utilities/types' +import { TSocket } from '../../utilities/types' import CharacterRepository from '../../repositories/characterRepository' -import CharacterManager from '../../managers/characterManager' type SocketResponseT = { character_id: number diff --git a/src/socketEvents/gameMaster/assetManager/object/list.ts b/src/socketEvents/gameMaster/assetManager/object/list.ts index febceb2..7ecea2d 100644 --- a/src/socketEvents/gameMaster/assetManager/object/list.ts +++ b/src/socketEvents/gameMaster/assetManager/object/list.ts @@ -2,7 +2,6 @@ import { Server } from 'socket.io' import { TSocket } from '../../../../utilities/types' import { Object } from '@prisma/client' import ObjectRepository from '../../../../repositories/objectRepository' -import CharacterManager from '../../../../managers/characterManager' import characterRepository from '../../../../repositories/characterRepository' interface IPayload {} diff --git a/src/socketEvents/gameMaster/assetManager/object/remove.ts b/src/socketEvents/gameMaster/assetManager/object/remove.ts index c27d034..b94ce60 100644 --- a/src/socketEvents/gameMaster/assetManager/object/remove.ts +++ b/src/socketEvents/gameMaster/assetManager/object/remove.ts @@ -1,10 +1,9 @@ +import fs from 'fs' import { Server } from 'socket.io' import { TSocket } from '../../../../utilities/types' -import path from 'path' -import fs from 'fs' import prisma from '../../../../utilities/prisma' -import CharacterManager from '../../../../managers/characterManager' import characterRepository from '../../../../repositories/characterRepository' +import { getPublicPath } from '../../../../utilities/utilities' interface IPayload { object: string @@ -36,10 +35,10 @@ export default class ObjectRemoveEvent { }) // get root path - const public_folder = path.join(process.cwd(), 'public', 'objects') + const public_folder = getPublicPath('objects') // remove the tile from the disk - const finalFilePath = path.join(public_folder, data.object + '.png') + const finalFilePath = getPublicPath('objects', data.object + '.png') fs.unlink(finalFilePath, (err) => { if (err) { console.log(err) diff --git a/src/socketEvents/gameMaster/assetManager/object/update.ts b/src/socketEvents/gameMaster/assetManager/object/update.ts index 634b42f..e9cfae9 100644 --- a/src/socketEvents/gameMaster/assetManager/object/update.ts +++ b/src/socketEvents/gameMaster/assetManager/object/update.ts @@ -1,7 +1,6 @@ import { Server } from 'socket.io' import { TSocket } from '../../../../utilities/types' import prisma from '../../../../utilities/prisma' -import CharacterManager from '../../../../managers/characterManager' import characterRepository from '../../../../repositories/characterRepository' type Payload = { diff --git a/src/socketEvents/gameMaster/assetManager/object/upload.ts b/src/socketEvents/gameMaster/assetManager/object/upload.ts index 22ab62c..a7bfb0e 100644 --- a/src/socketEvents/gameMaster/assetManager/object/upload.ts +++ b/src/socketEvents/gameMaster/assetManager/object/upload.ts @@ -1,12 +1,12 @@ import { Server } from 'socket.io' import { TSocket } from '../../../../utilities/types' import { writeFile } from 'node:fs/promises' -import path from 'path' import fs from 'fs/promises' import prisma from '../../../../utilities/prisma' import sharp from 'sharp' import characterRepository from '../../../../repositories/characterRepository' import { gameMasterLogger } from '../../../../utilities/logger' +import { getPublicPath } from '../../../../utilities/utilities' interface IObjectData { [key: string]: Buffer @@ -30,7 +30,7 @@ export default class ObjectUploadEvent { if (character.role !== 'gm') { return callback(false) } - const public_folder = path.join(process.cwd(), 'public', 'objects') + const public_folder = getPublicPath('objects') // Ensure the folder exists await fs.mkdir(public_folder, { recursive: true }) @@ -54,7 +54,7 @@ export default class ObjectUploadEvent { const uuid = object.id const filename = `${uuid}.png` - const finalFilePath = path.join(public_folder, filename) + const finalFilePath = getPublicPath('objects', filename) await writeFile(finalFilePath, objectData) gameMasterLogger.info('gm:object:upload', `Object ${key} uploaded with id ${uuid}`) diff --git a/src/socketEvents/gameMaster/assetManager/sprite/create.ts b/src/socketEvents/gameMaster/assetManager/sprite/create.ts index f8c1e49..b9b3bf0 100644 --- a/src/socketEvents/gameMaster/assetManager/sprite/create.ts +++ b/src/socketEvents/gameMaster/assetManager/sprite/create.ts @@ -1,10 +1,9 @@ import { Server } from 'socket.io' import { TSocket } from '../../../../utilities/types' -import path from 'path' import fs from 'fs/promises' import prisma from '../../../../utilities/prisma' -import CharacterManager from '../../../../managers/characterManager' import characterRepository from '../../../../repositories/characterRepository' +import { getPublicPath } from '../../../../utilities/utilities' export default class SpriteCreateEvent { constructor( @@ -25,7 +24,7 @@ export default class SpriteCreateEvent { return callback(false) } - const public_folder = path.join(process.cwd(), 'public', 'sprites') + const public_folder = getPublicPath('sprites') // Ensure the folder exists await fs.mkdir(public_folder, { recursive: true }) @@ -38,7 +37,7 @@ export default class SpriteCreateEvent { const uuid = sprite.id // Create folder with uuid - const sprite_folder = path.join(public_folder, uuid) + const sprite_folder = getPublicPath('sprites', uuid) await fs.mkdir(sprite_folder, { recursive: true }) callback(true) diff --git a/src/socketEvents/gameMaster/assetManager/sprite/delete.ts b/src/socketEvents/gameMaster/assetManager/sprite/delete.ts index 15f572c..024336b 100644 --- a/src/socketEvents/gameMaster/assetManager/sprite/delete.ts +++ b/src/socketEvents/gameMaster/assetManager/sprite/delete.ts @@ -1,10 +1,10 @@ import { Server } from 'socket.io' import { TSocket } from '../../../../utilities/types' import fs from 'fs' -import path from 'path' import prisma from '../../../../utilities/prisma' import CharacterManager from '../../../../managers/characterManager' import { gameMasterLogger } from '../../../../utilities/logger' +import { getPublicPath } from '../../../../utilities/utilities' type Payload = { id: string @@ -17,7 +17,7 @@ export default class GMSpriteDeleteEvent { private readonly io: Server, private readonly socket: TSocket ) { - this.public_folder = path.join(process.cwd(), 'public', 'sprites') + this.public_folder = getPublicPath('sprites') } public listen(): void { @@ -43,7 +43,7 @@ export default class GMSpriteDeleteEvent { } private async deleteSpriteFolder(spriteId: string): Promise { - const finalFilePath = path.join(this.public_folder, spriteId) + const finalFilePath = getPublicPath('sprites', spriteId) if (fs.existsSync(finalFilePath)) { await fs.promises.rmdir(finalFilePath, { recursive: true }) diff --git a/src/socketEvents/gameMaster/assetManager/sprite/list.ts b/src/socketEvents/gameMaster/assetManager/sprite/list.ts index 7c1ecac..6cf423c 100644 --- a/src/socketEvents/gameMaster/assetManager/sprite/list.ts +++ b/src/socketEvents/gameMaster/assetManager/sprite/list.ts @@ -2,7 +2,6 @@ import { Server } from 'socket.io' import { TSocket } from '../../../../utilities/types' import { Sprite } from '@prisma/client' import SpriteRepository from '../../../../repositories/spriteRepository' -import CharacterManager from '../../../../managers/characterManager' import characterRepository from '../../../../repositories/characterRepository' interface IPayload {} diff --git a/src/socketEvents/gameMaster/assetManager/sprite/update.ts b/src/socketEvents/gameMaster/assetManager/sprite/update.ts index 339666f..d23734a 100644 --- a/src/socketEvents/gameMaster/assetManager/sprite/update.ts +++ b/src/socketEvents/gameMaster/assetManager/sprite/update.ts @@ -2,10 +2,10 @@ import { Server } from 'socket.io' import { TSocket } from '../../../../utilities/types' import prisma from '../../../../utilities/prisma' import type { Prisma, SpriteAction } from '@prisma/client' -import path from 'path' import { writeFile, mkdir } from 'node:fs/promises' import sharp from 'sharp' import CharacterManager from '../../../../managers/characterManager' +import { getPublicPath } from '../../../../utilities/utilities' type SpriteActionInput = Omit & { sprites: string[] @@ -123,7 +123,7 @@ export default class SpriteUpdateEvent { } async function saveSpritesToDisk(id: string, processedActions: ProcessedSpriteAction[]) { - const publicFolder = path.join(process.cwd(), 'public', 'sprites', id) + const publicFolder = getPublicPath('sprites', id) await mkdir(publicFolder, { recursive: true }) await Promise.all( @@ -146,7 +146,7 @@ export default class SpriteUpdateEvent { .png() .toBuffer() - const filename = path.join(publicFolder, `${action}.png`) + const filename = getPublicPath('sprites', id, `${action}.png`) await writeFile(filename, combinedImage) }) ) diff --git a/src/socketEvents/gameMaster/assetManager/tile/delete.ts b/src/socketEvents/gameMaster/assetManager/tile/delete.ts index 0fca22b..f2be8de 100644 --- a/src/socketEvents/gameMaster/assetManager/tile/delete.ts +++ b/src/socketEvents/gameMaster/assetManager/tile/delete.ts @@ -1,10 +1,10 @@ -import path from 'path' import fs from 'fs/promises' import { Server } from 'socket.io' import { TSocket } from '../../../../utilities/types' import prisma from '../../../../utilities/prisma' import characterRepository from '../../../../repositories/characterRepository' import { gameMasterLogger } from '../../../../utilities/logger' +import { getPublicPath } from '../../../../utilities/utilities' type Payload = { id: string @@ -17,7 +17,7 @@ export default class GMTileDeleteEvent { private readonly io: Server, private readonly socket: TSocket ) { - this.public_folder = path.join(process.cwd(), 'public', 'tiles') + this.public_folder = getPublicPath('tiles') } public listen(): void { @@ -54,7 +54,7 @@ export default class GMTileDeleteEvent { } private async deleteTileFile(tileId: string): Promise { - const finalFilePath = path.join(this.public_folder, `${tileId}.png`) + const finalFilePath = getPublicPath('tiles', `${tileId}.png`) try { await fs.unlink(finalFilePath) } catch (error: any) { diff --git a/src/socketEvents/gameMaster/assetManager/tile/list.ts b/src/socketEvents/gameMaster/assetManager/tile/list.ts index 9000d58..9e6856f 100644 --- a/src/socketEvents/gameMaster/assetManager/tile/list.ts +++ b/src/socketEvents/gameMaster/assetManager/tile/list.ts @@ -2,7 +2,6 @@ import { Server } from 'socket.io' import { TSocket } from '../../../../utilities/types' import { Tile } from '@prisma/client' import TileRepository from '../../../../repositories/tileRepository' -import CharacterManager from '../../../../managers/characterManager' import characterRepository from '../../../../repositories/characterRepository' interface IPayload {} diff --git a/src/socketEvents/gameMaster/assetManager/tile/update.ts b/src/socketEvents/gameMaster/assetManager/tile/update.ts index 81058f8..372e2ab 100644 --- a/src/socketEvents/gameMaster/assetManager/tile/update.ts +++ b/src/socketEvents/gameMaster/assetManager/tile/update.ts @@ -1,7 +1,6 @@ import { Server } from 'socket.io' import { TSocket } from '../../../../utilities/types' import prisma from '../../../../utilities/prisma' -import CharacterManager from '../../../../managers/characterManager' import characterRepository from '../../../../repositories/characterRepository' type Payload = { diff --git a/src/socketEvents/gameMaster/assetManager/tile/upload.ts b/src/socketEvents/gameMaster/assetManager/tile/upload.ts index 69f1f40..8c680f0 100644 --- a/src/socketEvents/gameMaster/assetManager/tile/upload.ts +++ b/src/socketEvents/gameMaster/assetManager/tile/upload.ts @@ -1,11 +1,11 @@ import { Server } from 'socket.io' import { TSocket } from '../../../../utilities/types' import { writeFile } from 'node:fs/promises' -import path from 'path' import fs from 'fs/promises' import prisma from '../../../../utilities/prisma' import characterRepository from '../../../../repositories/characterRepository' import { gameMasterLogger } from '../../../../utilities/logger' +import { getPublicPath } from '../../../../utilities/utilities' interface ITileData { [key: string]: Buffer @@ -30,7 +30,7 @@ export default class TileUploadEvent { return } - const public_folder = path.join(process.cwd(), 'public', 'tiles') + const public_folder = getPublicPath('tiles') // Ensure the folder exists await fs.mkdir(public_folder, { recursive: true }) @@ -43,7 +43,7 @@ export default class TileUploadEvent { }) const uuid = tile.id const filename = `${uuid}.png` - const finalFilePath = path.join(public_folder, filename) + const finalFilePath = getPublicPath('tiles', filename) await writeFile(finalFilePath, tileData) }) diff --git a/src/utilities/http.ts b/src/utilities/http.ts index d90c38c..e0f6433 100644 --- a/src/utilities/http.ts +++ b/src/utilities/http.ts @@ -12,6 +12,7 @@ import fs from 'fs' import zoneRepository from '../repositories/zoneRepository' import zoneManager from '../managers/zoneManager' import { httpLogger } from './logger' +import { getPublicPath } from './utilities' async function addHttpRoutes(app: Application) { /** @@ -117,9 +118,9 @@ async function addHttpRoutes(app: Application) { let assetPath if (assetType === 'sprites' && spriteId) { - assetPath = path.join(process.cwd(), 'public', assetType, spriteId, fileName) + assetPath = getPublicPath(assetType, spriteId, fileName) } else { - assetPath = path.join(process.cwd(), 'public', assetType, fileName) + assetPath = getPublicPath(assetType, fileName) } if (!fs.existsSync(assetPath)) { diff --git a/src/utilities/logger.ts b/src/utilities/logger.ts index 7ac4eaf..24140be 100644 --- a/src/utilities/logger.ts +++ b/src/utilities/logger.ts @@ -1,6 +1,6 @@ import pino from 'pino' import fs from 'fs' -import path from 'path' +import { getRootPath } from './utilities' // Array of log types const LOG_TYPES = ['http', 'game', 'gameMaster', 'app', 'queue', 'command'] as const @@ -30,7 +30,7 @@ const loggers = Object.fromEntries(LOG_TYPES.map((type) => [type, createLogger(t const watchLogs = () => { LOG_TYPES.forEach((type) => { - const logFile = path.join(__dirname, '../../logs', `${type}.log`) + const logFile = getRootPath('logs', `${type}.log`) fs.watchFile(logFile, (curr, prev) => { if (curr.size > prev.size) { diff --git a/src/utilities/utilities.ts b/src/utilities/utilities.ts new file mode 100644 index 0000000..416816b --- /dev/null +++ b/src/utilities/utilities.ts @@ -0,0 +1,15 @@ +import config from './config' +import path from 'path' + +export function getRootPath(folder: string, ...additionalSegments: string[]) { + return path.join(process.cwd(), folder, ...additionalSegments) +} + +export function getAppPath(folder: string, ...additionalSegments: string[]) { + const baseDir = config.ENV === 'development' ? 'src' : 'dist' + return path.join(process.cwd(), baseDir, folder, ...additionalSegments) +} + +export function getPublicPath(folder: string, ...additionalSegments: string[]) { + return path.join(process.cwd(), 'public', folder, ...additionalSegments) +}