@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/commands/doctor.js
CHANGED
|
@@ -1,119 +1,98 @@
|
|
|
1
1
|
import { existsSync, statSync, readdirSync } from 'node:fs';
|
|
2
|
+
import { statfs } from 'node:fs/promises';
|
|
2
3
|
import { join } from 'node:path';
|
|
3
|
-
import {
|
|
4
|
+
import { spawnSync } from 'node:child_process';
|
|
4
5
|
import { createSocket } from 'node:dgram';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
6
|
+
import { freemem, totalmem } from 'node:os';
|
|
7
|
+
import { defineCommand } from 'citty';
|
|
8
|
+
import { intro, outro, log, section, spin, isJson, json } from '../ui/output.js';
|
|
9
|
+
import { resolveServerDir, checkJava } from '../services/server.js';
|
|
10
|
+
import { JAR_NAME } from '../infra/paths.js';
|
|
11
|
+
import { t } from '../i18n/index.js';
|
|
10
12
|
function checkPort(port) {
|
|
11
|
-
return new Promise(
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
return new Promise(res => {
|
|
14
|
+
const s = createSocket('udp4');
|
|
15
|
+
s.once('error', () => { s.close(); res(false); });
|
|
16
|
+
s.bind(port, '0.0.0.0', () => { s.close(); res(true); });
|
|
15
17
|
});
|
|
16
18
|
}
|
|
17
|
-
function
|
|
18
|
-
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
: 'java';
|
|
22
|
-
const child = spawn(java, ['-version']);
|
|
23
|
-
let output = '';
|
|
24
|
-
child.on('error', () => resolve({ ok: false, version: '?', path: java }));
|
|
25
|
-
child.stderr.on('data', (data) => { output += data.toString(); });
|
|
26
|
-
child.on('close', () => {
|
|
27
|
-
const match = output.match(/(?:openjdk|java) version "?(?:1\.)?(\d+)/);
|
|
28
|
-
if (match) {
|
|
29
|
-
const ver = parseInt(match[1], 10);
|
|
30
|
-
resolve({ ok: ver >= 21, version: output.split('\n')[0]?.trim() || String(ver), path: java });
|
|
31
|
-
}
|
|
32
|
-
else {
|
|
33
|
-
resolve({ ok: false, version: '?', path: java });
|
|
34
|
-
}
|
|
35
|
-
});
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
export async function doctorCmd() {
|
|
39
|
-
header(`🩺 ${t('doctor.title')}`);
|
|
40
|
-
// Java check
|
|
41
|
-
step(t('doctor.checkingJava'));
|
|
42
|
-
const java = await checkJavaVersion();
|
|
43
|
-
if (java.ok) {
|
|
44
|
-
success(t('doctor.javaOk', java.version));
|
|
45
|
-
}
|
|
46
|
-
else {
|
|
47
|
-
warn(`Java ${highlight('21+')} required — ${dim(java.path)}`);
|
|
48
|
-
}
|
|
49
|
-
// Server detection
|
|
50
|
-
step(t('doctor.checkingServer'));
|
|
51
|
-
const serverPath = await resolveServerDir();
|
|
52
|
-
if (!serverPath) {
|
|
53
|
-
error(t('doctor.serverNotFound'));
|
|
54
|
-
await pauseForExit();
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
success(`${t('doctor.serverFound')} ${dim(serverPath)}`);
|
|
58
|
-
// JAR check
|
|
59
|
-
step(t('doctor.checkingJar'));
|
|
60
|
-
const jarPath = join(serverPath, 'powernukkitx.jar');
|
|
61
|
-
if (existsSync(jarPath)) {
|
|
62
|
-
const size = statSync(jarPath).size;
|
|
63
|
-
const sizeMb = (size / 1024 / 1024).toFixed(1);
|
|
64
|
-
success(t('doctor.jarOk', sizeMb));
|
|
65
|
-
}
|
|
66
|
-
else {
|
|
67
|
-
error(t('doctor.jarMissing'));
|
|
68
|
-
}
|
|
69
|
-
// pnx.yml check
|
|
70
|
-
step(t('doctor.checkingConfig'));
|
|
71
|
-
const ymlPath = join(serverPath, 'pnx.yml');
|
|
72
|
-
const propsPath = join(serverPath, 'server.properties');
|
|
73
|
-
if (existsSync(ymlPath)) {
|
|
74
|
-
success(t('doctor.configFound'));
|
|
75
|
-
}
|
|
76
|
-
else if (existsSync(propsPath)) {
|
|
77
|
-
info(t('doctor.configLegacy'));
|
|
19
|
+
function pnxVersion(jarPath) {
|
|
20
|
+
try {
|
|
21
|
+
const r = spawnSync('java', ['-jar', jarPath, '--version'], { timeout: 3000, encoding: 'utf8' });
|
|
22
|
+
return ((r.stdout || '') + (r.stderr || '')).match(/(\d+\.\d+\.\d+[\w.-]*)/)?.[1] ?? null;
|
|
78
23
|
}
|
|
79
|
-
|
|
80
|
-
|
|
24
|
+
catch {
|
|
25
|
+
return null;
|
|
81
26
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
success(t('doctor.pluginsOk', String(files.length)));
|
|
88
|
-
// Check for updates
|
|
89
|
-
const tracked = await getInstalledPlugins();
|
|
90
|
-
if (tracked.length > 0) {
|
|
91
|
-
info(t('doctor.pluginCheckHint', 'pnx plugin update'));
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
else {
|
|
95
|
-
info(t('doctor.pluginsMissing'));
|
|
96
|
-
}
|
|
97
|
-
// worlds dir
|
|
98
|
-
step(t('doctor.checkingWorlds'));
|
|
99
|
-
const worldsDir = join(serverPath, 'worlds');
|
|
100
|
-
if (existsSync(worldsDir)) {
|
|
101
|
-
const worlds = readdirSync(worldsDir).filter(f => statSync(join(worldsDir, f)).isDirectory());
|
|
102
|
-
info(t('doctor.worldsFound', String(worlds.length)));
|
|
103
|
-
}
|
|
104
|
-
else {
|
|
105
|
-
info(t('doctor.worldsMissing'));
|
|
106
|
-
}
|
|
107
|
-
// Port check
|
|
108
|
-
step(t('doctor.checkingPort'));
|
|
109
|
-
const portFree = await checkPort(19132);
|
|
110
|
-
if (portFree) {
|
|
111
|
-
success(t('doctor.portFree'));
|
|
27
|
+
}
|
|
28
|
+
async function diskFreeMb(dir) {
|
|
29
|
+
try {
|
|
30
|
+
const s = await statfs(dir);
|
|
31
|
+
return Math.round((s.bfree * s.bsize) / 1024 / 1024);
|
|
112
32
|
}
|
|
113
|
-
|
|
114
|
-
|
|
33
|
+
catch {
|
|
34
|
+
return null;
|
|
115
35
|
}
|
|
116
|
-
divider();
|
|
117
|
-
success(t('doctor.done'));
|
|
118
|
-
await pauseForExit();
|
|
119
36
|
}
|
|
37
|
+
export const doctorCmd = defineCommand({
|
|
38
|
+
meta: { description: 'Diagnose server health' },
|
|
39
|
+
args: {},
|
|
40
|
+
async run() {
|
|
41
|
+
const sp = spin(t('doctor.checking'));
|
|
42
|
+
const java = await checkJava();
|
|
43
|
+
const serverPath = await resolveServerDir();
|
|
44
|
+
const jarPath = serverPath ? join(serverPath, JAR_NAME) : null;
|
|
45
|
+
const jarExists = jarPath ? existsSync(jarPath) : false;
|
|
46
|
+
const version = jarPath && jarExists ? pnxVersion(jarPath) : null;
|
|
47
|
+
const pluginsDir = serverPath ? join(serverPath, 'plugins') : null;
|
|
48
|
+
const worldsDir = serverPath ? join(serverPath, 'worlds') : null;
|
|
49
|
+
const pluginCount = pluginsDir && existsSync(pluginsDir)
|
|
50
|
+
? readdirSync(pluginsDir).filter(f => f.endsWith('.jar')).length : 0;
|
|
51
|
+
const worldCount = worldsDir && existsSync(worldsDir)
|
|
52
|
+
? readdirSync(worldsDir).filter(f => statSync(join(worldsDir, f)).isDirectory()).length : 0;
|
|
53
|
+
const freeMb = Math.round(freemem() / 1024 / 1024);
|
|
54
|
+
const totalMb = Math.round(totalmem() / 1024 / 1024);
|
|
55
|
+
const disk = serverPath ? await diskFreeMb(serverPath) : null;
|
|
56
|
+
const portFree = await checkPort(19132);
|
|
57
|
+
sp.stop(t('doctor.done'));
|
|
58
|
+
if (isJson())
|
|
59
|
+
json({
|
|
60
|
+
java: { ok: java.ok, version: java.raw, vendor: java.vendor },
|
|
61
|
+
server: { found: !!serverPath, path: serverPath, pnxVersion: version },
|
|
62
|
+
ram: { freeMb, totalMb },
|
|
63
|
+
disk: disk !== null ? { freeMb: disk } : null,
|
|
64
|
+
port: { free: portFree, port: 19132 },
|
|
65
|
+
plugins: { count: pluginCount },
|
|
66
|
+
worlds: { count: worldCount },
|
|
67
|
+
});
|
|
68
|
+
intro(`🩺 ${t('doctor.title')}`);
|
|
69
|
+
section(t('doctor.java'));
|
|
70
|
+
if (java.ok)
|
|
71
|
+
log.success(`${java.raw} (${java.vendor})`);
|
|
72
|
+
else {
|
|
73
|
+
log.error(t('doctor.javaMissing'));
|
|
74
|
+
log.info('https://adoptium.net/');
|
|
75
|
+
}
|
|
76
|
+
section(t('doctor.server'));
|
|
77
|
+
if (serverPath) {
|
|
78
|
+
log.success(serverPath);
|
|
79
|
+
if (jarExists)
|
|
80
|
+
log.success(`${JAR_NAME}${version ? ` v${version}` : ''}`);
|
|
81
|
+
else
|
|
82
|
+
log.error(t('doctor.jarMissing'));
|
|
83
|
+
}
|
|
84
|
+
else
|
|
85
|
+
log.error(t('doctor.noServer'));
|
|
86
|
+
section(t('doctor.plugins'));
|
|
87
|
+
log.info(`Plugins: ${pluginCount} | Worlds: ${worldCount}`);
|
|
88
|
+
section(t('doctor.system'));
|
|
89
|
+
log.info(`RAM: ${freeMb} Mo / ${totalMb} Mo`);
|
|
90
|
+
if (disk !== null)
|
|
91
|
+
log.info(`Disk: ${disk} Mo`);
|
|
92
|
+
if (portFree)
|
|
93
|
+
log.success(t('doctor.portFree', '19132'));
|
|
94
|
+
else
|
|
95
|
+
log.warn(t('doctor.portBusy', '19132'));
|
|
96
|
+
outro(t('doctor.done'));
|
|
97
|
+
},
|
|
98
|
+
});
|
package/dist/commands/info.d.ts
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
import type { CommandDef } from 'citty';
|
|
2
|
+
export declare const infoCmd: CommandDef;
|
package/dist/commands/info.js
CHANGED
|
@@ -1,36 +1,42 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
2
|
import { readdir } from 'node:fs/promises';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
4
|
+
import { freemem, totalmem } from 'node:os';
|
|
5
|
+
import { defineCommand } from 'citty';
|
|
6
|
+
import { intro, outro, log, section, isJson, json } from '../ui/output.js';
|
|
7
|
+
import { c } from '../ui/theme.js';
|
|
8
|
+
import { resolveServerDir } from '../services/server.js';
|
|
9
|
+
import { t } from '../i18n/index.js';
|
|
10
|
+
export const infoCmd = defineCommand({
|
|
11
|
+
meta: { description: 'Show server information' },
|
|
12
|
+
args: {},
|
|
13
|
+
async run() {
|
|
14
|
+
const serverPath = await resolveServerDir();
|
|
15
|
+
const installed = serverPath !== null;
|
|
16
|
+
let plugins = [], worlds = [];
|
|
17
|
+
if (serverPath && existsSync(join(serverPath, 'plugins'))) {
|
|
18
|
+
plugins = (await readdir(join(serverPath, 'plugins'))).filter(f => f.endsWith('.jar'));
|
|
19
|
+
}
|
|
20
|
+
if (serverPath && existsSync(join(serverPath, 'worlds'))) {
|
|
21
|
+
worlds = await readdir(join(serverPath, 'worlds'));
|
|
22
|
+
}
|
|
23
|
+
const freeMb = Math.round(freemem() / 1024 / 1024);
|
|
24
|
+
const totalMb = Math.round(totalmem() / 1024 / 1024);
|
|
25
|
+
if (isJson())
|
|
26
|
+
json({
|
|
27
|
+
status: installed ? 'installed' : 'not_installed',
|
|
28
|
+
serverDir: serverPath, plugins: plugins.length, worlds: worlds.length,
|
|
29
|
+
ram: { freeMb, totalMb },
|
|
30
|
+
});
|
|
31
|
+
intro(`📊 ${t('info.title')}`);
|
|
32
|
+
section(t('info.server'));
|
|
33
|
+
log.info(`${t('info.status').padEnd(12)} ${installed ? c.green('✔ ' + t('info.installed')) : c.red('✘ ' + t('info.notInstalled'))}`);
|
|
34
|
+
if (serverPath)
|
|
35
|
+
log.info(`${t('info.path').padEnd(12)} ${c.accent(serverPath)}`);
|
|
36
|
+
log.info(`${t('info.plugins').padEnd(12)} ${plugins.length}`);
|
|
37
|
+
log.info(`${t('info.worlds').padEnd(12)} ${worlds.length}`);
|
|
38
|
+
section(t('info.system'));
|
|
39
|
+
log.info(`${t('info.ram').padEnd(12)} ${freeMb} Mo / ${totalMb} Mo`);
|
|
40
|
+
outro(installed ? t('info.ready') : t('info.notReady'));
|
|
41
|
+
},
|
|
42
|
+
});
|
package/dist/commands/init.d.ts
CHANGED
package/dist/commands/init.js
CHANGED
|
@@ -2,72 +2,65 @@ import { existsSync } from 'node:fs';
|
|
|
2
2
|
import { mkdir } from 'node:fs/promises';
|
|
3
3
|
import { join, resolve } from 'node:path';
|
|
4
4
|
import { cwd } from 'node:process';
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
if (!asset) {
|
|
39
|
-
error('powernukkitx.jar not found in latest release.');
|
|
5
|
+
import { defineCommand } from 'citty';
|
|
6
|
+
import { intro, outro, log, spin, progress } from '../ui/output.js';
|
|
7
|
+
import { promptText, promptConfirm } from '../ui/prompts.js';
|
|
8
|
+
import { getLatestRelease, downloadCore } from '../services/release.js';
|
|
9
|
+
import { startScripts } from '../services/server.js';
|
|
10
|
+
import { downloadFile } from '../infra/http.js';
|
|
11
|
+
import { JAR_NAME } from '../infra/paths.js';
|
|
12
|
+
import { t } from '../i18n/index.js';
|
|
13
|
+
export const initCmd = defineCommand({
|
|
14
|
+
meta: { description: 'Create a new PowerNukkitX server' },
|
|
15
|
+
args: {
|
|
16
|
+
dir: { type: 'string', description: 'Target directory' },
|
|
17
|
+
dev: { type: 'boolean', description: 'Install dev version', default: false },
|
|
18
|
+
},
|
|
19
|
+
async run({ args }) {
|
|
20
|
+
intro(`⚡ ${t('init.title')}`);
|
|
21
|
+
let target = args.dir;
|
|
22
|
+
if (!target) {
|
|
23
|
+
target = await promptText({
|
|
24
|
+
message: t('init.promptDir'),
|
|
25
|
+
defaultValue: 'pnx-server',
|
|
26
|
+
validate: v => {
|
|
27
|
+
if (!v || !v.trim())
|
|
28
|
+
return 'Required';
|
|
29
|
+
if (existsSync(join(resolve(cwd(), v), JAR_NAME)))
|
|
30
|
+
return t('init.alreadyExists');
|
|
31
|
+
return undefined;
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
const serverDir = resolve(cwd(), target);
|
|
36
|
+
if (existsSync(join(serverDir, JAR_NAME)) && args.dir) {
|
|
37
|
+
log.error(t('init.alreadyExists'));
|
|
40
38
|
process.exit(1);
|
|
41
39
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
40
|
+
try {
|
|
41
|
+
const sp = spin(t('init.fetchingInfo'));
|
|
42
|
+
const release = await getLatestRelease();
|
|
43
|
+
sp.stop(`${t('init.latestVersion')}: ${release.tag}`);
|
|
44
|
+
await mkdir(serverDir, { recursive: true });
|
|
45
|
+
log.step(t('init.downloading'));
|
|
46
|
+
const { size } = await downloadCore(release, join(serverDir, JAR_NAME), pct => progress(pct, JAR_NAME));
|
|
47
|
+
log.success(t('init.downloaded', (size / 1024 / 1024).toFixed(1)));
|
|
48
|
+
const sp2 = spin(t('init.downloadingScripts'));
|
|
49
|
+
for (const s of startScripts())
|
|
50
|
+
await downloadFile(s.url, join(serverDir, s.name));
|
|
51
|
+
sp2.stop(t('init.scriptsDownloaded'));
|
|
52
|
+
log.success(t('init.success'));
|
|
53
|
+
if (await promptConfirm(t('init.launchPrompt'), false)) {
|
|
54
|
+
const { runStart } = await import('./start.js');
|
|
55
|
+
await runStart({ dir: serverDir });
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
outro(t('init.success'));
|
|
59
|
+
}
|
|
51
60
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
console.log(` ${dim(' 2.')} ${chalk.hex('#FFB347')('pnx start')} ${dim(`# ${t('init.nextStepStart')}`)}`);
|
|
56
|
-
console.log(` ${dim(' 3.')} ${chalk.hex('#FFB347')('pnx plugin list')} ${dim(`# ${t('init.nextStepPlugins')}`)}`);
|
|
57
|
-
console.log(` ${dim(' 4.')} ${chalk.hex('#FFB347')('pnx plugin install <name>')} ${dim(`# ${t('init.nextStepInstall')}`)}`);
|
|
58
|
-
console.log();
|
|
59
|
-
success(t('init.success'));
|
|
60
|
-
const launchNow = await confirm({
|
|
61
|
-
message: t('init.launchPrompt'),
|
|
62
|
-
default: false,
|
|
63
|
-
});
|
|
64
|
-
if (launchNow) {
|
|
65
|
-
console.log();
|
|
66
|
-
await startCmd({});
|
|
61
|
+
catch (e) {
|
|
62
|
+
log.error(e.message);
|
|
63
|
+
process.exit(1);
|
|
67
64
|
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
error(err.message);
|
|
71
|
-
process.exit(1);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
65
|
+
},
|
|
66
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { defineCommand } from 'citty';
|
|
2
|
+
import { intro, outro, log } from '../ui/output.js';
|
|
3
|
+
import { promptSelect } from '../ui/prompts.js';
|
|
4
|
+
import { setLang, t } from '../i18n/index.js';
|
|
5
|
+
import { readJson, writeJson } from '../infra/store.js';
|
|
6
|
+
async function persistLang(code) {
|
|
7
|
+
const cfg = await readJson('config.json', {});
|
|
8
|
+
cfg.lang = code;
|
|
9
|
+
await writeJson('config.json', cfg);
|
|
10
|
+
}
|
|
11
|
+
export const langCmd = defineCommand({
|
|
12
|
+
meta: { description: 'Change language / Changer la langue' },
|
|
13
|
+
args: { set: { type: 'string', description: 'Language code (en, fr)' } },
|
|
14
|
+
async run({ args }) {
|
|
15
|
+
intro(`🌐 ${t('lang.title')}`);
|
|
16
|
+
let lang = args.set;
|
|
17
|
+
if (lang !== 'en' && lang !== 'fr') {
|
|
18
|
+
lang = await promptSelect(t('lang.prompt'), [
|
|
19
|
+
{ value: 'fr', label: '🇫🇷 Français' },
|
|
20
|
+
{ value: 'en', label: '🇬🇧 English' },
|
|
21
|
+
]);
|
|
22
|
+
}
|
|
23
|
+
setLang(lang);
|
|
24
|
+
await persistLang(lang);
|
|
25
|
+
log.success(t('lang.set', lang));
|
|
26
|
+
outro(t('lang.set', lang));
|
|
27
|
+
},
|
|
28
|
+
});
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { existsSync, readdirSync, statSync, readFileSync, watch } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { defineCommand } from 'citty';
|
|
4
|
+
import { intro, 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
|
+
export const logsCmd = defineCommand({
|
|
9
|
+
meta: { description: 'View server logs' },
|
|
10
|
+
args: {
|
|
11
|
+
lines: { type: 'string', description: 'Number of lines to show', default: '50' },
|
|
12
|
+
follow: { type: 'boolean', description: 'Follow log output', default: true },
|
|
13
|
+
},
|
|
14
|
+
async run({ args }) {
|
|
15
|
+
intro(`📋 ${t('logs.title')}`);
|
|
16
|
+
const serverPath = await resolveServerDir();
|
|
17
|
+
if (!serverPath) {
|
|
18
|
+
log.error(t('logs.noServer'));
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
const logsDir = join(serverPath, 'logs');
|
|
22
|
+
if (!existsSync(logsDir)) {
|
|
23
|
+
log.error(t('logs.noLogs'));
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
const files = readdirSync(logsDir)
|
|
27
|
+
.filter(f => f.endsWith('.log') || f === 'latest.log')
|
|
28
|
+
.map(f => ({ name: f, mtime: statSync(join(logsDir, f)).mtime.getTime() }))
|
|
29
|
+
.sort((a, b) => b.mtime - a.mtime);
|
|
30
|
+
if (files.length === 0) {
|
|
31
|
+
log.error(t('logs.noLogs'));
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
const logPath = join(logsDir, files[0].name);
|
|
35
|
+
log.step(t('logs.watching', files[0].name));
|
|
36
|
+
const n = parseInt(args.lines) || 50;
|
|
37
|
+
const content = readFileSync(logPath, 'utf-8');
|
|
38
|
+
const colorize = (line) => line.replace(/\[ERROR\]/g, c.red('[ERROR]'))
|
|
39
|
+
.replace(/\[WARN\]/g, c.yellow('[WARN]'))
|
|
40
|
+
.replace(/\[INFO\]/g, c.blue('[INFO]'));
|
|
41
|
+
for (const line of content.split('\n').filter(Boolean).slice(-n)) {
|
|
42
|
+
process.stdout.write(colorize(line) + '\n');
|
|
43
|
+
}
|
|
44
|
+
if (args.follow) {
|
|
45
|
+
log.dim(t('logs.liveMode'));
|
|
46
|
+
process.on('SIGINT', () => { process.stdout.write('\n'); process.exit(0); });
|
|
47
|
+
let buf = content;
|
|
48
|
+
watch(logPath, () => {
|
|
49
|
+
try {
|
|
50
|
+
const updated = readFileSync(logPath, 'utf-8');
|
|
51
|
+
if (updated.length > buf.length) {
|
|
52
|
+
process.stdout.write(updated.slice(buf.length));
|
|
53
|
+
buf = updated;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
catch { /* rotation en cours */ }
|
|
57
|
+
});
|
|
58
|
+
await new Promise(() => { });
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
});
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { intro, outro, log } from '../ui/output.js';
|
|
2
|
+
import { promptSelect } from '../ui/prompts.js';
|
|
3
|
+
import { detectServers } from '../services/server.js';
|
|
4
|
+
import { loadPid, isAlive } from '../services/process.js';
|
|
5
|
+
import { t } from '../i18n/index.js';
|
|
6
|
+
/** Menu interactif racine, contextuel à l'état du serveur. */
|
|
7
|
+
export async function runMenu() {
|
|
8
|
+
const servers = detectServers();
|
|
9
|
+
const hasServer = servers.length > 0;
|
|
10
|
+
const pid = await loadPid();
|
|
11
|
+
const running = pid !== null && isAlive(pid.pid);
|
|
12
|
+
intro(`⚡ ${t('menu.title')}`);
|
|
13
|
+
if (running)
|
|
14
|
+
log.info(t('menu.statusRunning', String(pid.pid)));
|
|
15
|
+
else if (hasServer)
|
|
16
|
+
log.info(t('menu.statusStopped'));
|
|
17
|
+
else
|
|
18
|
+
log.warn(t('menu.statusNoServer'));
|
|
19
|
+
const options = [];
|
|
20
|
+
if (!hasServer)
|
|
21
|
+
options.push({ value: 'create', label: t('menu.create') });
|
|
22
|
+
if (hasServer && !running)
|
|
23
|
+
options.push({ value: 'start', label: t('menu.start') });
|
|
24
|
+
if (running)
|
|
25
|
+
options.push({ value: 'stop', label: t('menu.stop') });
|
|
26
|
+
if (hasServer) {
|
|
27
|
+
options.push({ value: 'plugins', label: t('menu.plugins') });
|
|
28
|
+
options.push({ value: 'update', label: t('menu.update') });
|
|
29
|
+
options.push({ value: 'backup', label: t('menu.backup') });
|
|
30
|
+
}
|
|
31
|
+
options.push({ value: 'doctor', label: t('menu.doctor') });
|
|
32
|
+
options.push({ value: 'info', label: t('menu.info') });
|
|
33
|
+
options.push({ value: 'lang', label: t('menu.lang') });
|
|
34
|
+
options.push({ value: 'quit', label: t('menu.quit') });
|
|
35
|
+
const choice = await promptSelect(t('menu.prompt'), options);
|
|
36
|
+
switch (choice) {
|
|
37
|
+
case 'create': {
|
|
38
|
+
const { initCmd } = await import('./init.js');
|
|
39
|
+
await initCmd.run({ args: {} });
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
case 'start': {
|
|
43
|
+
const { runStart } = await import('./start.js');
|
|
44
|
+
await runStart({});
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
case 'stop': {
|
|
48
|
+
const { runStop } = await import('./stop.js');
|
|
49
|
+
await runStop();
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
case 'plugins': {
|
|
53
|
+
const { installCmd } = await import('./plugin/install.js');
|
|
54
|
+
await installCmd.run({ args: {} });
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
case 'update': {
|
|
58
|
+
const { runUpdate } = await import('./update.js');
|
|
59
|
+
await runUpdate();
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
case 'backup': {
|
|
63
|
+
const { runBackup } = await import('./backup.js');
|
|
64
|
+
await runBackup();
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
case 'doctor': {
|
|
68
|
+
const { doctorCmd } = await import('./doctor.js');
|
|
69
|
+
await doctorCmd.run({ args: {} });
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
case 'info': {
|
|
73
|
+
const { infoCmd } = await import('./info.js');
|
|
74
|
+
await infoCmd.run({ args: {} });
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
case 'lang': {
|
|
78
|
+
const { langCmd } = await import('./lang.js');
|
|
79
|
+
await langCmd.run({ args: {} });
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
case 'quit':
|
|
83
|
+
outro(t('menu.bye'));
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { defineCommand } from 'citty';
|
|
2
|
+
export const pluginCmd = defineCommand({
|
|
3
|
+
meta: { description: 'Plugin management' },
|
|
4
|
+
subCommands: {
|
|
5
|
+
install: () => import('./install.js').then(m => m.installCmd),
|
|
6
|
+
list: () => import('./list.js').then(m => m.listCmd),
|
|
7
|
+
search: () => import('./search.js').then(m => m.searchCmd),
|
|
8
|
+
info: () => import('./info.js').then(m => m.infoCmd),
|
|
9
|
+
update: () => import('./update.js').then(m => m.updateCmd),
|
|
10
|
+
remove: () => import('./remove.js').then(m => m.removeCmd),
|
|
11
|
+
installed: () => import('./installed.js').then(m => m.installedCmd),
|
|
12
|
+
},
|
|
13
|
+
});
|