@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.
Files changed (81) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +110 -101
  3. package/dist/cli.js +34 -0
  4. package/dist/commands/backup.d.ts +2 -1
  5. package/dist/commands/backup.js +23 -74
  6. package/dist/commands/config.d.ts +1 -1
  7. package/dist/commands/config.js +60 -114
  8. package/dist/commands/console.d.ts +2 -0
  9. package/dist/commands/console.js +98 -0
  10. package/dist/commands/doctor.d.ts +2 -1
  11. package/dist/commands/doctor.js +97 -119
  12. package/dist/commands/info.d.ts +2 -1
  13. package/dist/commands/info.js +41 -36
  14. package/dist/commands/init.d.ts +2 -4
  15. package/dist/commands/init.js +87 -73
  16. package/dist/commands/install.js +45 -0
  17. package/dist/commands/logs.d.ts +2 -0
  18. package/dist/commands/logs.js +60 -0
  19. package/dist/commands/menu.js +94 -0
  20. package/dist/commands/plugin/index.d.ts +2 -0
  21. package/dist/commands/plugin/index.js +13 -0
  22. package/dist/commands/plugin/info.d.ts +2 -0
  23. package/dist/commands/plugin/info.js +39 -0
  24. package/dist/commands/plugin/install.d.ts +2 -0
  25. package/dist/commands/plugin/install.js +56 -0
  26. package/dist/commands/plugin/installed.d.ts +2 -0
  27. package/dist/commands/plugin/installed.js +41 -0
  28. package/dist/commands/plugin/list.d.ts +2 -0
  29. package/dist/commands/plugin/list.js +47 -0
  30. package/dist/commands/plugin/remove.d.ts +2 -0
  31. package/dist/commands/plugin/remove.js +44 -0
  32. package/dist/commands/plugin/search.d.ts +2 -0
  33. package/dist/commands/plugin/search.js +11 -0
  34. package/dist/commands/plugin/update.d.ts +2 -0
  35. package/dist/commands/plugin/update.js +52 -0
  36. package/dist/commands/start.d.ts +2 -7
  37. package/dist/commands/start.js +74 -108
  38. package/dist/commands/stop.d.ts +2 -0
  39. package/dist/commands/stop.js +26 -0
  40. package/dist/commands/update.d.ts +2 -5
  41. package/dist/commands/update.js +34 -55
  42. package/dist/commands/use.d.ts +2 -0
  43. package/dist/commands/use.js +39 -0
  44. package/dist/infra/http.js +119 -0
  45. package/dist/infra/paths.js +15 -0
  46. package/dist/infra/store.js +24 -0
  47. package/dist/services/backup.js +34 -0
  48. package/dist/services/plugins.js +139 -0
  49. package/dist/services/process.js +41 -0
  50. package/dist/services/release.js +51 -0
  51. package/dist/services/server.js +81 -0
  52. package/dist/ui/output.js +67 -0
  53. package/dist/ui/prompts.js +26 -0
  54. package/dist/ui/theme.js +16 -0
  55. package/package.json +44 -63
  56. package/dist/bundle.js +0 -18871
  57. package/dist/commands/plugin.d.ts +0 -12
  58. package/dist/commands/plugin.js +0 -385
  59. package/dist/index.d.ts +0 -2
  60. package/dist/index.js +0 -260
  61. package/dist/lang/en.json +0 -178
  62. package/dist/lang/fr.json +0 -178
  63. package/dist/lang/index.d.ts +0 -7
  64. package/dist/lang/index.js +0 -83
  65. package/dist/plugins.json +0 -66
  66. package/dist/types.d.ts +0 -61
  67. package/dist/types.js +0 -1
  68. package/dist/utils/api.d.ts +0 -18
  69. package/dist/utils/api.js +0 -74
  70. package/dist/utils/github.d.ts +0 -27
  71. package/dist/utils/github.js +0 -187
  72. package/dist/utils/logger.d.ts +0 -15
  73. package/dist/utils/logger.js +0 -72
  74. package/dist/utils/pause.d.ts +0 -1
  75. package/dist/utils/pause.js +0 -6
  76. package/dist/utils/plugins.d.ts +0 -4
  77. package/dist/utils/plugins.js +0 -34
  78. package/dist/utils/server.d.ts +0 -3
  79. package/dist/utils/server.js +0 -39
  80. package/dist/utils/updater.d.ts +0 -1
  81. package/dist/utils/updater.js +0 -86
