@dimzxzzx07/mc-headless 1.8.0 → 1.9.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.
Files changed (37) hide show
  1. package/README.md +317 -703
  2. package/dist/core/JavaChecker.d.ts +16 -3
  3. package/dist/core/JavaChecker.d.ts.map +1 -1
  4. package/dist/core/JavaChecker.js +171 -65
  5. package/dist/core/JavaChecker.js.map +1 -1
  6. package/dist/core/MinecraftServer.d.ts +15 -0
  7. package/dist/core/MinecraftServer.d.ts.map +1 -1
  8. package/dist/core/MinecraftServer.js +200 -110
  9. package/dist/core/MinecraftServer.js.map +1 -1
  10. package/dist/index.d.ts +3 -0
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +31 -16
  13. package/dist/index.js.map +1 -1
  14. package/dist/platforms/BedrockServer.d.ts.map +1 -1
  15. package/dist/platforms/BedrockServer.js +2 -0
  16. package/dist/platforms/BedrockServer.js.map +1 -1
  17. package/dist/platforms/JavaServer.d.ts.map +1 -1
  18. package/dist/platforms/JavaServer.js +2 -0
  19. package/dist/platforms/JavaServer.js.map +1 -1
  20. package/dist/platforms/SkinRestorer.d.ts +14 -0
  21. package/dist/platforms/SkinRestorer.d.ts.map +1 -0
  22. package/dist/platforms/SkinRestorer.js +145 -0
  23. package/dist/platforms/SkinRestorer.js.map +1 -0
  24. package/dist/platforms/SkinsRestorer.d.ts +14 -0
  25. package/dist/platforms/SkinsRestorer.d.ts.map +1 -0
  26. package/dist/platforms/SkinsRestorer.js +145 -0
  27. package/dist/platforms/SkinsRestorer.js.map +1 -0
  28. package/dist/types/index.d.ts +2 -0
  29. package/dist/types/index.d.ts.map +1 -1
  30. package/package.json +1 -1
  31. package/src/core/JavaChecker.ts +162 -61
  32. package/src/core/MinecraftServer.ts +233 -120
  33. package/src/index.ts +33 -17
  34. package/src/platforms/BedrockServer.ts +2 -0
  35. package/src/platforms/JavaServer.ts +2 -0
  36. package/src/platforms/SkinRestorer.ts +127 -0
  37. package/src/types/index.ts +2 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dimzxzzx07/mc-headless",
3
- "version": "1.8.0",
3
+ "version": "1.9.0",
4
4
  "description": "Minecraft Headless Server Manager - Run Java/Bedrock/Crossplay servers easily via Node.js",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -1,25 +1,49 @@
1
1
  import { execSync, exec } from 'child_process';
2
+ import * as fs from 'fs-extra';
3
+ import * as path from 'path';
4
+ import * as https from 'https';
5
+ import * as tar from 'tar';
2
6
  import { Logger } from '../utils/Logger';
3
- import { SystemDetector } from '../utils/SystemDetector';
7
+
8
+ export interface JavaInfo {
9
+ path: string;
10
+ version: string;
11
+ type: 'system' | 'portable';
12
+ }
4
13
 
