@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.
Files changed (2) hide show
  1. package/bin/forge.js +138 -18
  2. 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
- return [
7
- { command: "py", args: ["-3"] },
8
- { command: "python", args: [] },
9
- ];
10
- }
11
- return [
12
- { command: process.env.FORGE_PYTHON || "python3", args: [] },
13
- { command: "python", args: [] },
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
- for (const launcher of pythonLaunchers()) {
37
- const install = spawnSync(
38
- launcher.command,
39
- [...launcher.args, "-m", "pip", "install", "forge-framework"],
40
- { stdio: "inherit" },
41
- );
42
- if (install.status === 0) {
43
- return launcher;
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 run:\x1b[0m \x1b[36mpython -m pip install forge-framework\x1b[0m");
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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forgedesk/cli",
3
- "version": "2.0.0",
3
+ "version": "2.0.1",
4
4
  "description": "Node wrapper for the Forge Python CLI",
5
5
  "license": "MIT",
6
6
  "homepage": "https://forge-framework.dev",