@bobschlowinskii/clicraft 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +191 -0
- package/CONTRIBUTING.md +261 -0
- package/LICENSE.md +7 -0
- package/README.md +55 -0
- package/commands/auth.js +427 -0
- package/commands/config.js +356 -0
- package/commands/create.js +534 -0
- package/commands/info.js +113 -0
- package/commands/install.js +130 -0
- package/commands/launch.js +296 -0
- package/commands/search.js +50 -0
- package/commands/uninstall.js +94 -0
- package/commands/upgrade.js +343 -0
- package/commands/version.js +21 -0
- package/docs/README.md +119 -0
- package/docs/_config.yml +63 -0
- package/docs/commands/auth.md +376 -0
- package/docs/commands/config.md +294 -0
- package/docs/commands/create.md +276 -0
- package/docs/commands/info.md +460 -0
- package/docs/commands/install.md +320 -0
- package/docs/commands/launch.md +427 -0
- package/docs/commands/search.md +276 -0
- package/docs/commands/uninstall.md +128 -0
- package/docs/commands/upgrade.md +515 -0
- package/docs/commands.md +266 -0
- package/docs/configuration.md +410 -0
- package/docs/contributing.md +114 -0
- package/docs/index.md +82 -0
- package/docs/installation.md +162 -0
- package/helpers/auth/index.js +20 -0
- package/helpers/auth/microsoft.js +329 -0
- package/helpers/auth/storage.js +260 -0
- package/helpers/config.js +258 -0
- package/helpers/constants.js +38 -0
- package/helpers/getv.js +5 -0
- package/helpers/minecraft.js +308 -0
- package/helpers/modrinth.js +141 -0
- package/helpers/utils.js +334 -0
- package/helpers/versions.js +21 -0
- package/index.js +89 -0
- package/package.json +30 -0
|
@@ -0,0 +1,534 @@
|
|
|
1
|
+
import inquirer from 'inquirer';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import { exec } from 'child_process';
|
|
6
|
+
import { promisify } from 'util';
|
|
7
|
+
import version from '../helpers/getv.js';
|
|
8
|
+
import { writeGameSettings, loadDefaultGameSettings } from '../helpers/config.js';
|
|
9
|
+
import {
|
|
10
|
+
fetchJson,
|
|
11
|
+
downloadFile,
|
|
12
|
+
loadConfig,
|
|
13
|
+
saveConfig,
|
|
14
|
+
paginatedSelect
|
|
15
|
+
} from '../helpers/utils.js';
|
|
16
|
+
import { downloadMod } from '../helpers/modrinth.js';
|
|
17
|
+
import {
|
|
18
|
+
fetchMinecraftVersions,
|
|
19
|
+
getVersionManifest,
|
|
20
|
+
fetchFabricLoaderVersions,
|
|
21
|
+
fetchFabricGameVersions,
|
|
22
|
+
fetchFabricProfile,
|
|
23
|
+
fetchFabricInstallerVersions,
|
|
24
|
+
fetchForgeVersions,
|
|
25
|
+
fetchForgeGameVersions,
|
|
26
|
+
downloadLibraries,
|
|
27
|
+
downloadAssets
|
|
28
|
+
} from '../helpers/minecraft.js';
|
|
29
|
+
import { FORGE_MAVEN } from '../helpers/constants.js';
|
|
30
|
+
|
|
31
|
+
const execAsync = promisify(exec);
|
|
32
|
+
|
|
33
|
+
// ============================================
|
|
34
|
+
// Fabric Installation
|
|
35
|
+
// ============================================
|
|
36
|
+
|
|
37
|
+
async function installFabricClient(instancePath, mcVersion, loaderVersion) {
|
|
38
|
+
console.log(chalk.cyan('\nš„ Installing Fabric client...\n'));
|
|
39
|
+
|
|
40
|
+
const versionsPath = path.join(instancePath, 'versions');
|
|
41
|
+
const versionId = `fabric-loader-${loaderVersion}-${mcVersion}`;
|
|
42
|
+
const versionPath = path.join(versionsPath, versionId);
|
|
43
|
+
fs.mkdirSync(versionPath, { recursive: true });
|
|
44
|
+
|
|
45
|
+
// Get vanilla version data
|
|
46
|
+
console.log(chalk.gray(' Fetching Minecraft version data...'));
|
|
47
|
+
const vanillaData = await getVersionManifest(mcVersion);
|
|
48
|
+
|
|
49
|
+
// Download vanilla client jar
|
|
50
|
+
const vanillaVersionPath = path.join(versionsPath, mcVersion);
|
|
51
|
+
fs.mkdirSync(vanillaVersionPath, { recursive: true });
|
|
52
|
+
|
|
53
|
+
const clientJarPath = path.join(vanillaVersionPath, `${mcVersion}.jar`);
|
|
54
|
+
if (!fs.existsSync(clientJarPath)) {
|
|
55
|
+
console.log(chalk.gray(' Downloading Minecraft client...'));
|
|
56
|
+
await downloadFile(vanillaData.downloads.client.url, clientJarPath, 'Minecraft client');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Save vanilla version JSON
|
|
60
|
+
fs.writeFileSync(
|
|
61
|
+
path.join(vanillaVersionPath, `${mcVersion}.json`),
|
|
62
|
+
JSON.stringify(vanillaData, null, 2)
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
// Get Fabric profile
|
|
66
|
+
console.log(chalk.gray(' Fetching Fabric profile...'));
|
|
67
|
+
const fabricProfile = await fetchFabricProfile(mcVersion, loaderVersion);
|
|
68
|
+
|
|
69
|
+
// Save Fabric version JSON
|
|
70
|
+
fs.writeFileSync(
|
|
71
|
+
path.join(versionPath, `${versionId}.json`),
|
|
72
|
+
JSON.stringify(fabricProfile, null, 2)
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
// Download libraries
|
|
76
|
+
console.log(chalk.gray(' Downloading vanilla libraries...'));
|
|
77
|
+
await downloadLibraries(vanillaData, instancePath);
|
|
78
|
+
|
|
79
|
+
console.log(chalk.gray(' Downloading Fabric libraries...'));
|
|
80
|
+
await downloadLibraries(fabricProfile, instancePath);
|
|
81
|
+
|
|
82
|
+
// Download assets
|
|
83
|
+
console.log(chalk.gray(' Downloading assets (this may take a while)...'));
|
|
84
|
+
await downloadAssets(vanillaData, instancePath);
|
|
85
|
+
|
|
86
|
+
// Create launch script
|
|
87
|
+
createClientLaunchScript(instancePath, versionId);
|
|
88
|
+
|
|
89
|
+
return versionId;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function installFabricServer(instancePath, mcVersion, loaderVersion) {
|
|
93
|
+
console.log(chalk.cyan('\nš„ Installing Fabric server...\n'));
|
|
94
|
+
|
|
95
|
+
const installerVersions = await fetchFabricInstallerVersions();
|
|
96
|
+
const installerVersion = installerVersions[0].version;
|
|
97
|
+
|
|
98
|
+
const serverJarPath = path.join(instancePath, 'fabric-server-launch.jar');
|
|
99
|
+
const serverUrl = `https://meta.fabricmc.net/v2/versions/loader/${mcVersion}/${loaderVersion}/${installerVersion}/server/jar`;
|
|
100
|
+
|
|
101
|
+
console.log(chalk.gray(' Downloading Fabric server launcher...'));
|
|
102
|
+
await downloadFile(serverUrl, serverJarPath, 'Fabric server');
|
|
103
|
+
|
|
104
|
+
fs.writeFileSync(path.join(instancePath, 'eula.txt'), 'eula=true\n');
|
|
105
|
+
createServerStartScript(instancePath, 'fabric-server-launch.jar');
|
|
106
|
+
|
|
107
|
+
console.log(chalk.gray(' Server will download remaining files on first launch.'));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// ============================================
|
|
111
|
+
// Forge Installation
|
|
112
|
+
// ============================================
|
|
113
|
+
|
|
114
|
+
async function installForgeClient(instancePath, mcVersion, forgeVersion) {
|
|
115
|
+
console.log(chalk.cyan('\nš„ Installing Forge client...\n'));
|
|
116
|
+
|
|
117
|
+
const versionsPath = path.join(instancePath, 'versions');
|
|
118
|
+
fs.mkdirSync(versionsPath, { recursive: true });
|
|
119
|
+
|
|
120
|
+
console.log(chalk.gray(' Fetching Minecraft version data...'));
|
|
121
|
+
const vanillaData = await getVersionManifest(mcVersion);
|
|
122
|
+
|
|
123
|
+
const vanillaVersionPath = path.join(versionsPath, mcVersion);
|
|
124
|
+
fs.mkdirSync(vanillaVersionPath, { recursive: true });
|
|
125
|
+
|
|
126
|
+
const clientJarPath = path.join(vanillaVersionPath, `${mcVersion}.jar`);
|
|
127
|
+
if (!fs.existsSync(clientJarPath)) {
|
|
128
|
+
console.log(chalk.gray(' Downloading Minecraft client...'));
|
|
129
|
+
await downloadFile(vanillaData.downloads.client.url, clientJarPath, 'Minecraft client');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
fs.writeFileSync(
|
|
133
|
+
path.join(vanillaVersionPath, `${mcVersion}.json`),
|
|
134
|
+
JSON.stringify(vanillaData, null, 2)
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
console.log(chalk.gray(' Downloading vanilla libraries...'));
|
|
138
|
+
await downloadLibraries(vanillaData, instancePath);
|
|
139
|
+
|
|
140
|
+
console.log(chalk.gray(' Downloading assets (this may take a while)...'));
|
|
141
|
+
await downloadAssets(vanillaData, instancePath);
|
|
142
|
+
|
|
143
|
+
// Download Forge installer
|
|
144
|
+
const installerPath = path.join(instancePath, 'forge-installer.jar');
|
|
145
|
+
const forgeFullVersion = `${mcVersion}-${forgeVersion}`;
|
|
146
|
+
const forgeInstallerUrl = `${FORGE_MAVEN}/net/minecraftforge/forge/${forgeFullVersion}/forge-${forgeFullVersion}-installer.jar`;
|
|
147
|
+
|
|
148
|
+
console.log(chalk.gray(' Downloading Forge installer...'));
|
|
149
|
+
try {
|
|
150
|
+
await downloadFile(forgeInstallerUrl, installerPath, 'Forge installer');
|
|
151
|
+
} catch (err) {
|
|
152
|
+
console.log(chalk.yellow(` Warning: Could not download Forge installer.`));
|
|
153
|
+
console.log(chalk.gray(` URL: ${forgeInstallerUrl}`));
|
|
154
|
+
return `${mcVersion}-forge-${forgeVersion}`;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
console.log(chalk.gray(' Running Forge installer (this may take a while)...'));
|
|
158
|
+
try {
|
|
159
|
+
await execAsync(`java -jar "${installerPath}" --installClient "${instancePath}"`, {
|
|
160
|
+
cwd: instancePath,
|
|
161
|
+
timeout: 300000
|
|
162
|
+
});
|
|
163
|
+
console.log(chalk.green(' Forge installed successfully!'));
|
|
164
|
+
} catch (err) {
|
|
165
|
+
console.log(chalk.yellow(' Note: Forge installer requires Java. Run manually:'));
|
|
166
|
+
console.log(chalk.gray(` java -jar "${installerPath}" --installClient`));
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
createClientLaunchScript(instancePath, `${mcVersion}-forge-${forgeVersion}`);
|
|
170
|
+
|
|
171
|
+
return `${mcVersion}-forge-${forgeVersion}`;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async function installForgeServer(instancePath, mcVersion, forgeVersion) {
|
|
175
|
+
console.log(chalk.cyan('\nš„ Installing Forge server...\n'));
|
|
176
|
+
|
|
177
|
+
const forgeFullVersion = `${mcVersion}-${forgeVersion}`;
|
|
178
|
+
const installerPath = path.join(instancePath, 'forge-installer.jar');
|
|
179
|
+
const forgeInstallerUrl = `${FORGE_MAVEN}/net/minecraftforge/forge/${forgeFullVersion}/forge-${forgeFullVersion}-installer.jar`;
|
|
180
|
+
|
|
181
|
+
console.log(chalk.gray(' Downloading Forge installer...'));
|
|
182
|
+
try {
|
|
183
|
+
await downloadFile(forgeInstallerUrl, installerPath, 'Forge installer');
|
|
184
|
+
} catch (err) {
|
|
185
|
+
console.log(chalk.yellow(` Warning: Could not download Forge installer.`));
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
console.log(chalk.gray(' Running Forge installer (this may take a while)...'));
|
|
190
|
+
try {
|
|
191
|
+
await execAsync(`java -jar "${installerPath}" --installServer`, {
|
|
192
|
+
cwd: instancePath,
|
|
193
|
+
timeout: 300000
|
|
194
|
+
});
|
|
195
|
+
console.log(chalk.green(' Forge server installed successfully!'));
|
|
196
|
+
|
|
197
|
+
const files = fs.readdirSync(instancePath);
|
|
198
|
+
const serverJar = files.find(f => f.includes('forge') && f.endsWith('.jar') && !f.includes('installer'));
|
|
199
|
+
if (serverJar) {
|
|
200
|
+
createServerStartScript(instancePath, serverJar);
|
|
201
|
+
}
|
|
202
|
+
} catch (err) {
|
|
203
|
+
console.log(chalk.yellow(' Note: Forge installer requires Java. Run manually:'));
|
|
204
|
+
console.log(chalk.gray(` cd "${instancePath}" && java -jar forge-installer.jar --installServer`));
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
fs.writeFileSync(path.join(instancePath, 'eula.txt'), 'eula=true\n');
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// ============================================
|
|
211
|
+
// Launch Script Creation
|
|
212
|
+
// ============================================
|
|
213
|
+
|
|
214
|
+
function createClientLaunchScript(instancePath, versionId) {
|
|
215
|
+
const isWindows = process.platform === 'win32';
|
|
216
|
+
const scriptName = isWindows ? 'launch.bat' : 'launch.sh';
|
|
217
|
+
const scriptPath = path.join(instancePath, scriptName);
|
|
218
|
+
|
|
219
|
+
let script;
|
|
220
|
+
if (isWindows) {
|
|
221
|
+
script = `@echo off
|
|
222
|
+
cd /d "%~dp0"
|
|
223
|
+
echo Starting ${versionId}...
|
|
224
|
+
clicraft launch --instance "%~dp0"
|
|
225
|
+
if %ERRORLEVEL% neq 0 (
|
|
226
|
+
echo.
|
|
227
|
+
echo If clicraft is not installed globally, run:
|
|
228
|
+
echo npx clicraft launch --instance "%~dp0"
|
|
229
|
+
pause
|
|
230
|
+
)
|
|
231
|
+
`;
|
|
232
|
+
} else {
|
|
233
|
+
script = `#!/bin/bash
|
|
234
|
+
cd "$(dirname "$0")"
|
|
235
|
+
echo "Starting ${versionId}..."
|
|
236
|
+
clicraft launch --instance "$(pwd)" || {
|
|
237
|
+
echo ""
|
|
238
|
+
echo "If clicraft is not installed globally, run:"
|
|
239
|
+
echo " npx clicraft launch --instance \\"$(pwd)\\""
|
|
240
|
+
}
|
|
241
|
+
`;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
fs.writeFileSync(scriptPath, script);
|
|
245
|
+
if (!isWindows) {
|
|
246
|
+
fs.chmodSync(scriptPath, '755');
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function createServerStartScript(instancePath, serverJar) {
|
|
251
|
+
const isWindows = process.platform === 'win32';
|
|
252
|
+
const scriptName = isWindows ? 'start.bat' : 'start.sh';
|
|
253
|
+
const scriptPath = path.join(instancePath, scriptName);
|
|
254
|
+
|
|
255
|
+
let script;
|
|
256
|
+
if (isWindows) {
|
|
257
|
+
script = `@echo off
|
|
258
|
+
cd /d "%~dp0"
|
|
259
|
+
java -Xmx2G -Xms1G -jar ${serverJar} nogui
|
|
260
|
+
pause
|
|
261
|
+
`;
|
|
262
|
+
} else {
|
|
263
|
+
script = `#!/bin/bash
|
|
264
|
+
cd "$(dirname "$0")"
|
|
265
|
+
java -Xmx2G -Xms1G -jar ${serverJar} nogui
|
|
266
|
+
`;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
fs.writeFileSync(scriptPath, script);
|
|
270
|
+
if (!isWindows) {
|
|
271
|
+
fs.chmodSync(scriptPath, '755');
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// ============================================
|
|
276
|
+
// Instance Installation
|
|
277
|
+
// ============================================
|
|
278
|
+
|
|
279
|
+
async function installModLoader(instancePath, type, loader, mcVersion, loaderVersion) {
|
|
280
|
+
const isClient = type === 'client';
|
|
281
|
+
const isFabric = loader === 'fabric';
|
|
282
|
+
|
|
283
|
+
if (isFabric) {
|
|
284
|
+
if (isClient) {
|
|
285
|
+
return await installFabricClient(instancePath, mcVersion, loaderVersion);
|
|
286
|
+
} else {
|
|
287
|
+
await installFabricServer(instancePath, mcVersion, loaderVersion);
|
|
288
|
+
return `fabric-server-${mcVersion}`;
|
|
289
|
+
}
|
|
290
|
+
} else {
|
|
291
|
+
if (isClient) {
|
|
292
|
+
return await installForgeClient(instancePath, mcVersion, loaderVersion);
|
|
293
|
+
} else {
|
|
294
|
+
await installForgeServer(instancePath, mcVersion, loaderVersion);
|
|
295
|
+
return `forge-server-${mcVersion}`;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// ============================================
|
|
301
|
+
// Create from Config
|
|
302
|
+
// ============================================
|
|
303
|
+
|
|
304
|
+
async function createFromConfig(existingConfig, options) {
|
|
305
|
+
console.log(chalk.cyan(`\nš® Creating instance from mcconfig.json\n`));
|
|
306
|
+
console.log(chalk.gray(` Name: ${existingConfig.name}`));
|
|
307
|
+
console.log(chalk.gray(` Type: ${existingConfig.type}`));
|
|
308
|
+
console.log(chalk.gray(` Mod Loader: ${existingConfig.modLoader}`));
|
|
309
|
+
console.log(chalk.gray(` Minecraft: ${existingConfig.minecraftVersion}`));
|
|
310
|
+
console.log(chalk.gray(` Loader Version: ${existingConfig.loaderVersion}`));
|
|
311
|
+
if (existingConfig.mods?.length > 0) {
|
|
312
|
+
console.log(chalk.gray(` Mods: ${existingConfig.mods.length}`));
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const { confirm } = await inquirer.prompt([{
|
|
316
|
+
type: 'confirm',
|
|
317
|
+
name: 'confirm',
|
|
318
|
+
message: 'Create instance from this configuration?',
|
|
319
|
+
default: true
|
|
320
|
+
}]);
|
|
321
|
+
|
|
322
|
+
if (!confirm) {
|
|
323
|
+
console.log(chalk.yellow('\nInstance creation cancelled.'));
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const { instanceName } = await inquirer.prompt([{
|
|
328
|
+
type: 'input',
|
|
329
|
+
name: 'instanceName',
|
|
330
|
+
message: 'Instance Name:',
|
|
331
|
+
default: existingConfig.name,
|
|
332
|
+
validate: (input) => {
|
|
333
|
+
if (!input.trim()) return 'Instance name is required';
|
|
334
|
+
if (fs.existsSync(input.trim())) return 'A folder with this name already exists';
|
|
335
|
+
return true;
|
|
336
|
+
}
|
|
337
|
+
}]);
|
|
338
|
+
|
|
339
|
+
const instancePath = path.resolve(instanceName.trim());
|
|
340
|
+
fs.mkdirSync(instancePath, { recursive: true });
|
|
341
|
+
fs.mkdirSync(path.join(instancePath, 'mods'), { recursive: true });
|
|
342
|
+
|
|
343
|
+
const versionId = await installModLoader(
|
|
344
|
+
instancePath,
|
|
345
|
+
existingConfig.type,
|
|
346
|
+
existingConfig.modLoader,
|
|
347
|
+
existingConfig.minecraftVersion,
|
|
348
|
+
existingConfig.loaderVersion
|
|
349
|
+
);
|
|
350
|
+
|
|
351
|
+
const newConfig = {
|
|
352
|
+
configVersion: version,
|
|
353
|
+
name: instanceName.trim(),
|
|
354
|
+
type: existingConfig.type,
|
|
355
|
+
modLoader: existingConfig.modLoader,
|
|
356
|
+
minecraftVersion: existingConfig.minecraftVersion,
|
|
357
|
+
loaderVersion: existingConfig.loaderVersion,
|
|
358
|
+
versionId: versionId,
|
|
359
|
+
createdAt: new Date().toISOString(),
|
|
360
|
+
mods: []
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
// Install mods
|
|
364
|
+
if (existingConfig.mods?.length > 0) {
|
|
365
|
+
console.log(chalk.cyan('\nš¦ Installing mods from configuration...\n'));
|
|
366
|
+
const modsPath = path.join(instancePath, 'mods');
|
|
367
|
+
|
|
368
|
+
for (const mod of existingConfig.mods) {
|
|
369
|
+
console.log(chalk.gray(` Installing ${mod.name}...`));
|
|
370
|
+
try {
|
|
371
|
+
const installedMod = await downloadMod(
|
|
372
|
+
mod.projectId,
|
|
373
|
+
existingConfig.minecraftVersion,
|
|
374
|
+
existingConfig.modLoader,
|
|
375
|
+
modsPath
|
|
376
|
+
);
|
|
377
|
+
|
|
378
|
+
if (installedMod) {
|
|
379
|
+
newConfig.mods.push(installedMod);
|
|
380
|
+
console.log(chalk.green(` ā ${mod.name} installed`));
|
|
381
|
+
} else {
|
|
382
|
+
console.log(chalk.yellow(` ā Could not find compatible version for ${mod.name}`));
|
|
383
|
+
}
|
|
384
|
+
} catch (err) {
|
|
385
|
+
console.log(chalk.yellow(` ā Failed to install ${mod.name}: ${err.message}`));
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// Apply game settings
|
|
391
|
+
if (existingConfig.type === 'client' && existingConfig.gameSettings && Object.keys(existingConfig.gameSettings).length > 0) {
|
|
392
|
+
console.log(chalk.cyan('\nāļø Applying game settings...'));
|
|
393
|
+
writeGameSettings(instancePath, existingConfig.gameSettings);
|
|
394
|
+
newConfig.gameSettings = existingConfig.gameSettings;
|
|
395
|
+
console.log(chalk.gray(` Applied ${Object.keys(existingConfig.gameSettings).length} settings`));
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
saveConfig(instancePath, newConfig);
|
|
399
|
+
|
|
400
|
+
console.log(chalk.green(`\nā
Instance "${instanceName}" created successfully!`));
|
|
401
|
+
console.log(chalk.gray(` Location: ${instancePath}`));
|
|
402
|
+
if (newConfig.mods.length > 0) {
|
|
403
|
+
console.log(chalk.gray(` Mods installed: ${newConfig.mods.length}/${existingConfig.mods.length}`));
|
|
404
|
+
}
|
|
405
|
+
if (existingConfig.type === 'client') {
|
|
406
|
+
console.log(chalk.gray(` Use this folder as game directory in your Minecraft launcher`));
|
|
407
|
+
} else {
|
|
408
|
+
console.log(chalk.gray(` Start the server with: ./start.sh (or start.bat on Windows)`));
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// ============================================
|
|
413
|
+
// Main Create Command
|
|
414
|
+
// ============================================
|
|
415
|
+
|
|
416
|
+
export async function createInstance(options) {
|
|
417
|
+
// Check for existing config
|
|
418
|
+
const existingConfig = loadConfig(process.cwd());
|
|
419
|
+
if (existingConfig) {
|
|
420
|
+
console.log(chalk.cyan('\nš Found mcconfig.json in current directory'));
|
|
421
|
+
return await createFromConfig(existingConfig, options);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
console.log(chalk.cyan('\nš® Create a new Minecraft instance\n'));
|
|
425
|
+
|
|
426
|
+
try {
|
|
427
|
+
const { instanceName } = await inquirer.prompt([{
|
|
428
|
+
type: 'input',
|
|
429
|
+
name: 'instanceName',
|
|
430
|
+
message: 'Instance Name:',
|
|
431
|
+
validate: (input) => {
|
|
432
|
+
if (!input.trim()) return 'Instance name is required';
|
|
433
|
+
if (fs.existsSync(input.trim())) return 'A folder with this name already exists';
|
|
434
|
+
return true;
|
|
435
|
+
}
|
|
436
|
+
}]);
|
|
437
|
+
|
|
438
|
+
const { instanceType } = await inquirer.prompt([{
|
|
439
|
+
type: 'rawlist',
|
|
440
|
+
name: 'instanceType',
|
|
441
|
+
message: 'Client or Server:',
|
|
442
|
+
choices: ['Client', 'Server']
|
|
443
|
+
}]);
|
|
444
|
+
|
|
445
|
+
const { modLoader } = await inquirer.prompt([{
|
|
446
|
+
type: 'rawlist',
|
|
447
|
+
name: 'modLoader',
|
|
448
|
+
message: 'Mod Loader:',
|
|
449
|
+
choices: ['Fabric', 'Forge']
|
|
450
|
+
}]);
|
|
451
|
+
|
|
452
|
+
console.log(chalk.gray('Fetching available Minecraft versions...'));
|
|
453
|
+
const availableVersions = modLoader === 'Fabric'
|
|
454
|
+
? await fetchFabricGameVersions()
|
|
455
|
+
: await fetchForgeGameVersions();
|
|
456
|
+
|
|
457
|
+
const mcVersion = await paginatedSelect('Minecraft Version:', availableVersions);
|
|
458
|
+
|
|
459
|
+
console.log(chalk.gray(`Fetching available ${modLoader} versions...`));
|
|
460
|
+
let loaderVersion;
|
|
461
|
+
if (modLoader === 'Fabric') {
|
|
462
|
+
const fabricVersions = await fetchFabricLoaderVersions();
|
|
463
|
+
loaderVersion = await paginatedSelect('Fabric Loader Version:', fabricVersions);
|
|
464
|
+
} else {
|
|
465
|
+
const forgeVersions = await fetchForgeVersions(mcVersion);
|
|
466
|
+
if (forgeVersions.length === 0) {
|
|
467
|
+
console.log(chalk.yellow(`No Forge versions found for Minecraft ${mcVersion}`));
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
const forgeChoices = forgeVersions.map(v => ({
|
|
471
|
+
name: `${v.label} (${v.version})`,
|
|
472
|
+
value: v.version
|
|
473
|
+
}));
|
|
474
|
+
loaderVersion = await paginatedSelect('Forge Version:', forgeChoices);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
const instancePath = path.resolve(instanceName.trim());
|
|
478
|
+
fs.mkdirSync(instancePath, { recursive: true });
|
|
479
|
+
fs.mkdirSync(path.join(instancePath, 'mods'), { recursive: true });
|
|
480
|
+
|
|
481
|
+
const isClient = instanceType === 'Client';
|
|
482
|
+
const versionId = await installModLoader(
|
|
483
|
+
instancePath,
|
|
484
|
+
instanceType.toLowerCase(),
|
|
485
|
+
modLoader.toLowerCase(),
|
|
486
|
+
mcVersion,
|
|
487
|
+
loaderVersion
|
|
488
|
+
);
|
|
489
|
+
|
|
490
|
+
const config = {
|
|
491
|
+
configVersion: version,
|
|
492
|
+
name: instanceName.trim(),
|
|
493
|
+
type: instanceType.toLowerCase(),
|
|
494
|
+
modLoader: modLoader.toLowerCase(),
|
|
495
|
+
minecraftVersion: mcVersion,
|
|
496
|
+
loaderVersion: loaderVersion,
|
|
497
|
+
versionId: versionId,
|
|
498
|
+
createdAt: new Date().toISOString(),
|
|
499
|
+
mods: []
|
|
500
|
+
};
|
|
501
|
+
|
|
502
|
+
saveConfig(instancePath, config);
|
|
503
|
+
|
|
504
|
+
// Apply default game settings for clients
|
|
505
|
+
if (isClient) {
|
|
506
|
+
const defaultGameSettings = loadDefaultGameSettings();
|
|
507
|
+
if (defaultGameSettings && Object.keys(defaultGameSettings).length > 0) {
|
|
508
|
+
console.log(chalk.cyan('\nāļø Applying default game settings...'));
|
|
509
|
+
writeGameSettings(instancePath, defaultGameSettings);
|
|
510
|
+
console.log(chalk.gray(` Applied ${Object.keys(defaultGameSettings).length} settings`));
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
console.log(chalk.green(`\nā
Instance "${instanceName}" created successfully!`));
|
|
515
|
+
console.log(chalk.gray(` Location: ${instancePath}`));
|
|
516
|
+
if (isClient) {
|
|
517
|
+
console.log(chalk.gray(` Use this folder as game directory in your Minecraft launcher`));
|
|
518
|
+
} else {
|
|
519
|
+
console.log(chalk.gray(` Start the server with: ./start.sh (or start.bat on Windows)`));
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
} catch (error) {
|
|
523
|
+
if (error.name === 'ExitPromptError') {
|
|
524
|
+
console.log(chalk.yellow('\nInstance creation cancelled.'));
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
console.error(chalk.red('Error creating instance:'), error.message);
|
|
528
|
+
if (options?.verbose) {
|
|
529
|
+
console.error(error);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
export default { createInstance };
|
package/commands/info.js
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import {
|
|
5
|
+
loadConfig,
|
|
6
|
+
getInstancePath,
|
|
7
|
+
requireConfig,
|
|
8
|
+
formatBytes,
|
|
9
|
+
formatDate,
|
|
10
|
+
getDirSize
|
|
11
|
+
} from '../helpers/utils.js';
|
|
12
|
+
|
|
13
|
+
export async function instanceInfo(options) {
|
|
14
|
+
const instancePath = getInstancePath(options);
|
|
15
|
+
|
|
16
|
+
const config = requireConfig(instancePath);
|
|
17
|
+
if (!config) return;
|
|
18
|
+
|
|
19
|
+
// Mods-only mode
|
|
20
|
+
if (options.mods) {
|
|
21
|
+
const installedMods = config.mods || [];
|
|
22
|
+
if (installedMods.length > 0) {
|
|
23
|
+
console.log(chalk.cyan('\nš¦ Installed Mods\n'));
|
|
24
|
+
for (const mod of installedMods) {
|
|
25
|
+
console.log(chalk.gray(` - ${mod.name} (${mod.fileName}) [${mod.versionNumber}]`));
|
|
26
|
+
}
|
|
27
|
+
} else {
|
|
28
|
+
console.log(chalk.yellow('\nNo mods are currently installed in this instance.\n'));
|
|
29
|
+
}
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
console.log(chalk.cyan('\nš¦ Instance Information\n'));
|
|
34
|
+
|
|
35
|
+
// Basic info
|
|
36
|
+
console.log(chalk.white(' Name: ') + chalk.yellow(config.name));
|
|
37
|
+
console.log(chalk.white(' Type: ') + chalk.gray(config.type === 'client' ? 'š® Client' : 'š„ļø Server'));
|
|
38
|
+
console.log(chalk.white(' Mod Loader: ') + chalk.gray(config.modLoader.charAt(0).toUpperCase() + config.modLoader.slice(1)));
|
|
39
|
+
console.log(chalk.white(' Minecraft: ') + chalk.green(config.minecraftVersion));
|
|
40
|
+
console.log(chalk.white(' Loader Version: ') + chalk.gray(config.loaderVersion));
|
|
41
|
+
console.log(chalk.white(' Version ID: ') + chalk.gray(config.versionId));
|
|
42
|
+
console.log(chalk.white(' Created: ') + chalk.gray(formatDate(config.createdAt)));
|
|
43
|
+
|
|
44
|
+
if (config.configVersion) {
|
|
45
|
+
console.log(chalk.white(' Config Version: ') + chalk.gray(`v${config.configVersion}`));
|
|
46
|
+
} else {
|
|
47
|
+
console.log(chalk.white(' Config Version: ') + chalk.yellow('legacy (no version)'));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
console.log(chalk.white(' Location: ') + chalk.gray(instancePath));
|
|
51
|
+
|
|
52
|
+
// Mods info
|
|
53
|
+
const modsPath = path.join(instancePath, 'mods');
|
|
54
|
+
const installedMods = config.mods || [];
|
|
55
|
+
const modFiles = fs.existsSync(modsPath) ?
|
|
56
|
+
fs.readdirSync(modsPath).filter(f => f.endsWith('.jar')) : [];
|
|
57
|
+
|
|
58
|
+
console.log();
|
|
59
|
+
console.log(chalk.cyan('š Mods'));
|
|
60
|
+
console.log(chalk.white(' Tracked Mods: ') + chalk.yellow(installedMods.length));
|
|
61
|
+
console.log(chalk.white(' Mod Files: ') + chalk.gray(modFiles.length + ' .jar files'));
|
|
62
|
+
|
|
63
|
+
if (installedMods.length > 0) {
|
|
64
|
+
console.log(chalk.gray('\n Installed mods:'));
|
|
65
|
+
for (const mod of installedMods) {
|
|
66
|
+
console.log(chalk.gray(` - ${mod.name} (${mod.fileName}) [${mod.versionNumber}]`));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Storage info
|
|
71
|
+
console.log();
|
|
72
|
+
console.log(chalk.cyan('š¾ Storage'));
|
|
73
|
+
|
|
74
|
+
const directories = [
|
|
75
|
+
{ name: 'Libraries', path: 'libraries' },
|
|
76
|
+
{ name: 'Assets', path: 'assets' },
|
|
77
|
+
{ name: 'Mods', path: 'mods' },
|
|
78
|
+
{ name: 'Versions', path: 'versions' },
|
|
79
|
+
{ name: 'Saves', path: 'saves' }
|
|
80
|
+
];
|
|
81
|
+
|
|
82
|
+
let totalSize = 0;
|
|
83
|
+
for (const dir of directories) {
|
|
84
|
+
const dirPath = path.join(instancePath, dir.path);
|
|
85
|
+
const size = getDirSize(dirPath);
|
|
86
|
+
totalSize += size;
|
|
87
|
+
if (size > 0) {
|
|
88
|
+
console.log(chalk.white(` ${dir.name}:`.padEnd(16)) + chalk.gray(formatBytes(size)));
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
console.log(chalk.white(' Total:'.padEnd(16)) + chalk.yellow(formatBytes(totalSize)));
|
|
92
|
+
|
|
93
|
+
// World saves
|
|
94
|
+
const savesPath = path.join(instancePath, 'saves');
|
|
95
|
+
if (fs.existsSync(savesPath)) {
|
|
96
|
+
const saves = fs.readdirSync(savesPath, { withFileTypes: true })
|
|
97
|
+
.filter(d => d.isDirectory());
|
|
98
|
+
if (saves.length > 0) {
|
|
99
|
+
console.log();
|
|
100
|
+
console.log(chalk.cyan('š Worlds'));
|
|
101
|
+
console.log(chalk.white(' Save Count: ') + chalk.yellow(saves.length));
|
|
102
|
+
if (options.verbose) {
|
|
103
|
+
for (const save of saves) {
|
|
104
|
+
console.log(chalk.gray(` - ${save.name}`));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
console.log();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export default { instanceInfo };
|