@powernukkitx/cli 0.0.3 → 1.0.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/README.md +203 -177
- package/dist/bundle.js +3 -18856
- package/dist/cli.js +46 -0
- package/dist/commands/backup.d.ts +2 -1
- package/dist/commands/backup.js +21 -71
- package/dist/commands/cfg.d.ts +2 -0
- package/dist/commands/cfg.js +61 -0
- package/dist/commands/config.js +52 -105
- package/dist/commands/console.d.ts +2 -0
- package/dist/commands/console.js +99 -0
- package/dist/commands/doctor.d.ts +2 -1
- package/dist/commands/doctor.js +87 -108
- package/dist/commands/info.d.ts +2 -1
- package/dist/commands/info.js +39 -33
- package/dist/commands/init.d.ts +2 -4
- package/dist/commands/init.js +58 -65
- package/dist/commands/lang.d.ts +2 -0
- package/dist/commands/lang.js +28 -0
- package/dist/commands/logs.d.ts +2 -0
- package/dist/commands/logs.js +61 -0
- package/dist/commands/menu.js +86 -0
- package/dist/commands/plugin/index.d.ts +2 -0
- package/dist/commands/plugin/index.js +13 -0
- package/dist/commands/plugin/info.d.ts +2 -0
- package/dist/commands/plugin/info.js +40 -0
- package/dist/commands/plugin/install.d.ts +2 -0
- package/dist/commands/plugin/install.js +57 -0
- package/dist/commands/plugin/installed.d.ts +2 -0
- package/dist/commands/plugin/installed.js +42 -0
- package/dist/commands/plugin/list.d.ts +2 -0
- package/dist/commands/plugin/list.js +49 -0
- package/dist/commands/plugin/remove.d.ts +2 -0
- package/dist/commands/plugin/remove.js +45 -0
- package/dist/commands/plugin/search.d.ts +2 -0
- package/dist/commands/plugin/search.js +12 -0
- package/dist/commands/plugin/update.d.ts +2 -0
- package/dist/commands/plugin/update.js +53 -0
- package/dist/commands/plugin.d.ts +1 -12
- package/dist/commands/plugin.js +1 -385
- package/dist/commands/start.d.ts +2 -7
- package/dist/commands/start.js +61 -94
- package/dist/commands/stop.d.ts +2 -0
- package/dist/commands/stop.js +27 -0
- package/dist/commands/update.d.ts +2 -5
- package/dist/commands/update.js +30 -50
- package/dist/commands/use.d.ts +2 -0
- package/dist/commands/use.js +40 -0
- package/dist/core/config.d.ts +13 -0
- package/dist/core/config.js +31 -0
- package/dist/core/network.d.ts +13 -0
- package/dist/core/network.js +139 -0
- package/dist/core/output.d.ts +33 -0
- package/dist/core/output.js +75 -0
- package/dist/core/process.d.ts +10 -0
- package/dist/core/process.js +53 -0
- package/dist/i18n/en.json +174 -0
- package/dist/i18n/fr.json +174 -0
- package/dist/i18n/index.js +58 -0
- package/dist/index.d.ts +0 -1
- package/dist/index.js +1 -260
- package/dist/infra/http.js +121 -0
- package/dist/infra/paths.js +20 -0
- package/dist/infra/store.js +27 -0
- package/dist/lang/en.json +116 -148
- package/dist/lang/fr.json +115 -147
- package/dist/main.d.ts +2 -0
- package/dist/main.js +28 -0
- package/dist/services/backup.js +40 -0
- package/dist/services/plugins.js +111 -0
- package/dist/services/process.js +43 -0
- package/dist/services/release.js +38 -0
- package/dist/services/server.js +89 -0
- package/dist/ui/output.js +71 -0
- package/dist/ui/prompts.js +30 -0
- package/dist/ui/theme.js +18 -0
- package/dist/utils/api.d.ts +3 -12
- package/dist/utils/api.js +7 -57
- package/dist/utils/github.d.ts +3 -10
- package/dist/utils/github.js +27 -154
- package/dist/utils/server.d.ts +2 -2
- package/dist/utils/server.js +17 -24
- package/package.json +15 -32
package/dist/cli.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { defineCommand, runMain } from 'citty';
|
|
3
|
+
import { initLang } from './i18n/index.js';
|
|
4
|
+
import { setColorEnabled } from './ui/theme.js';
|
|
5
|
+
// Flags globaux traités avant le routing citty.
|
|
6
|
+
const argv = process.argv.slice(2);
|
|
7
|
+
const langFlag = (() => {
|
|
8
|
+
const i = argv.findIndex(a => a === '--lang');
|
|
9
|
+
if (i >= 0 && argv[i + 1])
|
|
10
|
+
return argv[i + 1];
|
|
11
|
+
const eq = argv.find(a => a.startsWith('--lang='));
|
|
12
|
+
return eq ? eq.split('=')[1] : undefined;
|
|
13
|
+
})();
|
|
14
|
+
if (argv.includes('--no-color'))
|
|
15
|
+
setColorEnabled(false);
|
|
16
|
+
initLang(langFlag);
|
|
17
|
+
const main = defineCommand({
|
|
18
|
+
meta: {
|
|
19
|
+
name: 'pnx',
|
|
20
|
+
version: '1.0.0',
|
|
21
|
+
description: 'PowerNukkitX CLI — Manage your Minecraft Bedrock servers',
|
|
22
|
+
},
|
|
23
|
+
subCommands: {
|
|
24
|
+
init: () => import('./commands/init.js').then(m => m.initCmd),
|
|
25
|
+
start: () => import('./commands/start.js').then(m => m.startCmd),
|
|
26
|
+
stop: () => import('./commands/stop.js').then(m => m.stopCmd),
|
|
27
|
+
update: () => import('./commands/update.js').then(m => m.updateCmd),
|
|
28
|
+
info: () => import('./commands/info.js').then(m => m.infoCmd),
|
|
29
|
+
doctor: () => import('./commands/doctor.js').then(m => m.doctorCmd),
|
|
30
|
+
backup: () => import('./commands/backup.js').then(m => m.backupCmd),
|
|
31
|
+
config: () => import('./commands/config.js').then(m => m.configCmd),
|
|
32
|
+
use: () => import('./commands/use.js').then(m => m.useCmd),
|
|
33
|
+
lang: () => import('./commands/lang.js').then(m => m.langCmd),
|
|
34
|
+
logs: () => import('./commands/logs.js').then(m => m.logsCmd),
|
|
35
|
+
console: () => import('./commands/console.js').then(m => m.consoleCmd),
|
|
36
|
+
plugin: () => import('./commands/plugin/index.js').then(m => m.pluginCmd),
|
|
37
|
+
},
|
|
38
|
+
async run({ args }) {
|
|
39
|
+
// Aucune sous-commande → menu interactif.
|
|
40
|
+
if (args._.length === 0) {
|
|
41
|
+
const { runMenu } = await import('./commands/menu.js');
|
|
42
|
+
await runMenu();
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
runMain(main);
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
import type { CommandDef } from 'citty';
|
|
2
|
+
export declare const backupCmd: CommandDef;
|
package/dist/commands/backup.js
CHANGED
|
@@ -1,74 +1,24 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
import { pauseForExit } from '../utils/pause.js';
|
|
10
|
-
const IGNORE = new Set(['logs', 'cache', 'crash-reports', 'backups', 'powernukkitx.jar.old']);
|
|
11
|
-
function getDirSize(dir) {
|
|
12
|
-
let total = 0;
|
|
13
|
-
try {
|
|
14
|
-
const items = readdirSync(dir);
|
|
15
|
-
for (const item of items) {
|
|
16
|
-
const full = join(dir, item);
|
|
17
|
-
const s = statSync(full);
|
|
18
|
-
if (s.isDirectory())
|
|
19
|
-
total += getDirSize(full);
|
|
20
|
-
else
|
|
21
|
-
total += s.size;
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
catch { /* ignore */ }
|
|
25
|
-
return total;
|
|
26
|
-
}
|
|
27
|
-
function formatSize(bytes) {
|
|
28
|
-
if (bytes < 1024)
|
|
29
|
-
return `${bytes} B`;
|
|
30
|
-
if (bytes < 1024 * 1024)
|
|
31
|
-
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
32
|
-
return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
|
|
33
|
-
}
|
|
34
|
-
export async function backupCmd() {
|
|
35
|
-
const serverPath = await resolveServerDir(t('backup.selectServer'));
|
|
1
|
+
import { defineCommand } from 'citty';
|
|
2
|
+
import { intro, outro, log, spin } from '../ui/output.js';
|
|
3
|
+
import { resolveServerDir } from '../services/server.js';
|
|
4
|
+
import { createBackup, dirSize, fmtSize } from '../services/backup.js';
|
|
5
|
+
import { t } from '../i18n/index.js';
|
|
6
|
+
export async function runBackup() {
|
|
7
|
+
intro(`💾 ${t('backup.title')}`);
|
|
8
|
+
const serverPath = await resolveServerDir();
|
|
36
9
|
if (!serverPath) {
|
|
37
|
-
error(t('backup.noServer'));
|
|
38
|
-
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
if (serverPath === cwd()) {
|
|
42
|
-
const ok = await confirm({
|
|
43
|
-
message: t('backup.backupCurrent', highlight(serverPath)),
|
|
44
|
-
default: true,
|
|
45
|
-
});
|
|
46
|
-
if (!ok) {
|
|
47
|
-
info(t('backup.cancelled'));
|
|
48
|
-
await pauseForExit();
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
header(`💾 ${t('backup.title')}`);
|
|
53
|
-
step(t('backup.collecting'));
|
|
54
|
-
const size = getDirSize(serverPath);
|
|
55
|
-
info(t('backup.estimatedSize', formatSize(size)));
|
|
56
|
-
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
|
|
57
|
-
const backupDir = join(serverPath, 'backups');
|
|
58
|
-
const destDir = join(backupDir, `pnx-backup-${timestamp}`);
|
|
59
|
-
await mkdir(destDir, { recursive: true });
|
|
60
|
-
step(t('backup.copying'));
|
|
61
|
-
const items = await readdir(serverPath);
|
|
62
|
-
let count = 0;
|
|
63
|
-
for (const item of items) {
|
|
64
|
-
if (IGNORE.has(item) || item.startsWith('.'))
|
|
65
|
-
continue;
|
|
66
|
-
const src = join(serverPath, item);
|
|
67
|
-
const dst = join(destDir, item);
|
|
68
|
-
await cp(src, dst, { recursive: true, force: true });
|
|
69
|
-
count++;
|
|
10
|
+
log.error(t('backup.noServer'));
|
|
11
|
+
process.exit(1);
|
|
70
12
|
}
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
13
|
+
const sp = spin(t('backup.collecting'));
|
|
14
|
+
sp.stop(t('backup.size', fmtSize(dirSize(serverPath))));
|
|
15
|
+
const sp2 = spin(t('backup.copying'));
|
|
16
|
+
const { path, size } = await createBackup(serverPath);
|
|
17
|
+
sp2.stop(t('backup.done', path, fmtSize(size)));
|
|
18
|
+
outro(t('backup.done', path, fmtSize(size)));
|
|
74
19
|
}
|
|
20
|
+
export const backupCmd = defineCommand({
|
|
21
|
+
meta: { description: 'Create a server backup' },
|
|
22
|
+
args: {},
|
|
23
|
+
run: () => runBackup(),
|
|
24
|
+
});
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import { intro, outro, log } from '../core/output.js';
|
|
5
|
+
import { resolveServerDir } from '../utils/server.js';
|
|
6
|
+
import { t } from '../lang/index.js';
|
|
7
|
+
function colorVal(val) {
|
|
8
|
+
if (val === 'true' || val === 'on')
|
|
9
|
+
return chalk.green(val);
|
|
10
|
+
if (val === 'false' || val === 'off')
|
|
11
|
+
return chalk.red(val);
|
|
12
|
+
if (/^\d+(\.\d+)?$/.test(val))
|
|
13
|
+
return chalk.cyan(val);
|
|
14
|
+
return chalk.white(val);
|
|
15
|
+
}
|
|
16
|
+
export const configCmd = {
|
|
17
|
+
meta: { description: 'View server configuration' },
|
|
18
|
+
args: {},
|
|
19
|
+
async run() {
|
|
20
|
+
intro(`⚙️ ${t('config.title')}`);
|
|
21
|
+
const serverPath = await resolveServerDir();
|
|
22
|
+
if (!serverPath) {
|
|
23
|
+
log.error(t('config.noServer'));
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
let cfgPath = join(serverPath, 'pnx.yml');
|
|
27
|
+
if (!existsSync(cfgPath))
|
|
28
|
+
cfgPath = join(serverPath, 'server.properties');
|
|
29
|
+
if (!existsSync(cfgPath)) {
|
|
30
|
+
log.error(t('config.notFound'));
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
let content = readFileSync(cfgPath, 'utf-8');
|
|
34
|
+
if (content.charCodeAt(0) === 0xFEFF)
|
|
35
|
+
content = content.slice(1);
|
|
36
|
+
for (const line of content.split('\n')) {
|
|
37
|
+
const trimmed = line.trim();
|
|
38
|
+
if (!trimmed) {
|
|
39
|
+
console.log();
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
if (trimmed.startsWith('#')) {
|
|
43
|
+
console.log(` ${chalk.dim(line)}`);
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
const sep = cfgPath.endsWith('.yml') ? ':' : '=';
|
|
47
|
+
const idx = trimmed.indexOf(sep);
|
|
48
|
+
if (idx > 0) {
|
|
49
|
+
const key = trimmed.slice(0, idx);
|
|
50
|
+
const val = trimmed.slice(idx + 1).trim();
|
|
51
|
+
const indent = chalk.dim(line.match(/^(\s*)/)?.[1] ?? '');
|
|
52
|
+
console.log(` ${indent}${chalk.hex('#6C8EBF')(key)}${sep}${val ? ' ' + colorVal(val) : ''}`);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
console.log(` ${chalk.dim(trimmed)}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
log.info(t('config.editHint', cfgPath));
|
|
59
|
+
outro('');
|
|
60
|
+
},
|
|
61
|
+
};
|
package/dist/commands/config.js
CHANGED
|
@@ -1,114 +1,61 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { resolveServerDir } from '../
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
return;
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
3
|
+
import { defineCommand } from 'citty';
|
|
4
|
+
import { intro, outro, log } from '../ui/output.js';
|
|
5
|
+
import { c } from '../ui/theme.js';
|
|
6
|
+
import { resolveServerDir } from '../services/server.js';
|
|
7
|
+
import { t } from '../i18n/index.js';
|
|
8
|
+
function colorVal(val) {
|
|
9
|
+
if (val === 'true' || val === 'on')
|
|
10
|
+
return c.green(val);
|
|
11
|
+
if (val === 'false' || val === 'off')
|
|
12
|
+
return c.red(val);
|
|
13
|
+
if (/^\d+(\.\d+)?$/.test(val))
|
|
14
|
+
return c.cyan(val);
|
|
15
|
+
return val;
|
|
16
|
+
}
|
|
17
|
+
export const configCmd = defineCommand({
|
|
18
|
+
meta: { description: 'View server configuration' },
|
|
19
|
+
args: {},
|
|
20
|
+
async run() {
|
|
21
|
+
intro(`⚙️ ${t('config.title')}`);
|
|
22
|
+
const serverPath = await resolveServerDir();
|
|
23
|
+
if (!serverPath) {
|
|
24
|
+
log.error(t('config.noServer'));
|
|
25
|
+
process.exit(1);
|
|
25
26
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
if (line.trim() === '' || line.trim().startsWith('#')) {
|
|
39
|
-
if (line.trim().startsWith('#')) {
|
|
40
|
-
console.log(` ${dim(line)}`);
|
|
41
|
-
}
|
|
42
|
-
else {
|
|
43
|
-
console.log();
|
|
44
|
-
}
|
|
45
|
-
continue;
|
|
46
|
-
}
|
|
47
|
-
// Key: value or nested key
|
|
48
|
-
const indent = line.match(/^(\s*)/)?.[1] || '';
|
|
27
|
+
let cfgPath = join(serverPath, 'pnx.yml');
|
|
28
|
+
if (!existsSync(cfgPath))
|
|
29
|
+
cfgPath = join(serverPath, 'server.properties');
|
|
30
|
+
if (!existsSync(cfgPath)) {
|
|
31
|
+
log.error(t('config.notFound'));
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
let content = readFileSync(cfgPath, 'utf-8');
|
|
35
|
+
if (content.charCodeAt(0) === 0xfeff)
|
|
36
|
+
content = content.slice(1);
|
|
37
|
+
const sep = cfgPath.endsWith('.yml') ? ':' : '=';
|
|
38
|
+
for (const line of content.split('\n')) {
|
|
49
39
|
const trimmed = line.trim();
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const rest = trimmed.slice(colonIdx + 1).trim();
|
|
54
|
-
const suffix = rest
|
|
55
|
-
? ` ${colorYamlValue(rest)}`
|
|
56
|
-
: '';
|
|
57
|
-
const prefix = indent ? chalk.dim(indent) : '';
|
|
58
|
-
console.log(` ${prefix}${chalk.hex('#6C8EBF')(key)}:${suffix}`);
|
|
59
|
-
}
|
|
60
|
-
else {
|
|
61
|
-
console.log(` ${chalk.dim(indent)}${chalk.white(trimmed)}`);
|
|
40
|
+
if (!trimmed) {
|
|
41
|
+
log.raw('');
|
|
42
|
+
continue;
|
|
62
43
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
else {
|
|
66
|
-
// Display server.properties with highlighting
|
|
67
|
-
let inSection = false;
|
|
68
|
-
for (let i = 0; i < lines.length; i++) {
|
|
69
|
-
const line = lines[i];
|
|
70
|
-
if (line.startsWith('#') || line.trim() === '') {
|
|
71
|
-
const trimmed = line.replace(/^#\s*/, '').trim();
|
|
72
|
-
if (trimmed && !inSection) {
|
|
73
|
-
console.log(`\n ${dim(trimmed)}`);
|
|
74
|
-
inSection = true;
|
|
75
|
-
}
|
|
44
|
+
if (trimmed.startsWith('#')) {
|
|
45
|
+
log.raw(` ${c.dim(line)}`);
|
|
76
46
|
continue;
|
|
77
47
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
const
|
|
82
|
-
const
|
|
83
|
-
|
|
48
|
+
const idx = trimmed.indexOf(sep);
|
|
49
|
+
if (idx > 0) {
|
|
50
|
+
const key = trimmed.slice(0, idx);
|
|
51
|
+
const val = trimmed.slice(idx + 1).trim();
|
|
52
|
+
const indent = line.match(/^(\s*)/)?.[1] ?? '';
|
|
53
|
+
log.raw(` ${indent}${c.blue(key)}${sep}${val ? ' ' + colorVal(val) : ''}`);
|
|
84
54
|
}
|
|
55
|
+
else
|
|
56
|
+
log.raw(` ${c.dim(trimmed)}`);
|
|
85
57
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}
|
|
91
|
-
function colorYamlValue(val) {
|
|
92
|
-
if (val === 'true')
|
|
93
|
-
return chalk.green(val);
|
|
94
|
-
if (val === 'false')
|
|
95
|
-
return chalk.red(val);
|
|
96
|
-
if (/^\d+(\.\d+)?$/.test(val))
|
|
97
|
-
return chalk.cyan(val);
|
|
98
|
-
if (val.startsWith('[') || val.startsWith('-'))
|
|
99
|
-
return chalk.hex('#E040FB')(val);
|
|
100
|
-
if (val.startsWith('"') && val.endsWith('"'))
|
|
101
|
-
return chalk.green(val);
|
|
102
|
-
if (val.startsWith("'") && val.endsWith("'"))
|
|
103
|
-
return chalk.green(val);
|
|
104
|
-
return chalk.white(val);
|
|
105
|
-
}
|
|
106
|
-
function colorPropsValue(val) {
|
|
107
|
-
if (val === 'true' || val === 'on')
|
|
108
|
-
return chalk.green(val);
|
|
109
|
-
if (val === 'false' || val === 'off')
|
|
110
|
-
return chalk.red(val);
|
|
111
|
-
if (/^\d+$/.test(val))
|
|
112
|
-
return chalk.cyan(val);
|
|
113
|
-
return chalk.white(val);
|
|
114
|
-
}
|
|
58
|
+
log.info(t('config.editHint', cfgPath));
|
|
59
|
+
outro('');
|
|
60
|
+
},
|
|
61
|
+
});
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { createInterface } from 'node:readline';
|
|
4
|
+
import { createConnection } from 'node:net';
|
|
5
|
+
import { defineCommand } from 'citty';
|
|
6
|
+
import { intro, outro, log } from '../ui/output.js';
|
|
7
|
+
import { resolveServerDir } from '../services/server.js';
|
|
8
|
+
import { t } from '../i18n/index.js';
|
|
9
|
+
function parseServerProperties(serverPath) {
|
|
10
|
+
const propsPath = join(serverPath, 'server.properties');
|
|
11
|
+
if (!existsSync(propsPath))
|
|
12
|
+
return {};
|
|
13
|
+
const props = {};
|
|
14
|
+
for (const line of readFileSync(propsPath, 'utf-8').split('\n')) {
|
|
15
|
+
if (line.startsWith('#') || !line.includes('='))
|
|
16
|
+
continue;
|
|
17
|
+
const [k, ...v] = line.split('=');
|
|
18
|
+
props[k.trim()] = v.join('=').trim();
|
|
19
|
+
}
|
|
20
|
+
return props;
|
|
21
|
+
}
|
|
22
|
+
function rconPacket(id, type, body) {
|
|
23
|
+
const bodyBuf = Buffer.from(body + '\0\0', 'utf-8');
|
|
24
|
+
const packet = Buffer.alloc(12 + bodyBuf.length);
|
|
25
|
+
packet.writeInt32LE(8 + bodyBuf.length, 0);
|
|
26
|
+
packet.writeInt32LE(id, 4);
|
|
27
|
+
packet.writeInt32LE(type, 8);
|
|
28
|
+
bodyBuf.copy(packet, 12);
|
|
29
|
+
return packet;
|
|
30
|
+
}
|
|
31
|
+
function connectRcon(host, port, password) {
|
|
32
|
+
return new Promise(resolve => {
|
|
33
|
+
const socket = createConnection({ host, port }, () => socket.write(rconPacket(1, 3, password)));
|
|
34
|
+
socket.once('data', data => {
|
|
35
|
+
if (data.readInt32LE(4) === -1) {
|
|
36
|
+
socket.destroy();
|
|
37
|
+
resolve(null);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
resolve({
|
|
41
|
+
send: (cmd) => {
|
|
42
|
+
socket.write(rconPacket(2, 2, cmd));
|
|
43
|
+
socket.once('data', d => {
|
|
44
|
+
const resp = d.slice(12, d.length - 2).toString('utf-8');
|
|
45
|
+
if (resp)
|
|
46
|
+
process.stdout.write(resp + '\n');
|
|
47
|
+
});
|
|
48
|
+
},
|
|
49
|
+
close: () => socket.destroy(),
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
socket.on('error', () => resolve(null));
|
|
53
|
+
setTimeout(() => resolve(null), 3000);
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
export const consoleCmd = defineCommand({
|
|
57
|
+
meta: { description: 'Attach to server console via RCON' },
|
|
58
|
+
args: {},
|
|
59
|
+
async run() {
|
|
60
|
+
intro(`💻 ${t('console.title')}`);
|
|
61
|
+
const serverPath = await resolveServerDir();
|
|
62
|
+
if (!serverPath) {
|
|
63
|
+
log.error(t('console.noServer'));
|
|
64
|
+
process.exit(1);
|
|
65
|
+
}
|
|
66
|
+
const props = parseServerProperties(serverPath);
|
|
67
|
+
const enabled = props['enable-rcon'] === 'true';
|
|
68
|
+
const port = parseInt(props['rcon.port'] ?? '25575');
|
|
69
|
+
const password = props['rcon.password'] ?? '';
|
|
70
|
+
if (enabled && password) {
|
|
71
|
+
log.step(t('console.connecting'));
|
|
72
|
+
const rcon = await connectRcon('127.0.0.1', port, password);
|
|
73
|
+
if (rcon) {
|
|
74
|
+
log.success(t('console.connected'));
|
|
75
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout, prompt: t('console.prompt') });
|
|
76
|
+
rl.prompt();
|
|
77
|
+
rl.on('line', line => {
|
|
78
|
+
const cmd = line.trim();
|
|
79
|
+
if (cmd === 'exit') {
|
|
80
|
+
rcon.close();
|
|
81
|
+
rl.close();
|
|
82
|
+
outro(t('console.disconnected'));
|
|
83
|
+
process.exit(0);
|
|
84
|
+
}
|
|
85
|
+
rcon.send(cmd);
|
|
86
|
+
rl.prompt();
|
|
87
|
+
});
|
|
88
|
+
rl.on('close', () => { rcon.close(); process.exit(0); });
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
log.warn(t('console.rconDisabled'));
|
|
93
|
+
log.info(t('console.rconEnable'));
|
|
94
|
+
log.dim('enable-rcon=true');
|
|
95
|
+
log.dim('rcon.port=25575');
|
|
96
|
+
log.dim('rcon.password=your_password');
|
|
97
|
+
process.exit(1);
|
|
98
|
+
},
|
|
99
|
+
});
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
import type { CommandDef } from 'citty';
|
|
2
|
+
export declare const doctorCmd: CommandDef;
|