@powernukkitx/cli 0.0.3 → 1.0.1
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/LICENSE +21 -0
- package/README.md +110 -101
- package/dist/cli.js +34 -0
- package/dist/commands/backup.d.ts +2 -1
- package/dist/commands/backup.js +23 -74
- package/dist/commands/config.d.ts +1 -1
- package/dist/commands/config.js +60 -114
- package/dist/commands/console.d.ts +2 -0
- package/dist/commands/console.js +98 -0
- package/dist/commands/doctor.d.ts +2 -1
- package/dist/commands/doctor.js +97 -119
- package/dist/commands/info.d.ts +2 -1
- package/dist/commands/info.js +41 -36
- package/dist/commands/init.d.ts +2 -4
- package/dist/commands/init.js +87 -73
- package/dist/commands/install.js +45 -0
- package/dist/commands/logs.d.ts +2 -0
- package/dist/commands/logs.js +60 -0
- package/dist/commands/menu.js +94 -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 +39 -0
- package/dist/commands/plugin/install.d.ts +2 -0
- package/dist/commands/plugin/install.js +56 -0
- package/dist/commands/plugin/installed.d.ts +2 -0
- package/dist/commands/plugin/installed.js +41 -0
- package/dist/commands/plugin/list.d.ts +2 -0
- package/dist/commands/plugin/list.js +47 -0
- package/dist/commands/plugin/remove.d.ts +2 -0
- package/dist/commands/plugin/remove.js +44 -0
- package/dist/commands/plugin/search.d.ts +2 -0
- package/dist/commands/plugin/search.js +11 -0
- package/dist/commands/plugin/update.d.ts +2 -0
- package/dist/commands/plugin/update.js +52 -0
- package/dist/commands/start.d.ts +2 -7
- package/dist/commands/start.js +74 -108
- package/dist/commands/stop.d.ts +2 -0
- package/dist/commands/stop.js +26 -0
- package/dist/commands/update.d.ts +2 -5
- package/dist/commands/update.js +34 -55
- package/dist/commands/use.d.ts +2 -0
- package/dist/commands/use.js +39 -0
- package/dist/infra/http.js +119 -0
- package/dist/infra/paths.js +15 -0
- package/dist/infra/store.js +24 -0
- package/dist/services/backup.js +34 -0
- package/dist/services/plugins.js +139 -0
- package/dist/services/process.js +41 -0
- package/dist/services/release.js +51 -0
- package/dist/services/server.js +81 -0
- package/dist/ui/output.js +67 -0
- package/dist/ui/prompts.js +26 -0
- package/dist/ui/theme.js +16 -0
- package/package.json +44 -63
- package/dist/bundle.js +0 -18871
- package/dist/commands/plugin.d.ts +0 -12
- package/dist/commands/plugin.js +0 -385
- package/dist/index.d.ts +0 -2
- package/dist/index.js +0 -260
- package/dist/lang/en.json +0 -178
- package/dist/lang/fr.json +0 -178
- package/dist/lang/index.d.ts +0 -7
- package/dist/lang/index.js +0 -83
- package/dist/plugins.json +0 -66
- package/dist/types.d.ts +0 -61
- package/dist/types.js +0 -1
- package/dist/utils/api.d.ts +0 -18
- package/dist/utils/api.js +0 -74
- package/dist/utils/github.d.ts +0 -27
- package/dist/utils/github.js +0 -187
- package/dist/utils/logger.d.ts +0 -15
- package/dist/utils/logger.js +0 -72
- package/dist/utils/pause.d.ts +0 -1
- package/dist/utils/pause.js +0 -6
- package/dist/utils/plugins.d.ts +0 -4
- package/dist/utils/plugins.js +0 -34
- package/dist/utils/server.d.ts +0 -3
- package/dist/utils/server.js +0 -39
- package/dist/utils/updater.d.ts +0 -1
- package/dist/utils/updater.js +0 -86
package/dist/commands/start.js
CHANGED
|
@@ -1,108 +1,74 @@
|
|
|
1
|
-
import { existsSync } from 'node:fs';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
memory
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const javaArgs = getDefaultJavaArgs();
|
|
76
|
-
const cmdArgs = [
|
|
77
|
-
`-Xms${memory}`,
|
|
78
|
-
`-XX:MaxRAMPercentage=75.0`,
|
|
79
|
-
...javaArgs,
|
|
80
|
-
'-jar', 'powernukkitx.jar',
|
|
81
|
-
];
|
|
82
|
-
if (options.generateOnly) {
|
|
83
|
-
console.log(`\n ${highlight.bold(t('start.generatedCommand'))}`);
|
|
84
|
-
console.log(` ${chalk.cyan(`${java} ${cmdArgs.join(' ')}`)}\n`);
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
console.log(`\n ${dim('JVM:')} ${highlight(java)}`);
|
|
88
|
-
console.log(` ${dim('Memory:')} ${highlight(memory)}`);
|
|
89
|
-
console.log(` ${dim('Restart:')} ${highlight(autoRestart ? 'Yes' : 'No')}\n`);
|
|
90
|
-
const runServer = () => {
|
|
91
|
-
const child = spawn(java, cmdArgs, {
|
|
92
|
-
stdio: 'inherit',
|
|
93
|
-
env: getJavaEnv(),
|
|
94
|
-
cwd: serverPath,
|
|
95
|
-
});
|
|
96
|
-
child.on('exit', (code) => {
|
|
97
|
-
if (code !== 0 && autoRestart) {
|
|
98
|
-
info(t('start.restarting'));
|
|
99
|
-
runServer();
|
|
100
|
-
}
|
|
101
|
-
else if (code !== 0) {
|
|
102
|
-
error(t('start.stopped', String(code ?? '?')));
|
|
103
|
-
process.exit(code ?? 0);
|
|
104
|
-
}
|
|
105
|
-
});
|
|
106
|
-
};
|
|
107
|
-
runServer();
|
|
108
|
-
}
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { cwd } from 'node:process';
|
|
4
|
+
import { spawn } from 'node:child_process';
|
|
5
|
+
import { defineCommand } from 'citty';
|
|
6
|
+
import { intro, outro, log, spin } from '../ui/output.js';
|
|
7
|
+
import { promptConfirm } from '../ui/prompts.js';
|
|
8
|
+
import { resolveServerDir, checkJava, findJava, buildLaunchArgs, javaEnv } from '../services/server.js';
|
|
9
|
+
import { savePid } from '../services/process.js';
|
|
10
|
+
import { JAR_NAME } from '../infra/paths.js';
|
|
11
|
+
export async function runStart(opts) {
|
|
12
|
+
const serverPath = opts.dir
|
|
13
|
+
? (opts.dir.includes(':') || opts.dir.startsWith('/') ? opts.dir : join(cwd(), opts.dir))
|
|
14
|
+
: (await resolveServerDir()) ?? cwd();
|
|
15
|
+
if (!existsSync(join(serverPath, JAR_NAME))) {
|
|
16
|
+
log.warn("No server found here");
|
|
17
|
+
if (!(await promptConfirm("Create a server now?", true)))
|
|
18
|
+
process.exit(0);
|
|
19
|
+
const { initCmd } = await import('./init.js');
|
|
20
|
+
await initCmd.run({ args: { dir: serverPath } });
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
intro(`🚀 ${"Start server"}`);
|
|
24
|
+
const sp = spin("Checking Java...");
|
|
25
|
+
const java = await checkJava();
|
|
26
|
+
if (!java.ok) {
|
|
27
|
+
sp.fail("Java 21+ required — https://adoptium.net/");
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
sp.stop(`Java ${String(java.version)} detected`);
|
|
31
|
+
const mem = opts.memory ?? '2G';
|
|
32
|
+
const cmdArgs = buildLaunchArgs(mem);
|
|
33
|
+
const javaExe = findJava();
|
|
34
|
+
if (opts.dryRun) {
|
|
35
|
+
log.info(`${javaExe} ${cmdArgs.join(' ')}`);
|
|
36
|
+
outro("Command generated");
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
log.step(`${"Starting..."} (memory: ${mem})`);
|
|
40
|
+
const run = () => {
|
|
41
|
+
const child = spawn(javaExe, cmdArgs, { stdio: 'inherit', env: javaEnv(), cwd: serverPath });
|
|
42
|
+
if (child.pid)
|
|
43
|
+
savePid(child.pid, serverPath);
|
|
44
|
+
log.success(`Server running (PID ${String(child.pid)})`);
|
|
45
|
+
child.on('exit', code => {
|
|
46
|
+
if (code !== 0 && opts.restart) {
|
|
47
|
+
log.warn("Restarting...");
|
|
48
|
+
run();
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
log.info(`Server stopped (code ${String(code ?? '?')})`);
|
|
52
|
+
process.exit(code ?? 0);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
};
|
|
56
|
+
run();
|
|
57
|
+
}
|
|
58
|
+
export const startCmd = defineCommand({
|
|
59
|
+
meta: { description: 'Start the PowerNukkitX server' },
|
|
60
|
+
args: {
|
|
61
|
+
dir: { type: 'string', description: 'Server directory' },
|
|
62
|
+
memory: { type: 'string', description: 'Memory (e.g. 2G)', default: '2G' },
|
|
63
|
+
restart: { type: 'boolean', description: 'Auto-restart on crash', default: false },
|
|
64
|
+
'dry-run': { type: 'boolean', description: 'Print command without running', default: false },
|
|
65
|
+
},
|
|
66
|
+
async run({ args }) {
|
|
67
|
+
await runStart({
|
|
68
|
+
dir: args.dir,
|
|
69
|
+
memory: args.memory,
|
|
70
|
+
restart: args.restart,
|
|
71
|
+
dryRun: args['dry-run'],
|
|
72
|
+
});
|
|
73
|
+
},
|
|
74
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { defineCommand } from 'citty';
|
|
2
|
+
import { intro, outro, log, spin } from '../ui/output.js';
|
|
3
|
+
import { loadPid, clearPid, killProcess, isAlive } from '../services/process.js';
|
|
4
|
+
export async function runStop() {
|
|
5
|
+
intro(`⏹ ${"Stop server"}`);
|
|
6
|
+
const pid = await loadPid();
|
|
7
|
+
if (!pid || !isAlive(pid.pid)) {
|
|
8
|
+
log.warn("No server running");
|
|
9
|
+
await clearPid();
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
const sp = spin(`Stopping server (PID ${String(pid.pid)})...`);
|
|
13
|
+
const ok = await killProcess(pid.pid);
|
|
14
|
+
await clearPid();
|
|
15
|
+
if (ok) {
|
|
16
|
+
sp.stop("Server stopped");
|
|
17
|
+
outro("Server stopped");
|
|
18
|
+
}
|
|
19
|
+
else
|
|
20
|
+
sp.fail("Failed to stop");
|
|
21
|
+
}
|
|
22
|
+
export const stopCmd = defineCommand({
|
|
23
|
+
meta: { description: 'Stop the running PowerNukkitX server' },
|
|
24
|
+
args: {},
|
|
25
|
+
run: () => runStop(),
|
|
26
|
+
});
|
package/dist/commands/update.js
CHANGED
|
@@ -1,55 +1,34 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
7
|
-
import {
|
|
8
|
-
|
|
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
|
-
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);
|
|
53
|
-
process.exit(1);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
1
|
+
import { rename } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
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
|
+
export async function runUpdate(force = false) {
|
|
9
|
+
intro(`🔄 ${"Update PowerNukkitX"}`);
|
|
10
|
+
const serverPath = await resolveServerDir();
|
|
11
|
+
if (!serverPath) {
|
|
12
|
+
log.error("No server found");
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
const sp = spin("Checking for updates...");
|
|
16
|
+
const release = await getLatestRelease(force);
|
|
17
|
+
sp.stop(`New version available: ${release.tag}`);
|
|
18
|
+
const jarPath = join(serverPath, JAR_NAME);
|
|
19
|
+
log.step("Backing up old version...");
|
|
20
|
+
await rename(jarPath, `${jarPath}.old`).catch(() => { });
|
|
21
|
+
log.step("Downloading...");
|
|
22
|
+
await downloadCore(release, jarPath, pct => progress(pct, JAR_NAME));
|
|
23
|
+
log.success("Update complete!");
|
|
24
|
+
log.info("Restart the server to apply changes");
|
|
25
|
+
outro("Update complete!");
|
|
26
|
+
}
|
|
27
|
+
export const updateCmd = defineCommand({
|
|
28
|
+
meta: { description: 'Update PowerNukkitX to the latest version' },
|
|
29
|
+
args: {
|
|
30
|
+
force: { type: 'boolean', description: 'Force update', default: false },
|
|
31
|
+
dev: { type: 'boolean', description: 'Dev version', default: false },
|
|
32
|
+
},
|
|
33
|
+
run: ({ args }) => runUpdate(args.force),
|
|
34
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
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
|
+
export const useCmd = defineCommand({
|
|
10
|
+
meta: { description: 'Set the default PowerNukkitX server directory' },
|
|
11
|
+
args: {
|
|
12
|
+
dir: { type: 'positional', description: 'Server path', required: false },
|
|
13
|
+
clear: { type: 'boolean', description: 'Remove default server', default: false },
|
|
14
|
+
},
|
|
15
|
+
async run({ args }) {
|
|
16
|
+
intro(`📌 ${"Default server"}`);
|
|
17
|
+
if (args.clear) {
|
|
18
|
+
await setDefaultServer(null);
|
|
19
|
+
log.success("Default server cleared");
|
|
20
|
+
outro("Default server cleared");
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
let target = args.dir;
|
|
24
|
+
if (!target) {
|
|
25
|
+
const servers = detectServers();
|
|
26
|
+
target = servers.length > 0
|
|
27
|
+
? await promptSelect("Select a server:", servers.map(s => ({ value: s, label: s })))
|
|
28
|
+
: await promptText({ message: "Server path:", defaultValue: cwd() });
|
|
29
|
+
}
|
|
30
|
+
const absPath = resolve(cwd(), target);
|
|
31
|
+
if (!existsSync(join(absPath, JAR_NAME))) {
|
|
32
|
+
log.error(`powernukkitx.jar not found in: ${absPath}`);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
await setDefaultServer(absPath);
|
|
36
|
+
log.success(`Default server set: ${absPath}`);
|
|
37
|
+
outro(`Default server set: ${absPath}`);
|
|
38
|
+
},
|
|
39
|
+
});
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import https from 'node:https';
|
|
2
|
+
import http from 'node:http';
|
|
3
|
+
import { createWriteStream } from 'node:fs';
|
|
4
|
+
const REDIRECT_CODES = [301, 302, 303, 307, 308];
|
|
5
|
+
export class NetworkError extends Error {
|
|
6
|
+
statusCode;
|
|
7
|
+
constructor(message, statusCode) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.statusCode = statusCode;
|
|
10
|
+
this.name = 'NetworkError';
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
|
|
14
|
+
function get(url, timeoutMs, headers) {
|
|
15
|
+
return new Promise((resolve, reject) => {
|
|
16
|
+
const mod = url.startsWith('https') ? https : http;
|
|
17
|
+
const req = mod.get(url, { timeout: timeoutMs, headers }, (res) => {
|
|
18
|
+
let body = '';
|
|
19
|
+
res.on('data', (chunk) => (body += chunk.toString()));
|
|
20
|
+
res.on('end', () => resolve({ statusCode: res.statusCode ?? 0, location: res.headers.location, body }));
|
|
21
|
+
res.on('error', reject);
|
|
22
|
+
});
|
|
23
|
+
req.on('error', reject);
|
|
24
|
+
req.on('timeout', function () {
|
|
25
|
+
this.destroy();
|
|
26
|
+
reject(new NetworkError('Request timed out'));
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
async function getFollow(url, timeout, headers, redirects = 5) {
|
|
31
|
+
if (redirects <= 0)
|
|
32
|
+
throw new NetworkError('Too many redirects');
|
|
33
|
+
const res = await get(url, timeout, { 'User-Agent': 'pnx-cli', ...headers });
|
|
34
|
+
if (REDIRECT_CODES.includes(res.statusCode) && res.location) {
|
|
35
|
+
return getFollow(res.location, timeout, headers, redirects - 1);
|
|
36
|
+
}
|
|
37
|
+
if (res.statusCode !== 200)
|
|
38
|
+
throw new NetworkError(`HTTP ${res.statusCode}`, res.statusCode);
|
|
39
|
+
return res.body;
|
|
40
|
+
}
|
|
41
|
+
export async function fetchJSON(url, opts) {
|
|
42
|
+
const retries = opts?.retries ?? 3;
|
|
43
|
+
const timeout = opts?.timeout ?? 15_000;
|
|
44
|
+
const headers = opts?.headers ?? {};
|
|
45
|
+
let lastErr = new Error('Unknown error');
|
|
46
|
+
for (let attempt = 1; attempt <= retries; attempt++) {
|
|
47
|
+
try {
|
|
48
|
+
return JSON.parse(await getFollow(url, timeout, headers));
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
lastErr = err;
|
|
52
|
+
if (attempt < retries) {
|
|
53
|
+
opts?.onRetry?.(lastErr.message, attempt, retries);
|
|
54
|
+
await sleep(2 ** (attempt - 1) * 1000);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
throw lastErr;
|
|
59
|
+
}
|
|
60
|
+
function stream(url, dest, timeout, onData) {
|
|
61
|
+
return new Promise((resolve, reject) => {
|
|
62
|
+
const mod = url.startsWith('https') ? https : http;
|
|
63
|
+
const file = createWriteStream(dest);
|
|
64
|
+
const req = mod.get(url, { timeout, headers: { 'User-Agent': 'pnx-cli' } }, (res) => {
|
|
65
|
+
const code = res.statusCode ?? 0;
|
|
66
|
+
if (REDIRECT_CODES.includes(code) && res.headers.location) {
|
|
67
|
+
file.close();
|
|
68
|
+
resolve(stream(res.headers.location, dest, timeout, onData));
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (code !== 200) {
|
|
72
|
+
file.close();
|
|
73
|
+
reject(new NetworkError(`HTTP ${code}`, code));
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const total = parseInt(res.headers['content-length'] || '0', 10);
|
|
77
|
+
res.on('data', (chunk) => onData(chunk, total));
|
|
78
|
+
res.pipe(file);
|
|
79
|
+
file.on('finish', () => { file.close(); resolve(); });
|
|
80
|
+
file.on('error', reject);
|
|
81
|
+
});
|
|
82
|
+
req.on('error', err => { file.close(); reject(err); });
|
|
83
|
+
req.on('timeout', function () {
|
|
84
|
+
this.destroy();
|
|
85
|
+
file.close();
|
|
86
|
+
reject(new NetworkError('Download timed out'));
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
export async function downloadFile(url, dest, opts) {
|
|
91
|
+
const retries = opts?.retries ?? 3;
|
|
92
|
+
const timeout = opts?.timeout ?? 120_000;
|
|
93
|
+
let lastErr = new Error('Unknown error');
|
|
94
|
+
for (let attempt = 1; attempt <= retries; attempt++) {
|
|
95
|
+
try {
|
|
96
|
+
let downloaded = 0;
|
|
97
|
+
let lastPct = -1;
|
|
98
|
+
await stream(url, dest, timeout, (chunk, total) => {
|
|
99
|
+
downloaded += chunk.length;
|
|
100
|
+
if (opts?.onProgress && total > 0) {
|
|
101
|
+
const pct = Math.round((downloaded / total) * 100);
|
|
102
|
+
if (pct !== lastPct && pct % 5 === 0) {
|
|
103
|
+
opts.onProgress(pct, downloaded, total);
|
|
104
|
+
lastPct = pct;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
catch (err) {
|
|
111
|
+
lastErr = err;
|
|
112
|
+
if (attempt < retries) {
|
|
113
|
+
opts?.onRetry?.(lastErr.message, attempt, retries);
|
|
114
|
+
await sleep(2 ** (attempt - 1) * 1000);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
throw lastErr;
|
|
119
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { homedir } from 'node:os';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
export function pnxHome() {
|
|
4
|
+
return process.env.PNX_HOME || join(homedir(), '.pnx-cli');
|
|
5
|
+
}
|
|
6
|
+
export function storePath(name) {
|
|
7
|
+
return join(pnxHome(), name);
|
|
8
|
+
}
|
|
9
|
+
export const JAR_NAME = 'powernukkitx.jar';
|
|
10
|
+
export const GITHUB_LATEST_RELEASE = 'https://api.github.com/repos/PowerNukkitX/PowerNukkitX/releases/latest';
|
|
11
|
+
export const GITHUB_RELEASES = 'https://api.github.com/repos/PowerNukkitX/PowerNukkitX/releases';
|
|
12
|
+
export const SCRIPTS_BASE = 'https://raw.githubusercontent.com/PowerNukkitX/scripts/master';
|
|
13
|
+
export function apiBase() {
|
|
14
|
+
return (process.env.PNX_API_URL ?? 'https://api.powernukkitx.org').replace(/\/$/, '');
|
|
15
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { readFile, writeFile, mkdir, rm } from 'node:fs/promises';
|
|
3
|
+
import { dirname } from 'node:path';
|
|
4
|
+
import { storePath } from './paths.js';
|
|
5
|
+
export async function readJson(name, fallback) {
|
|
6
|
+
try {
|
|
7
|
+
const file = storePath(name);
|
|
8
|
+
if (existsSync(file))
|
|
9
|
+
return JSON.parse(await readFile(file, 'utf-8'));
|
|
10
|
+
}
|
|
11
|
+
catch { }
|
|
12
|
+
return fallback;
|
|
13
|
+
}
|
|
14
|
+
export async function writeJson(name, data) {
|
|
15
|
+
const file = storePath(name);
|
|
16
|
+
await mkdir(dirname(file), { recursive: true });
|
|
17
|
+
await writeFile(file, JSON.stringify(data, null, 2), 'utf-8');
|
|
18
|
+
}
|
|
19
|
+
export async function removeFile(name) {
|
|
20
|
+
try {
|
|
21
|
+
await rm(storePath(name));
|
|
22
|
+
}
|
|
23
|
+
catch { }
|
|
24
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { readdirSync, statSync } from 'node:fs';
|
|
2
|
+
import { mkdir, cp, readdir } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
const IGNORE = new Set(['logs', 'cache', 'crash-reports', 'backups', '.git']);
|
|
5
|
+
export function dirSize(dir) {
|
|
6
|
+
let total = 0;
|
|
7
|
+
try {
|
|
8
|
+
for (const f of readdirSync(dir)) {
|
|
9
|
+
const full = join(dir, f);
|
|
10
|
+
const s = statSync(full);
|
|
11
|
+
total += s.isDirectory() ? dirSize(full) : s.size;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
catch { }
|
|
15
|
+
return total;
|
|
16
|
+
}
|
|
17
|
+
export function fmtSize(b) {
|
|
18
|
+
if (b < 1024)
|
|
19
|
+
return `${b} B`;
|
|
20
|
+
if (b < 1024 ** 2)
|
|
21
|
+
return `${(b / 1024).toFixed(1)} KB`;
|
|
22
|
+
return `${(b / 1024 / 1024).toFixed(1)} MB`;
|
|
23
|
+
}
|
|
24
|
+
export async function createBackup(serverDir) {
|
|
25
|
+
const ts = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
|
|
26
|
+
const destDir = join(serverDir, 'backups', `pnx-backup-${ts}`);
|
|
27
|
+
await mkdir(destDir, { recursive: true });
|
|
28
|
+
for (const item of await readdir(serverDir)) {
|
|
29
|
+
if (IGNORE.has(item) || item.startsWith('.'))
|
|
30
|
+
continue;
|
|
31
|
+
await cp(join(serverDir, item), join(destDir, item), { recursive: true, force: true });
|
|
32
|
+
}
|
|
33
|
+
return { path: destDir, size: dirSize(destDir) };
|
|
34
|
+
}
|