@powernukkitx/cli 1.0.0 → 1.0.2

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 (98) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +186 -203
  3. package/dist/cli.js +34 -46
  4. package/dist/commands/backup.d.ts +2 -2
  5. package/dist/commands/backup.js +23 -24
  6. package/dist/commands/config.d.ts +1 -1
  7. package/dist/commands/config.js +60 -61
  8. package/dist/commands/console.d.ts +2 -2
  9. package/dist/commands/console.js +98 -99
  10. package/dist/commands/doctor.d.ts +2 -2
  11. package/dist/commands/doctor.js +97 -98
  12. package/dist/commands/info.d.ts +2 -2
  13. package/dist/commands/info.js +41 -42
  14. package/dist/commands/init.d.ts +2 -2
  15. package/dist/commands/init.js +87 -66
  16. package/dist/commands/install.js +45 -0
  17. package/dist/commands/logs.d.ts +2 -2
  18. package/dist/commands/logs.js +60 -61
  19. package/dist/commands/menu.js +94 -86
  20. package/dist/commands/plugin/index.d.ts +2 -2
  21. package/dist/commands/plugin/index.js +13 -13
  22. package/dist/commands/plugin/info.d.ts +2 -2
  23. package/dist/commands/plugin/info.js +39 -40
  24. package/dist/commands/plugin/install.d.ts +2 -2
  25. package/dist/commands/plugin/install.js +56 -57
  26. package/dist/commands/plugin/installed.d.ts +2 -2
  27. package/dist/commands/plugin/installed.js +41 -42
  28. package/dist/commands/plugin/list.d.ts +2 -2
  29. package/dist/commands/plugin/list.js +47 -49
  30. package/dist/commands/plugin/remove.d.ts +2 -2
  31. package/dist/commands/plugin/remove.js +44 -45
  32. package/dist/commands/plugin/search.d.ts +2 -2
  33. package/dist/commands/plugin/search.js +11 -12
  34. package/dist/commands/plugin/update.d.ts +2 -2
  35. package/dist/commands/plugin/update.js +52 -53
  36. package/dist/commands/start.d.ts +2 -2
  37. package/dist/commands/start.js +74 -75
  38. package/dist/commands/stop.d.ts +2 -2
  39. package/dist/commands/stop.js +26 -27
  40. package/dist/commands/update.d.ts +2 -2
  41. package/dist/commands/update.js +34 -35
  42. package/dist/commands/use.d.ts +2 -2
  43. package/dist/commands/use.js +39 -40
  44. package/dist/infra/http.js +119 -121
  45. package/dist/infra/paths.js +15 -20
  46. package/dist/infra/store.js +24 -27
  47. package/dist/services/backup.js +34 -40
  48. package/dist/services/plugins.js +139 -111
  49. package/dist/services/process.js +41 -43
  50. package/dist/services/release.js +51 -38
  51. package/dist/services/server.js +81 -89
  52. package/dist/ui/output.js +67 -71
  53. package/dist/ui/prompts.js +26 -30
  54. package/dist/ui/theme.js +16 -18
  55. package/package.json +44 -46
  56. package/dist/bundle.js +0 -18
  57. package/dist/commands/cfg.d.ts +0 -2
  58. package/dist/commands/cfg.js +0 -61
  59. package/dist/commands/lang.d.ts +0 -2
  60. package/dist/commands/lang.js +0 -28
  61. package/dist/commands/plugin.d.ts +0 -1
  62. package/dist/commands/plugin.js +0 -1
  63. package/dist/core/config.d.ts +0 -13
  64. package/dist/core/config.js +0 -31
  65. package/dist/core/network.d.ts +0 -13
  66. package/dist/core/network.js +0 -139
  67. package/dist/core/output.d.ts +0 -33
  68. package/dist/core/output.js +0 -75
  69. package/dist/core/process.d.ts +0 -10
  70. package/dist/core/process.js +0 -53
  71. package/dist/i18n/en.json +0 -174
  72. package/dist/i18n/fr.json +0 -174
  73. package/dist/i18n/index.js +0 -58
  74. package/dist/index.d.ts +0 -1
  75. package/dist/index.js +0 -1
  76. package/dist/lang/en.json +0 -146
  77. package/dist/lang/fr.json +0 -146
  78. package/dist/lang/index.d.ts +0 -7
  79. package/dist/lang/index.js +0 -83
  80. package/dist/main.d.ts +0 -2
  81. package/dist/main.js +0 -28
  82. package/dist/plugins.json +0 -66
  83. package/dist/types.d.ts +0 -61
  84. package/dist/types.js +0 -1
  85. package/dist/utils/api.d.ts +0 -9
  86. package/dist/utils/api.js +0 -24
  87. package/dist/utils/github.d.ts +0 -20
  88. package/dist/utils/github.js +0 -60
  89. package/dist/utils/logger.d.ts +0 -15
  90. package/dist/utils/logger.js +0 -72
  91. package/dist/utils/pause.d.ts +0 -1
  92. package/dist/utils/pause.js +0 -6
  93. package/dist/utils/plugins.d.ts +0 -4
  94. package/dist/utils/plugins.js +0 -34
  95. package/dist/utils/server.d.ts +0 -3
  96. package/dist/utils/server.js +0 -32
  97. package/dist/utils/updater.d.ts +0 -1
  98. package/dist/utils/updater.js +0 -86
