jvm-manager-cli 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/.github/workflows/publish.yml +39 -0
- package/LICENSE +21 -0
- package/README.md +224 -0
- package/bin/jvm.js +796 -0
- package/package.json +36 -0
- package/test/basic.spec.mjs +45 -0
- package/test/debug.js +36 -0
- package/test/detectLatestBuild.js +16 -0
- package/test/installSystem.js +153 -0
- package/test/temp-main.js +73 -0
- package/test/temp.js +44 -0
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "jvm-manager-cli",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A simple CLI to manage Java installations on Linux",
|
|
5
|
+
"main": "bin/jvm.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
|
|
8
|
+
"check": "node bin/jvm.js --help",
|
|
9
|
+
"link": "npm unlink 2>/dev/null || true && npm link",
|
|
10
|
+
"build": "mkdir -p dist && cp bin/jvm.js dist/ && cp package.json dist/ && cp README.md dist/ && cp LICENSE dist/"
|
|
11
|
+
},
|
|
12
|
+
"author": "",
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"bin": {
|
|
15
|
+
"jvm": "bin/jvm.js"
|
|
16
|
+
},
|
|
17
|
+
"keywords": ["java", "jdk", "jvm", "cli", "linux", "openjdk"],
|
|
18
|
+
"engines": {
|
|
19
|
+
"node": ">=14"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"jest": "^29.7.0"
|
|
24
|
+
},
|
|
25
|
+
"type": "module",
|
|
26
|
+
"jest": {
|
|
27
|
+
"testMatch": [
|
|
28
|
+
"**/test/**/*.spec.mjs",
|
|
29
|
+
"**/test/**/*.test.js",
|
|
30
|
+
"**/test/**/*.spec.js"
|
|
31
|
+
],
|
|
32
|
+
"transform": {},
|
|
33
|
+
"testEnvironment": "node",
|
|
34
|
+
"verbose": true
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { execSync } from "child_process";
|
|
2
|
+
import { existsSync, readFileSync, rmSync, writeFileSync } from "fs";
|
|
3
|
+
import { join, resolve } from "path";
|
|
4
|
+
import { homedir } from "os";
|
|
5
|
+
|
|
6
|
+
const HOME = homedir();
|
|
7
|
+
const APP_NAME = "jvm-manager";
|
|
8
|
+
const APP_DIR = join(HOME, ".local", "share", APP_NAME);
|
|
9
|
+
const JDKS_DIR = join(APP_DIR, "jdks");
|
|
10
|
+
const PROJECT_FILE = ".jvmrc";
|
|
11
|
+
|
|
12
|
+
// Helper function to run jvm commands
|
|
13
|
+
function runJvmCmd(cmd, options = {}) {
|
|
14
|
+
return execSync(`node bin/jvm.js ${cmd}`, {
|
|
15
|
+
encoding: "utf8",
|
|
16
|
+
stdio: "pipe",
|
|
17
|
+
...options
|
|
18
|
+
}).trim();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
describe("jvm CLI basic functionality", () => {
|
|
22
|
+
it("should display help information", () => {
|
|
23
|
+
const helpOutput = runJvmCmd("help");
|
|
24
|
+
expect(helpOutput.toLowerCase()).toContain("jvm - linux jdk manager");
|
|
25
|
+
expect(helpOutput.toLowerCase()).toContain("usage:");
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("should check system compatibility", () => {
|
|
29
|
+
const doctorOutput = runJvmCmd("doctor");
|
|
30
|
+
expect(doctorOutput.toLowerCase()).toContain("node.js");
|
|
31
|
+
expect(doctorOutput.toLowerCase()).toContain("architecture");
|
|
32
|
+
expect(doctorOutput.toLowerCase()).toContain("os platform");
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("should handle list command when no JDK installed", () => {
|
|
36
|
+
expect(() => {
|
|
37
|
+
runJvmCmd("list");
|
|
38
|
+
}).not.toThrowError();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("should create temporary directories for testing", () => {
|
|
42
|
+
// This test is to ensure that the test environment is set up correctly
|
|
43
|
+
expect(true).toBeTruthy();
|
|
44
|
+
});
|
|
45
|
+
});
|
package/test/debug.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync, readdirSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
|
|
5
|
+
const APP_NAME = "jvm-manager";
|
|
6
|
+
const HOME = process.env.HOME;
|
|
7
|
+
const APP_DIR = join(HOME, ".local", "share", APP_NAME);
|
|
8
|
+
const JDKS_DIR = join(APP_DIR, "jdks");
|
|
9
|
+
|
|
10
|
+
console.log("=== System JVM directory ===");
|
|
11
|
+
if (existsSync("/usr/lib/jvm")) {
|
|
12
|
+
console.log("/usr/lib/jvm exists");
|
|
13
|
+
console.log("Directories in /usr/lib/jvm:");
|
|
14
|
+
readdirSync("/usr/lib/jvm", { withFileTypes: true }).forEach(entry => {
|
|
15
|
+
console.log(` ${entry.name} (${entry.isDirectory() ? "directory" : "file"})`);
|
|
16
|
+
});
|
|
17
|
+
} else {
|
|
18
|
+
console.log("/usr/lib/jvm does NOT exist");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
console.log("\n=== Local jvm-manager directory ===");
|
|
22
|
+
if (existsSync(APP_DIR)) {
|
|
23
|
+
console.log(APP_DIR + " exists");
|
|
24
|
+
console.log("Contents:");
|
|
25
|
+
readdirSync(APP_DIR, { withFileTypes: true }).forEach(entry => {
|
|
26
|
+
console.log(` ${entry.name} (${entry.isDirectory() ? "directory" : "file"})`);
|
|
27
|
+
if (entry.isDirectory() && entry.name === "jdks") {
|
|
28
|
+
console.log(" jdks contents:");
|
|
29
|
+
readdirSync(JDKS_DIR, { withFileTypes: true }).forEach(jdkEntry => {
|
|
30
|
+
console.log(` ${jdkEntry.name}`);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
} else {
|
|
35
|
+
console.log(APP_DIR + " does NOT exist");
|
|
36
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
function detectLatestBuild(version) {
|
|
2
|
+
const arch = detectArch();
|
|
3
|
+
|
|
4
|
+
let osArch = "x64";
|
|
5
|
+
if (arch === "arm64") {
|
|
6
|
+
osArch = "aarch64";
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const url = `https://download.java.net/java/GA/jdk${version}/latest/openjdk-${version}_linux-${osArch}_bin.tar.gz`;
|
|
10
|
+
const filename = `openjdk-${version}_linux-${osArch}_bin.tar.gz`;
|
|
11
|
+
|
|
12
|
+
return {
|
|
13
|
+
link: url,
|
|
14
|
+
filename: filename
|
|
15
|
+
};
|
|
16
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
function install(version) {
|
|
2
|
+
ensureDirs();
|
|
3
|
+
if (!/^\d+$/.test(version)) {
|
|
4
|
+
fail("Version should be a major number, e.g. 8, 11, 17, 21");
|
|
5
|
+
}
|
|
6
|
+
const target = join(JDKS_DIR, version);
|
|
7
|
+
if (existsSync(target)) {
|
|
8
|
+
console.log(`JDK ${version} already installed: ${target}`);
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Detect OS and use appropriate package manager
|
|
13
|
+
const osRelease = getOSRelease();
|
|
14
|
+
console.log(`Detected OS: ${osRelease.PRETTY_NAME}`);
|
|
15
|
+
|
|
16
|
+
if (osRelease.ID === "ubuntu" || osRelease.ID === "debian") {
|
|
17
|
+
// For Ubuntu/Debian, use apt package manager
|
|
18
|
+
installUbuntuDebian(version, target);
|
|
19
|
+
} else if (osRelease.ID === "fedora" || osRelease.ID === "centos" || osRelease.ID === "rhel") {
|
|
20
|
+
// For Fedora/CentOS/RHEL, use dnf/yum
|
|
21
|
+
installFedoraCentOSRHEL(version, target);
|
|
22
|
+
} else if (osRelease.ID === "arch") {
|
|
23
|
+
// For Arch Linux, use pacman
|
|
24
|
+
installArchLinux(version, target);
|
|
25
|
+
} else {
|
|
26
|
+
// For other OSes, fall back to downloading
|
|
27
|
+
installFromDownload(version, target);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function installUbuntuDebian(version, target) {
|
|
32
|
+
const packageName = `openjdk-${version}-jdk`;
|
|
33
|
+
|
|
34
|
+
console.log(`Installing OpenJDK ${version} using apt...`);
|
|
35
|
+
run(`sudo apt update`);
|
|
36
|
+
run(`sudo apt install -y ${packageName}`);
|
|
37
|
+
|
|
38
|
+
// Find the installed JDK path
|
|
39
|
+
let jdkPath;
|
|
40
|
+
if (existsSync("/usr/lib/jvm")) {
|
|
41
|
+
const entries = readdirSync("/usr/lib/jvm", { withFileTypes: true });
|
|
42
|
+
for (const entry of entries) {
|
|
43
|
+
if (entry.isDirectory() && entry.name.includes(`java-${version}`)) {
|
|
44
|
+
jdkPath = join("/usr/lib/jvm", entry.name);
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (!jdkPath) {
|
|
51
|
+
fail(`Could not find installed OpenJDK ${version} in /usr/lib/jvm`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Create symlink to system JDK
|
|
55
|
+
symlinkSync(jdkPath, target, "dir");
|
|
56
|
+
console.log(`Installed OpenJDK ${version} at ${target}`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function installFedoraCentOSRHEL(version, target) {
|
|
60
|
+
const packageName = `java-${version}-openjdk-devel`;
|
|
61
|
+
|
|
62
|
+
console.log(`Installing OpenJDK ${version} using dnf...`);
|
|
63
|
+
run(`sudo dnf install -y ${packageName}`);
|
|
64
|
+
|
|
65
|
+
// Find the installed JDK path
|
|
66
|
+
let jdkPath;
|
|
67
|
+
if (existsSync("/usr/lib/jvm")) {
|
|
68
|
+
const entries = readdirSync("/usr/lib/jvm", { withFileTypes: true });
|
|
69
|
+
for (const entry of entries) {
|
|
70
|
+
if (entry.isDirectory() && entry.name.includes(`java-${version}`)) {
|
|
71
|
+
jdkPath = join("/usr/lib/jvm", entry.name);
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (!jdkPath) {
|
|
78
|
+
fail(`Could not find installed OpenJDK ${version} in /usr/lib/jvm`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Create symlink to system JDK
|
|
82
|
+
symlinkSync(jdkPath, target, "dir");
|
|
83
|
+
console.log(`Installed OpenJDK ${version} at ${target}`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function installArchLinux(version, target) {
|
|
87
|
+
const packageName = `jdk${version}-openjdk`;
|
|
88
|
+
|
|
89
|
+
console.log(`Installing OpenJDK ${version} using pacman...`);
|
|
90
|
+
run(`sudo pacman -Syu --noconfirm ${packageName}`);
|
|
91
|
+
|
|
92
|
+
// Find the installed JDK path
|
|
93
|
+
let jdkPath;
|
|
94
|
+
if (existsSync("/usr/lib/jvm")) {
|
|
95
|
+
const entries = readdirSync("/usr/lib/jvm", { withFileTypes: true });
|
|
96
|
+
for (const entry of entries) {
|
|
97
|
+
if (entry.isDirectory() && entry.name.includes(`java-${version}`)) {
|
|
98
|
+
jdkPath = join("/usr/lib/jvm", entry.name);
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (!jdkPath) {
|
|
105
|
+
fail(`Could not find installed OpenJDK ${version} in /usr/lib/jvm`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Create symlink to system JDK
|
|
109
|
+
symlinkSync(jdkPath, target, "dir");
|
|
110
|
+
console.log(`Installed OpenJDK ${version} at ${target}`);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function installFromDownload(version, target) {
|
|
114
|
+
const { link, filename } = detectLatestBuild(version);
|
|
115
|
+
const tmpDir = run("mktemp -d");
|
|
116
|
+
const tarPath = join(tmpDir, filename);
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
console.log(`Downloading JDK ${version}...`);
|
|
120
|
+
run(`curl -fL "${link}" -o "${tarPath}"`, { stdio: "inherit" });
|
|
121
|
+
mkdirSync(target, { recursive: true });
|
|
122
|
+
run(`tar -xzf "${tarPath}" -C "${target}" --strip-components=1`, { stdio: "inherit" });
|
|
123
|
+
} catch (error) {
|
|
124
|
+
rmSync(target, { recursive: true, force: true });
|
|
125
|
+
throw error;
|
|
126
|
+
} finally {
|
|
127
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
128
|
+
}
|
|
129
|
+
console.log(`Installed JDK ${version} at ${target}`);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function getOSRelease() {
|
|
133
|
+
const releaseFile = "/etc/os-release";
|
|
134
|
+
if (!existsSync(releaseFile)) {
|
|
135
|
+
return {
|
|
136
|
+
ID: "unknown",
|
|
137
|
+
PRETTY_NAME: "Unknown"
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const content = readFileSync(releaseFile, "utf8");
|
|
142
|
+
const releaseInfo = {};
|
|
143
|
+
|
|
144
|
+
content.split("\n").forEach(line => {
|
|
145
|
+
const trimmed = line.trim();
|
|
146
|
+
if (trimmed && !trimmed.startsWith("#")) {
|
|
147
|
+
const [key, value] = trimmed.split("=");
|
|
148
|
+
releaseInfo[key] = value ? value.replace(/['"]/g, "") : "";
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
return releaseInfo;
|
|
153
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
function main() {
|
|
2
|
+
const args = process.argv.slice(2);
|
|
3
|
+
const cmd = args[0];
|
|
4
|
+
if (!cmd || cmd === "-h" || cmd === "--help" || cmd === "help") {
|
|
5
|
+
usage();
|
|
6
|
+
return;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
try {
|
|
10
|
+
if (cmd === "install") {
|
|
11
|
+
const version = args[1];
|
|
12
|
+
if (!version) fail("Missing version. Example: jvm install 17");
|
|
13
|
+
install(version);
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
if (cmd === "link") {
|
|
17
|
+
const systemPath = args[1];
|
|
18
|
+
const version = args[2];
|
|
19
|
+
if (!systemPath || !version) fail("Usage: jvm link <system-path> <version>");
|
|
20
|
+
linkSystemJdk(systemPath, version);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
if (cmd === "list") {
|
|
24
|
+
listInstalled();
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
if (cmd === "use") {
|
|
28
|
+
const version = args[1];
|
|
29
|
+
if (!version) fail("Missing version. Example: jvm use 21 --global");
|
|
30
|
+
const flags = parseFlags(args.slice(2));
|
|
31
|
+
if (flags.global && flags.project) {
|
|
32
|
+
fail("Cannot use --global and --project together");
|
|
33
|
+
}
|
|
34
|
+
if (flags.project) {
|
|
35
|
+
setProject(version);
|
|
36
|
+
} else {
|
|
37
|
+
setGlobal(version);
|
|
38
|
+
}
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
if (cmd === "env") {
|
|
42
|
+
const flags = parseFlags(args.slice(1));
|
|
43
|
+
if (flags.global && flags.project) {
|
|
44
|
+
fail("Cannot use --global and --project together");
|
|
45
|
+
}
|
|
46
|
+
if (flags.project) {
|
|
47
|
+
printEnv("project");
|
|
48
|
+
} else {
|
|
49
|
+
printEnv("global");
|
|
50
|
+
}
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (cmd === "current") {
|
|
54
|
+
showCurrent();
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
if (cmd === "doctor") {
|
|
58
|
+
doctor();
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (cmd === "remove") {
|
|
62
|
+
const version = args[1];
|
|
63
|
+
if (!version) fail("Missing version. Example: jvm remove 17");
|
|
64
|
+
removeJdk(version);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
} catch (error) {
|
|
68
|
+
const message = error?.stderr || error?.message || String(error);
|
|
69
|
+
fail(message);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
fail(`Unknown command: ${cmd}`);
|
|
73
|
+
}
|
package/test/temp.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
function detectSystemJdks() {
|
|
2
|
+
const versions = new Set();
|
|
3
|
+
|
|
4
|
+
try {
|
|
5
|
+
if (existsSync("/usr/lib/jvm")) {
|
|
6
|
+
const entries = readdirSync("/usr/lib/jvm", { withFileTypes: true });
|
|
7
|
+
for (const entry of entries) {
|
|
8
|
+
if (entry.isDirectory() && entry.name.includes("-amd64") || entry.name.includes("-aarch64")) {
|
|
9
|
+
const match17 = entry.name.match(/java-17/);
|
|
10
|
+
const match8 = entry.name.match(/java-8/);
|
|
11
|
+
const match21 = entry.name.match(/java-21/);
|
|
12
|
+
if (match17) versions.add("17");
|
|
13
|
+
if (match8) versions.add("8");
|
|
14
|
+
if (match21) versions.add("21");
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
} catch (err) {
|
|
19
|
+
// Ignore errors when /usr/lib/jvm doesn't exist
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
const versionStr = run("java -version 2>&1 | head -1");
|
|
24
|
+
const match = versionStr.match(/version "(.*)"/);
|
|
25
|
+
if (match) {
|
|
26
|
+
const javaVersion = match[1];
|
|
27
|
+
if (javaVersion.startsWith("1.")) {
|
|
28
|
+
const oldVersion = javaVersion.match(/1\.(\d+)\.0/);
|
|
29
|
+
if (oldVersion) {
|
|
30
|
+
versions.add(oldVersion[1]);
|
|
31
|
+
}
|
|
32
|
+
} else {
|
|
33
|
+
const newVersion = javaVersion.match(/(\d+)/);
|
|
34
|
+
if (newVersion) {
|
|
35
|
+
versions.add(newVersion[1]);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
} catch (err) {
|
|
40
|
+
// Ignore errors when java is not available
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return Array.from(versions).sort((a, b) => Number(a) - Number(b));
|
|
44
|
+
}
|