flashjdk 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.
@@ -0,0 +1,409 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const os = require('os');
5
+ const path = require('path');
6
+ const { execFile } = require('child_process');
7
+
8
+ const projectRoot = path.join(__dirname, '..');
9
+ const pkg = require(path.join(projectRoot, 'package.json'));
10
+
11
+ function normalizePath(rawPath) {
12
+ if (!rawPath || typeof rawPath !== 'string') return '';
13
+ let value = rawPath.trim().replace(/^["']|["']$/g, '');
14
+ while (value.length > 1 && /[\\/]$/.test(value)) {
15
+ value = value.slice(0, -1);
16
+ }
17
+ try {
18
+ return fs.existsSync(value) ? fs.realpathSync(value) : path.resolve(value);
19
+ } catch {
20
+ return value;
21
+ }
22
+ }
23
+
24
+ function existsDir(target) {
25
+ try {
26
+ return fs.statSync(target).isDirectory();
27
+ } catch {
28
+ return false;
29
+ }
30
+ }
31
+
32
+ function existsFile(target) {
33
+ try {
34
+ return fs.statSync(target).isFile();
35
+ } catch {
36
+ return false;
37
+ }
38
+ }
39
+
40
+ function getPlatformKind() {
41
+ if (process.platform === 'win32') return 'windows';
42
+ if (process.platform === 'darwin') return 'macos';
43
+ return 'linux';
44
+ }
45
+
46
+ function getCacheInfo() {
47
+ if (process.platform === 'win32') {
48
+ const base = process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming');
49
+ return {
50
+ dir: path.join(base, 'flashjdk'),
51
+ file: path.join(base, 'flashjdk', 'jdk-roots-cache.json'),
52
+ format: 'json',
53
+ };
54
+ }
55
+
56
+ return {
57
+ dir: path.join(os.homedir(), '.config', 'flashjdk'),
58
+ file: path.join(os.homedir(), '.config', 'flashjdk', 'custom-roots.txt'),
59
+ format: 'text',
60
+ };
61
+ }
62
+
63
+ function getDefaultRoots() {
64
+ const home = os.homedir();
65
+ if (process.platform === 'win32') {
66
+ return [
67
+ 'C:\\Program Files\\Java',
68
+ 'C:\\Program Files (x86)\\Java',
69
+ 'D:\\Java',
70
+ 'D:\\ProgramFiles\\Java',
71
+ 'E:\\Java',
72
+ path.join(home, '.jdks'),
73
+ ];
74
+ }
75
+
76
+ if (process.platform === 'darwin') {
77
+ return [
78
+ '/Library/Java/JavaVirtualMachines',
79
+ path.join(home, 'Library', 'Java', 'JavaVirtualMachines'),
80
+ '/usr/local/opt',
81
+ '/opt/homebrew/opt',
82
+ path.join(home, '.sdkman', 'candidates', 'java'),
83
+ path.join(home, '.jdks'),
84
+ ];
85
+ }
86
+
87
+ return [
88
+ '/usr/lib/jvm',
89
+ '/usr/local/java',
90
+ '/usr/local/jdk',
91
+ '/opt/java',
92
+ '/opt/jdk',
93
+ path.join(home, '.sdkman', 'candidates', 'java'),
94
+ path.join(home, '.jdks'),
95
+ ];
96
+ }
97
+
98
+ function uniquePaths(paths) {
99
+ const seen = new Set();
100
+ const output = [];
101
+ for (const item of paths) {
102
+ const normalized = normalizePath(item);
103
+ const key = process.platform === 'win32' ? normalized.toLowerCase() : normalized;
104
+ if (!normalized || seen.has(key)) continue;
105
+ seen.add(key);
106
+ output.push(normalized);
107
+ }
108
+ return output;
109
+ }
110
+
111
+ function samePath(left, right) {
112
+ const a = normalizePath(left);
113
+ const b = normalizePath(right);
114
+ return process.platform === 'win32' ? a.toLowerCase() === b.toLowerCase() : a === b;
115
+ }
116
+
117
+ function readCachedRoots() {
118
+ const cache = getCacheInfo();
119
+ if (!existsFile(cache.file)) return [];
120
+
121
+ try {
122
+ if (cache.format === 'json') {
123
+ const parsed = JSON.parse(fs.readFileSync(cache.file, 'utf8'));
124
+ return uniquePaths(Array.isArray(parsed.customRoots) ? parsed.customRoots : []);
125
+ }
126
+
127
+ const lines = fs.readFileSync(cache.file, 'utf8')
128
+ .split(/\r?\n/)
129
+ .map(line => line.trim())
130
+ .filter(line => line && !line.startsWith('#'));
131
+ return uniquePaths(lines);
132
+ } catch {
133
+ return [];
134
+ }
135
+ }
136
+
137
+ function writeCachedRoots(roots) {
138
+ const cache = getCacheInfo();
139
+ fs.mkdirSync(cache.dir, { recursive: true });
140
+ const normalized = uniquePaths(roots).filter(existsDir);
141
+
142
+ if (cache.format === 'json') {
143
+ fs.writeFileSync(cache.file, JSON.stringify({ customRoots: normalized }, null, 2), 'utf8');
144
+ } else {
145
+ fs.writeFileSync(cache.file, `${normalized.join('\n')}\n`, 'utf8');
146
+ }
147
+
148
+ return normalized;
149
+ }
150
+
151
+ function getSearchRoots() {
152
+ return uniquePaths([...getDefaultRoots(), ...readCachedRoots()]);
153
+ }
154
+
155
+ function javaExecutable(jdkHome) {
156
+ return path.join(jdkHome, 'bin', process.platform === 'win32' ? 'java.exe' : 'java');
157
+ }
158
+
159
+ function isJdkHome(jdkHome) {
160
+ return existsFile(javaExecutable(jdkHome));
161
+ }
162
+
163
+ function safeChildren(root) {
164
+ try {
165
+ return fs.readdirSync(root, { withFileTypes: true })
166
+ .filter(entry => entry.isDirectory())
167
+ .map(entry => path.join(root, entry.name));
168
+ } catch {
169
+ return [];
170
+ }
171
+ }
172
+
173
+ function findMacJdks(root) {
174
+ const found = [];
175
+ for (const child of safeChildren(root)) {
176
+ if (child.endsWith('.jdk')) {
177
+ const home = path.join(child, 'Contents', 'Home');
178
+ if (isJdkHome(home)) found.push(home);
179
+ continue;
180
+ }
181
+
182
+ const directBundle = path.join(child, 'Contents', 'Home');
183
+ if (isJdkHome(directBundle)) found.push(directBundle);
184
+
185
+ const libexec = path.join(child, 'libexec');
186
+ for (const inner of safeChildren(libexec)) {
187
+ if (!inner.endsWith('.jdk')) continue;
188
+ const home = path.join(inner, 'Contents', 'Home');
189
+ if (isJdkHome(home)) found.push(home);
190
+ }
191
+
192
+ if (isJdkHome(child)) found.push(child);
193
+ }
194
+
195
+ if (isJdkHome(root)) found.push(root);
196
+ return found;
197
+ }
198
+
199
+ function findJdksInRoot(root) {
200
+ const normalizedRoot = normalizePath(root);
201
+ if (!existsDir(normalizedRoot)) return [];
202
+
203
+ if (process.platform === 'darwin') {
204
+ return findMacJdks(normalizedRoot);
205
+ }
206
+
207
+ const found = [];
208
+ if (isJdkHome(normalizedRoot)) found.push(normalizedRoot);
209
+
210
+ for (const child of safeChildren(normalizedRoot)) {
211
+ if (isJdkHome(child)) found.push(child);
212
+ }
213
+
214
+ return found;
215
+ }
216
+
217
+ function runFile(command, args, options = {}) {
218
+ return new Promise(resolve => {
219
+ execFile(command, args, {
220
+ windowsHide: true,
221
+ timeout: options.timeout || 15000,
222
+ maxBuffer: 1024 * 1024,
223
+ env: options.env || process.env,
224
+ }, (error, stdout, stderr) => {
225
+ resolve({
226
+ ok: !error,
227
+ code: error && typeof error.code === 'number' ? error.code : 0,
228
+ stdout: stdout || '',
229
+ stderr: stderr || '',
230
+ message: error ? error.message : '',
231
+ });
232
+ });
233
+ });
234
+ }
235
+
236
+ async function getJavaVersion(jdkHome) {
237
+ const result = await runFile(javaExecutable(jdkHome), ['-version'], { timeout: 5000 });
238
+ return (result.stderr || result.stdout || '').trim();
239
+ }
240
+
241
+ async function listJdks() {
242
+ const currentJavaHome = normalizePath(process.env.JAVA_HOME || '');
243
+ const roots = getSearchRoots();
244
+ const cachedRoots = readCachedRoots();
245
+ const homes = uniquePaths(roots.flatMap(findJdksInRoot));
246
+ const items = [];
247
+
248
+ for (const home of homes) {
249
+ items.push({
250
+ home,
251
+ current: !!currentJavaHome && samePath(home, currentJavaHome),
252
+ version: await getJavaVersion(home),
253
+ });
254
+ }
255
+
256
+ return {
257
+ currentJavaHome,
258
+ cache: getCacheInfo(),
259
+ roots: roots.map(root => ({ path: root, exists: existsDir(root), cached: cachedRoots.some(cached => samePath(cached, root)) })),
260
+ jdks: items,
261
+ };
262
+ }
263
+
264
+ function addSearchRoot(root) {
265
+ const normalized = normalizePath(root);
266
+ if (!existsDir(normalized)) {
267
+ const error = new Error(`Path does not exist: ${normalized}`);
268
+ error.code = 'ENOENT';
269
+ throw error;
270
+ }
271
+
272
+ return writeCachedRoots([...readCachedRoots(), normalized]);
273
+ }
274
+
275
+ function buildWindowsEnvScript() {
276
+ return `
277
+ param([string]$JdkHome)
278
+ $ErrorActionPreference = "Stop"
279
+ function Test-IsJdkEntry {
280
+ param([string]$Entry)
281
+ if ([string]::IsNullOrWhiteSpace($Entry)) { return $false }
282
+ $e = [Environment]::ExpandEnvironmentVariables($Entry.Trim().Trim('"').TrimEnd('\\').TrimEnd('/'))
283
+ if (Test-Path -LiteralPath (Join-Path $e "java.exe")) { return $true }
284
+ $vendorPattern = '(jdk|jre|java|openjdk|temurin|zulu|corretto|graal|dragonwell|kona|semeru|liberica|oracle)'
285
+ return ($e -imatch "\\\\$vendorPattern[^\\\\]*\\\\bin$") -or
286
+ ($e -imatch "\\\\$vendorPattern[^\\\\]*\\\\jre\\\\bin$")
287
+ }
288
+ $newBin = Join-Path $JdkHome "bin"
289
+ $newJre = Join-Path $JdkHome "jre\\bin"
290
+ if (-not (Test-Path -LiteralPath (Join-Path $newBin "java.exe"))) {
291
+ throw "java.exe not found under $newBin"
292
+ }
293
+ $machinePath = [Environment]::GetEnvironmentVariable("Path", "Machine")
294
+ $parts = @($machinePath -split ";" | Where-Object { $_ -ne "" })
295
+ $cleaned = @($parts | Where-Object { -not (Test-IsJdkEntry $_) })
296
+ $newEntries = @($newBin)
297
+ if (Test-Path -LiteralPath $newJre) { $newEntries += $newJre }
298
+ $finalParts = @($newEntries + $cleaned | Select-Object -Unique)
299
+ [Environment]::SetEnvironmentVariable("Path", ($finalParts -join ";"), "Machine")
300
+ [Environment]::SetEnvironmentVariable("JAVA_HOME", $JdkHome, "Machine")
301
+ Write-Output "JAVA_HOME=$JdkHome"
302
+ `;
303
+ }
304
+
305
+ function getProfileFile() {
306
+ const shell = path.basename(process.env.SHELL || 'bash');
307
+ if (shell === 'zsh') return path.join(os.homedir(), '.zshrc');
308
+ if (shell === 'bash') {
309
+ return process.platform === 'darwin'
310
+ ? path.join(os.homedir(), '.bash_profile')
311
+ : path.join(os.homedir(), '.bashrc');
312
+ }
313
+ return path.join(os.homedir(), '.profile');
314
+ }
315
+
316
+ function escapeRegExp(value) {
317
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
318
+ }
319
+
320
+ function updateUnixProfile(jdkHome) {
321
+ const profileFile = getProfileFile();
322
+ const begin = '# flashjdk: managed block - do not edit';
323
+ const end = '# flashjdk: end';
324
+ const block = `${begin}
325
+ export JAVA_HOME="${jdkHome.replace(/"/g, '\\"')}"
326
+ export PATH="$JAVA_HOME/bin:$PATH"
327
+ ${end}`;
328
+
329
+ let content = '';
330
+ if (existsFile(profileFile)) {
331
+ content = fs.readFileSync(profileFile, 'utf8');
332
+ }
333
+
334
+ const pattern = new RegExp(`\\n?${escapeRegExp(begin)}[\\s\\S]*?${escapeRegExp(end)}\\n?`, 'g');
335
+ const cleaned = content.replace(pattern, '\n').replace(/\n{3,}/g, '\n\n').trimEnd();
336
+ const next = `${cleaned}${cleaned ? '\n\n' : ''}${block}\n`;
337
+ fs.writeFileSync(profileFile, next, 'utf8');
338
+ return profileFile;
339
+ }
340
+
341
+ async function switchJdk(jdkHome) {
342
+ const normalized = normalizePath(jdkHome);
343
+ if (!isJdkHome(normalized)) {
344
+ const error = new Error(`Not a valid JDK home: ${normalized}`);
345
+ error.code = 'EINVAL';
346
+ throw error;
347
+ }
348
+
349
+ let result;
350
+ if (process.platform === 'win32') {
351
+ result = await runFile('powershell.exe', [
352
+ '-NoProfile',
353
+ '-ExecutionPolicy',
354
+ 'Bypass',
355
+ '-Command',
356
+ buildWindowsEnvScript(),
357
+ normalized,
358
+ ], { timeout: 20000 });
359
+
360
+ if (!result.ok) {
361
+ const error = new Error(result.stderr || result.stdout || result.message || 'Failed to update Windows environment variables.');
362
+ error.code = result.code;
363
+ error.details = 'Windows requires running this app as Administrator to update Machine PATH and JAVA_HOME.';
364
+ throw error;
365
+ }
366
+ } else {
367
+ const profileFile = updateUnixProfile(normalized);
368
+ result = { ok: true, stdout: `Updated ${profileFile}`, stderr: '' };
369
+ }
370
+
371
+ process.env.JAVA_HOME = normalized;
372
+ const pathKey = process.platform === 'win32'
373
+ ? Object.keys(process.env).find(key => key.toLowerCase() === 'path') || 'Path'
374
+ : 'PATH';
375
+ process.env[pathKey] = `${path.join(normalized, 'bin')}${path.delimiter}${process.env[pathKey] || ''}`;
376
+ if (process.platform === 'win32') {
377
+ for (const key of Object.keys(process.env)) {
378
+ if (key !== pathKey && key.toLowerCase() === 'path') delete process.env[key];
379
+ }
380
+ }
381
+
382
+ return {
383
+ home: normalized,
384
+ output: `${result.stdout}${result.stderr}`.trim(),
385
+ version: await getJavaVersion(normalized),
386
+ restartHint: process.platform === 'win32'
387
+ ? 'Open a new terminal window after switching.'
388
+ : `Open a new terminal or source ${getProfileFile()}.`,
389
+ };
390
+ }
391
+
392
+ function getAppInfo() {
393
+ return {
394
+ name: pkg.name,
395
+ version: pkg.version,
396
+ platform: getPlatformKind(),
397
+ node: process.version,
398
+ cache: getCacheInfo(),
399
+ };
400
+ }
401
+
402
+ module.exports = {
403
+ addSearchRoot,
404
+ getAppInfo,
405
+ getSearchRoots,
406
+ listJdks,
407
+ normalizePath,
408
+ switchJdk,
409
+ };
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "flashjdk",
3
+ "version": "1.0.0",
4
+ "description": "flashJDK — 极速切换 JDK 版本,自动更新 PATH 和 JAVA_HOME,支持 Windows / macOS / Linux",
5
+ "keywords": [
6
+ "jdk",
7
+ "java",
8
+ "flashjdk",
9
+ "java-home",
10
+ "jdk-version",
11
+ "jdk-switch",
12
+ "windows",
13
+ "macos",
14
+ "linux",
15
+ "cross-platform",
16
+ "cli"
17
+ ],
18
+ "homepage": "https://github.com/lydAndtry/flashJDK",
19
+ "bugs": {
20
+ "url": "https://github.com/lydAndtry/flashJDK/issues"
21
+ },
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "git+https://github.com/lydAndtry/flashJDK.git"
25
+ },
26
+ "license": "Apache-2.0",
27
+ "author": "liyongde",
28
+ "bin": {
29
+ "flashjdk": "bin/flashjdk.js"
30
+ },
31
+ "files": [
32
+ "bin/",
33
+ "lib/",
34
+ "scripts/",
35
+ "README.md",
36
+ "LICENSE"
37
+ ],
38
+ "os": [
39
+ "win32",
40
+ "darwin",
41
+ "linux"
42
+ ],
43
+ "engines": {
44
+ "node": ">=12.0.0"
45
+ }
46
+ }