@@ -1,111 +1,139 @@
1
- import { join } from 'node:path';
2
- import { mkdir, rename, rm } from 'node:fs/promises';
3
- import { fetchJSON, downloadFile } from '../infra/http.js';
4
- import { readJson, writeJson } from '../infra/store.js';
5
- import { apiBase } from '../infra/paths.js';
6
- const REGISTRY = 'installed-plugins.json';
7
- /** Liste/recherche de plugins depuis la marketplace. */
8
- export async function searchPlugins(params) {
9
- const qs = new URLSearchParams();
10
- if (params?.sort)
11
- qs.set('sort', params.sort);
12
- if (params?.limit)
13
- qs.set('limit', String(params.limit));
14
- if (params?.page)
15
- qs.set('page', String(params.page));
16
- if (params?.q)
17
- qs.set('q', params.q);
18
- const q = qs.toString();
19
- return fetchJSON(`${apiBase()}/api/plugins/cli${q ? `?${q}` : ''}`);
20
- }
21
- /** Fiche détaillée d'un plugin. */
22
- export async function getPluginDetail(slug) {
23
- return fetchJSON(`${apiBase()}/api/plugins/cli/${encodeURIComponent(slug)}`);
24
- }
25
- /** URL de download proxifiée pour un plugin. */
26
- export function proxyDownloadUrl(slug, version) {
27
- const qs = new URLSearchParams({ proxy: '1' });
28
- if (version)
29
- qs.set('version', version);
30
- return `${apiBase()}/api/plugins/${encodeURIComponent(slug)}/download?${qs}`;
31
- }
32
- /** Sélectionne la release stable la plus récente, sinon la première. */
33
- export function pickRelease(detail) {
34
- return detail.releases.find(r => r.is_stable) ?? detail.releases[0] ?? null;
35
- }
36
- // --- Registre local ---
37
- export async function getInstalled() {
38
- return readJson(REGISTRY, []);
39
- }
40
- /** Upsert par slug dans le registre local. */
41
- export async function recordInstalled(plugin) {
42
- const list = await getInstalled();
43
- const idx = list.findIndex(p => p.slug === plugin.slug);
44
- if (idx >= 0)
45
- list[idx] = plugin;
46
- else
47
- list.push(plugin);
48
- await writeJson(REGISTRY, list);
49
- }
50
- export async function removeInstalled(slug) {
51
- const list = (await getInstalled()).filter(p => p.slug !== slug);
52
- await writeJson(REGISTRY, list);
53
- }
54
- // --- Installation ---
55
- /** Installe un plugin depuis la marketplace. Renvoie l'entrée enregistrée. */
56
- export async function installPlugin(slug, pluginsDir, onProgress) {
57
- const detail = await getPluginDetail(slug);
58
- const release = pickRelease(detail);
59
- if (!release)
60
- throw new Error(`no-release:${slug}`);
61
- const file = release.files[0];
62
- if (!file)
63
- throw new Error(`no-file:${slug}`);
64
- await mkdir(pluginsDir, { recursive: true });
65
- const dest = join(pluginsDir, file.name);
66
- await downloadFile(proxyDownloadUrl(slug, release.version), dest, {
67
- onProgress: onProgress ? pct => onProgress(pct, file.name) : undefined,
68
- });
69
- const entry = {
70
- slug, name: detail.name, version: release.version,
71
- installedAt: new Date().toISOString(), fileName: file.name,
72
- };
73
- await recordInstalled(entry);
74
- return entry;
75
- }
76
- /** Installe un plugin depuis une URL .jar directe. */
77
- export async function installFromUrl(url, pluginsDir, onProgress) {
78
- if (!url.endsWith('.jar'))
79
- throw new Error('url-invalid');
80
- await mkdir(pluginsDir, { recursive: true });
81
- const fileName = url.split('/').pop() ?? 'plugin.jar';
82
- const dest = join(pluginsDir, fileName);
83
- await downloadFile(url, dest, { onProgress: onProgress ? pct => onProgress(pct, fileName) : undefined });
84
- return fileName;
85
- }
86
- /** Met à jour un plugin installé; null si déjà à jour. */
87
- export async function updatePlugin(slug, pluginsDir, currentVersion, existingJar, onProgress) {
88
- const detail = await getPluginDetail(slug);
89
- const release = pickRelease(detail);
90
- if (!release)
91
- throw new Error(`no-release:${slug}`);
92
- if (currentVersion && currentVersion === release.version)
93
- return null;
94
- const file = release.files[0];
95
- if (!file)
96
- throw new Error(`no-file:${slug}`);
97
- if (existingJar) {
98
- await rename(join(pluginsDir, existingJar), join(pluginsDir, `${existingJar}.old`)).catch(() => { });
99
- }
100
- await downloadFile(proxyDownloadUrl(slug, release.version), join(pluginsDir, file.name), {
101
- onProgress: onProgress ? pct => onProgress(pct, file.name) : undefined,
102
- });
103
- if (existingJar)
104
- await rm(join(pluginsDir, `${existingJar}.old`)).catch(() => { });
105
- const entry = {
106
- slug, name: detail.name, version: release.version,
107
- installedAt: new Date().toISOString(), fileName: file.name,
108
- };
109
- await recordInstalled(entry);
110
- return entry;
111
- }
1
+ import { join } from 'node:path';
2
+ import { mkdir, rename, rm } from 'node:fs/promises';
3
+ import { fetchJSON, downloadFile } from '../infra/http.js';
4
+ import { readJson, writeJson } from '../infra/store.js';
5
+ import { apiBase } from '../infra/paths.js';
6
+ const REGISTRY = 'installed-plugins.json';
7
+ export async function searchPlugins(params) {
8
+ const qs = new URLSearchParams();
9
+ if (params?.sort)
10
+ qs.set('sort', params.sort);
11
+ if (params?.limit)
12
+ qs.set('limit', String(params.limit));
13
+ if (params?.page)
14
+ qs.set('page', String(params.page));
15
+ if (params?.q)
16
+ qs.set('q', params.q);
17
+ const q = qs.toString();
18
+ return fetchJSON(`${apiBase()}/api/plugins/cli${q ? `?${q}` : ''}`);
19
+ }
20
+ export async function getPluginDetail(slug) {
21
+ return fetchJSON(`${apiBase()}/api/plugins/cli/${encodeURIComponent(slug)}`);
22
+ }
23
+ export async function fetchApprovedPlugin(slug) {
24
+ try {
25
+ const res = await fetchJSON(`${apiBase()}/api/v1/plugins/${encodeURIComponent(slug)}`);
26
+ return res?.data ?? null;
27
+ }
28
+ catch (e) {
29
+ if (e?.statusCode === 404)
30
+ return null;
31
+ throw e;
32
+ }
33
+ }
34
+ function pickJarAsset(detail) {
35
+ for (const rel of detail.releases ?? []) {
36
+ const jar = (rel.assets ?? []).find(a => a.name.endsWith('.jar'));
37
+ if (jar)
38
+ return { url: jar.downloadUrl, name: jar.name, version: rel.tag };
39
+ }
40
+ return null;
41
+ }
42
+ export async function installApprovedPlugin(slug, pluginsDir, onProgress) {
43
+ const detail = await fetchApprovedPlugin(slug);
44
+ if (!detail || detail.status !== 'published')
45
+ throw new Error(`not-approved:${slug}`);
46
+ const asset = pickJarAsset(detail);
47
+ if (!asset)
48
+ throw new Error(`no-file:${slug}`);
49
+ await mkdir(pluginsDir, { recursive: true });
50
+ const dest = join(pluginsDir, asset.name);
51
+ await downloadFile(asset.url, dest, {
52
+ onProgress: onProgress ? pct => onProgress(pct, asset.name) : undefined,
53
+ });
54
+ const entry = {
55
+ slug, name: detail.name, version: asset.version,
56
+ installedAt: new Date().toISOString(), fileName: asset.name,
57
+ };
58
+ await recordInstalled(entry);
59
+ return entry;
60
+ }
61
+ export function proxyDownloadUrl(slug, version) {
62
+ const qs = new URLSearchParams({ proxy: '1' });
63
+ if (version)
64
+ qs.set('version', version);
65
+ return `${apiBase()}/api/plugins/${encodeURIComponent(slug)}/download?${qs}`;
66
+ }
67
+ export function pickRelease(detail) {
68
+ return detail.releases.find(r => r.is_stable) ?? detail.releases[0] ?? null;
69
+ }
70
+ export async function getInstalled() {
71
+ return readJson(REGISTRY, []);
72
+ }
73
+ export async function recordInstalled(plugin) {
74
+ const list = await getInstalled();
75
+ const idx = list.findIndex(p => p.slug === plugin.slug);
76
+ if (idx >= 0)
77
+ list[idx] = plugin;
78
+ else
79
+ list.push(plugin);
80
+ await writeJson(REGISTRY, list);
81
+ }
82
+ export async function removeInstalled(slug) {
83
+ const list = (await getInstalled()).filter(p => p.slug !== slug);
84
+ await writeJson(REGISTRY, list);
85
+ }
86
+ export async function installPlugin(slug, pluginsDir, onProgress) {
87
+ const detail = await getPluginDetail(slug);
88
+ const release = pickRelease(detail);
89
+ if (!release)
90
+ throw new Error(`no-release:${slug}`);
91
+ const file = release.files[0];
92
+ if (!file)
93
+ throw new Error(`no-file:${slug}`);
94
+ await mkdir(pluginsDir, { recursive: true });
95
+ const dest = join(pluginsDir, file.name);
96
+ await downloadFile(proxyDownloadUrl(slug, release.version), dest, {
97
+ onProgress: onProgress ? pct => onProgress(pct, file.name) : undefined,
98
+ });
99
+ const entry = {
100
+ slug, name: detail.name, version: release.version,
101
+ installedAt: new Date().toISOString(), fileName: file.name,
102
+ };
103
+ await recordInstalled(entry);
104
+ return entry;
105
+ }
106
+ export async function installFromUrl(url, pluginsDir, onProgress) {
107
+ if (!url.endsWith('.jar'))
108
+ throw new Error('url-invalid');
109
+ await mkdir(pluginsDir, { recursive: true });
110
+ const fileName = url.split('/').pop() ?? 'plugin.jar';
111
+ const dest = join(pluginsDir, fileName);
112
+ await downloadFile(url, dest, { onProgress: onProgress ? pct => onProgress(pct, fileName) : undefined });
113
+ return fileName;
114
+ }
115
+ export async function updatePlugin(slug, pluginsDir, currentVersion, existingJar, onProgress) {
116
+ const detail = await getPluginDetail(slug);
117
+ const release = pickRelease(detail);
118
+ if (!release)
119
+ throw new Error(`no-release:${slug}`);
120
+ if (currentVersion && currentVersion === release.version)
121
+ return null;
122
+ const file = release.files[0];
123
+ if (!file)
124
+ throw new Error(`no-file:${slug}`);
125
+ if (existingJar) {
126
+ await rename(join(pluginsDir, existingJar), join(pluginsDir, `${existingJar}.old`)).catch(() => { });
127
+ }
128
+ await downloadFile(proxyDownloadUrl(slug, release.version), join(pluginsDir, file.name), {
129
+ onProgress: onProgress ? pct => onProgress(pct, file.name) : undefined,
130
+ });
131
+ if (existingJar)
132
+ await rm(join(pluginsDir, `${existingJar}.old`)).catch(() => { });
133
+ const entry = {
134
+ slug, name: detail.name, version: release.version,
135
+ installedAt: new Date().toISOString(), fileName: file.name,
136
+ };
137
+ await recordInstalled(entry);
138
+ return entry;
139
+ }
@@ -1,43 +1,41 @@
1
- import { platform } from 'node:os';
2
- import { spawnSync } from 'node:child_process';
3
- import { readJson, writeJson, removeFile } from '../infra/store.js';
4
- const PID_FILE = 'pid.json';
5
- export async function savePid(pid, serverPath) {
6
- await writeJson(PID_FILE, { pid, serverPath, startedAt: new Date().toISOString() });
7
- }
8
- export async function loadPid() {
9
- return readJson(PID_FILE, null);
10
- }
11
- export async function clearPid() {
12
- await removeFile(PID_FILE);
13
- }
14
- /** True si le processus `pid` est vivant. */
15
- export function isAlive(pid) {
16
- try {
17
- process.kill(pid, 0);
18
- return true;
19
- }
20
- catch {
21
- return false;
22
- }
23
- }
24
- /** Arrête un processus (taskkill sous Windows, SIGTERM→SIGKILL sinon). */
25
- export async function killProcess(pid) {
26
- if (!isAlive(pid))
27
- return false;
28
- try {
29
- if (platform() === 'win32') {
30
- spawnSync('taskkill', ['/PID', String(pid), '/F']);
31
- }
32
- else {
33
- process.kill(pid, 'SIGTERM');
34
- await new Promise(r => setTimeout(r, 5000));
35
- if (isAlive(pid))
36
- process.kill(pid, 'SIGKILL');
37
- }
38
- return true;
39
- }
40
- catch {
41
- return false;
42
- }
43
- }
1
+ import { platform } from 'node:os';
2
+ import { spawnSync } from 'node:child_process';
3
+ import { readJson, writeJson, removeFile } from '../infra/store.js';
4
+ const PID_FILE = 'pid.json';
5
+ export async function savePid(pid, serverPath) {
6
+ await writeJson(PID_FILE, { pid, serverPath, startedAt: new Date().toISOString() });
7
+ }
8
+ export async function loadPid() {
9
+ return readJson(PID_FILE, null);
10
+ }
11
+ export async function clearPid() {
12
+ await removeFile(PID_FILE);
13
+ }
14
+ export function isAlive(pid) {
15
+ try {
16
+ process.kill(pid, 0);
17
+ return true;
18
+ }
19
+ catch {
20
+ return false;
21
+ }
22
+ }
23
+ export async function killProcess(pid) {
24
+ if (!isAlive(pid))
25
+ return false;
26
+ try {
27
+ if (platform() === 'win32') {
28
+ spawnSync('taskkill', ['/PID', String(pid), '/F']);
29
+ }
30
+ else {
31
+ process.kill(pid, 'SIGTERM');
32
+ await new Promise(r => setTimeout(r, 5000));
33
+ if (isAlive(pid))
34
+ process.kill(pid, 'SIGKILL');
35
+ }
36
+ return true;
37
+ }
38
+ catch {
39
+ return false;
40
+ }
41
+ }
@@ -1,38 +1,51 @@
1
- import { fetchJSON, downloadFile } from '../infra/http.js';
2
- import { readJson, writeJson } from '../infra/store.js';
3
- import { GITHUB_LATEST_RELEASE, JAR_NAME } from '../infra/paths.js';
4
- const CACHE_FILE = 'latest-release.json';
5
- const CACHE_TTL = 3_600_000; // 1h
6
- function normalize(gh) {
7
- return {
8
- tag: gh.tag_name,
9
- version: gh.tag_name.replace(/^v/, ''),
10
- assets: gh.assets.map(a => ({ name: a.name, url: a.browser_download_url, size: a.size })),
11
- publishedAt: gh.published_at,
12
- body: gh.body,
13
- };
14
- }
15
- /** Dernière release du core, mise en cache 1h (sauf `force`). */
16
- export async function getLatestRelease(force = false) {
17
- if (!force) {
18
- const cached = await readJson(CACHE_FILE, null);
19
- if (cached && Date.now() - cached.fetchedAt < CACHE_TTL)
20
- return cached.data;
21
- }
22
- const gh = await fetchJSON(GITHUB_LATEST_RELEASE);
23
- const release = normalize(gh);
24
- await writeJson(CACHE_FILE, { fetchedAt: Date.now(), data: release });
25
- return release;
26
- }
27
- /** Retrouve un asset par nom exact. */
28
- export function findAsset(release, name) {
29
- return release.assets.find(a => a.name === name);
30
- }
31
- /** Télécharge le jar du core vers `dest`. Renvoie sa taille. */
32
- export async function downloadCore(release, dest, onProgress) {
33
- const asset = findAsset(release, JAR_NAME);
34
- if (!asset)
35
- throw new Error(`${JAR_NAME} not found in release ${release.tag}`);
36
- await downloadFile(asset.url, dest, { onProgress: onProgress ? pct => onProgress(pct) : undefined });
37
- return { size: asset.size };
38
- }
1
+ import { fetchJSON, downloadFile } from '../infra/http.js';
2
+ import { readJson, writeJson } from '../infra/store.js';
3
+ import { GITHUB_LATEST_RELEASE, GITHUB_RELEASES, JAR_NAME } from '../infra/paths.js';
4
+ const CACHE_FILE = 'latest-release.json';
5
+ const LIST_CACHE_FILE = 'releases.json';
6
+ const CACHE_TTL = 3_600_000;
7
+ function normalize(gh) {
8
+ return {
9
+ tag: gh.tag_name,
10
+ version: gh.tag_name.replace(/^v/, ''),
11
+ assets: gh.assets.map(a => ({ name: a.name, url: a.browser_download_url, size: a.size })),
12
+ publishedAt: gh.published_at,
13
+ prerelease: gh.prerelease ?? false,
14
+ body: gh.body,
15
+ };
16
+ }
17
+ export async function getLatestRelease(force = false) {
18
+ if (!force) {
19
+ const cached = await readJson(CACHE_FILE, null);
20
+ if (cached && Date.now() - cached.fetchedAt < CACHE_TTL)
21
+ return cached.data;
22
+ }
23
+ const gh = await fetchJSON(GITHUB_LATEST_RELEASE);
24
+ const release = normalize(gh);
25
+ await writeJson(CACHE_FILE, { fetchedAt: Date.now(), data: release });
26
+ return release;
27
+ }
28
+ export async function listReleases(force = false) {
29
+ if (!force) {
30
+ const cached = await readJson(LIST_CACHE_FILE, null);
31
+ if (cached && Date.now() - cached.fetchedAt < CACHE_TTL)
32
+ return cached.data;
33
+ }
34
+ const gh = await fetchJSON(`${GITHUB_RELEASES}?per_page=30`);
35
+ const releases = gh
36
+ .filter(r => !r.draft)
37
+ .map(normalize)
38
+ .filter(r => r.assets.some(a => a.name === JAR_NAME));
39
+ await writeJson(LIST_CACHE_FILE, { fetchedAt: Date.now(), data: releases });
40
+ return releases;
41
+ }
42
+ export function findAsset(release, name) {
43
+ return release.assets.find(a => a.name === name);
44
+ }
45
+ export async function downloadCore(release, dest, onProgress) {
46
+ const asset = findAsset(release, JAR_NAME);
47
+ if (!asset)
48
+ throw new Error(`${JAR_NAME} not found in release ${release.tag}`);
49
+ await downloadFile(asset.url, dest, { onProgress: onProgress ? pct => onProgress(pct) : undefined });
50
+ return { size: asset.size };
51
+ }
@@ -1,89 +1,81 @@
1
- import { existsSync } from 'node:fs';
2
- import { join, dirname, resolve } from 'node:path';
3
- import { cwd } from 'node:process';
4
- import { platform } from 'node:os';
5
- import { spawn } from 'node:child_process';
6
- import { readJson, writeJson } from '../infra/store.js';
7
- import { JAR_NAME, SCRIPTS_BASE } from '../infra/paths.js';
8
- const CONFIG_FILE = 'config.json';
9
- export async function getDefaultServer() {
10
- return (await readJson(CONFIG_FILE, {})).defaultServer ?? null;
11
- }
12
- export async function setDefaultServer(path) {
13
- const cfg = await readJson(CONFIG_FILE, {});
14
- if (path === null)
15
- delete cfg.defaultServer;
16
- else
17
- cfg.defaultServer = path;
18
- await writeJson(CONFIG_FILE, cfg);
19
- }
20
- /** Détecte les dossiers serveur (jar présent) autour d'un point de départ. */
21
- export function detectServers(startPath) {
22
- const base = startPath || cwd();
23
- const found = [];
24
- for (const dir of [base, join(base, 'pnx-server'), dirname(resolve(base))]) {
25
- if (existsSync(join(dir, JAR_NAME)) && !found.includes(dir))
26
- found.push(dir);
27
- }
28
- return found;
29
- }
30
- /** Résout le dossier serveur: défaut mémorisé, sinon détection (`pick` si plusieurs). */
31
- export async function resolveServerDir(pick) {
32
- const def = await getDefaultServer();
33
- if (def && existsSync(join(def, JAR_NAME)))
34
- return def;
35
- const all = detectServers();
36
- if (all.length === 0)
37
- return null;
38
- if (all.length === 1)
39
- return all[0];
40
- return pick ? pick(all) : all[0];
41
- }
42
- /** Chemin de l'exécutable Java (JAVA_HOME sinon PATH). */
43
- export function findJava() {
44
- return process.env.JAVA_HOME ? join(process.env.JAVA_HOME, 'bin', 'java') : 'java';
45
- }
46
- /** Vérifie Java (ok si >= 21) et son vendor. */
47
- export function checkJava() {
48
- return new Promise(res => {
49
- const child = spawn(findJava(), ['-version']);
50
- let out = '';
51
- child.on('error', () => res({ ok: false, version: 0, raw: '?', vendor: 'Unknown' }));
52
- child.stderr.on('data', (d) => (out += d.toString()));
53
- child.on('close', () => {
54
- const m = out.match(/(?:openjdk|java) version "?(?:1\.)?(\d+)/i);
55
- const version = m ? parseInt(m[1]) : 0;
56
- const vendors = ['Temurin', 'GraalVM', 'Zulu', 'Corretto', 'Oracle', 'Microsoft', 'OpenJDK'];
57
- const vendor = vendors.find(v => out.toLowerCase().includes(v.toLowerCase())) ?? 'Unknown';
58
- res({ ok: version >= 21, version, raw: out.split('\n')[0]?.trim() || '?', vendor });
59
- });
60
- });
61
- }
62
- /** Arguments JVM par défaut (encodage, ZGC generational, add-opens). */
63
- export function defaultJavaArgs() {
64
- return [
65
- '-Dfile.encoding=UTF-8', '-Dstdout.encoding=UTF-8', '-Dstderr.encoding=UTF-8',
66
- '-Djansi.passthrough=true', '-Dterminal.ansi=true',
67
- '-XX:+UseZGC', '-XX:+ZGenerational', '-XX:+UseStringDeduplication',
68
- '--add-opens', 'java.base/java.lang=ALL-UNNAMED',
69
- '--add-opens', 'java.base/java.io=ALL-UNNAMED',
70
- '--add-opens', 'java.base/java.net=ALL-UNNAMED',
71
- ];
72
- }
73
- /** Ligne de commande complète pour lancer le serveur. */
74
- export function buildLaunchArgs(memory) {
75
- return [`-Xms${memory}`, '-XX:MaxRAMPercentage=75.0', ...defaultJavaArgs(), '-jar', JAR_NAME];
76
- }
77
- /** Environnement d'exécution du serveur (encodage UTF-8). */
78
- export function javaEnv() {
79
- return { ...process.env, JAVA_TOOL_OPTIONS: '-Dfile.encoding=UTF-8', LANG: process.env.LANG ?? 'en_US.UTF-8' };
80
- }
81
- /** Scripts de démarrage à télécharger selon la plateforme. */
82
- export function startScripts() {
83
- if (platform() === 'win32')
84
- return [
85
- { url: `${SCRIPTS_BASE}/start.bat`, name: 'start.bat' },
86
- { url: `${SCRIPTS_BASE}/start.ps1`, name: 'start.ps1' },
87
- ];
88
- return [{ url: `${SCRIPTS_BASE}/start.sh`, name: 'start.sh' }];
89
- }
1
+ import { existsSync } from 'node:fs';
2
+ import { join, dirname, resolve } from 'node:path';
3
+ import { cwd } from 'node:process';
4
+ import { platform } from 'node:os';
5
+ import { spawn } from 'node:child_process';
6
+ import { readJson, writeJson } from '../infra/store.js';
7
+ import { JAR_NAME, SCRIPTS_BASE } from '../infra/paths.js';
8
+ const CONFIG_FILE = 'config.json';
9
+ export async function getDefaultServer() {
10
+ return (await readJson(CONFIG_FILE, {})).defaultServer ?? null;
11
+ }
12
+ export async function setDefaultServer(path) {
13
+ const cfg = await readJson(CONFIG_FILE, {});
14
+ if (path === null)
15
+ delete cfg.defaultServer;
16
+ else
17
+ cfg.defaultServer = path;
18
+ await writeJson(CONFIG_FILE, cfg);
19
+ }
20
+ export function detectServers(startPath) {
21
+ const base = startPath || cwd();
22
+ const found = [];
23
+ for (const dir of [base, join(base, 'pnx-server'), dirname(resolve(base))]) {
24
+ if (existsSync(join(dir, JAR_NAME)) && !found.includes(dir))
25
+ found.push(dir);
26
+ }
27
+ return found;
28
+ }
29
+ export async function resolveServerDir(pick) {
30
+ const def = await getDefaultServer();
31
+ if (def && existsSync(join(def, JAR_NAME)))
32
+ return def;
33
+ const all = detectServers();
34
+ if (all.length === 0)
35
+ return null;
36
+ if (all.length === 1)
37
+ return all[0];
38
+ return pick ? pick(all) : all[0];
39
+ }
40
+ export function findJava() {
41
+ return process.env.JAVA_HOME ? join(process.env.JAVA_HOME, 'bin', 'java') : 'java';
42
+ }
43
+ export function checkJava() {
44
+ return new Promise(res => {
45
+ const child = spawn(findJava(), ['-version']);
46
+ let out = '';
47
+ child.on('error', () => res({ ok: false, version: 0, raw: '?', vendor: 'Unknown' }));
48
+ child.stderr.on('data', (d) => (out += d.toString()));
49
+ child.on('close', () => {
50
+ const m = out.match(/(?:openjdk|java) version "?(?:1\.)?(\d+)/i);
51
+ const version = m ? parseInt(m[1]) : 0;
52
+ const vendors = ['Temurin', 'GraalVM', 'Zulu', 'Corretto', 'Oracle', 'Microsoft', 'OpenJDK'];
53
+ const vendor = vendors.find(v => out.toLowerCase().includes(v.toLowerCase())) ?? 'Unknown';
54
+ res({ ok: version >= 21, version, raw: out.split('\n')[0]?.trim() || '?', vendor });
55
+ });
56
+ });
57
+ }
58
+ export function defaultJavaArgs() {
59
+ return [
60
+ '-Dfile.encoding=UTF-8', '-Dstdout.encoding=UTF-8', '-Dstderr.encoding=UTF-8',
61
+ '-Djansi.passthrough=true', '-Dterminal.ansi=true',
62
+ '-XX:+UseZGC', '-XX:+ZGenerational', '-XX:+UseStringDeduplication',
63
+ '--add-opens', 'java.base/java.lang=ALL-UNNAMED',
64
+ '--add-opens', 'java.base/java.io=ALL-UNNAMED',
65
+ '--add-opens', 'java.base/java.net=ALL-UNNAMED',
66
+ ];
67
+ }
68
+ export function buildLaunchArgs(memory) {
69
+ return [`-Xms${memory}`, '-XX:MaxRAMPercentage=75.0', ...defaultJavaArgs(), '-jar', JAR_NAME];
70
+ }
71
+ export function javaEnv() {
72
+ return { ...process.env, JAVA_TOOL_OPTIONS: '-Dfile.encoding=UTF-8', LANG: process.env.LANG ?? 'en_US.UTF-8' };
73
+ }
74
+ export function startScripts() {
75
+ if (platform() === 'win32')
76
+ return [
77
+ { url: `${SCRIPTS_BASE}/start.bat`, name: 'start.bat' },
78
+ { url: `${SCRIPTS_BASE}/start.ps1`, name: 'start.ps1' },
79
+ ];
80
+ return [{ url: `${SCRIPTS_BASE}/start.sh`, name: 'start.sh' }];
81
+ }