UNPKG

@origami-minecraft/devbuilds

Version:

Origami is a terminal-first Minecraft launcher that supports authentication, installation, and launching of Minecraft versions — with built-in support for Microsoft accounts, mod loaders, profile management, and more. Designed for power users, modders, an

358 lines 17.3 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ModInstaller = void 0; const inquirer_1 = __importDefault(require("inquirer")); const chalk_1 = __importDefault(require("chalk")); const path_1 = __importDefault(require("path")); const fs_1 = __importDefault(require("fs")); const modrinth_1 = require("./modrinth"); const common_1 = require("../../../utils/common"); const download_1 = require("../../../utils/download"); const modrinth_2 = require("../../../../types/modrinth"); const manager_1 = __importDefault(require("./manager")); const ora_1 = __importDefault(require("ora")); class ModInstaller { logger; modrinth; pageSize = 10; constructor(logger) { this.logger = logger; this.modrinth = new modrinth_1.ModrinthProjects(logger); } async configure_filters(project_type, version, loader, manager, defaults) { const all_categories = (await this.modrinth.tags.getCategories(project_type)) || []; const categoryOptions = all_categories .filter(cat => project_type === 'mod' ? cat.name.toLowerCase() !== loader.toLowerCase() : true) .map(cat => ({ name: cat.name, value: cat.name })); const { sort } = await inquirer_1.default.prompt([ { type: 'list', name: 'sort', message: '📊 Sort results by:', choices: modrinth_2.ModrinthSortOptions.map(opt => ({ name: opt.charAt(0).toUpperCase() + opt.slice(1), value: opt, })), default: defaults?.sort ?? 'relevance' } ]); const { versionMatch } = await inquirer_1.default.prompt([ { type: 'list', name: 'versionMatch', message: '🎯 Minecraft version match strategy:', choices: [ { name: 'Strict (exact version match)', value: 'strict' }, { name: 'Match (minor version match)', value: 'match' }, { name: 'None (ignore version)', value: 'none' } ], default: defaults?.versionMatch ?? 'strict' } ]); let versionFilter = undefined; if (versionMatch === 'strict') { versionFilter = []; versionFilter.push(version); } else if (versionMatch === 'match') { versionFilter = []; const matchedVersion = await this.modrinth.fetchAllMatchVersions(version); versionFilter.push(version); matchedVersion.forEach(ver => { if (!versionFilter?.find(v => v === ver)) { versionFilter?.push(ver); } }); } let categories = defaults?.selectedCategories; if (categoryOptions.length > 0) { const { selectedCategories } = await inquirer_1.default.prompt([ { type: 'checkbox', name: 'selectedCategories', message: '🧩 Select categories to filter by:', choices: categoryOptions, default: defaults?.selectedCategories ?? [], } ]); categories = selectedCategories.length > 0 ? selectedCategories : undefined; } if (project_type === 'mod' && !categories?.some(v => v.toLowerCase() === loader.toLowerCase())) { categories = [...(categories ?? []), loader.toLowerCase()]; } const { page_limit } = await inquirer_1.default.prompt([ { type: 'input', name: 'page_limit', message: '📄 How many results per page?', default: `${manager.getPageLimit()}`, filter: input => parseInt(input, 10), validate: input => { const num = parseInt(input, 10); if (isNaN(num) || num <= 0) return 'Page limit must be a positive number'; if (num > 100) return 'Maximum allowed is 100'; return true; } } ]); manager.currentPageLimit(typeof page_limit === 'string' ? parseInt(page_limit) : page_limit); manager.configureFilter(project_type, { sort, versionFilter, selectedCategories: categories }); return { sort, versionFilter, categories }; } async ask_confirmation(message, _applyToAll = false, _default = undefined) { const { choice } = _default ? { choice: _default } : await inquirer_1.default.prompt([ { type: 'list', name: 'choice', message, choices: [ { name: 'Keep existing file', value: 'keep' }, { name: 'Replace with new file', value: 'replace' } ], default: _default ?? 'keep', } ]); const { applyToAll } = _applyToAll ? { applyToAll: _applyToAll } : await inquirer_1.default.prompt([ { type: 'confirm', name: 'applyToAll', message: 'Apply this choice to all remaining items?', default: false } ]); return { choice, applyToAll }; } async install_modrinth_content(profile) { const manager = new manager_1.default(profile); const { type } = await inquirer_1.default.prompt({ type: 'list', name: 'type', message: '📦 Select content type:', choices: [ { name: 'Mods', value: 'mod' }, { name: 'Resource Packs', value: 'resourcepack' }, { name: 'Shaders', value: 'shader' } ] }); let page = manager.getPage(); let mode = 'home'; let query = ''; const mcVersion = profile.lastVersionId; const loader = profile.origami.metadata.name.toLowerCase(); let defaults_p = manager.getDefaultFilters(type); let sort_p = defaults_p?.sort ?? 'relevance'; let versions_p = defaults_p?.versionFilter ?? (type === 'mod' ? [profile.lastVersionId] : undefined); let categories_p = defaults_p?.selectedCategories ?? (type === 'mod' ? [loader] : undefined); const version_folder = path_1.default.join((0, common_1.minecraft_dir)(true), 'instances', profile.origami.path); const folder = { mod: 'mods', resourcepack: 'resourcepacks', shader: 'shaderpacks' }[type] || 'mods'; const dest = path_1.default.join(version_folder, folder); (0, common_1.ensureDir)(dest); while (true) { console.clear(); console.log(chalk_1.default.bold(`📦 ${mode === 'home' ? 'Featured' : 'Search'} ${type}s (MC ${mcVersion}) — Page ${page + 1}\n`)); const spinner = (0, ora_1.default)('🐾 Warming up the search engine...').start(); this.pageSize = manager.getPageLimit(); let searchResults; const commonQuery = { query: mode === 'search' ? (query || '*') : '*', limit: this.pageSize, offset: page * this.pageSize, index: sort_p, facets: { project_type: type, versions: versions_p, categories: categories_p, } }; spinner.text = '🔍 Looking through Modrinth...'; searchResults = await this.modrinth.searchProject(commonQuery); const hits = searchResults?.hits ?? []; const total = searchResults?.total_hits ?? 0; const choices = []; choices.push({ name: '[🔍 Search]', value: '__search' }); choices.push({ name: '[🛠️ Configure Filters]', value: '__configure_filters' }); let versions_data = []; spinner.text = `🎀 Gathering ${type} files...`; spinner.color = 'yellow'; for (const hit of hits) { const versions = await this.modrinth.versions.fetchVersions(hit.project_id, type === 'mod' ? [loader] : undefined, versions_p); const isInstalled = versions?.find(v => v.files.find(f => manager.getFromType(f.filename, type))); const file = isInstalled ? isInstalled.files.find(f => manager.getFromType(f.filename, type)) : undefined; if (versions) { versions_data.push({ hit: hit.project_id, is_installed: isInstalled, specific: file, versions }); } else { versions_data.push({ hit: hit.project_id, is_installed: undefined, specific: undefined, versions: [] }); } const displayName = isInstalled ? chalk_1.default.italic.underline(`${hit.title} — ⬇ ${hit.downloads.toLocaleString()} / ⭐ ${hit.follows.toLocaleString()}`) : `${hit.title} — ⬇ ${hit.downloads.toLocaleString()} / ⭐ ${hit.follows.toLocaleString()}`; choices.push({ name: displayName, value: hit.project_id }); } if (page > 0) choices.push({ name: '⬅ Previous page', value: '__prev' }); if ((page + 1) * this.pageSize < total) choices.push({ name: '➡ Next page', value: '__next' }); choices.push({ name: '🔙 Back', value: '__back' }); spinner.succeed('Done'); const { selected } = await inquirer_1.default.prompt({ type: 'list', name: 'selected', message: 'Select an option:', choices, loop: false, }); if (selected === '__back') break; if (selected === '__next') { page++; manager.currentPage(page); continue; } if (selected === '__prev') { page--; manager.currentPage(page); continue; } if (selected === '__search') { mode = 'search'; const resp = await inquirer_1.default.prompt({ type: 'input', name: 'query', message: `Search for ${type}s:`, default: query }); query = resp.query; page = 0; continue; } if (selected === '__configure_filters') { let results = await this.configure_filters(type, profile.lastVersionId, loader, manager, { sort: sort_p, versionMatch: (versions_p?.length || 0) < 1 ? 'none' : versions_p?.length === 1 ? 'strict' : 'match', selectedCategories: categories_p }); versions_p = results.versionFilter; sort_p = results.sort; categories_p = results.categories; continue; } let version_data = versions_data.find(v => v.hit === selected); await this.handleProjectInstall(version_data?.versions, type, profile, dest, version_data, manager); break; } } async handleProjectInstall(versions_raw, type, profile, dest, data, manager) { console.clear(); console.log(chalk_1.default.bold('🔄 Fetching versions...')); const versions = versions_raw; if (!versions?.length) { console.log(chalk_1.default.red('❌ No compatible versions found.')); return; } const versionChoices = versions.map(v => ({ name: `${v.name} (${v.version_number})`, value: v })); const { selectedVersion } = await inquirer_1.default.prompt({ type: 'list', name: 'selectedVersion', message: 'Select version to install:', choices: versionChoices, loop: false, }); const file = selectedVersion.files.find((f) => f.primary) || selectedVersion.files[0]; if (!file) { console.log(chalk_1.default.red('❌ No downloadable file found.')); return; } let main_apply_to_all = false; let main_default = undefined; if (data?.is_installed && data?.specific && fs_1.default.existsSync(path_1.default.join(dest, data.specific.filename))) { const confirm = await this.ask_confirmation(`You've already installed mod version '${data.specific.filename}'. What do you want to do?`, main_apply_to_all, main_default); if (confirm.applyToAll) { main_apply_to_all = confirm.applyToAll; main_default = confirm.choice; } ; if (confirm.choice === 'replace') { const fullPath = path_1.default.join(dest, data.specific.filename); fs_1.default.unlinkSync(fullPath); this.logger.log(chalk_1.default.yellow(`🗑 Removed old version: ${data.specific.filename}`)); manager.deleteFromType(data.specific.filename, type); await downloadMod(file, this.logger, type); } else if (confirm.choice === 'keep') { this.logger.log(chalk_1.default.gray(`⏭️ Skipped: ${data.specific.filename} (already installed)`)); } } else await downloadMod(file, this.logger, type); async function downloadMod(file, logger, type) { const filename = file.filename; const outPath = path_1.default.join(dest, filename); if (fs_1.default.existsSync(outPath)) fs_1.default.unlinkSync(outPath); logger.log(chalk_1.default.green(`📥 Downloading ${filename}...`)); await (0, download_1.downloader)(file.url, outPath); logger.log(chalk_1.default.green(`✅ Installed ${filename} to ${type}s folder.`)); manager.addFromType(filename, type); } ; let deps_apply_to_all = false; let deps_default = undefined; for (const dep of selectedVersion.dependencies) { if (dep.dependency_type !== 'required') continue; const depProject = await this.modrinth.getProject(dep.project_id); if (!depProject) { this.logger.log(chalk_1.default.yellow(`⚠️ Skipped missing dependency: ${dep.project_id}`)); continue; } this.logger.log(chalk_1.default.blue(`📦 Installing dependency: ${depProject.title}`)); const depVersions = await this.modrinth.versions.fetchVersions(dep.project_id, type === 'mod' ? [profile.origami.metadata.name.toLowerCase()] : undefined, selectedVersion.game_versions); if (!depVersions?.length) { this.logger.log(chalk_1.default.red(`❌ No compatible version found for dependency: ${depProject.title}`)); continue; } const depFile = depVersions[0].files.find(f => f.primary) || depVersions[0].files[0]; if (!depFile) { this.logger.log(chalk_1.default.red(`❌ No file found for dependency: ${depProject.title}`)); continue; } const isInstalled = depVersions?.find(v => v.files.find(f => manager.getFromType(f.filename, type))); const file = isInstalled ? isInstalled.files.find(f => manager.getFromType(f.filename, type)) : undefined; if (isInstalled && file && fs_1.default.existsSync(path_1.default.join(dest, file.filename))) { const confirm = await this.ask_confirmation(`You've already installed mod version '${file.filename}'. What do you want to do?`, deps_apply_to_all, deps_default); if (confirm.applyToAll) { deps_apply_to_all = confirm.applyToAll; deps_default = confirm.choice; } ; if (confirm.choice === 'replace') { const fullPath = path_1.default.join(dest, file.filename); fs_1.default.unlinkSync(fullPath); this.logger.log(chalk_1.default.yellow(`🗑 Removed old version: ${file.filename}`)); manager.deleteFromType(file.filename, type); await downloadMod(depFile, this.logger, type); } else if (confirm.choice === 'keep') { this.logger.log(chalk_1.default.gray(`⏭️ Skipped: ${file.filename} (already installed)`)); } } else await downloadMod(depFile, this.logger, type); } } } exports.ModInstaller = ModInstaller; //# sourceMappingURL=install.js.map