UNPKG

minecraft-utils-shared

Version:

Shared utils for Minecraft Bedrock / Forge development related utilities.

263 lines (249 loc) 8.18 kB
/** * @file Minecraft Utils Shared - Template * @license Apache-2.0 * @author Markus@Bordihn.de (Markus Bordihn) */ import chalk from 'chalk'; import fs from 'fs-extra'; import path from 'path'; import templateFile from '../formats/template_file.mjs'; /** * @returns {string} */ const getTemplatePath = () => { if (process.env) { if ( (process.env.npm_lifecycle_script || '').includes( 'minecraft-bedrock-utils' ) || (process.env.npm_package_name || '').includes('minecraft-bedrock-utils') ) { return '.minecraft-bedrock-utils-templates'; } else if ( (process.env.npm_lifecycle_script || '').includes( 'minecraft-forge-utils' ) || (process.env.npm_package_name || '').includes('minecraft-forge-utils') ) { return '.minecraft-forge-utils-templates'; } } return '.minecraft-utils-shared-templates'; }; // eslint-disable-next-line prefer-const let templatePath = path.join(process.cwd(), getTemplatePath()); /** * Template Processing engine * * The target path could be a simple string for easier operations or * and object for more advanced requirements like Forge Mods. * We expect the targetPath Object in the following format: * { * assetsPath: '', * classPath: '', * dataPath: '', * } * * @param {string} template * @param {object} placeholder * @param {string|object} targetPath */ const processTemplateFile = (template, placeholder, targetPath = '') => { if (!fs.existsSync(template)) { console.error('Unable to find template:', template); return; } if (targetPath && typeof targetPath == 'object') { console.log( chalk.green( '🏷️ Processing multi template', template, 'with', placeholder, 'and', targetPath ) ); } else { if (targetPath && !fs.existsSync(targetPath)) { console.error('Unable to access target path:', targetPath); return; } console.log( chalk.green('🏷️ Processing template', template, 'with', placeholder) ); } const templateDefinition = templateFile.parse(template, placeholder); templateDefinition.forEach((definition) => { const fileName = definition.fileName; let filePath = definition.filePath; // Automatically detect correct target path based on file type. if (targetPath && typeof targetPath == 'object') { switch (definition.fileType) { case templateFile.fileType.JAVA: filePath = path.join(targetPath.classPath, definition.filePath); break; case templateFile.fileType.RESOURCE: filePath = path.join(targetPath.assetsPath, definition.filePath); break; case templateFile.fileType.DATA: filePath = path.join(targetPath.dataPath, definition.filePath); break; case templateFile.fileType.DATA_MINECRAFT: filePath = path.join(targetPath.dataPath, '..', definition.filePath); break; } } else if (targetPath) { filePath = path.join(targetPath, definition.filePath); } let content = ''; // Handle different kind of operation based on the patch definition. if (definition.copy) { // Copy src file to dst file, if not already exists if (fs.existsSync(filePath)) { console.info(chalk.red('[Skipping Copy]'), fileName, 'already exists.'); } else { console.info(chalk.green('[Copy]'), fileName); fs.ensureDirSync(path.dirname(filePath)); fs.copyFileSync(definition.copy, filePath); } } else if (definition.create) { // Create new file if (fs.existsSync(filePath)) { console.info( chalk.red('[Skipping Create]'), fileName, 'already exists.' ); } else { console.info(chalk.green('[Create]'), fileName); content = definition.code; } } else if (definition.before || definition.after) { // Patch existing files according the "after" or "before" placeholder. // We will only processing the file if the code is not already included! if (filePath && fs.existsSync(filePath)) { content = fs.readFileSync(filePath, 'utf-8'); if (!content) { console.error( chalk.red('[Skipping after/before]'), fileName, 'file is empty.' ); } else if (content.includes(definition.code)) { console.info( chalk.yellow('[Skipping after/before]'), fileName, 'code is already injected.' ); content = ''; } else if (definition.after) { const insertPositionRegExp = new RegExp( `${escapeRegExp(definition.after)}[ \t]*(\r\n|\n|\r)?` ); const insertPosition = content.search(insertPositionRegExp); if (insertPosition != -1) { const insertPositionLength = content.match(insertPositionRegExp)[0].length; console.info(chalk.green('[After]'), 'Inject code into', fileName); content = content.slice(0, insertPosition + insertPositionLength) + definition.code + content.slice(insertPosition + insertPositionLength); } else { console.warn( chalk.yellow('[After]'), 'Unable to find entry point', definition.after, 'in', fileName ); } } else if (definition.before) { const insertPosition = content.search( new RegExp(`[ \t]*${escapeRegExp(definition.before)}`) ); if (insertPosition != -1) { console.info(chalk.green('[Before]'), 'Inject code into', fileName); content = content.slice(0, insertPosition) + definition.code + content.slice(insertPosition); } else { console.warn( chalk.yellow('[Before]'), 'Unable to find entry point', definition.before, 'in', fileName ); } } } else { // Show an error, if the file doesn't exists under the location! console.error( chalk.red('[Skipping after/before]'), fileName, 'file does not exists at', filePath ); } } else { // Show an general error for any unknown template option. console.warn( chalk.yellow('[?] Unsupported template option, skipping', fileName, '!') ); } if (content) { fs.outputFileSync(filePath, content); } }); }; /** * @param {string} content * @param {object} placeholder * @returns {string} */ const replacePlaceholder = (content, placeholder = {}) => { const newContent = content.replace( /\[\[ --([A-Za-z0-9_. ]+)-- \]\]/g, (matchString) => { const placeholderString = matchString .replace('[[ --', '') .replace('-- ]]', '') .trim(); return placeholder[placeholderString] || placeholderString; } ); return newContent; }; /** * @param {string} text * @returns {string} */ const escapeRegExp = (text) => { return text.replace(/[\\(){}[\]^$+*?.]/g, '\\$&'); }; const hasCustomTemplateFiles = () => { return !fs.existsSync(templatePath) ? false : !fs.emptyDirSync(templatePath); }; const getCustomTemplateFiles = () => { if (!fs.existsSync(templatePath)) { return null; } const result = []; fs.readdirSync(templatePath).forEach((file) => { const filePath = path.resolve(templatePath, file); if (fs.lstatSync(filePath).isFile()) { result.push(filePath); } }); return result; }; export default { getCustomTemplateFiles, hasCustomTemplateFiles, processTemplateFile, replacePlaceholder, templatePath, };