@powernukkitx/cli 0.0.2 → 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.
Files changed (82) hide show
  1. package/README.md +203 -177
  2. package/dist/bundle.js +3 -18856
  3. package/dist/cli.js +46 -0
  4. package/dist/commands/backup.d.ts +2 -1
  5. package/dist/commands/backup.js +21 -71
  6. package/dist/commands/cfg.d.ts +2 -0
  7. package/dist/commands/cfg.js +61 -0
  8. package/dist/commands/config.js +52 -105
  9. package/dist/commands/console.d.ts +2 -0
  10. package/dist/commands/console.js +99 -0
  11. package/dist/commands/doctor.d.ts +2 -1
  12. package/dist/commands/doctor.js +87 -108
  13. package/dist/commands/info.d.ts +2 -1
  14. package/dist/commands/info.js +39 -33
  15. package/dist/commands/init.d.ts +2 -4
  16. package/dist/commands/init.js +58 -65
  17. package/dist/commands/lang.d.ts +2 -0
  18. package/dist/commands/lang.js +28 -0
  19. package/dist/commands/logs.d.ts +2 -0
  20. package/dist/commands/logs.js +61 -0
  21. package/dist/commands/menu.js +86 -0
  22. package/dist/commands/plugin/index.d.ts +2 -0
  23. package/dist/commands/plugin/index.js +13 -0
  24. package/dist/commands/plugin/info.d.ts +2 -0
  25. package/dist/commands/plugin/info.js +40 -0
  26. package/dist/commands/plugin/install.d.ts +2 -0
  27. package/dist/commands/plugin/install.js +57 -0
  28. package/dist/commands/plugin/installed.d.ts +2 -0
  29. package/dist/commands/plugin/installed.js +42 -0
  30. package/dist/commands/plugin/list.d.ts +2 -0
  31. package/dist/commands/plugin/list.js +49 -0
  32. package/dist/commands/plugin/remove.d.ts +2 -0
  33. package/dist/commands/plugin/remove.js +45 -0
  34. package/dist/commands/plugin/search.d.ts +2 -0
  35. package/dist/commands/plugin/search.js +12 -0
  36. package/dist/commands/plugin/update.d.ts +2 -0
  37. package/dist/commands/plugin/update.js +53 -0
  38. package/dist/commands/plugin.d.ts +1 -12
  39. package/dist/commands/plugin.js +1 -385
  40. package/dist/commands/start.d.ts +2 -7
  41. package/dist/commands/start.js +61 -94
  42. package/dist/commands/stop.d.ts +2 -0
  43. package/dist/commands/stop.js +27 -0
  44. package/dist/commands/update.d.ts +2 -5
  45. package/dist/commands/update.js +30 -50
  46. package/dist/commands/use.d.ts +2 -0
  47. package/dist/commands/use.js +40 -0
  48. package/dist/core/config.d.ts +13 -0
  49. package/dist/core/config.js +31 -0
  50. package/dist/core/network.d.ts +13 -0
  51. package/dist/core/network.js +139 -0
  52. package/dist/core/output.d.ts +33 -0
  53. package/dist/core/output.js +75 -0
  54. package/dist/core/process.d.ts +10 -0
  55. package/dist/core/process.js +53 -0
  56. package/dist/i18n/en.json +174 -0
  57. package/dist/i18n/fr.json +174 -0
  58. package/dist/i18n/index.js +58 -0
  59. package/dist/index.d.ts +0 -1
  60. package/dist/index.js +1 -260
  61. package/dist/infra/http.js +121 -0
  62. package/dist/infra/paths.js +20 -0
  63. package/dist/infra/store.js +27 -0
  64. package/dist/lang/en.json +116 -148
  65. package/dist/lang/fr.json +115 -147
  66. package/dist/main.d.ts +2 -0
  67. package/dist/main.js +28 -0
  68. package/dist/services/backup.js +40 -0
  69. package/dist/services/plugins.js +111 -0
  70. package/dist/services/process.js +43 -0
  71. package/dist/services/release.js +38 -0
  72. package/dist/services/server.js +89 -0
  73. package/dist/ui/output.js +71 -0
  74. package/dist/ui/prompts.js +30 -0
  75. package/dist/ui/theme.js +18 -0
  76. package/dist/utils/api.d.ts +3 -12
  77. package/dist/utils/api.js +7 -57
  78. package/dist/utils/github.d.ts +3 -10
  79. package/dist/utils/github.js +27 -154
  80. package/dist/utils/server.d.ts +2 -2
  81. package/dist/utils/server.js +17 -24
  82. package/package.json +15 -32