@@ -1,12 +0,0 @@
1
- export declare function pluginListCmd(options?: {
2
- sort?: string;
3
- limit?: number;
4
- page?: number;
5
- q?: string;
6
- }): Promise<void>;
7
- export declare function pluginSearchCmd(term?: string): Promise<void>;
8
- export declare function pluginInfoCmd(slug?: string): Promise<void>;
9
- export declare function pluginInstallCmd(names: string[]): Promise<void>;
10
- export declare function pluginInstalledCmd(): Promise<void>;
11
- export declare function pluginRemoveCmd(names: string[]): Promise<void>;
12
- export declare function pluginUpdateCmd(names: string[]): Promise<void>;
@@ -1,385 +0,0 @@
1
- import { existsSync } from 'node:fs';
2
- import { readdir, mkdir, rm, rename as fsRename } from 'node:fs/promises';
3
- import { join, resolve } from 'node:path';
4
- import { checkbox, confirm, input, select } from '@inquirer/prompts';
5
- import chalk from 'chalk';
6
- import { header, success, error, info, warn, highlight, dim, step, bullet, divider } from '../utils/logger.js';
7
- import { t } from '../lang/index.js';
8
- import { fetchPluginsList, fetchPluginDetail, proxyDownloadUrl } from '../utils/api.js';
9
- import { downloadFile } from '../utils/github.js';
10
- import { saveInstalledPlugin, getInstalledPlugins } from '../utils/plugins.js';
11
- import { resolveServerDir } from '../utils/server.js';
12
- async function getPluginsDir() {
13
- const server = await resolveServerDir();
14
- return server ? join(server, 'plugins') : resolve('plugins');
15
- }
16
- function formatDownloads(n) {
17
- if (n >= 1000)
18
- return `${(n / 1000).toFixed(1)}k`;
19
- return String(n);
20
- }
21
- function renderStars(n) {
22
- const full = '★'.repeat(Math.min(Math.floor(n / 10), 5));
23
- const empty = '☆'.repeat(Math.max(5 - full.length, 0));
24
- const color = n >= 50 ? chalk.yellow : chalk.dim;
25
- return color(full + empty);
26
- }
27
- // ─── List ─────────────────────────────────────────────────────
28
- export async function pluginListCmd(options) {
29
- header(`📋 ${t('plugin.titleList')}`);
30
- const params = {};
31
- if (options?.sort)
32
- params.sort = options.sort;
33
- if (options?.limit)
34
- params.limit = options.limit;
35
- if (options?.page)
36
- params.page = options.page;
37
- if (options?.q)
38
- params.q = options.q;
39
- step(t('plugin.fetching'));
40
- try {
41
- const resp = await fetchPluginsList(params);
42
- const list = resp.plugins;
43
- if (list.length === 0) {
44
- info(t('plugin.catalogEmpty'));
45
- return;
46
- }
47
- for (const p of list) {
48
- const name = chalk.hex('#FFB347')(p.name);
49
- const author = chalk.dim(`by ${p.author}`);
50
- const stars = renderStars(p.stars);
51
- const downloads = chalk.cyan(`${formatDownloads(p.downloads)} downloads`);
52
- const version = chalk.dim(`v${p.version}`);
53
- const installCmd = chalk.hex('#6C8EBF')(`pnx plugin install ${p.slug}`);
54
- console.log(` ${name} ${author} ${stars} ${downloads} ${version}`);
55
- if (p.description) {
56
- console.log(` ${chalk.dim(' ')}${p.description}`);
57
- }
58
- console.log(` ${chalk.dim(' →')} ${installCmd}\n`);
59
- }
60
- if (resp.count > list.length) {
61
- info(t('plugin.showingCount', String(list.length), String(resp.count)));
62
- }
63
- }
64
- catch (err) {
65
- error(t('plugin.fetchError', err.message));
66
- }
67
- }
68
- // ─── Search ───────────────────────────────────────────────────
69
- export async function pluginSearchCmd(term) {
70
- if (!term) {
71
- term = await input({ message: t('plugin.searchPrompt'), default: '' });
72
- if (!term) {
73
- info(t('plugin.searchCancelled'));
74
- return;
75
- }
76
- }
77
- await pluginListCmd({ q: term });
78
- }
79
- // ─── Info ─────────────────────────────────────────────────────
80
- export async function pluginInfoCmd(slug) {
81
- if (!slug) {
82
- slug = await input({ message: t('plugin.infoPrompt'), default: '' });
83
- if (!slug) {
84
- info(t('plugin.infoCancelled'));
85
- return;
86
- }
87
- }
88
- header(`📖 ${t('plugin.titleInfo')} — ${highlight(slug)}`);
89
- step(t('plugin.fetching'));
90
- try {
91
- const detail = await fetchPluginDetail(slug);
92
- const latest = detail.releases.find(r => r.is_stable) || detail.releases[0];
93
- console.log(`\n ${chalk.hex('#FFB347').bold(detail.name)}`);
94
- if (detail.description)
95
- console.log(` ${dim(detail.description)}`);
96
- console.log();
97
- console.log(` ${dim('Author:')} ${highlight(detail.author)}`);
98
- console.log(` ${dim('Stars:')} ${renderStars(detail.stars)} ${detail.stars}`);
99
- console.log(` ${dim('Downloads:')} ${chalk.cyan(formatDownloads(detail.downloads))}`);
100
- console.log(` ${dim('Version:')} ${chalk.green(detail.latest_version)}`);
101
- console.log(` ${dim('Updated:')} ${dim(new Date(detail.updated_at).toLocaleDateString())}`);
102
- console.log(` ${dim('Tags:')} ${(detail.tags || []).map(t => chalk.cyan(t)).join(', ')}`);
103
- if (detail.repository_url)
104
- console.log(` ${dim('Repo:')} ${chalk.blue(detail.repository_url)}`);
105
- if (detail.documentation_url)
106
- console.log(` ${dim('Docs:')} ${chalk.blue(detail.documentation_url)}`);
107
- if (detail.license_url)
108
- console.log(` ${dim('License:')} ${chalk.blue(detail.license_url)}`);
109
- // Releases
110
- if (detail.releases.length > 0) {
111
- divider();
112
- step(t('plugin.releases'));
113
- for (const r of detail.releases.slice(0, 5)) {
114
- const stable = r.is_stable ? chalk.green('●') : chalk.dim('○');
115
- const fileInfo = r.files.length > 0
116
- ? `${chalk.cyan((r.files[0].size / 1024).toFixed(0) + ' KB')}`
117
- : '';
118
- console.log(` ${stable} ${chalk.bold('v' + r.version)} ${dim(new Date(r.created_at).toLocaleDateString())} ${fileInfo}`);
119
- }
120
- if (detail.releases.length > 5) {
121
- info(t('plugin.moreReleases', String(detail.releases.length - 5)));
122
- }
123
- }
124
- }
125
- catch (err) {
126
- error(t('plugin.notFound', slug));
127
- if (err.message)
128
- warn(err.message);
129
- }
130
- }
131
- // ─── Install ──────────────────────────────────────────────────
132
- async function pickVersion(releases) {
133
- const choices = releases.map(r => ({
134
- name: `${r.is_stable ? chalk.green('●') : chalk.dim('○')} v${r.version} ${dim(new Date(r.created_at).toLocaleDateString())} ${r.is_stable ? chalk.green('stable') : chalk.dim('dev')}`,
135
- value: r,
136
- }));
137
- return await select({
138
- message: t('plugin.selectVersion'),
139
- choices,
140
- });
141
- }
142
- export async function pluginInstallCmd(names) {
143
- const pluginsDir = await getPluginsDir();
144
- await mkdir(pluginsDir, { recursive: true });
145
- // interactive picker if no names
146
- if (names.length === 0) {
147
- header(`📦 ${t('plugin.titleInstall')}`);
148
- step(t('plugin.fetching'));
149
- try {
150
- const resp = await fetchPluginsList({ sort: 'stars', limit: 50 });
151
- const choices = resp.plugins.map(p => ({
152
- name: `${chalk.hex('#FFB347')(p.name)} ${chalk.dim('—')} ${chalk.dim(p.description || '')}`,
153
- value: p.slug,
154
- }));
155
- const selected = await checkbox({
156
- message: t('plugin.selectPrompt'),
157
- choices,
158
- pageSize: 10,
159
- });
160
- names = selected;
161
- if (names.length === 0) {
162
- info(t('plugin.noSelection'));
163
- return;
164
- }
165
- }
166
- catch (err) {
167
- error(t('plugin.fetchError', err.message));
168
- return;
169
- }
170
- }
171
- header(`📦 ${t('plugin.titleInstall')}`);
172
- for (const slug of names) {
173
- try {
174
- step(t('plugin.fetchingInfo', slug));
175
- // URL install
176
- if (slug.startsWith('http://') || slug.startsWith('https://')) {
177
- if (slug.endsWith('.jar')) {
178
- const dest = join(pluginsDir, slug.split('/').pop() || 'plugin.jar');
179
- await downloadFile(slug, dest, dest.split('/').pop() || 'plugin.jar');
180
- success(t('plugin.installSuccess', slug.split('/').pop() || ''));
181
- }
182
- else {
183
- error(t('plugin.urlInvalid'));
184
- }
185
- continue;
186
- }
187
- const detail = await fetchPluginDetail(slug);
188
- let release = detail.releases.find(r => r.is_stable) || detail.releases[0];
189
- if (!release) {
190
- error(t('plugin.noRelease', slug));
191
- continue;
192
- }
193
- // Ask for version if multiple releases
194
- if (detail.releases.length > 1) {
195
- release = await pickVersion(detail.releases);
196
- }
197
- const file = release.files[0];
198
- if (!file) {
199
- error(t('plugin.noFile', slug, release.version));
200
- continue;
201
- }
202
- // Download via proxy
203
- const downloadUrl = proxyDownloadUrl(slug, release.version);
204
- const dest = join(pluginsDir, file.name);
205
- await downloadFile(downloadUrl, dest, file.name);
206
- // Track installation
207
- await saveInstalledPlugin({
208
- slug,
209
- name: detail.name,
210
- version: release.version,
211
- installedAt: new Date().toISOString(),
212
- fileName: file.name,
213
- });
214
- success(t('plugin.installSuccess', detail.name));
215
- }
216
- catch (err) {
217
- error(t('plugin.installError', slug));
218
- if (err.message)
219
- warn(err.message);
220
- }
221
- }
222
- // Summary
223
- const jars = await getInstalledJars(pluginsDir);
224
- if (jars.length > 0) {
225
- console.log();
226
- info(t('plugin.installedCount', String(jars.length)));
227
- info(t('plugin.restartHint'));
228
- }
229
- }
230
- // ─── Installed ────────────────────────────────────────────────
231
- async function getInstalledJars(dir) {
232
- if (!existsSync(dir))
233
- return [];
234
- const files = await readdir(dir);
235
- return files.filter(f => f.endsWith('.jar'));
236
- }
237
- export async function pluginInstalledCmd() {
238
- const pluginsDir = await getPluginsDir();
239
- header(`📁 ${t('plugin.titleInstalled')}`);
240
- if (!existsSync(pluginsDir)) {
241
- info(t('plugin.noPluginsDir'));
242
- return;
243
- }
244
- const jars = await getInstalledJars(pluginsDir);
245
- if (jars.length === 0) {
246
- info(t('plugin.noPlugins'));
247
- return;
248
- }
249
- const tracked = await getInstalledPlugins();
250
- bullet(jars.map((j) => {
251
- const slug = j.replace('.jar', '');
252
- const meta = tracked.find(p => p.fileName === j || p.slug === slug);
253
- if (meta) {
254
- return `${highlight(slug)} ${dim('v' + meta.version)}`;
255
- }
256
- return highlight(slug);
257
- }));
258
- console.log();
259
- info(t('plugin.installedCount', String(jars.length)));
260
- info(t('plugin.restartHint'));
261
- }
262
- // ─── Remove ───────────────────────────────────────────────────
263
- export async function pluginRemoveCmd(names) {
264
- const pluginsDir = await getPluginsDir();
265
- header(`🗑️ ${t('plugin.titleRemove')}`);
266
- if (!existsSync(pluginsDir)) {
267
- error(t('plugin.noPluginsDir'));
268
- return;
269
- }
270
- const jars = await getInstalledJars(pluginsDir);
271
- if (names.length === 0) {
272
- const choices = jars.map(j => ({
273
- name: highlight(j.replace('.jar', '')),
274
- value: j,
275
- }));
276
- if (choices.length === 0) {
277
- info(t('plugin.noPlugins'));
278
- return;
279
- }
280
- const selected = await checkbox({ message: t('plugin.removePrompt'), choices });
281
- names = selected;
282
- if (names.length === 0) {
283
- info(t('plugin.noSelection'));
284
- return;
285
- }
286
- }
287
- for (const rawName of names) {
288
- const target = rawName.endsWith('.jar') ? rawName : `${rawName}.jar`;
289
- const found = jars.find(j => j.toLowerCase() === target.toLowerCase());
290
- if (!found) {
291
- warn(t('plugin.notFound', rawName));
292
- continue;
293
- }
294
- const ok = await confirm({ message: t('plugin.removeConfirm', found), default: false });
295
- if (ok) {
296
- await rm(join(pluginsDir, found));
297
- success(t('plugin.removed', found));
298
- }
299
- else {
300
- info(t('plugin.removeCancelled', found));
301
- }
302
- }
303
- }
304
- // ─── Update ───────────────────────────────────────────────────
305
- export async function pluginUpdateCmd(names) {
306
- const pluginsDir = await getPluginsDir();
307
- header(`🔄 ${t('plugin.titleUpdate')}`);
308
- if (!existsSync(pluginsDir)) {
309
- error(t('plugin.noPluginsDir'));
310
- return;
311
- }
312
- const jars = await getInstalledJars(pluginsDir);
313
- if (jars.length === 0) {
314
- info(t('plugin.noPlugins'));
315
- return;
316
- }
317
- // interactive picker if no names
318
- if (names.length === 0) {
319
- const choices = jars.map(j => ({
320
- name: highlight(j.replace('.jar', '')),
321
- value: j,
322
- }));
323
- const selected = await checkbox({
324
- message: t('plugin.updateSelect'),
325
- choices,
326
- });
327
- names = selected.map(n => n.replace('.jar', ''));
328
- if (names.length === 0) {
329
- info(t('plugin.noSelection'));
330
- return;
331
- }
332
- }
333
- for (const rawName of names) {
334
- const slug = rawName.endsWith('.jar') ? rawName.replace('.jar', '') : rawName;
335
- const jarFile = jars.find(j => j.toLowerCase().startsWith(slug.toLowerCase()));
336
- if (!jarFile) {
337
- warn(t('plugin.notFound', rawName));
338
- continue;
339
- }
340
- step(`${t('plugin.checkingUpdates')} ${highlight(slug)}`);
341
- try {
342
- const detail = await fetchPluginDetail(slug);
343
- const latest = detail.releases.find(r => r.is_stable) || detail.releases[0];
344
- if (!latest) {
345
- warn(t('plugin.noRelease', slug));
346
- continue;
347
- }
348
- const tracked = await getInstalledPlugins();
349
- const meta = tracked.find(p => p.slug === slug);
350
- if (meta && meta.version === latest.version) {
351
- info(t('plugin.alreadyLatest', slug, latest.version));
352
- continue;
353
- }
354
- const file = latest.files[0];
355
- if (!file) {
356
- warn(t('plugin.noFile', slug, latest.version));
357
- continue;
358
- }
359
- // Backup old
360
- const oldPath = join(pluginsDir, jarFile);
361
- const backupPath = join(pluginsDir, `${jarFile}.old`);
362
- await fsRename(oldPath, backupPath);
363
- // Download new
364
- const downloadUrl = proxyDownloadUrl(slug, latest.version);
365
- const newPath = join(pluginsDir, file.name);
366
- await downloadFile(downloadUrl, newPath, file.name);
367
- // Remove backup
368
- await rm(backupPath).catch(() => { });
369
- // Update tracking
370
- await saveInstalledPlugin({
371
- slug,
372
- name: detail.name,
373
- version: latest.version,
374
- installedAt: new Date().toISOString(),
375
- fileName: file.name,
376
- });
377
- success(t('plugin.updateDone', detail.name, latest.version));
378
- }
379
- catch (err) {
380
- error(t('plugin.updateError', rawName));
381
- if (err.message)
382
- warn(err.message);
383
- }
384
- }
385
- }
package/dist/index.d.ts DELETED
@@ -1,2 +0,0 @@
1
- #!/usr/bin/env node
2
- export {};
package/dist/index.js DELETED
@@ -1,260 +0,0 @@
1
- #!/usr/bin/env node
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();