UNPKG

@evitcastudio/kit

Version:

A single-player/multiplayer framework for the Vylocity Game Engine.

192 lines (191 loc) 6.14 kB
import { promises as fs } from 'fs'; import { join, extname, basename } from 'path'; import chalk from 'chalk'; import { v4 as uuidv4 } from 'uuid'; // Logging helpers const log = console.log; const info = chalk.hex('#ffa552'); const error = chalk.hex('#c42847'); const alert = chalk.hex('#EFF2C0'); // Resource types and valid file extensions const RESOURCE_TYPES = ['interface', 'icon', 'map', 'sound', 'macros']; const VALID_EXTENSIONS = ['vyint', 'vyi', 'vym', 'vymac', 'mp3', 'aac', 'wav', 'm4a', 'ogg', 'flac']; // State variables let resourceJSON = initializeResourceJSON(); let isVerbose = false; let ignoringSound = false; let resourceInDirectory = ''; let resourceOutDirectory = ''; const resourcesToProcess = []; /** * Initializes the resource JSON structure. */ function initializeResourceJSON() { return RESOURCE_TYPES.reduce((acc, type) => { acc[type] = []; return acc; }, {}); } /** * Processes a single file and updates the resource JSON. */ function prepareFileForProcessing(pFilePath) { const extension = extname(pFilePath).slice(1); const fileName = basename(pFilePath); const resourceIdentifier = `${uuidv4()}.vyr`; const type = getResourceType(extension); if (!type) return; if (type === 'sound' && ignoringSound) { logVerbose(`[Ignored File] ${pFilePath} (ignoreSound flag enabled)`); return; } resourcesToProcess.push({ filePath: pFilePath, type }); resourceJSON[type].push({ resourceIdentifier, fileName }); } /** * Determines the resource type based on the file extension. */ function getResourceType(pExtension) { switch (pExtension) { case 'vyint': return 'interface'; case 'vyi': return 'icon'; case 'vym': return 'map'; case 'vymac': return 'macros'; case 'mp3': case 'aac': case 'wav': case 'm4a': case 'ogg': case 'flac': return 'sound'; default: return null; } } /** * Processes a directory and its contents recursively. */ async function processDirectory(pDirectoryPath) { try { const contents = await fs.readdir(pDirectoryPath); for (const item of contents) { const itemPath = join(pDirectoryPath, item); const stats = await fs.stat(itemPath); if (stats.isDirectory()) { await processDirectory(itemPath); } else if (isValidExtension(extname(itemPath).slice(1))) { prepareFileForProcessing(itemPath); } } } catch (pError) { logError(`[Error] Processing directory: ${pError}`); } } /** * Checks if a file extension is valid. */ function isValidExtension(pExtension) { return VALID_EXTENSIONS.includes(pExtension); } /** * Executes all file copy operations in parallel after preparation. */ async function processAllFiles() { try { await clearResourceTypeDirectories(`${resourceOutDirectory}/resources`); // Create copy operations for all files const copyOperations = resourcesToProcess.map(({ filePath, type }) => { const fileName = basename(filePath); const resource = resourceJSON[type].find(res => res.fileName === fileName); if (!resource) { throw new Error(`Resource not found for file: ${fileName}`); } const destination = join(resourceOutDirectory, 'resources'); const resourceName = resource.resourceIdentifier; return copyFile(filePath, destination, resourceName); }); // Execute all copy operations concurrently await Promise.all(copyOperations); logVerbose(`[Kit CLI] All resources have been processed.`); await saveResourceJSON(); } catch (pError) { const errorMessage = pError instanceof Error ? pError.message : String(pError); logError(`[Error] Processing files in batch: ${errorMessage}`); } } /** * Clears specified directories within a base directory. * @param pBaseDirectory - The path to the base directory. */ async function clearResourceTypeDirectories(pBaseDirectory) { try { const directoryExists = await fs.stat(pBaseDirectory).then(stat => stat.isDirectory()).catch(() => false); if (directoryExists) { await fs.rm(pBaseDirectory, { recursive: true }); } } catch (pError) { log(`${error(`[Error]`)} clearing resource directory: ${pError}`); } } /** * Copies a file to the specified directory. */ async function copyFile(pSource, pDestinationDir, pNewName) { try { await fs.mkdir(pDestinationDir, { recursive: true }); await fs.copyFile(pSource, join(pDestinationDir, pNewName)); } catch (pError) { logError(`[Error] Copying file ${pSource}: ${pError}`); } } /** * Saves the resource JSON to a file. */ async function saveResourceJSON() { const filePath = 'resource.json'; try { await fs.writeFile(filePath, JSON.stringify(resourceJSON, null, 4)); } catch (pError) { logError(`[Error] Saving resource JSON: ${pError}`); } } /** * Main entry point for building resources. */ export async function processResources({ inDirectory, outDirectory, verbose, ignoreSound }) { // Initialize state resourceInDirectory = inDirectory; resourceOutDirectory = outDirectory; isVerbose = verbose; ignoringSound = ignoreSound; resourceJSON = initializeResourceJSON(); // Validate inputs if (!resourceInDirectory || !resourceOutDirectory) { logError('[Error] Input and output directories must be specified'); return; } // Process resources await processDirectory(resourceInDirectory); if (resourcesToProcess.length > 0) { await processAllFiles(); } else { logAlert('No resources found!'); await saveResourceJSON(); } } function logVerbose(pMessage) { if (isVerbose) log(info(pMessage)); } function logError(pMessage) { log(error(pMessage)); } function logAlert(pMessage) { log(alert(pMessage)); }