@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
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
{
|
|
2
|
+
"cli": { "description": "PowerNukkitX CLI — Gérez vos serveurs Minecraft Bedrock" },
|
|
3
|
+
"menu": {
|
|
4
|
+
"title": "Menu PowerNukkitX",
|
|
5
|
+
"prompt": "Que voulez-vous faire ?",
|
|
6
|
+
"create": "Créer un serveur",
|
|
7
|
+
"start": "Démarrer le serveur",
|
|
8
|
+
"stop": "Arrêter le serveur",
|
|
9
|
+
"plugins": "Gérer les plugins",
|
|
10
|
+
"update": "Mettre à jour le core",
|
|
11
|
+
"doctor": "Diagnostic",
|
|
12
|
+
"backup": "Sauvegarde",
|
|
13
|
+
"info": "Informations",
|
|
14
|
+
"lang": "Changer la langue",
|
|
15
|
+
"quit": "Quitter",
|
|
16
|
+
"statusRunning": "Serveur en cours (PID {0})",
|
|
17
|
+
"statusStopped": "Serveur arrêté",
|
|
18
|
+
"statusNoServer": "Aucun serveur détecté",
|
|
19
|
+
"bye": "À bientôt !"
|
|
20
|
+
},
|
|
21
|
+
"init": {
|
|
22
|
+
"title": "Créer un serveur PowerNukkitX",
|
|
23
|
+
"promptDir": "Répertoire du serveur ?",
|
|
24
|
+
"alreadyExists": "Un serveur existe déjà ici",
|
|
25
|
+
"fetchingInfo": "Récupération de la dernière version...",
|
|
26
|
+
"latestVersion": "Dernière version",
|
|
27
|
+
"downloading": "Téléchargement",
|
|
28
|
+
"downloaded": "Téléchargé ({0} Mo)",
|
|
29
|
+
"downloadingScripts": "Téléchargement des scripts...",
|
|
30
|
+
"scriptsDownloaded": "Scripts téléchargés",
|
|
31
|
+
"success": "Serveur prêt !",
|
|
32
|
+
"launchPrompt": "Démarrer le serveur maintenant ?"
|
|
33
|
+
},
|
|
34
|
+
"start": {
|
|
35
|
+
"title": "Démarrage du serveur",
|
|
36
|
+
"notFound": "Aucun serveur trouvé ici",
|
|
37
|
+
"createPrompt": "Créer un serveur maintenant ?",
|
|
38
|
+
"checkingJava": "Vérification de Java...",
|
|
39
|
+
"javaOk": "Java {0} détecté",
|
|
40
|
+
"javaNotFound": "Java 21+ requis — https://adoptium.net/",
|
|
41
|
+
"starting": "Démarrage...",
|
|
42
|
+
"running": "Serveur en cours d'exécution (PID {0})",
|
|
43
|
+
"stopped": "Serveur arrêté (code {0})",
|
|
44
|
+
"restarting": "Redémarrage...",
|
|
45
|
+
"generated": "Commande générée"
|
|
46
|
+
},
|
|
47
|
+
"stop": {
|
|
48
|
+
"title": "Arrêt du serveur",
|
|
49
|
+
"notRunning": "Aucun serveur en cours d'exécution",
|
|
50
|
+
"killing": "Arrêt du serveur (PID {0})...",
|
|
51
|
+
"done": "Serveur arrêté",
|
|
52
|
+
"failed": "Échec de l'arrêt"
|
|
53
|
+
},
|
|
54
|
+
"update": {
|
|
55
|
+
"title": "Mise à jour PowerNukkitX",
|
|
56
|
+
"checking": "Vérification des mises à jour...",
|
|
57
|
+
"notFound": "Aucun serveur trouvé",
|
|
58
|
+
"available": "Nouvelle version disponible : {0}",
|
|
59
|
+
"upToDate": "Déjà à jour ({0})",
|
|
60
|
+
"saving": "Sauvegarde de l'ancienne version...",
|
|
61
|
+
"downloading": "Téléchargement...",
|
|
62
|
+
"done": "Mise à jour terminée !",
|
|
63
|
+
"restart": "Redémarrez le serveur pour appliquer les changements"
|
|
64
|
+
},
|
|
65
|
+
"doctor": {
|
|
66
|
+
"title": "Diagnostic du serveur",
|
|
67
|
+
"checking": "Analyse en cours...",
|
|
68
|
+
"java": "Java",
|
|
69
|
+
"server": "Serveur",
|
|
70
|
+
"system": "Système",
|
|
71
|
+
"plugins": "Plugins & Mondes",
|
|
72
|
+
"network": "Réseau",
|
|
73
|
+
"done": "Diagnostic terminé",
|
|
74
|
+
"javaMissing": "Java 21+ requis",
|
|
75
|
+
"jarMissing": "powernukkitx.jar manquant",
|
|
76
|
+
"noServer": "Aucun serveur — exécutez 'pnx init'",
|
|
77
|
+
"portFree": "Port {0} disponible",
|
|
78
|
+
"portBusy": "Port {0} occupé"
|
|
79
|
+
},
|
|
80
|
+
"info": {
|
|
81
|
+
"title": "Informations du serveur",
|
|
82
|
+
"server": "Serveur",
|
|
83
|
+
"system": "Système",
|
|
84
|
+
"status": "Statut",
|
|
85
|
+
"installed": "Installé",
|
|
86
|
+
"notInstalled": "Non installé",
|
|
87
|
+
"path": "Chemin",
|
|
88
|
+
"plugins": "Plugins",
|
|
89
|
+
"worlds": "Mondes",
|
|
90
|
+
"ram": "RAM libre",
|
|
91
|
+
"ready": "Prêt à démarrer avec 'pnx start'",
|
|
92
|
+
"notReady": "Exécutez 'pnx init' pour créer un serveur"
|
|
93
|
+
},
|
|
94
|
+
"backup": {
|
|
95
|
+
"title": "Sauvegarde",
|
|
96
|
+
"noServer": "Aucun serveur détecté",
|
|
97
|
+
"collecting": "Collecte des fichiers...",
|
|
98
|
+
"size": "Taille estimée : {0}",
|
|
99
|
+
"copying": "Copie en cours...",
|
|
100
|
+
"done": "Sauvegarde créée : {0} ({1})"
|
|
101
|
+
},
|
|
102
|
+
"config": {
|
|
103
|
+
"title": "Configuration",
|
|
104
|
+
"noServer": "Aucun serveur détecté",
|
|
105
|
+
"notFound": "Aucun fichier de config trouvé",
|
|
106
|
+
"editHint": "Modifiez {0} pour changer ces valeurs"
|
|
107
|
+
},
|
|
108
|
+
"use": {
|
|
109
|
+
"title": "Serveur par défaut",
|
|
110
|
+
"prompt": "Sélectionnez un serveur :",
|
|
111
|
+
"pathPrompt": "Chemin du serveur :",
|
|
112
|
+
"set": "Serveur par défaut défini : {0}",
|
|
113
|
+
"cleared": "Serveur par défaut supprimé",
|
|
114
|
+
"notFound": "powernukkitx.jar introuvable dans : {0}"
|
|
115
|
+
},
|
|
116
|
+
"lang": {
|
|
117
|
+
"title": "Langue",
|
|
118
|
+
"prompt": "Choisissez votre langue",
|
|
119
|
+
"set": "Langue définie sur {0}"
|
|
120
|
+
},
|
|
121
|
+
"logs": {
|
|
122
|
+
"title": "Logs du serveur",
|
|
123
|
+
"noServer": "Aucun serveur détecté",
|
|
124
|
+
"noLogs": "Aucun fichier de logs trouvé",
|
|
125
|
+
"watching": "Lecture de {0}",
|
|
126
|
+
"liveMode": "Mode live (Ctrl+C pour quitter)"
|
|
127
|
+
},
|
|
128
|
+
"console": {
|
|
129
|
+
"title": "Console du serveur",
|
|
130
|
+
"noServer": "Aucun serveur détecté",
|
|
131
|
+
"rconDisabled": "RCON désactivé dans server.properties",
|
|
132
|
+
"rconEnable": "Activez enable-rcon=true dans server.properties",
|
|
133
|
+
"connecting": "Connexion RCON...",
|
|
134
|
+
"connected": "Connecté — tapez des commandes (exit pour quitter)",
|
|
135
|
+
"prompt": "> ",
|
|
136
|
+
"disconnected": "Déconnecté"
|
|
137
|
+
},
|
|
138
|
+
"plugin": {
|
|
139
|
+
"titleList": "Catalogue de plugins",
|
|
140
|
+
"titleInstall": "Installation de plugins",
|
|
141
|
+
"titleInstalled": "Plugins installés",
|
|
142
|
+
"titleRemove": "Suppression de plugins",
|
|
143
|
+
"titleInfo": "Informations du plugin",
|
|
144
|
+
"titleSearch": "Recherche de plugins",
|
|
145
|
+
"titleUpdate": "Mise à jour des plugins",
|
|
146
|
+
"fetching": "Récupération depuis la marketplace...",
|
|
147
|
+
"fetchError": "Erreur : {0}",
|
|
148
|
+
"notFound": "Plugin introuvable : {0}",
|
|
149
|
+
"noPlugins": "Aucun plugin installé",
|
|
150
|
+
"noPluginsDir": "Aucun répertoire plugins/",
|
|
151
|
+
"noneSelected": "Aucun plugin sélectionné",
|
|
152
|
+
"selectInstall": "Sélectionnez les plugins à installer :",
|
|
153
|
+
"selectRemove": "Sélectionnez les plugins à supprimer :",
|
|
154
|
+
"selectUpdate": "Sélectionnez les plugins à mettre à jour :",
|
|
155
|
+
"searchPrompt": "Rechercher des plugins :",
|
|
156
|
+
"slugPrompt": "Slug du plugin :",
|
|
157
|
+
"installSuccess": "{0} installé",
|
|
158
|
+
"installError": "Erreur lors de l'installation de {0}",
|
|
159
|
+
"removeConfirm": "Supprimer {0} ?",
|
|
160
|
+
"removed": "{0} supprimé",
|
|
161
|
+
"alreadyLatest": "{0} est déjà à jour (v{1})",
|
|
162
|
+
"updated": "{0} mis à jour vers v{1}",
|
|
163
|
+
"noRelease": "Aucune version disponible pour {0}",
|
|
164
|
+
"noFile": "Aucun fichier pour {0}",
|
|
165
|
+
"urlInvalid": "L'URL doit pointer vers un .jar",
|
|
166
|
+
"restartHint": "Redémarrez le serveur pour charger les plugins",
|
|
167
|
+
"count": "{0} plugin(s)",
|
|
168
|
+
"versionsAvailable": "Versions disponibles :"
|
|
169
|
+
},
|
|
170
|
+
"errors": {
|
|
171
|
+
"timeout": "Délai dépassé",
|
|
172
|
+
"network": "Erreur réseau : {0}"
|
|
173
|
+
}
|
|
174
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
2
|
+
import { storePath } from '../infra/paths.js';
|
|
3
|
+
import en from './en.json' with { type: 'json' };
|
|
4
|
+
import fr from './fr.json' with { type: 'json' };
|
|
5
|
+
const tables = { en, fr };
|
|
6
|
+
let _code = 'fr';
|
|
7
|
+
let _strings = fr;
|
|
8
|
+
/** Langue sauvegardée dans le store (config.json), sinon null. */
|
|
9
|
+
function savedLang() {
|
|
10
|
+
try {
|
|
11
|
+
const file = storePath('config.json');
|
|
12
|
+
if (existsSync(file)) {
|
|
13
|
+
const cfg = JSON.parse(readFileSync(file, 'utf-8'));
|
|
14
|
+
if (cfg.lang === 'en' || cfg.lang === 'fr')
|
|
15
|
+
return cfg.lang;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
catch { /* ignore */ }
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
function envLang() {
|
|
22
|
+
const l = process.env.PNX_LANG || process.env.LANG || '';
|
|
23
|
+
if (l.startsWith('fr'))
|
|
24
|
+
return 'fr';
|
|
25
|
+
if (l.startsWith('en'))
|
|
26
|
+
return 'en';
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
/** Applique une langue en mémoire. */
|
|
30
|
+
export function setLang(code) {
|
|
31
|
+
_code = tables[code] ? code : 'fr';
|
|
32
|
+
_strings = tables[_code];
|
|
33
|
+
}
|
|
34
|
+
/** Résout la langue: flag explicite > store > env > défaut fr. */
|
|
35
|
+
export function initLang(explicit) {
|
|
36
|
+
const code = (explicit === 'en' || explicit === 'fr' ? explicit : null) ?? savedLang() ?? envLang() ?? 'fr';
|
|
37
|
+
setLang(code);
|
|
38
|
+
return _code;
|
|
39
|
+
}
|
|
40
|
+
export function currentLang() { return _code; }
|
|
41
|
+
/** Traduit une clé pointée, interpole {0} {1}…, fallback anglais puis clé. */
|
|
42
|
+
export function t(key, ...args) {
|
|
43
|
+
const resolve = (table) => {
|
|
44
|
+
let v = table;
|
|
45
|
+
for (const k of key.split('.')) {
|
|
46
|
+
if (v && typeof v === 'object' && k in v) {
|
|
47
|
+
v = v[k];
|
|
48
|
+
}
|
|
49
|
+
else
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
return typeof v === 'string' ? v : undefined;
|
|
53
|
+
};
|
|
54
|
+
const str = resolve(_strings) ?? resolve(tables.en);
|
|
55
|
+
if (str === undefined)
|
|
56
|
+
return key;
|
|
57
|
+
return str.replace(/{(\d+)}/g, (_, i) => String(args[parseInt(i)] ?? `{${i}}`));
|
|
58
|
+
}
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,260 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { Command } from 'commander';
|
|
3
|
-
import { select } from '@inquirer/prompts';
|
|
4
|
-
import chalk from 'chalk';
|
|
5
|
-
import { logo, highlight, dim, success } from './utils/logger.js';
|
|
6
|
-
import { t, initLanguage, setLanguage, saveLanguage } from './lang/index.js';
|
|
7
|
-
import { initCmd } from './commands/init.js';
|
|
8
|
-
import { startCmd } from './commands/start.js';
|
|
9
|
-
import { pluginInstallCmd, pluginListCmd, pluginInstalledCmd, pluginRemoveCmd, pluginSearchCmd, pluginInfoCmd, pluginUpdateCmd } from './commands/plugin.js';
|
|
10
|
-
import { updateCmd } from './commands/update.js';
|
|
11
|
-
import { infoCmd } from './commands/info.js';
|
|
12
|
-
import { doctorCmd } from './commands/doctor.js';
|
|
13
|
-
import { backupCmd } from './commands/backup.js';
|
|
14
|
-
import { configCmd } from './commands/config.js';
|
|
15
|
-
import { detectServer } from './utils/server.js';
|
|
16
|
-
import { checkForUpdates } from './utils/updater.js';
|
|
17
|
-
const program = new Command();
|
|
18
|
-
// -- detect --lang from argv before Commander parses
|
|
19
|
-
const langIdx = process.argv.indexOf('--lang');
|
|
20
|
-
const cliLang = langIdx >= 0 && process.argv.length > langIdx + 1
|
|
21
|
-
? process.argv[langIdx + 1]
|
|
22
|
-
: undefined;
|
|
23
|
-
initLanguage(cliLang);
|
|
24
|
-
program
|
|
25
|
-
.name('pnx')
|
|
26
|
-
.description(highlight('PowerNukkitX CLI — Manage your Minecraft Bedrock servers'))
|
|
27
|
-
.version('0.2.0')
|
|
28
|
-
.option('--lang <code>', 'Set language (en, fr)')
|
|
29
|
-
.addHelpText('before', logo)
|
|
30
|
-
.addHelpText('after', `\n ${dim('📖')} ${highlight('Documentation')}: https://docs.powernukkitx.org\n`);
|
|
31
|
-
program
|
|
32
|
-
.command('init')
|
|
33
|
-
.description('Create a new PowerNukkitX server')
|
|
34
|
-
.option('-d, --dir <path>', 'Target directory')
|
|
35
|
-
.option('--dev', 'Install development version')
|
|
36
|
-
.action(async (opts) => { await initCmd(opts); });
|
|
37
|
-
program
|
|
38
|
-
.command('start')
|
|
39
|
-
.description('Start the PowerNukkitX server')
|
|
40
|
-
.option('-g, --generate-only', 'Generate the start command without running')
|
|
41
|
-
.option('-r, --restart', 'Auto-restart on crash')
|
|
42
|
-
.option('-m, --memory <size>', 'Memory allocation (e.g. 2G, 4G)', '2G')
|
|
43
|
-
.option('-i, --interactive', 'Interactive mode with prompts')
|
|
44
|
-
.action(async (opts) => {
|
|
45
|
-
await checkForUpdates();
|
|
46
|
-
await startCmd(opts);
|
|
47
|
-
});
|
|
48
|
-
const pluginCmd = program
|
|
49
|
-
.command('plugin')
|
|
50
|
-
.description('Plugin management')
|
|
51
|
-
.alias('plugins');
|
|
52
|
-
pluginCmd
|
|
53
|
-
.command('install [names...]')
|
|
54
|
-
.description('Install plugins (slug from marketplace or direct .jar URL)')
|
|
55
|
-
.action(async (names) => { await pluginInstallCmd(names); });
|
|
56
|
-
pluginCmd
|
|
57
|
-
.command('list')
|
|
58
|
-
.description('List available plugins from the marketplace')
|
|
59
|
-
.option('-s, --sort <type>', 'Sort by: newest, updated, stars')
|
|
60
|
-
.option('-l, --limit <n>', 'Results per page (max 100)')
|
|
61
|
-
.option('-p, --page <n>', 'Page number')
|
|
62
|
-
.option('-q, --query <term>', 'Search query')
|
|
63
|
-
.action(async (opts) => {
|
|
64
|
-
await pluginListCmd(opts);
|
|
65
|
-
});
|
|
66
|
-
pluginCmd
|
|
67
|
-
.command('search [term]')
|
|
68
|
-
.description('Search plugins by name, description, or author')
|
|
69
|
-
.action(async (term) => { await pluginSearchCmd(term); });
|
|
70
|
-
pluginCmd
|
|
71
|
-
.command('info [slug]')
|
|
72
|
-
.description('Show detailed plugin information')
|
|
73
|
-
.action(async (slug) => { await pluginInfoCmd(slug); });
|
|
74
|
-
pluginCmd
|
|
75
|
-
.command('installed')
|
|
76
|
-
.description('List locally installed plugins')
|
|
77
|
-
.action(async () => { await pluginInstalledCmd(); });
|
|
78
|
-
pluginCmd
|
|
79
|
-
.command('update [names...]')
|
|
80
|
-
.description('Update installed plugins to latest version')
|
|
81
|
-
.action(async (names) => { await pluginUpdateCmd(names); });
|
|
82
|
-
pluginCmd
|
|
83
|
-
.command('remove [names...]')
|
|
84
|
-
.description('Remove installed plugins')
|
|
85
|
-
.alias('rm')
|
|
86
|
-
.action(async (names) => { await pluginRemoveCmd(names); });
|
|
87
|
-
program
|
|
88
|
-
.command('update')
|
|
89
|
-
.description('Update powernukkitx.jar to the latest version')
|
|
90
|
-
.option('--dev', 'Install development version')
|
|
91
|
-
.option('-f, --force', 'Force update even if versions match')
|
|
92
|
-
.action(async (opts) => { await updateCmd(opts); });
|
|
93
|
-
program
|
|
94
|
-
.command('doctor')
|
|
95
|
-
.description('Diagnose server health')
|
|
96
|
-
.action(async () => { await doctorCmd(); });
|
|
97
|
-
program
|
|
98
|
-
.command('backup')
|
|
99
|
-
.description('Create a server backup')
|
|
100
|
-
.action(async () => { await backupCmd(); });
|
|
101
|
-
program
|
|
102
|
-
.command('config')
|
|
103
|
-
.description('View server configuration (pnx.yml or server.properties)')
|
|
104
|
-
.action(async () => { await configCmd(); });
|
|
105
|
-
program
|
|
106
|
-
.command('info')
|
|
107
|
-
.description('Show server information')
|
|
108
|
-
.alias('status')
|
|
109
|
-
.action(async () => { await infoCmd(); });
|
|
110
|
-
program
|
|
111
|
-
.command('lang')
|
|
112
|
-
.description('Change language / Changer la langue')
|
|
113
|
-
.option('-s, --set <code>', 'Set language (en, fr)')
|
|
114
|
-
.action(async (opts) => {
|
|
115
|
-
if (opts.set) {
|
|
116
|
-
setLanguage(opts.set);
|
|
117
|
-
saveLanguage(opts.set);
|
|
118
|
-
success(t('lang.detected', opts.set));
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
121
|
-
const lang = await select({
|
|
122
|
-
message: t('lang.prompt'),
|
|
123
|
-
choices: [
|
|
124
|
-
{ name: 'English', value: 'en' },
|
|
125
|
-
{ name: 'Français', value: 'fr' },
|
|
126
|
-
],
|
|
127
|
-
});
|
|
128
|
-
setLanguage(lang);
|
|
129
|
-
saveLanguage(lang);
|
|
130
|
-
success(t('lang.detected', lang));
|
|
131
|
-
});
|
|
132
|
-
// ─── interactive menu ────────────────────────────────────────
|
|
133
|
-
async function pickLanguage() {
|
|
134
|
-
const lang = await select({
|
|
135
|
-
message: '🌐 Select your language / Choisissez votre langue',
|
|
136
|
-
default: 'en',
|
|
137
|
-
choices: [
|
|
138
|
-
{ name: '🇬🇧 English', value: 'en' },
|
|
139
|
-
{ name: '🇫🇷 Français', value: 'fr' },
|
|
140
|
-
],
|
|
141
|
-
});
|
|
142
|
-
setLanguage(lang);
|
|
143
|
-
saveLanguage(lang);
|
|
144
|
-
}
|
|
145
|
-
async function showMainMenu() {
|
|
146
|
-
await pickLanguage();
|
|
147
|
-
while (true) {
|
|
148
|
-
const serverPath = detectServer();
|
|
149
|
-
const hasServer = serverPath !== null;
|
|
150
|
-
const statusTag = hasServer
|
|
151
|
-
? chalk.green(`● ${t('info.installed')}`)
|
|
152
|
-
: chalk.red(`○ ${t('info.notInstalled')}`);
|
|
153
|
-
const choice = await select({
|
|
154
|
-
message: `${highlight.bold(t('lang.menu'))} ${dim('·')} ${statusTag}`,
|
|
155
|
-
pageSize: 10,
|
|
156
|
-
choices: [
|
|
157
|
-
{ name: `🚀 ${t('init.title')}`, value: 'init' },
|
|
158
|
-
{ name: `▶️ ${t('start.title')}`, value: 'start' },
|
|
159
|
-
{ name: `📦 ${t('plugin.titleList')}`, value: 'plugin' },
|
|
160
|
-
{ name: `🔄 ${t('update.title')}`, value: 'update' },
|
|
161
|
-
{ name: `🩺 ${t('doctor.title')}`, value: 'doctor' },
|
|
162
|
-
{ name: `💾 ${t('backup.title')}`, value: 'backup' },
|
|
163
|
-
{ name: `⚙️ ${t('config.title')}`, value: 'config' },
|
|
164
|
-
{ name: `📊 ${t('info.title')}`, value: 'info' },
|
|
165
|
-
{ name: `🌐 ${t('lang.prompt')}`, value: 'lang' },
|
|
166
|
-
{ name: `❌ ${t('lang.exit')}`, value: 'exit' },
|
|
167
|
-
],
|
|
168
|
-
});
|
|
169
|
-
console.log();
|
|
170
|
-
switch (choice) {
|
|
171
|
-
case 'init':
|
|
172
|
-
await initCmd({});
|
|
173
|
-
break;
|
|
174
|
-
case 'start':
|
|
175
|
-
await checkForUpdates();
|
|
176
|
-
await startCmd({});
|
|
177
|
-
break;
|
|
178
|
-
case 'plugin':
|
|
179
|
-
await pluginListCmd({});
|
|
180
|
-
await showPluginSubmenu();
|
|
181
|
-
break;
|
|
182
|
-
case 'update':
|
|
183
|
-
await updateCmd({});
|
|
184
|
-
break;
|
|
185
|
-
case 'doctor':
|
|
186
|
-
await doctorCmd();
|
|
187
|
-
break;
|
|
188
|
-
case 'backup':
|
|
189
|
-
await backupCmd();
|
|
190
|
-
break;
|
|
191
|
-
case 'config':
|
|
192
|
-
await configCmd();
|
|
193
|
-
break;
|
|
194
|
-
case 'info':
|
|
195
|
-
await infoCmd();
|
|
196
|
-
break;
|
|
197
|
-
case 'lang':
|
|
198
|
-
await pickLanguage();
|
|
199
|
-
break;
|
|
200
|
-
case 'exit':
|
|
201
|
-
process.exit(0);
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
async function showPluginSubmenu() {
|
|
206
|
-
while (true) {
|
|
207
|
-
const action = await select({
|
|
208
|
-
message: highlight.bold(t('lang.pluginMenu')),
|
|
209
|
-
pageSize: 6,
|
|
210
|
-
choices: [
|
|
211
|
-
{ name: `📥 ${t('plugin.titleInstall')}`, value: 'install' },
|
|
212
|
-
{ name: `📋 ${t('plugin.titleList')}`, value: 'list' },
|
|
213
|
-
{ name: `📖 ${t('plugin.titleInfo')}`, value: 'info' },
|
|
214
|
-
{ name: `🔄 ${t('plugin.titleUpdate')}`, value: 'update' },
|
|
215
|
-
{ name: `🗑️ ${t('plugin.titleRemove')}`, value: 'remove' },
|
|
216
|
-
{ name: `🔙 ${t('lang.back')}`, value: 'back' },
|
|
217
|
-
],
|
|
218
|
-
});
|
|
219
|
-
if (action === 'back')
|
|
220
|
-
return;
|
|
221
|
-
console.log();
|
|
222
|
-
switch (action) {
|
|
223
|
-
case 'install':
|
|
224
|
-
await pluginInstallCmd([]);
|
|
225
|
-
break;
|
|
226
|
-
case 'list':
|
|
227
|
-
await pluginListCmd({});
|
|
228
|
-
break;
|
|
229
|
-
case 'info':
|
|
230
|
-
await pluginInfoCmd();
|
|
231
|
-
break;
|
|
232
|
-
case 'update':
|
|
233
|
-
await pluginUpdateCmd([]);
|
|
234
|
-
break;
|
|
235
|
-
case 'remove':
|
|
236
|
-
await pluginRemoveCmd([]);
|
|
237
|
-
break;
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
// ─── decide: menu or direct command ───────────────────────────
|
|
242
|
-
async function main() {
|
|
243
|
-
const rawArgs = process.argv.slice(2);
|
|
244
|
-
const helpFlags = ['--help', '-h', '--version', '-V'];
|
|
245
|
-
const hasSubcommand = rawArgs.length > 0 && !rawArgs.every(a => a.startsWith('-'));
|
|
246
|
-
if (rawArgs.some(a => helpFlags.includes(a))) {
|
|
247
|
-
await program.parseAsync(process.argv);
|
|
248
|
-
}
|
|
249
|
-
else if (!hasSubcommand) {
|
|
250
|
-
console.log(logo);
|
|
251
|
-
await showMainMenu();
|
|
252
|
-
}
|
|
253
|
-
else {
|
|
254
|
-
await program.parseAsync(process.argv).catch((err) => {
|
|
255
|
-
console.error(err);
|
|
256
|
-
process.exit(1);
|
|
257
|
-
});
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
main();
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,121 @@
|
|
|
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
|
+
/** GET JSON avec suivi des redirections et retry exponentiel. */
|
|
42
|
+
export async function fetchJSON(url, opts) {
|
|
43
|
+
const retries = opts?.retries ?? 3;
|
|
44
|
+
const timeout = opts?.timeout ?? 15_000;
|
|
45
|
+
const headers = opts?.headers ?? {};
|
|
46
|
+
let lastErr = new Error('Unknown error');
|
|
47
|
+
for (let attempt = 1; attempt <= retries; attempt++) {
|
|
48
|
+
try {
|
|
49
|
+
return JSON.parse(await getFollow(url, timeout, headers));
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
lastErr = err;
|
|
53
|
+
if (attempt < retries) {
|
|
54
|
+
opts?.onRetry?.(lastErr.message, attempt, retries);
|
|
55
|
+
await sleep(2 ** (attempt - 1) * 1000);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
throw lastErr;
|
|
60
|
+
}
|
|
61
|
+
function stream(url, dest, timeout, onData) {
|
|
62
|
+
return new Promise((resolve, reject) => {
|
|
63
|
+
const mod = url.startsWith('https') ? https : http;
|
|
64
|
+
const file = createWriteStream(dest);
|
|
65
|
+
const req = mod.get(url, { timeout, headers: { 'User-Agent': 'pnx-cli' } }, (res) => {
|
|
66
|
+
const code = res.statusCode ?? 0;
|
|
67
|
+
if (REDIRECT_CODES.includes(code) && res.headers.location) {
|
|
68
|
+
file.close();
|
|
69
|
+
resolve(stream(res.headers.location, dest, timeout, onData));
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
if (code !== 200) {
|
|
73
|
+
file.close();
|
|
74
|
+
reject(new NetworkError(`HTTP ${code}`, code));
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const total = parseInt(res.headers['content-length'] || '0', 10);
|
|
78
|
+
res.on('data', (chunk) => onData(chunk, total));
|
|
79
|
+
res.pipe(file);
|
|
80
|
+
file.on('finish', () => { file.close(); resolve(); });
|
|
81
|
+
file.on('error', reject);
|
|
82
|
+
});
|
|
83
|
+
req.on('error', err => { file.close(); reject(err); });
|
|
84
|
+
req.on('timeout', function () {
|
|
85
|
+
this.destroy();
|
|
86
|
+
file.close();
|
|
87
|
+
reject(new NetworkError('Download timed out'));
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
/** Télécharge un fichier vers `dest` avec progression et retry. */
|
|
92
|
+
export async function downloadFile(url, dest, opts) {
|
|
93
|
+
const retries = opts?.retries ?? 3;
|
|
94
|
+
const timeout = opts?.timeout ?? 120_000;
|
|
95
|
+
let lastErr = new Error('Unknown error');
|
|
96
|
+
for (let attempt = 1; attempt <= retries; attempt++) {
|
|
97
|
+
try {
|
|
98
|
+
let downloaded = 0;
|
|
99
|
+
let lastPct = -1;
|
|
100
|
+
await stream(url, dest, timeout, (chunk, total) => {
|
|
101
|
+
downloaded += chunk.length;
|
|
102
|
+
if (opts?.onProgress && total > 0) {
|
|
103
|
+
const pct = Math.round((downloaded / total) * 100);
|
|
104
|
+
if (pct !== lastPct && pct % 5 === 0) {
|
|
105
|
+
opts.onProgress(pct, downloaded, total);
|
|
106
|
+
lastPct = pct;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
catch (err) {
|
|
113
|
+
lastErr = err;
|
|
114
|
+
if (attempt < retries) {
|
|
115
|
+
opts?.onRetry?.(lastErr.message, attempt, retries);
|
|
116
|
+
await sleep(2 ** (attempt - 1) * 1000);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
throw lastErr;
|
|
121
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { homedir } from 'node:os';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
/** Répertoire d'état du CLI (~/.pnx-cli), surchargeable via PNX_HOME (tests). */
|
|
4
|
+
export function pnxHome() {
|
|
5
|
+
return process.env.PNX_HOME || join(homedir(), '.pnx-cli');
|
|
6
|
+
}
|
|
7
|
+
/** Chemin absolu d'un fichier du store dans le répertoire d'état. */
|
|
8
|
+
export function storePath(name) {
|
|
9
|
+
return join(pnxHome(), name);
|
|
10
|
+
}
|
|
11
|
+
/** Nom du fichier jar du core. */
|
|
12
|
+
export const JAR_NAME = 'powernukkitx.jar';
|
|
13
|
+
/** Endpoint GitHub de la dernière release du core. */
|
|
14
|
+
export const GITHUB_LATEST_RELEASE = 'https://api.github.com/repos/PowerNukkitX/PowerNukkitX/releases/latest';
|
|
15
|
+
/** Base des scripts de démarrage officiels. */
|
|
16
|
+
export const SCRIPTS_BASE = 'https://raw.githubusercontent.com/PowerNukkitX/scripts/master';
|
|
17
|
+
/** Base de l'API marketplace, surchargeable via PNX_API_URL. */
|
|
18
|
+
export function apiBase() {
|
|
19
|
+
return (process.env.PNX_API_URL ?? 'https://api.powernukkitx.org').replace(/\/$/, '');
|
|
20
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
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
|
+
/** Lit un fichier JSON du store, renvoie `fallback` si absent ou illisible. */
|
|
6
|
+
export async function readJson(name, fallback) {
|
|
7
|
+
try {
|
|
8
|
+
const file = storePath(name);
|
|
9
|
+
if (existsSync(file))
|
|
10
|
+
return JSON.parse(await readFile(file, 'utf-8'));
|
|
11
|
+
}
|
|
12
|
+
catch { /* corrompu → fallback */ }
|
|
13
|
+
return fallback;
|
|
14
|
+
}
|
|
15
|
+
/** Écrit un objet en JSON dans le store (crée le répertoire au besoin). */
|
|
16
|
+
export async function writeJson(name, data) {
|
|
17
|
+
const file = storePath(name);
|
|
18
|
+
await mkdir(dirname(file), { recursive: true });
|
|
19
|
+
await writeFile(file, JSON.stringify(data, null, 2), 'utf-8');
|
|
20
|
+
}
|
|
21
|
+
/** Supprime un fichier du store (silencieux s'il n'existe pas). */
|
|
22
|
+
export async function removeFile(name) {
|
|
23
|
+
try {
|
|
24
|
+
await rm(storePath(name));
|
|
25
|
+
}
|
|
26
|
+
catch { /* absent */ }
|
|
27
|
+
}
|