@@ -1,55 +1,35 @@
1
- import { existsSync } from 'node:fs';
2
1
  import { rename } from 'node:fs/promises';
3
2
  import { join } from 'node:path';
4
- import { cwd } from 'node:process';
5
- import { confirm } from '@inquirer/prompts';
6
- import chalk from 'chalk';
7
- import { header, success, error, info, step } from '../utils/logger.js';
8
- import { t } from '../lang/index.js';
9
- import { getLatestRelease, findAsset, downloadFile } from '../utils/github.js';
10
- import { resolveServerDir } from '../utils/server.js';
11
- import { initCmd } from './init.js';
12
- export async function updateCmd(options) {
13
- const serverPath = options.dir ? join(cwd(), options.dir) : ((await resolveServerDir()) || cwd());
14
- if (!existsSync(join(serverPath, 'powernukkitx.jar'))) {
15
- info(t('update.notFound'));
16
- const createNow = await confirm({
17
- message: t('start.createPrompt'),
18
- default: true,
19
- });
20
- if (createNow) {
21
- await initCmd({ dir: serverPath });
22
- return;
23
- }
24
- error('powernukkitx.jar not found. Run "pnx init" first.');
25
- process.exit(1);
26
- }
27
- header(`🔄 ${t('update.title')}`);
28
- process.chdir(serverPath);
29
- try {
30
- step(t('update.checking'));
31
- const release = await getLatestRelease();
32
- const asset = await findAsset(release, 'powernukkitx.jar');
33
- if (!asset) {
34
- error('powernukkitx.jar not found in latest release.');
35
- process.exit(1);
36
- }
37
- success(`${t('update.available')} ${chalk.bold(release.tag_name)}`);
38
- if (existsSync('powernukkitx.jar.old') && !options.force) {
39
- info('Old backup found, replacing...');
40
- }
41
- step(t('update.saving'));
42
- await rename('powernukkitx.jar', 'powernukkitx.jar.old');
43
- success(t('update.saved'));
44
- await downloadFile(asset.browser_download_url, 'powernukkitx.jar', 'powernukkitx.jar');
45
- success(t('update.done'));
46
- console.log();
47
- success(t('update.done'));
48
- info(t('update.restartHint'));
49
- info(t('update.deleteOldHint'));
50
- }
51
- catch (err) {
52
- error(err.message);
3
+ import { defineCommand } from 'citty';
4
+ import { intro, outro, log, spin, progress } from '../ui/output.js';
5
+ import { resolveServerDir } from '../services/server.js';
6
+ import { getLatestRelease, downloadCore } from '../services/release.js';
7
+ import { JAR_NAME } from '../infra/paths.js';
8
+ import { t } from '../i18n/index.js';
9
+ export async function runUpdate(force = false) {
10
+ intro(`🔄 ${t('update.title')}`);
11
+ const serverPath = await resolveServerDir();
12
+ if (!serverPath) {
13
+ log.error(t('update.notFound'));
53
14
  process.exit(1);
54
15
  }
16
+ const sp = spin(t('update.checking'));
17
+ const release = await getLatestRelease(force);
18
+ sp.stop(t('update.available', release.tag));
19
+ const jarPath = join(serverPath, JAR_NAME);
20
+ log.step(t('update.saving'));
21
+ await rename(jarPath, `${jarPath}.old`).catch(() => { });
22
+ log.step(t('update.downloading'));
23
+ await downloadCore(release, jarPath, pct => progress(pct, JAR_NAME));
24
+ log.success(t('update.done'));
25
+ log.info(t('update.restart'));
26
+ outro(t('update.done'));
55
27
  }
28
+ export const updateCmd = defineCommand({
29
+ meta: { description: 'Update PowerNukkitX to the latest version' },
30
+ args: {
31
+ force: { type: 'boolean', description: 'Force update', default: false },
32
+ dev: { type: 'boolean', description: 'Dev version', default: false },
33
+ },
34
+ run: ({ args }) => runUpdate(args.force),
35
+ });
@@ -0,0 +1,2 @@
1
+ import type { CommandDef } from 'citty';
2
+ export declare const useCmd: CommandDef;
@@ -0,0 +1,40 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { join, resolve } from 'node:path';
3
+ import { cwd } from 'node:process';
4
+ import { defineCommand } from 'citty';
5
+ import { intro, outro, log } from '../ui/output.js';
6
+ import { promptSelect, promptText } from '../ui/prompts.js';
7
+ import { detectServers, setDefaultServer } from '../services/server.js';
8
+ import { JAR_NAME } from '../infra/paths.js';
9
+ import { t } from '../i18n/index.js';
10
+ export const useCmd = defineCommand({
11
+ meta: { description: 'Set the default PowerNukkitX server directory' },
12
+ args: {
13
+ dir: { type: 'positional', description: 'Server path', required: false },
14
+ clear: { type: 'boolean', description: 'Remove default server', default: false },
15
+ },
16
+ async run({ args }) {
17
+ intro(`📌 ${t('use.title')}`);
18
+ if (args.clear) {
19
+ await setDefaultServer(null);
20
+ log.success(t('use.cleared'));
21
+ outro(t('use.cleared'));
22
+ return;
23
+ }
24
+ let target = args.dir;
25
+ if (!target) {
26
+ const servers = detectServers();
27
+ target = servers.length > 0
28
+ ? await promptSelect(t('use.prompt'), servers.map(s => ({ value: s, label: s })))
29
+ : await promptText({ message: t('use.pathPrompt'), defaultValue: cwd() });
30
+ }
31
+ const absPath = resolve(cwd(), target);
32
+ if (!existsSync(join(absPath, JAR_NAME))) {
33
+ log.error(t('use.notFound', absPath));
34
+ process.exit(1);
35
+ }
36
+ await setDefaultServer(absPath);
37
+ log.success(t('use.set', absPath));
38
+ outro(t('use.set', absPath));
39
+ },
40
+ });
@@ -0,0 +1,13 @@
1
+ export interface PnxConfig {
2
+ defaultServer?: string;
3
+ lang?: string;
4
+ lastUpdateCheck?: number;
5
+ lastCoreVersion?: string;
6
+ }
7
+ export declare function getConfig(): Promise<PnxConfig>;
8
+ export declare function setConfig<K extends keyof PnxConfig>(key: K, value: PnxConfig[K]): Promise<void>;
9
+ export declare const getDefaultServer: () => Promise<string | null>;
10
+ export declare const setDefaultServer: (p: string) => Promise<void>;
11
+ export declare const clearDefaultServer: () => Promise<void>;
12
+ export declare const getLang: () => Promise<string>;
13
+ export declare const setLang: (l: string) => Promise<void>;
@@ -0,0 +1,31 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { readFile, writeFile, mkdir } from 'node:fs/promises';
3
+ import { homedir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ const DIR = join(homedir(), '.pnx-cli');
6
+ const FILE = join(DIR, 'config.json');
7
+ export async function getConfig() {
8
+ try {
9
+ if (existsSync(FILE))
10
+ return JSON.parse(await readFile(FILE, 'utf-8'));
11
+ }
12
+ catch { }
13
+ return {};
14
+ }
15
+ export async function setConfig(key, value) {
16
+ try {
17
+ const cfg = await getConfig();
18
+ if (value === undefined)
19
+ delete cfg[key];
20
+ else
21
+ cfg[key] = value;
22
+ await mkdir(DIR, { recursive: true });
23
+ await writeFile(FILE, JSON.stringify(cfg, null, 2), 'utf-8');
24
+ }
25
+ catch { }
26
+ }
27
+ export const getDefaultServer = async () => (await getConfig()).defaultServer ?? null;
28
+ export const setDefaultServer = (p) => setConfig('defaultServer', p);
29
+ export const clearDefaultServer = () => setConfig('defaultServer', undefined);
30
+ export const getLang = async () => (await getConfig()).lang ?? 'fr';
31
+ export const setLang = (l) => setConfig('lang', l);
@@ -0,0 +1,13 @@
1
+ export interface FetchOpts {
2
+ retries?: number;
3
+ timeout?: number;
4
+ headers?: Record<string, string>;
5
+ }
6
+ export interface DownloadOpts {
7
+ label?: string;
8
+ onProgress?: (pct: number, downloaded: number, total: number) => void;
9
+ timeout?: number;
10
+ retries?: number;
11
+ }
12
+ export declare function fetchJSON(url: string, opts?: FetchOpts): Promise<unknown>;
13
+ export declare function downloadFile(url: string, dest: string, opts?: DownloadOpts): Promise<void>;
@@ -0,0 +1,139 @@
1
+ import https from 'node:https';
2
+ import http from 'node:http';
3
+ import { createWriteStream } from 'node:fs';
4
+ import { log } from './output.js';
5
+ class NetworkError extends Error {
6
+ statusCode;
7
+ attempt;
8
+ constructor(message, statusCode, attempt) {
9
+ super(message);
10
+ this.statusCode = statusCode;
11
+ this.attempt = attempt;
12
+ this.name = 'NetworkError';
13
+ }
14
+ }
15
+ function sleep(ms) {
16
+ return new Promise((r) => setTimeout(r, ms));
17
+ }
18
+ function doGet(url, timeoutMs, headers) {
19
+ return new Promise((resolve, reject) => {
20
+ const mod = url.startsWith('https') ? https : http;
21
+ const req = mod.get(url, { timeout: timeoutMs, headers }, (res) => {
22
+ let body = '';
23
+ res.on('data', (chunk) => (body += chunk.toString()));
24
+ res.on('end', () => resolve({
25
+ statusCode: res.statusCode ?? 0,
26
+ location: res.headers.location,
27
+ body,
28
+ contentLength: parseInt(res.headers['content-length'] || '0', 10),
29
+ raw: res,
30
+ }));
31
+ res.on('error', reject);
32
+ });
33
+ req.on('error', reject);
34
+ req.on('timeout', function () {
35
+ this.destroy();
36
+ reject(new NetworkError('Request timed out'));
37
+ });
38
+ });
39
+ }
40
+ async function fetchRaw(url, opts, redirects = 5) {
41
+ if (redirects <= 0)
42
+ throw new NetworkError('Too many redirects');
43
+ const res = await doGet(url, opts.timeout, { 'User-Agent': 'pnx-cli', ...opts.headers });
44
+ if ([301, 302, 303, 307, 308].includes(res.statusCode) && res.location) {
45
+ return fetchRaw(res.location, opts, redirects - 1);
46
+ }
47
+ if (res.statusCode !== 200)
48
+ throw new NetworkError(`HTTP ${res.statusCode}`, res.statusCode);
49
+ return res.body;
50
+ }
51
+ export async function fetchJSON(url, opts) {
52
+ const config = {
53
+ retries: opts?.retries ?? 3,
54
+ timeout: opts?.timeout ?? 15_000,
55
+ headers: opts?.headers ?? {},
56
+ };
57
+ let lastErr = new Error('Unknown error');
58
+ for (let attempt = 1; attempt <= config.retries; attempt++) {
59
+ try {
60
+ const body = await fetchRaw(url, config);
61
+ return JSON.parse(body);
62
+ }
63
+ catch (err) {
64
+ lastErr = err;
65
+ if (attempt < config.retries) {
66
+ log.warn(`Network error (attempt ${attempt}/${config.retries}): ${err.message}`);
67
+ await sleep(Math.pow(2, attempt - 1) * 1000);
68
+ }
69
+ }
70
+ }
71
+ throw lastErr;
72
+ }
73
+ function doStream(url, dest, timeoutMs, onData) {
74
+ return new Promise((resolve, reject) => {
75
+ const mod = url.startsWith('https') ? https : http;
76
+ const file = createWriteStream(dest);
77
+ const req = mod.get(url, { timeout: timeoutMs, headers: { 'User-Agent': 'pnx-cli' } }, (res) => {
78
+ const code = res.statusCode ?? 0;
79
+ if ([301, 302, 303, 307, 308].includes(code) && res.headers.location) {
80
+ file.close();
81
+ resolve(doStream(res.headers.location, dest, timeoutMs, onData));
82
+ return;
83
+ }
84
+ if (code !== 200) {
85
+ file.close();
86
+ reject(new NetworkError(`HTTP ${code}`, code));
87
+ return;
88
+ }
89
+ const total = parseInt(res.headers['content-length'] || '0', 10);
90
+ res.on('data', (chunk) => onData(chunk, total));
91
+ res.pipe(file);
92
+ file.on('finish', () => {
93
+ file.close();
94
+ resolve();
95
+ });
96
+ file.on('error', reject);
97
+ });
98
+ req.on('error', (err) => {
99
+ file.close();
100
+ reject(err);
101
+ });
102
+ req.on('timeout', function () {
103
+ this.destroy();
104
+ file.close();
105
+ reject(new NetworkError('Download timed out'));
106
+ });
107
+ });
108
+ }
109
+ export async function downloadFile(url, dest, opts) {
110
+ const retries = opts?.retries ?? 3;
111
+ const timeout = opts?.timeout ?? 120_000;
112
+ const onProgress = opts?.onProgress;
113
+ let lastErr = new Error('Unknown error');
114
+ for (let attempt = 1; attempt <= retries; attempt++) {
115
+ try {
116
+ let downloaded = 0;
117
+ let lastPct = -1;
118
+ await doStream(url, dest, timeout, (chunk, total) => {
119
+ downloaded += chunk.length;
120
+ if (onProgress && total > 0) {
121
+ const pct = Math.round((downloaded / total) * 100);
122
+ if (pct !== lastPct && pct % 5 === 0) {
123
+ onProgress(pct, downloaded, total);
124
+ lastPct = pct;
125
+ }
126
+ }
127
+ });
128
+ return;
129
+ }
130
+ catch (err) {
131
+ lastErr = err;
132
+ if (attempt < retries) {
133
+ log.warn(`Download error (attempt ${attempt}/${retries}): ${err.message}`);
134
+ await sleep(Math.pow(2, attempt - 1) * 1000);
135
+ }
136
+ }
137
+ }
138
+ throw lastErr;
139
+ }
@@ -0,0 +1,33 @@
1
+ import * as clack from '@clack/prompts';
2
+ import chalk from 'chalk';
3
+ declare const c: {
4
+ primary: import("chalk").ChalkInstance;
5
+ accent: import("chalk").ChalkInstance;
6
+ green: import("chalk").ChalkInstance;
7
+ red: import("chalk").ChalkInstance;
8
+ yellow: import("chalk").ChalkInstance;
9
+ blue: import("chalk").ChalkInstance;
10
+ cyan: import("chalk").ChalkInstance;
11
+ };
12
+ export declare function isJson(): boolean;
13
+ export declare function json(data: unknown): never;
14
+ export declare function intro(title: string): void;
15
+ export declare function outro(msg: string): void;
16
+ export declare function section(title: string): void;
17
+ export declare function cancel(msg?: string): never;
18
+ export declare function spin(msg: string): {
19
+ update: (m: string) => void;
20
+ stop: (m: string) => void;
21
+ fail: (m: string) => void;
22
+ };
23
+ export declare const log: {
24
+ success: (msg: string) => void;
25
+ error: (msg: string) => void;
26
+ warn: (msg: string) => void;
27
+ info: (msg: string) => void;
28
+ step: (msg: string) => void;
29
+ dim: (msg: string) => void;
30
+ raw: (msg: string) => void;
31
+ };
32
+ export declare function progress(pct: number, label?: string): void;
33
+ export { clack, chalk, c };
@@ -0,0 +1,75 @@
1
+ import * as clack from '@clack/prompts';
2
+ import chalk from 'chalk';
3
+ const c = {
4
+ primary: chalk.hex('#FF6B35'),
5
+ accent: chalk.hex('#FFB347'),
6
+ green: chalk.hex('#4CAF50'),
7
+ red: chalk.hex('#E53935'),
8
+ yellow: chalk.hex('#FFC107'),
9
+ blue: chalk.hex('#6C8EBF'),
10
+ cyan: chalk.hex('#4ECDC4'),
11
+ };
12
+ const _isJson = process.argv.includes('--json');
13
+ export function isJson() { return _isJson; }
14
+ export function json(data) {
15
+ process.stdout.write(JSON.stringify(data, null, 2) + '\n');
16
+ process.exit(0);
17
+ }
18
+ export function intro(title) {
19
+ if (_isJson)
20
+ return;
21
+ clack.intro(c.primary.bold(` ${title} `));
22
+ }
23
+ export function outro(msg) {
24
+ if (_isJson)
25
+ return;
26
+ clack.outro(c.accent(msg));
27
+ }
28
+ export function section(title) {
29
+ if (_isJson)
30
+ return;
31
+ clack.log.step(c.accent.bold(title));
32
+ }
33
+ export function cancel(msg = 'Annulé') {
34
+ clack.cancel(c.yellow(msg));
35
+ process.exit(0);
36
+ }
37
+ export function spin(msg) {
38
+ if (_isJson)
39
+ return { update: () => { }, stop: () => { }, fail: () => { } };
40
+ const s = clack.spinner();
41
+ s.start(msg);
42
+ return {
43
+ update: (m) => s.message(m),
44
+ stop: (m) => s.stop(m),
45
+ fail: (m) => s.stop(m, 1),
46
+ };
47
+ }
48
+ export const log = {
49
+ success: (msg) => { if (!_isJson)
50
+ clack.log.success(c.green(msg)); },
51
+ error: (msg) => { if (!_isJson)
52
+ clack.log.error(c.red(msg)); },
53
+ warn: (msg) => { if (!_isJson)
54
+ clack.log.warn(c.yellow(msg)); },
55
+ info: (msg) => { if (!_isJson)
56
+ clack.log.info(c.blue(msg)); },
57
+ step: (msg) => { if (!_isJson)
58
+ clack.log.step(chalk.dim(msg)); },
59
+ dim: (msg) => { if (!_isJson)
60
+ clack.log.message(chalk.dim(msg)); },
61
+ raw: (msg) => { if (!_isJson)
62
+ process.stdout.write(msg + '\n'); },
63
+ };
64
+ export function progress(pct, label) {
65
+ if (_isJson)
66
+ return;
67
+ const clamped = Math.max(0, Math.min(100, pct));
68
+ const filled = Math.floor(clamped / 5);
69
+ const bar = c.cyan('━'.repeat(filled)) + chalk.dim('─'.repeat(20 - filled));
70
+ const suffix = label ? ` ${chalk.dim(label)}` : '';
71
+ process.stdout.write(`\r ${bar} ${chalk.bold(String(clamped))}%${suffix}`);
72
+ if (clamped >= 100)
73
+ process.stdout.write('\n');
74
+ }
75
+ export { clack, chalk, c };
@@ -0,0 +1,10 @@
1
+ export interface PidInfo {
2
+ pid: number;
3
+ serverPath: string;
4
+ startedAt: string;
5
+ }
6
+ export declare function savePid(pid: number, serverPath: string): Promise<void>;
7
+ export declare function loadPid(): Promise<PidInfo | null>;
8
+ export declare function clearPid(): Promise<void>;
9
+ export declare function isProcessAlive(pid: number): boolean;
10
+ export declare function killProcess(pid: number): Promise<boolean>;
@@ -0,0 +1,53 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { readFile, writeFile, mkdir, rm } from 'node:fs/promises';
3
+ import { homedir, platform } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import { spawnSync } from 'node:child_process';
6
+ const DIR = join(homedir(), '.pnx-cli');
7
+ const PID_FILE = join(DIR, 'pid.json');
8
+ export async function savePid(pid, serverPath) {
9
+ await mkdir(DIR, { recursive: true });
10
+ await writeFile(PID_FILE, JSON.stringify({ pid, serverPath, startedAt: new Date().toISOString() }), 'utf-8');
11
+ }
12
+ export async function loadPid() {
13
+ try {
14
+ if (existsSync(PID_FILE))
15
+ return JSON.parse(await readFile(PID_FILE, 'utf-8'));
16
+ }
17
+ catch { }
18
+ return null;
19
+ }
20
+ export async function clearPid() {
21
+ try {
22
+ await rm(PID_FILE);
23
+ }
24
+ catch { }
25
+ }
26
+ export function isProcessAlive(pid) {
27
+ try {
28
+ process.kill(pid, 0);
29
+ return true;
30
+ }
31
+ catch {
32
+ return false;
33
+ }
34
+ }
35
+ export async function killProcess(pid) {
36
+ if (!isProcessAlive(pid))
37
+ return false;
38
+ try {
39
+ if (platform() === 'win32') {
40
+ spawnSync('taskkill', ['/PID', String(pid), '/F']);
41
+ }
42
+ else {
43
+ process.kill(pid, 'SIGTERM');
44
+ await new Promise(r => setTimeout(r, 5000));
45
+ if (isProcessAlive(pid))
46
+ process.kill(pid, 'SIGKILL');
47
+ }
48
+ return true;
49
+ }
50
+ catch {
51
+ return false;
52
+ }
53
+ }
@@ -0,0 +1,174 @@
1
+ {
2
+ "cli": { "description": "PowerNukkitX CLI — Manage your Minecraft Bedrock servers" },
3
+ "menu": {
4
+ "title": "PowerNukkitX Menu",
5
+ "prompt": "What do you want to do?",
6
+ "create": "Create a server",
7
+ "start": "Start the server",
8
+ "stop": "Stop the server",
9
+ "plugins": "Manage plugins",
10
+ "update": "Update the core",
11
+ "doctor": "Diagnostics",
12
+ "backup": "Backup",
13
+ "info": "Information",
14
+ "lang": "Change language",
15
+ "quit": "Quit",
16
+ "statusRunning": "Server running (PID {0})",
17
+ "statusStopped": "Server stopped",
18
+ "statusNoServer": "No server detected",
19
+ "bye": "See you soon!"
20
+ },
21
+ "init": {
22
+ "title": "Create a PowerNukkitX server",
23
+ "promptDir": "Server directory?",
24
+ "alreadyExists": "A server already exists here",
25
+ "fetchingInfo": "Fetching latest version...",
26
+ "latestVersion": "Latest version",
27
+ "downloading": "Downloading",
28
+ "downloaded": "Downloaded ({0} MB)",
29
+ "downloadingScripts": "Downloading start scripts...",
30
+ "scriptsDownloaded": "Start scripts downloaded",
31
+ "success": "Server ready!",
32
+ "launchPrompt": "Start the server now?"
33
+ },
34
+ "start": {
35
+ "title": "Start server",
36
+ "notFound": "No server found here",
37
+ "createPrompt": "Create a server now?",
38
+ "checkingJava": "Checking Java...",
39
+ "javaOk": "Java {0} detected",
40
+ "javaNotFound": "Java 21+ required — https://adoptium.net/",
41
+ "starting": "Starting...",
42
+ "running": "Server running (PID {0})",
43
+ "stopped": "Server stopped (code {0})",
44
+ "restarting": "Restarting...",
45
+ "generated": "Command generated"
46
+ },
47
+ "stop": {
48
+ "title": "Stop server",
49
+ "notRunning": "No server running",
50
+ "killing": "Stopping server (PID {0})...",
51
+ "done": "Server stopped",
52
+ "failed": "Failed to stop"
53
+ },
54
+ "update": {
55
+ "title": "Update PowerNukkitX",
56
+ "checking": "Checking for updates...",
57
+ "notFound": "No server found",
58
+ "available": "New version available: {0}",
59
+ "upToDate": "Already up to date ({0})",
60
+ "saving": "Backing up old version...",
61
+ "downloading": "Downloading...",
62
+ "done": "Update complete!",
63
+ "restart": "Restart the server to apply changes"
64
+ },
65
+ "doctor": {
66
+ "title": "Server diagnostics",
67
+ "checking": "Running diagnostics...",
68
+ "java": "Java",
69
+ "server": "Server",
70
+ "system": "System",
71
+ "plugins": "Plugins & Worlds",
72
+ "network": "Network",
73
+ "done": "Diagnostics complete",
74
+ "javaMissing": "Java 21+ required",
75
+ "jarMissing": "powernukkitx.jar missing",
76
+ "noServer": "No server — run 'pnx init'",
77
+ "portFree": "Port {0} available",
78
+ "portBusy": "Port {0} busy"
79
+ },
80
+ "info": {
81
+ "title": "Server info",
82
+ "server": "Server",
83
+ "system": "System",
84
+ "status": "Status",
85
+ "installed": "Installed",
86
+ "notInstalled": "Not installed",
87
+ "path": "Path",
88
+ "plugins": "Plugins",
89
+ "worlds": "Worlds",
90
+ "ram": "Free RAM",
91
+ "ready": "Ready to start with 'pnx start'",
92
+ "notReady": "Run 'pnx init' to create a server"
93
+ },
94
+ "backup": {
95
+ "title": "Backup",
96
+ "noServer": "No server detected",
97
+ "collecting": "Collecting files...",
98
+ "size": "Estimated size: {0}",
99
+ "copying": "Copying files...",
100
+ "done": "Backup created: {0} ({1})"
101
+ },
102
+ "config": {
103
+ "title": "Configuration",
104
+ "noServer": "No server detected",
105
+ "notFound": "No config file found",
106
+ "editHint": "Edit {0} to change these values"
107
+ },
108
+ "use": {
109
+ "title": "Default server",
110
+ "prompt": "Select a server:",
111
+ "pathPrompt": "Server path:",
112
+ "set": "Default server set: {0}",
113
+ "cleared": "Default server cleared",
114
+ "notFound": "powernukkitx.jar not found in: {0}"
115
+ },
116
+ "lang": {
117
+ "title": "Language",
118
+ "prompt": "Choose your language",
119
+ "set": "Language set to {0}"
120
+ },
121
+ "logs": {
122
+ "title": "Server logs",
123
+ "noServer": "No server detected",
124
+ "noLogs": "No log files found",
125
+ "watching": "Reading {0}",
126
+ "liveMode": "Live mode (Ctrl+C to quit)"
127
+ },
128
+ "console": {
129
+ "title": "Server console",
130
+ "noServer": "No server detected",
131
+ "rconDisabled": "RCON disabled in server.properties",
132
+ "rconEnable": "Enable enable-rcon=true in server.properties",
133
+ "connecting": "Connecting via RCON...",
134
+ "connected": "Connected — type commands (exit to quit)",
135
+ "prompt": "> ",
136
+ "disconnected": "Disconnected"
137
+ },
138
+ "plugin": {
139
+ "titleList": "Plugin catalog",
140
+ "titleInstall": "Install plugins",
141
+ "titleInstalled": "Installed plugins",
142
+ "titleRemove": "Remove plugins",
143
+ "titleInfo": "Plugin info",
144
+ "titleSearch": "Search plugins",
145
+ "titleUpdate": "Update plugins",
146
+ "fetching": "Fetching from marketplace...",
147
+ "fetchError": "Error: {0}",
148
+ "notFound": "Plugin not found: {0}",
149
+ "noPlugins": "No plugins installed",
150
+ "noPluginsDir": "No plugins/ directory",
151
+ "noneSelected": "No plugin selected",
152
+ "selectInstall": "Select plugins to install:",
153
+ "selectRemove": "Select plugins to remove:",
154
+ "selectUpdate": "Select plugins to update:",
155
+ "searchPrompt": "Search plugins:",
156
+ "slugPrompt": "Plugin slug:",
157
+ "installSuccess": "{0} installed",
158
+ "installError": "Error installing {0}",
159
+ "removeConfirm": "Remove {0}?",
160
+ "removed": "{0} removed",
161
+ "alreadyLatest": "{0} is already up to date (v{1})",
162
+ "updated": "{0} updated to v{1}",
163
+ "noRelease": "No release available for {0}",
164
+ "noFile": "No file for {0}",
165
+ "urlInvalid": "URL must point to a .jar file",
166
+ "restartHint": "Restart the server to load plugins",
167
+ "count": "{0} plugin(s)",
168
+ "versionsAvailable": "Available versions:"
169
+ },
170
+ "errors": {
171
+ "timeout": "Request timed out",
172
+ "network": "Network error: {0}"
173
+ }
174
+ }