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
JavaScript
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
};