@forgedesk/cli 2.0.0 → 2.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.
- package/bin/forge.js +138 -18
- package/package.json +1 -1
package/bin/forge.js
CHANGED
|
@@ -1,17 +1,49 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { spawnSync } from "node:child_process";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { existsSync } from "node:fs";
|
|
3
6
|
|
|
4
7
|
function pythonLaunchers() {
|
|
8
|
+
const launchers = [];
|
|
9
|
+
const seen = new Set();
|
|
10
|
+
|
|
11
|
+
function addLauncher(command, args = []) {
|
|
12
|
+
const key = `${command}::${args.join(" ")}`;
|
|
13
|
+
if (!seen.has(key)) {
|
|
14
|
+
seen.add(key);
|
|
15
|
+
launchers.push({ command, args });
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const explicit = process.env.FORGE_PYTHON;
|
|
20
|
+
if (explicit) {
|
|
21
|
+
addLauncher(explicit, []);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const virtualEnv = process.env.VIRTUAL_ENV;
|
|
25
|
+
if (virtualEnv) {
|
|
26
|
+
if (process.platform === "win32") {
|
|
27
|
+
addLauncher(path.join(virtualEnv, "Scripts", "python.exe"), []);
|
|
28
|
+
} else {
|
|
29
|
+
addLauncher(path.join(virtualEnv, "bin", "python"), []);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const localVenv = process.platform === "win32"
|
|
34
|
+
? path.join(process.cwd(), ".venv", "Scripts", "python.exe")
|
|
35
|
+
: path.join(process.cwd(), ".venv", "bin", "python");
|
|
36
|
+
addLauncher(localVenv, []);
|
|
37
|
+
|
|
5
38
|
if (process.platform === "win32") {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
];
|
|
39
|
+
addLauncher("py", ["-3"]);
|
|
40
|
+
addLauncher("python", []);
|
|
41
|
+
return launchers;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
addLauncher("python3", []);
|
|
45
|
+
addLauncher("python", []);
|
|
46
|
+
return launchers;
|
|
15
47
|
}
|
|
16
48
|
|
|
17
49
|
function run(command, args) {
|
|
@@ -33,16 +65,104 @@ function ensurePythonForge() {
|
|
|
33
65
|
return null;
|
|
34
66
|
}
|
|
35
67
|
|
|
36
|
-
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
68
|
+
function findUvBinary() {
|
|
69
|
+
const candidates = [
|
|
70
|
+
process.env.FORGE_UV,
|
|
71
|
+
"uv",
|
|
72
|
+
path.join(os.homedir(), ".local", "bin", "uv"),
|
|
73
|
+
path.join(os.homedir(), ".cargo", "bin", "uv"),
|
|
74
|
+
path.join(os.homedir(), "AppData", "Local", "uv", "bin", "uv.exe"),
|
|
75
|
+
].filter(Boolean);
|
|
76
|
+
|
|
77
|
+
for (const candidate of candidates) {
|
|
78
|
+
const probe = spawnSync(candidate, ["--version"], { stdio: "ignore" });
|
|
79
|
+
if (probe.status === 0) {
|
|
80
|
+
return candidate;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function ensureUv() {
|
|
87
|
+
const existing = findUvBinary();
|
|
88
|
+
if (existing) {
|
|
89
|
+
return existing;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const installer = process.platform === "win32"
|
|
93
|
+
? spawnSync("powershell", ["-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", "irm https://astral.sh/uv/install.ps1 | iex"], { stdio: "inherit" })
|
|
94
|
+
: spawnSync("sh", ["-c", "set -e; if command -v curl >/dev/null 2>&1; then curl -LsSf https://astral.sh/uv/install.sh | sh; elif command -v wget >/dev/null 2>&1; then wget -qO- https://astral.sh/uv/install.sh | sh; else exit 1; fi"], { stdio: "inherit" });
|
|
95
|
+
|
|
96
|
+
if (installer.status !== 0) {
|
|
97
|
+
return null;
|
|
44
98
|
}
|
|
99
|
+
|
|
100
|
+
return findUvBinary();
|
|
45
101
|
}
|
|
102
|
+
|
|
103
|
+
const uv = ensureUv();
|
|
104
|
+
if (!uv) {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const uvEnv = {
|
|
109
|
+
...process.env,
|
|
110
|
+
UV_LINK_MODE: process.env.UV_LINK_MODE || "copy",
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const runtimeRoot = path.join(os.homedir(), ".cache", "forgedesk", "python-runtime");
|
|
114
|
+
const runtimePython = process.platform === "win32"
|
|
115
|
+
? path.join(runtimeRoot, "Scripts", "python.exe")
|
|
116
|
+
: path.join(runtimeRoot, "bin", "python");
|
|
117
|
+
|
|
118
|
+
const createVenv = spawnSync(uv, ["venv", runtimeRoot, "--python", "3.14", "--allow-existing"], {
|
|
119
|
+
stdio: "inherit",
|
|
120
|
+
env: uvEnv,
|
|
121
|
+
});
|
|
122
|
+
if (createVenv.status !== 0) {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const installAttempts = [
|
|
127
|
+
["pip", "install", "--python", runtimePython, "--index-url", "https://pypi.org/simple", "forge-framework"],
|
|
128
|
+
["pip", "install", "--python", runtimePython, "forge-framework"],
|
|
129
|
+
[
|
|
130
|
+
"pip",
|
|
131
|
+
"install",
|
|
132
|
+
"--python",
|
|
133
|
+
runtimePython,
|
|
134
|
+
"git+https://github.com/swadhinbiswas/Forge.git",
|
|
135
|
+
],
|
|
136
|
+
];
|
|
137
|
+
|
|
138
|
+
const scriptDir = path.dirname(process.argv[1] || "");
|
|
139
|
+
const repoCandidate = path.resolve(scriptDir, "..", "..", "..");
|
|
140
|
+
if (existsSync(path.join(repoCandidate, "pyproject.toml")) && existsSync(path.join(repoCandidate, "forge_cli"))) {
|
|
141
|
+
installAttempts.push(["pip", "install", "--python", runtimePython, "-e", repoCandidate]);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
let installed = false;
|
|
145
|
+
for (const args of installAttempts) {
|
|
146
|
+
const attempt = spawnSync(uv, args, { stdio: "inherit", env: uvEnv });
|
|
147
|
+
if (attempt.status !== 0) {
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
const probe = spawnSync(runtimePython, ["-m", "forge_cli.main", "--help"], { stdio: "ignore" });
|
|
151
|
+
if (probe.status === 0) {
|
|
152
|
+
installed = true;
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (!installed) {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const probeRuntime = spawnSync(runtimePython, ["-m", "forge_cli.main", "--help"], { stdio: "ignore" });
|
|
162
|
+
if (probeRuntime.status === 0) {
|
|
163
|
+
return { command: runtimePython, args: [] };
|
|
164
|
+
}
|
|
165
|
+
|
|
46
166
|
return null;
|
|
47
167
|
}
|
|
48
168
|
|
|
@@ -50,7 +170,7 @@ const argv = process.argv.slice(2);
|
|
|
50
170
|
|
|
51
171
|
const launcher = ensurePythonForge();
|
|
52
172
|
if (!launcher) {
|
|
53
|
-
console.error("\x1b[1;31m✖\x1b[0m \x1b[1mForge CLI is unavailable.\x1b[0m\n\x1b[33mInstall Python 3.14+ and
|
|
173
|
+
console.error("\x1b[1;31m✖\x1b[0m \x1b[1mForge CLI is unavailable.\x1b[0m\n\x1b[33mInstall Python 3.14+ and uv, then run again.\x1b[0m");
|
|
54
174
|
process.exit(1);
|
|
55
175
|
}
|
|
56
176
|
|