UNPKG

nmdk

Version:

CLI tool for downloading and setting up Minecraft mod development kits (MDK) for Forge, Fabric, and NeoForge

359 lines (280 loc) 11.8 kB
const fs = require('fs-extra'); const path = require('path'); const { exec } = require('child_process'); const { promisify } = require('util'); const ora = require('ora'); const chalk = require('chalk'); const execAsync = promisify(exec); async function sanitizeGradleConfigurationFiles(projectPath) { const spinner = ora('Sanitizing Gradle configuration files').start(); try { const gradleFiles = [ 'build.gradle', 'build.gradle.kts', 'gradle.properties', 'settings.gradle', 'settings.gradle.kts' ]; for (const fileName of gradleFiles) { const filePath = path.join(projectPath, fileName); if (await fs.pathExists(filePath)) { await sanitizeGradleFile(filePath); } } spinner.succeed(chalk.green('Gradle configuration files sanitized')); } catch (error) { spinner.fail('Failed to cleanup Gradle files'); throw new Error(`Failed to cleanup Gradle files: ${error.message}`); } } async function sanitizeGradleFile(filePath) { try { let content = await fs.readFile(filePath, 'utf8'); const fileName = path.basename(filePath); if (fileName === 'gradle.properties') { content = content.replace(/^\s*#.*$/gm, ''); content = content.replace(/^\s*$/gm, ''); content = content.replace(/\s+$/gm, ''); content = content.replace(/\n{3,}/g, '\n\n'); } else if (fileName === 'build.gradle' || fileName === 'build.gradle.kts') { content = content.replace(/^\s*\/\/ .*$/gm, ''); content = content.replace(/\s+$/gm, ''); content = content.replace(/\n{3,}/g, '\n\n'); } else { content = content.replace(/\/\/.*$/gm, ''); content = content.replace(/\/\*[\s\S]*?\*\//g, ''); content = content.replace(/^\s*$/gm, ''); content = content.replace(/\s+$/gm, ''); content = content.replace(/\n{3,}/g, '\n\n'); } await fs.writeFile(filePath, content.trim() + '\n'); } catch (error) { throw new Error(`Failed to cleanup file ${filePath}: ${error.message}`); } } async function configureModMetadata(projectPath, modConfig) { const spinner = ora('Configuring mod metadata').start(); try { await configureGradleProperties(projectPath, modConfig); await configureBuildGradle(projectPath, modConfig); await restructureJavaPackageHierarchy(projectPath, modConfig); spinner.succeed(chalk.green('Mod metadata configured')); } catch (error) { spinner.fail('Failed to update mod metadata'); throw new Error(`Failed to update mod metadata: ${error.message}`); } } async function configureFabricModMetadata(projectPath, modConfig) { const spinner = ora('Configuring Fabric mod metadata').start(); try { await configureGradleProperties(projectPath, modConfig); await configureBuildGradle(projectPath, modConfig); await updateFabricJavaFiles(projectPath, modConfig); spinner.succeed(chalk.green('Fabric mod metadata configured')); } catch (error) { spinner.fail('Failed to update Fabric mod metadata'); throw new Error(`Failed to update Fabric mod metadata: ${error.message}`); } } async function configureGradleProperties(projectPath, modConfig) { const filePath = path.join(projectPath, 'gradle.properties'); if (await fs.pathExists(filePath)) { let content = await fs.readFile(filePath, 'utf8'); content = content.replace(/mod_name=.*/g, `mod_name=${modConfig.modName}`); content = content.replace(/mod_id=.*/g, `mod_id=${modConfig.modId}`); content = content.replace(/mod_version=.*/g, `mod_version=${modConfig.version}`); content = content.replace(/mod_group_id=.*/g, `mod_group_id=${modConfig.groupId}`); content = content.replace(/mod_authors=.*/g, `mod_authors=${modConfig.author}`); content = content.replace(/mod_description=.*/g, `mod_description=${modConfig.description}`); await fs.writeFile(filePath, content); } } async function configureBuildGradle(projectPath, modConfig) { const filePath = path.join(projectPath, 'build.gradle'); if (await fs.pathExists(filePath)) { let content = await fs.readFile(filePath, 'utf8'); content = content.replace(/group\s*=\s*['"][^'"]*['"]/g, `group = "${modConfig.groupId}"`); content = content.replace(/version\s*=\s*['"][^'"]*['"]/g, `version = "${modConfig.version}"`); await fs.writeFile(filePath, content); } } async function restructureJavaPackageHierarchy(projectPath, modConfig) { const javaSrcPath = path.join(projectPath, 'src/main/java'); if (!await fs.pathExists(javaSrcPath)) { return; } const items = await fs.readdir(javaSrcPath); if (items.length === 0) { return; } const newPackageParts = modConfig.groupId.split('.'); let currentPath = javaSrcPath; for (const part of newPackageParts) { currentPath = path.join(currentPath, part); await fs.ensureDir(currentPath); } const finalPackagePath = path.join(currentPath, modConfig.modId); await fs.ensureDir(finalPackagePath); await findAndRelocateExampleMod(javaSrcPath, finalPackagePath, modConfig); } async function findAndRelocateExampleMod(currentPath, finalPackagePath, modConfig) { if (!await fs.pathExists(currentPath)) { return; } const items = await fs.readdir(currentPath); for (const item of items) { const itemPath = path.join(currentPath, item); const stat = await fs.stat(itemPath); if (stat.isDirectory()) { if (item === 'examplemod') { await relocateJavaSourceFiles(itemPath, finalPackagePath, modConfig); await fs.remove(itemPath); return; } else { await findAndRelocateExampleMod(itemPath, finalPackagePath, modConfig); } } } } async function handleConflictingPackageStructure(oldPath, newPath, modConfig) { const tempPath = path.join(path.dirname(path.dirname(path.dirname(oldPath))), 'temp_' + Date.now()); try { await fs.copy(oldPath, tempPath, { overwrite: true }); const javaFiles = await discoverJavaFilesRecursively(tempPath); for (const javaFile of javaFiles) { const fileName = path.basename(javaFile); let newFileName = fileName; if (fileName === 'ExampleMod.java') { newFileName = `${modConfig.modId.charAt(0).toUpperCase() + modConfig.modId.slice(1)}Mod.java`; const newFilePath = path.join(path.dirname(javaFile), newFileName); await fs.move(javaFile, newFilePath); await updateJavaFile(newFilePath, modConfig); } else { await updateJavaFile(javaFile, modConfig); } } await fs.copy(tempPath, newPath, { overwrite: true }); } finally { if (await fs.pathExists(tempPath)) { await fs.remove(tempPath); } } } async function relocateJavaSourceFiles(oldPath, newPath, modConfig) { if (!await fs.pathExists(oldPath)) { return; } await fs.copy(oldPath, newPath, { overwrite: true }); const javaFiles = await discoverJavaFilesRecursively(newPath); for (const javaFile of javaFiles) { const fileName = path.basename(javaFile); let newFileName = fileName; if (fileName === 'ExampleMod.java') { newFileName = `${modConfig.modId.charAt(0).toUpperCase() + modConfig.modId.slice(1)}Mod.java`; const newFilePath = path.join(path.dirname(javaFile), newFileName); await fs.move(javaFile, newFilePath); await updateJavaFile(newFilePath, modConfig); } else { await updateJavaFile(javaFile, modConfig); } } } async function discoverJavaFilesRecursively(dirPath) { const javaFiles = []; async function searchDirectory(currentPath) { if (!await fs.pathExists(currentPath)) { return; } const items = await fs.readdir(currentPath); for (const item of items) { const itemPath = path.join(currentPath, item); const stat = await fs.stat(itemPath); if (stat.isDirectory()) { await searchDirectory(itemPath); } else if (item.endsWith('.java')) { javaFiles.push(itemPath); } } } await searchDirectory(dirPath); return javaFiles; } async function updateJavaFile(filePath, modConfig) { let content = await fs.readFile(filePath, 'utf8'); content = content.replace(/@Mod\("examplemod"\)/g, `@Mod("${modConfig.modId}")`); content = content.replace(/class\s+ExampleMod/g, `class ${modConfig.modId.charAt(0).toUpperCase() + modConfig.modId.slice(1)}Mod`); content = content.replace(/ExampleMod/g, `${modConfig.modId.charAt(0).toUpperCase() + modConfig.modId.slice(1)}Mod`); content = content.replace(/examplemod/g, modConfig.modId); content = content.replace(/Examplemod/g, modConfig.modId.charAt(0).toUpperCase() + modConfig.modId.slice(1)); await fs.writeFile(filePath, content); } async function updateFabricJavaFiles(projectPath, modConfig) { const javaSrcPath = path.join(projectPath, 'src/main/java'); if (await fs.pathExists(javaSrcPath)) { const javaFiles = await discoverJavaFilesRecursively(javaSrcPath); for (const javaFile of javaFiles) { const fileName = path.basename(javaFile); let newFileName = fileName; if (fileName === 'ExampleMod.java') { newFileName = `${modConfig.modId.charAt(0).toUpperCase() + modConfig.modId.slice(1)}Mod.java`; const newFilePath = path.join(path.dirname(javaFile), newFileName); await fs.move(javaFile, newFilePath); await updateJavaFile(newFilePath, modConfig); } else { await updateJavaFile(javaFile, modConfig); } } } } async function executeGradleBuildCommands(projectPath, commands) { const spinner = ora('Executing Gradle build commands').start(); try { for (const command of commands) { spinner.text = `Running: ${command}`; const { stdout, stderr } = await execAsync(command, { cwd: projectPath, timeout: 300000 }); if (stderr && !stderr.includes('warning')) { console.warn(`Gradle warning: ${stderr}`); } } spinner.succeed(chalk.green('Gradle build commands executed')); } catch (error) { spinner.fail('Gradle commands failed'); throw new Error(`Gradle command failed: ${error.message}`); } } async function finalizeProjectStructure(projectPath) { const filesToRemove = [ 'LICENSE.txt', 'README.txt', 'CREDITS.txt', 'changelog.txt', '.gitattributes' ]; for (const file of filesToRemove) { const filePath = path.join(projectPath, file); if (await fs.pathExists(filePath)) { await fs.remove(filePath); } } await repairSettingsGradleConfiguration(projectPath); } async function repairSettingsGradleConfiguration(projectPath) { const settingsPath = path.join(projectPath, 'settings.gradle'); if (await fs.pathExists(settingsPath)) { let content = await fs.readFile(settingsPath, 'utf8'); content = content.replace(/url = 'https:$/gm, "url = 'https://maven.minecraftforge.net'"); content = content.replace(/#.*$/gm, ''); content = content.replace(/\n\s*\n\s*\n/g, '\n\n'); await fs.writeFile(settingsPath, content); } } module.exports = { sanitizeGradleConfigurationFiles, configureModMetadata, configureFabricModMetadata, executeGradleBuildCommands, finalizeProjectStructure };