5
14
  export class JavaChecker {
6
15
  private static logger = Logger.getInstance();
16
+ private static readonly JAVA_DIR = path.join('/tmp', '.mc-headless-java');
17
+ private static readonly JAVA_17_URL = 'https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.10%2B7/OpenJDK17U-jre_x64_linux_hotspot_17.0.10_7.tar.gz';
18
+ private static readonly JAVA_21_URL = 'https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.2%2B13/OpenJDK21U-jre_x64_linux_hotspot_21.0.2_13.tar.gz';
19
+ private static readonly JAVA_11_URL = 'https://github.com/adoptium/temurin11-binaries/releases/download/jdk-11.0.22%2B7/OpenJDK11U-jre_x64_linux_hotspot_11.0.22_7.tar.gz';
20
+ private static readonly JAVA_8_URL = 'https://github.com/adoptium/temurin8-binaries/releases/download/jdk8u402-b06/OpenJDK8U-jre_x64_linux_hotspot_8u402b06.tar.gz';
7
21
 
8
- public static checkJava(): Promise<boolean> {
22
+ public static async checkJava(): Promise<boolean> {
9
23
  return new Promise((resolve) => {
10
- exec('java -version', (error) => {
11
- if (error) {
12
- this.logger.warning('Java is not installed');
24
+ exec('which java', (error, stdout) => {
25
+ if (error || !stdout.trim()) {
26
+ this.logger.warning('Java is not installed in system');
13
27
  resolve(false);
14
28
  } else {
15
- this.logger.success('Java is installed');
29
+ const javaPath = stdout.trim();
30
+ this.logger.success(`System Java found at: ${javaPath}`);
16
31
  resolve(true);
17
32
  }
18
33
  });
19
34
  });
20
35
  }
21
36
 
22
- public static getJavaVersion(): string | null {
37
+ public static getSystemJavaPath(): string | null {
38
+ try {
39
+ const stdout = execSync('which java').toString().trim();
40
+ return stdout || null;
41
+ } catch {
42
+ return null;
43
+ }
44
+ }
45
+
46
+ public static getSystemJavaVersion(): string | null {
23
47
  try {
24
48
  const output = execSync('java -version 2>&1').toString();
25
49
  if (output.includes('version "21')) return '21';
@@ -33,74 +57,151 @@ export class JavaChecker {
33
57
  }
34
58
  }
35
59
 
36
- public static async ensureJava(version: '17' | '21' | 'auto' = 'auto'): Promise<void> {
37
- const hasJava = await this.checkJava();
60
+ public static async getOrDownloadPortableJava(version: '17' | '21' | '11' | '8' = '17'): Promise<JavaInfo> {
61
+ await fs.ensureDir(this.JAVA_DIR);
62
+
63
+ const javaHome = path.join(this.JAVA_DIR, `jre-${version}`);
64
+ const javaBin = path.join(javaHome, 'bin', 'java');
38
65
 
39
- if (!hasJava) {
40
- this.logger.warning('Java not found. Attempting to install...');
41
- await this.installJava(version);
42
- return;
66
+ if (await fs.pathExists(javaBin)) {
67
+ this.logger.info(`Portable Java ${version} already exists at ${javaBin}`);
68
+
69
+ const versionOutput = execSync(`"${javaBin}" -version 2>&1`).toString();
70
+ this.logger.debug(`Portable Java version: ${versionOutput.split('\n')[0]}`);
71
+
72
+ return {
73
+ path: javaBin,
74
+ version: version,
75
+ type: 'portable'
76
+ };
43
77
  }
44
78
 
45
- const currentVersion = this.getJavaVersion();
46
- this.logger.info(`Detected Java version: ${currentVersion}`);
47
-
48
- if (version !== 'auto' && currentVersion !== version) {
49
- this.logger.warning(`This server recommends Java ${version} but found ${currentVersion}. This may cause issues.`);
79
+ this.logger.info(`Downloading portable Java ${version} JRE...`);
80
+
81
+ let downloadUrl = '';
82
+ switch (version) {
83
+ case '21':
84
+ downloadUrl = this.JAVA_21_URL;
85
+ break;
86
+ case '17':
87
+ downloadUrl = this.JAVA_17_URL;
88
+ break;
89
+ case '11':
90
+ downloadUrl = this.JAVA_11_URL;
91
+ break;
92
+ case '8':
93
+ downloadUrl = this.JAVA_8_URL;
94
+ break;
95
+ default:
96
+ downloadUrl = this.JAVA_17_URL;
50
97
  }
51
98
 
52
- if (currentVersion === '8' || currentVersion === '11') {
53
- this.logger.warning(`Java ${currentVersion} is outdated. Minecraft 1.21+ works best with Java 17 or 21.`);
54
- }
99
+ const tarPath = path.join(this.JAVA_DIR, `jre-${version}.tar.gz`);
100
+
101
+ await this.downloadFile(downloadUrl, tarPath);
102
+
103
+ this.logger.info(`Extracting Java ${version}...`);
104
+ await fs.ensureDir(javaHome);
105
+ await tar.extract({
106
+ file: tarPath,
107
+ cwd: javaHome,
108
+ strip: 1
109
+ });
110
+
111
+ await fs.remove(tarPath);
112
+
113
+ await fs.chmod(javaBin, 0o755);
114
+
115
+ this.logger.success(`Portable Java ${version} installed at ${javaBin}`);
116
+
117
+ const versionOutput = execSync(`"${javaBin}" -version 2>&1`).toString();
118
+ this.logger.debug(`Portable Java version: ${versionOutput.split('\n')[0]}`);
119
+
120
+ return {
121
+ path: javaBin,
122
+ version: version,
123
+ type: 'portable'
124
+ };
55
125
  }
56
126
 
57
- public static async installJava(version: '17' | '21' | 'auto' = 'auto'): Promise<void> {
58
- const osType = SystemDetector.getOS();
59
- const distro = SystemDetector.getDistro();
60
- const targetVersion = version === 'auto' ? '17' : version;
127
+ private static downloadFile(url: string, destPath: string): Promise<void> {
128
+ return new Promise((resolve, reject) => {
129
+ this.logger.info(`Downloading from ${url}`);
130
+
131
+ const file = fs.createWriteStream(destPath);
132
+ https.get(url, (response) => {
133
+ if (response.statusCode !== 200) {
134
+ reject(new Error(`Download failed: ${response.statusCode}`));
135
+ return;
136
+ }
61
137
 
62
- let command = '';
63
- const javaPackage = targetVersion === '21' ? 'openjdk-21-jre-headless' : 'openjdk-17-jre-headless';
138
+ const totalSize = parseInt(response.headers['content-length'] || '0');
139
+ let downloadedSize = 0;
140
+
141
+ response.on('data', (chunk) => {
142
+ downloadedSize += chunk.length;
143
+ if (totalSize > 0) {
144
+ const percent = ((downloadedSize / totalSize) * 100).toFixed(1);
145
+ process.stdout.write(`\rDownloading Java: ${percent}% (${(downloadedSize / 1024 / 1024).toFixed(1)} MB / ${(totalSize / 1024 / 1024).toFixed(1)} MB)`);
146
+ }
147
+ });
148
+
149
+ response.pipe(file);
150
+
151
+ file.on('finish', () => {
152
+ process.stdout.write('\n');
153
+ file.close();
154
+ resolve();
155
+ });
156
+
157
+ file.on('error', (err) => {
158
+ fs.unlink(destPath).catch(() => {});
159
+ reject(err);
160
+ });
161
+ }).on('error', (err) => {
162
+ fs.unlink(destPath).catch(() => {});
163
+ reject(err);
164
+ });
165
+ });
166
+ }
167
+
168
+ public static async ensureJava(version: '17' | '21' | 'auto' = 'auto', usePortable: boolean = true): Promise<JavaInfo> {
169
+ const targetVersion = version === 'auto' ? '17' : version;
64
170
 
65
- if (osType === 'linux' || osType === 'android') {
66
- if (distro === 'ubuntu' || distro === 'debian') {
67
- command = `apt update && apt install -y ${javaPackage}`;
68
- } else if (distro === 'centos' || distro === 'fedora') {
69
- command = `yum install -y java-${targetVersion}-openjdk-headless`;
70
- } else if (distro === 'arch') {
71
- const archPackage = targetVersion === '21' ? 'jre21-openjdk-headless' : 'jre17-openjdk-headless';
72
- command = `pacman -S --noconfirm ${archPackage}`;
73
- } else if (distro === 'termux') {
74
- command = 'pkg install -y openjdk-17';
75
- } else {
76
- command = `curl -s https://raw.githubusercontent.com/Dimzxzzx07/mc-headless/main/scripts/install-java.sh | bash -s ${targetVersion}`;
171
+ if (!usePortable) {
172
+ const systemJava = this.getSystemJavaPath();
173
+ if (systemJava) {
174
+ const systemVersion = this.getSystemJavaVersion();
175
+ if (systemVersion === targetVersion || (targetVersion === '17' && systemVersion === '21')) {
176
+ this.logger.success(`Using system Java ${systemVersion} at ${systemJava}`);
177
+ return {
178
+ path: systemJava,
179
+ version: systemVersion || targetVersion,
180
+ type: 'system'
181
+ };
182
+ }
77
183
  }
78
- } else if (osType === 'darwin') {
79
- command = `brew install openjdk@${targetVersion}`;
80
- } else {
81
- this.logger.error('Automatic Java installation not supported on this OS');
82
- throw new Error('Java not installed');
83
184
  }
185
+
186
+ return this.getOrDownloadPortableJava(targetVersion as any);
187
+ }
84
188
 
85
- this.logger.info(`Installing Java ${targetVersion} with: ${command}`);
189
+ public static cleanupOldJava(): void {
190
+ try {
191
+ const files = fs.readdirSync(this.JAVA_DIR);
192
+ const now = Date.now();
193
+ const oneDay = 24 * 60 * 60 * 1000;
86
194
 
87
- return new Promise((resolve, reject) => {
88
- const child = exec(command, (error) => {
89
- if (error) {
90
- this.logger.error('Failed to install Java');
91
- reject(error);
92
- } else {
93
- this.logger.success(`Java ${targetVersion} installed successfully`);
94
- resolve();
195
+ files.forEach(file => {
196
+ const filePath = path.join(this.JAVA_DIR, file);
197
+ const stats = fs.statSync(filePath);
198
+ if (now - stats.mtimeMs > oneDay) {
199
+ fs.removeSync(filePath);
200
+ this.logger.debug(`Cleaned up old Java: ${file}`);
95
201
  }
96
202
  });
97
-
98
- if (child.stdout) {
99
- child.stdout.pipe(process.stdout);
100
- }
101
- if (child.stderr) {
102
- child.stderr.pipe(process.stderr);
103
- }
104
- });
203
+ } catch (error) {
204
+ this.logger.debug('No old Java to cleanup');
205
+ }
105
206
  }
106
207
  }