solforge 0.2.10 → 0.2.12

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "solforge",
3
- "version": "0.2.10",
3
+ "version": "0.2.12",
4
4
  "module": "index.ts",
5
5
  "type": "module",
6
6
  "private": false,
@@ -13,10 +13,10 @@
13
13
  "url": "https://github.com/nitishxyz/solforge/issues"
14
14
  },
15
15
  "bin": {
16
- "solforge": "cli.cjs"
16
+ "solforge": "start.cjs"
17
17
  },
18
18
  "files": [
19
- "cli.cjs",
19
+ "start.cjs",
20
20
  "scripts",
21
21
  "src",
22
22
  "server",
@@ -37,8 +37,7 @@
37
37
  "build:gui": "bun build src/gui/src/main.tsx --outdir src/gui/public/build --target=browser --minify",
38
38
  "cli": "bun src/cli/main.ts",
39
39
  "lint": "biome check",
40
- "postinstall": "node scripts/postinstall.cjs || true",
41
- "prepack": "chmod +x cli.cjs || true"
40
+ "prepack": "chmod +x start.cjs || true"
42
41
  },
43
42
  "devDependencies": {
44
43
  "@biomejs/biome": "2.2.4",
package/src/cli/main.ts CHANGED
@@ -1,7 +1,13 @@
1
1
  // Minimal, fast CLI router with @clack/prompts for UX
2
2
  import * as p from "@clack/prompts";
3
- // CLI version string; keep in sync with package.json if possible
4
- const VERSION = "0.2.4";
3
+ import { readFileSync } from "fs";
4
+ import { join, dirname } from "path";
5
+ import { fileURLToPath } from "url";
6
+
7
+ const __dirname = dirname(fileURLToPath(import.meta.url));
8
+ const pkgPath = join(__dirname, "../../package.json");
9
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
10
+ const VERSION = pkg.version;
5
11
 
6
12
  // Robust arg parsing for both bun script and compiled binary
7
13
  const known = new Set([
package/start.cjs ADDED
@@ -0,0 +1,277 @@
1
+ #!/usr/bin/env node
2
+ // SolForge CLI bootstrapper
3
+ // - Checks if solforge binary exists in PATH (preinstalled/global)
4
+ // - Downloads and installs to ~/.local/bin if not found
5
+ // - Falls back to Bun-based TS entry if available
6
+
7
+ const fs = require("node:fs");
8
+ const path = require("node:path");
9
+ const https = require("node:https");
10
+ const os = require("node:os");
11
+ const { spawn, spawnSync } = require("node:child_process");
12
+
13
+ function pkg() {
14
+ // Resolve package.json next to this file regardless of install location
15
+ const p = path.join(__dirname, "package.json");
16
+ try {
17
+ return require(p);
18
+ } catch {
19
+ return { version: "" };
20
+ }
21
+ }
22
+
23
+ function isInWorkspace() {
24
+ return (
25
+ fs.existsSync(path.join(__dirname, "src")) &&
26
+ fs.existsSync(path.join(__dirname, "package.json"))
27
+ );
28
+ }
29
+
30
+ function findBinaryInPath() {
31
+ const pathDirs = (process.env.PATH || "").split(path.delimiter);
32
+ const ext = process.platform === "win32" ? ".exe" : "";
33
+ const binName = "solforge" + ext;
34
+
35
+ for (const dir of pathDirs) {
36
+ const binPath = path.join(dir, binName);
37
+ if (fs.existsSync(binPath)) {
38
+ try {
39
+ const stat = fs.statSync(binPath);
40
+ if (stat.isFile() && binPath !== __filename) {
41
+ const result = spawnSync("file", [binPath], { encoding: "utf8" });
42
+ if (!result.stdout || !result.stdout.includes("script text")) {
43
+ return binPath;
44
+ }
45
+ }
46
+ } catch (err) {}
47
+ }
48
+ }
49
+ return null;
50
+ }
51
+
52
+ function assetName() {
53
+ const p = process.platform;
54
+ const a = process.arch;
55
+ if (p === "darwin" && a === "arm64") return "solforge-darwin-arm64";
56
+ if (p === "darwin" && a === "x64") return "solforge-darwin-x64";
57
+ if (p === "linux" && a === "x64") return "solforge-linux-x64";
58
+ if (p === "linux" && a === "arm64") return "solforge-linux-arm64";
59
+ if (p === "win32" && a === "x64") return "solforge-windows-x64.exe";
60
+ return null;
61
+ }
62
+
63
+ function downloadWithProgress(url, dest) {
64
+ return new Promise((resolve, reject) => {
65
+ const file = fs.createWriteStream(dest);
66
+ let totalBytes = 0;
67
+ let downloadedBytes = 0;
68
+
69
+ function handleRedirect(response) {
70
+ if (
71
+ response.statusCode >= 300 &&
72
+ response.statusCode < 400 &&
73
+ response.headers.location
74
+ ) {
75
+ https.get(response.headers.location, handleRedirect);
76
+ } else if (response.statusCode === 200) {
77
+ totalBytes = Number.parseInt(
78
+ response.headers["content-length"] || "0",
79
+ 10
80
+ );
81
+
82
+ response.on("data", (chunk) => {
83
+ downloadedBytes += chunk.length;
84
+ if (totalBytes > 0) {
85
+ const percent = ((downloadedBytes / totalBytes) * 100).toFixed(1);
86
+ const downloadedMB = (downloadedBytes / 1024 / 1024).toFixed(1);
87
+ const totalMB = (totalBytes / 1024 / 1024).toFixed(1);
88
+ process.stdout.write(
89
+ `\rDownloading: ${percent}% (${downloadedMB}MB / ${totalMB}MB)`
90
+ );
91
+ }
92
+ });
93
+
94
+ response.pipe(file);
95
+ file.on("finish", () => {
96
+ file.close();
97
+ process.stdout.write("\n");
98
+ resolve();
99
+ });
100
+ } else {
101
+ reject(new Error(`Download failed: ${response.statusCode}`));
102
+ }
103
+ }
104
+
105
+ https.get(url, handleRedirect).on("error", (err) => {
106
+ file.close();
107
+ reject(err);
108
+ });
109
+ });
110
+ }
111
+
112
+ function updateShellProfile(userBin) {
113
+ if (process.platform === "win32") return;
114
+
115
+ const shell = process.env.SHELL || "";
116
+ let configFile;
117
+ let shellType;
118
+
119
+ if (shell.includes("zsh")) {
120
+ configFile = path.resolve(os.homedir(), ".zshrc");
121
+ shellType = "zsh";
122
+ } else if (shell.includes("bash")) {
123
+ configFile = path.resolve(os.homedir(), ".bashrc");
124
+ shellType = "bash";
125
+ } else {
126
+ configFile = path.resolve(os.homedir(), ".profile");
127
+ shellType = "shell";
128
+ }
129
+
130
+ const pathExport = 'export PATH="$HOME/.local/bin:$PATH"';
131
+
132
+ try {
133
+ let fileContent = "";
134
+ if (fs.existsSync(configFile)) {
135
+ fileContent = fs.readFileSync(configFile, "utf8");
136
+ }
137
+
138
+ if (fileContent.includes(".local/bin")) {
139
+ console.log(`✓ PATH already configured in ${configFile}`);
140
+ return;
141
+ }
142
+
143
+ fs.appendFileSync(configFile, `\n${pathExport}\n`);
144
+ console.log(`✓ Added ${userBin} to PATH in ${configFile}`);
145
+ console.log(`✓ Restart your ${shellType} or run: source ${configFile}`);
146
+ } catch (error) {
147
+ console.log(`⚠️ Could not automatically update ${configFile}`);
148
+ }
149
+ }
150
+
151
+ async function install() {
152
+ try {
153
+ const asset = assetName();
154
+ if (!asset) {
155
+ throw new Error(`Unsupported platform: ${process.platform}-${process.arch}`);
156
+ }
157
+
158
+ const { version, repository } = pkg();
159
+ const repo =
160
+ process.env.SOLFORGE_REPO ||
161
+ (repository &&
162
+ (typeof repository === "string"
163
+ ? repository.replace(/^github:/, "")
164
+ : repository.url &&
165
+ (repository.url.match(/github\.com[:/](.+?)\.git$/) || [])[1])) ||
166
+ "nitishxyz/solforge";
167
+
168
+ const url = `https://github.com/${repo}/releases/latest/download/${asset}`;
169
+
170
+ console.log(`Installing solforge (${process.platform}/${process.arch})...`);
171
+
172
+ const userBin = path.resolve(os.homedir(), ".local", "bin");
173
+ fs.mkdirSync(userBin, { recursive: true });
174
+ const ext = process.platform === "win32" ? ".exe" : "";
175
+ const binPath = path.resolve(userBin, `solforge${ext}`);
176
+
177
+ await downloadWithProgress(url, binPath);
178
+
179
+ fs.chmodSync(binPath, 0o755);
180
+
181
+ const result = spawnSync(binPath, ["--version"], { encoding: "utf8" });
182
+ if (result.status === 0) {
183
+ console.log("\n✓ solforge installed successfully!");
184
+ console.log(`Version: ${result.stdout.trim()}`);
185
+ console.log(`Location: ${binPath}`);
186
+
187
+ const pathDirs = (process.env.PATH || "").split(path.delimiter);
188
+ if (!pathDirs.includes(userBin)) {
189
+ updateShellProfile(userBin);
190
+ console.log(`\n⚠️ Add ${userBin} to your PATH:`);
191
+ console.log(
192
+ ` echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc`
193
+ );
194
+ console.log(
195
+ ` Or for zsh: echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrc`
196
+ );
197
+ } else {
198
+ console.log(`✓ ${userBin} already in PATH`);
199
+ }
200
+ } else {
201
+ console.log(`\n✓ Installed to ${binPath}`);
202
+ }
203
+
204
+ console.log("\nRun: solforge --help");
205
+ return binPath;
206
+ } catch (error) {
207
+ console.error("Failed to install solforge CLI:", error.message);
208
+ console.error("\nPlease try installing manually:");
209
+ console.error(" curl -fsSL https://sh.solforge.sh | sh");
210
+ process.exit(1);
211
+ }
212
+ }
213
+
214
+ function run(cmd, args) {
215
+ return new Promise((resolve) => {
216
+ const child = spawn(cmd, args, { stdio: "inherit" });
217
+ child.on("exit", (code) => resolve(typeof code === "number" ? code : 0));
218
+ });
219
+ }
220
+
221
+ (async () => {
222
+ // Fast path for --version/--help without booting the app
223
+ const args = process.argv.slice(2);
224
+ if (
225
+ args.includes("-v") ||
226
+ args.includes("--version") ||
227
+ args[0] === "version"
228
+ ) {
229
+ console.log(pkg().version || "");
230
+ process.exit(0);
231
+ }
232
+ if (args.includes("-h") || args.includes("--help") || args[0] === "help") {
233
+ console.log(`
234
+ solforge <command>
235
+
236
+ Commands:
237
+ (no command) Run setup then start RPC & WS servers
238
+ rpc start Start RPC & WS servers
239
+ start Alias for 'rpc start'
240
+ config init Create sf.config.json in CWD
241
+ config get <key> Read a config value (dot path)
242
+ config set <k> <v> Set a config value
243
+ airdrop --to <pubkey> --sol <amount> Airdrop SOL via RPC faucet
244
+ mint Interactive: pick mint, receiver, amount
245
+ token clone <mint> Clone SPL token mint + accounts
246
+ program clone <programId> Clone program code (and optionally accounts)
247
+ program accounts clone <programId> Clone accounts owned by program
248
+
249
+ Options:
250
+ -h, --help Show help
251
+ -v, --version Show version
252
+ --network Bind servers to 0.0.0.0 (LAN access)
253
+ -y, --ci Non-interactive; auto-accept prompts (use existing config)
254
+ `);
255
+ process.exit(0);
256
+ }
257
+
258
+ if (isInWorkspace()) {
259
+ const bun = process.env.SOLFORGE_BUN || "bun";
260
+ const entry = path.join(__dirname, "src", "cli", "main.ts");
261
+ const code = await run(bun, [entry, ...process.argv.slice(2)]);
262
+ process.exit(code);
263
+ }
264
+
265
+ const pathBinary = findBinaryInPath();
266
+ if (pathBinary) {
267
+ const code = await run(pathBinary, process.argv.slice(2));
268
+ process.exit(code);
269
+ }
270
+
271
+ const installedPath = await install();
272
+
273
+ if (process.argv.length > 2) {
274
+ const code = await run(installedPath, process.argv.slice(2));
275
+ process.exit(code);
276
+ }
277
+ })();
package/cli.cjs DELETED
@@ -1,159 +0,0 @@
1
- #!/usr/bin/env node
2
- // SolForge CLI bootstrapper
3
- // - Prefers a prebuilt vendor binary (downloaded via postinstall or on first run)
4
- // - Falls back to Bun-based TS entry if available
5
-
6
- const fs = require("node:fs");
7
- const path = require("node:path");
8
- const https = require("node:https");
9
- const { spawn } = require("node:child_process");
10
-
11
- function pkg() {
12
- // Resolve package.json next to this file regardless of install location
13
- const p = path.join(__dirname, "package.json");
14
- try {
15
- return require(p);
16
- } catch {
17
- return { version: "" };
18
- }
19
- }
20
-
21
- function assetName() {
22
- const p = process.platform;
23
- const a = process.arch;
24
- if (p === "darwin" && a === "arm64") return "solforge-darwin-arm64";
25
- if (p === "darwin" && a === "x64") return "solforge-darwin-x64";
26
- if (p === "linux" && a === "x64") return "solforge-linux-x64";
27
- if (p === "linux" && a === "arm64") return "solforge-linux-arm64";
28
- if (p === "win32" && a === "x64") return "solforge-windows-x64.exe";
29
- return null;
30
- }
31
-
32
- function vendorPath() {
33
- const name = assetName();
34
- if (!name) return null;
35
- return path.join(__dirname, "vendor", name);
36
- }
37
-
38
- function download(url, outPath) {
39
- return new Promise((resolve, reject) => {
40
- const req = https.get(url, (res) => {
41
- if (
42
- [301, 302, 307, 308].includes(res.statusCode) &&
43
- res.headers.location
44
- ) {
45
- return resolve(download(res.headers.location, outPath));
46
- }
47
- if (res.statusCode !== 200) {
48
- return reject(new Error(`HTTP ${res.statusCode}`));
49
- }
50
- const file = fs.createWriteStream(outPath, {
51
- mode: process.platform === "win32" ? undefined : 0o755,
52
- });
53
- res.pipe(file);
54
- file.on("finish", () => file.close(() => resolve()));
55
- file.on("error", reject);
56
- });
57
- req.on("error", reject);
58
- });
59
- }
60
-
61
- async function ensureBinary() {
62
- const vp = vendorPath();
63
- if (!vp) return null;
64
- if (fs.existsSync(vp)) return vp;
65
-
66
- // Respect opt-out
67
- if (
68
- String(process.env.SOLFORGE_SKIP_DOWNLOAD || "").toLowerCase() === "true"
69
- ) {
70
- return null;
71
- }
72
-
73
- const { version, repository } = pkg();
74
- const repo =
75
- process.env.SOLFORGE_REPO ||
76
- (repository &&
77
- (typeof repository === "string"
78
- ? repository.replace(/^github:/, "")
79
- : repository.url &&
80
- (repository.url.match(/github\.com[:/](.+?)\.git$/) || [])[1])) ||
81
- "nitishxyz/solforge";
82
- if (!version) return null;
83
- const asset = path.basename(vp);
84
- const url = `https://github.com/${repo}/releases/download/v${version}/${asset}`;
85
-
86
- try {
87
- fs.mkdirSync(path.dirname(vp), { recursive: true });
88
- await download(url, vp);
89
- if (process.platform !== "win32") {
90
- try {
91
- fs.chmodSync(vp, 0o755);
92
- } catch {}
93
- }
94
- return fs.existsSync(vp) ? vp : null;
95
- } catch {
96
- return null;
97
- }
98
- }
99
-
100
- function run(cmd, args) {
101
- return new Promise((resolve) => {
102
- const child = spawn(cmd, args, { stdio: "inherit" });
103
- child.on("exit", (code) => resolve(typeof code === "number" ? code : 0));
104
- });
105
- }
106
-
107
- (async () => {
108
- // Fast path for --version/--help without booting the app
109
- const args = process.argv.slice(2);
110
- if (
111
- args.includes("-v") ||
112
- args.includes("--version") ||
113
- args[0] === "version"
114
- ) {
115
- console.log(pkg().version || "");
116
- process.exit(0);
117
- }
118
- if (args.includes("-h") || args.includes("--help") || args[0] === "help") {
119
- console.log(`
120
- solforge <command>
121
-
122
- Commands:
123
- (no command) Run setup then start RPC & WS servers
124
- rpc start Start RPC & WS servers
125
- start Alias for 'rpc start'
126
- config init Create sf.config.json in CWD
127
- config get <key> Read a config value (dot path)
128
- config set <k> <v> Set a config value
129
- airdrop --to <pubkey> --sol <amount> Airdrop SOL via RPC faucet
130
- mint Interactive: pick mint, receiver, amount
131
- token clone <mint> Clone SPL token mint + accounts
132
- program clone <programId> Clone program code (and optionally accounts)
133
- program accounts clone <programId> Clone accounts owned by program
134
-
135
- Options:
136
- -h, --help Show help
137
- -v, --version Show version
138
- --network Bind servers to 0.0.0.0 (LAN access)
139
- -y, --ci Non-interactive; auto-accept prompts (use existing config)
140
- `);
141
- process.exit(0);
142
- }
143
-
144
- const vp = await ensureBinary();
145
- if (vp) {
146
- const code = await run(vp, process.argv.slice(2));
147
- process.exit(code);
148
- }
149
- // Fallback: try to run TS entry via Bun
150
- const bun = process.env.SOLFORGE_BUN || "bun";
151
- const entry = path.join(__dirname, "src", "cli", "main.ts");
152
- const code = await run(bun, [entry, ...process.argv.slice(2)]);
153
- if (code !== 0) {
154
- console.error(
155
- "solforge: failed to run binary and Bun fallback. Install Bun or ensure a release asset exists.",
156
- );
157
- }
158
- process.exit(code);
159
- })();
@@ -1,118 +0,0 @@
1
- #!/usr/bin/env node
2
- /*
3
- SolForge postinstall: fetch the platform-specific prebuilt binary from GitHub Releases.
4
- - Skips if SOLFORGE_SKIP_DOWNLOAD=true
5
- - Falls back silently on errors (CLI will still work via Bun if installed)
6
- */
7
- const fs = require("node:fs");
8
- const path = require("node:path");
9
- const https = require("node:https");
10
-
11
- function log(msg) {
12
- console.log(`[solforge] ${msg}`);
13
- }
14
- function warn(msg) {
15
- console.warn(`[solforge] ${msg}`);
16
- }
17
-
18
- if (String(process.env.SOLFORGE_SKIP_DOWNLOAD || "").toLowerCase() === "true") {
19
- log("Skipping binary download due to SOLFORGE_SKIP_DOWNLOAD=true");
20
- process.exit(0);
21
- }
22
-
23
- function assetName() {
24
- const p = process.platform;
25
- const a = process.arch;
26
- if (p === "darwin" && a === "arm64") return "solforge-darwin-arm64";
27
- if (p === "darwin" && a === "x64") return "solforge-darwin-x64";
28
- if (p === "linux" && a === "x64") return "solforge-linux-x64";
29
- if (p === "linux" && a === "arm64") return "solforge-linux-arm64";
30
- if (p === "win32" && a === "x64") return "solforge-windows-x64.exe";
31
- return null;
32
- }
33
-
34
- function getRepo() {
35
- try {
36
- const pkg = require(path.join(__dirname, "..", "package.json"));
37
- if (pkg.repository) {
38
- if (typeof pkg.repository === "string")
39
- return pkg.repository.replace(/^github:/, "");
40
- if (pkg.repository.url) {
41
- const m = pkg.repository.url.match(/github\.com[:/](.+?)\.git$/);
42
- if (m) return m[1];
43
- }
44
- }
45
- } catch {}
46
- return process.env.SOLFORGE_REPO || "nitishxyz/solforge";
47
- }
48
-
49
- function getVersion() {
50
- try {
51
- const pkg = require(path.join(__dirname, "..", "package.json"));
52
- return pkg.version;
53
- } catch {
54
- return process.env.npm_package_version || "";
55
- }
56
- }
57
-
58
- const name = assetName();
59
- if (!name) {
60
- warn(`No prebuilt binary for ${process.platform}/${process.arch}; skipping`);
61
- process.exit(0);
62
- }
63
-
64
- const version = getVersion();
65
- if (!version) {
66
- warn("Unable to determine package version; skipping binary download");
67
- process.exit(0);
68
- }
69
-
70
- const repo = getRepo();
71
- const url = `https://github.com/${repo}/releases/download/v${version}/${name}`;
72
-
73
- const vendorDir = path.join(__dirname, "..", "vendor");
74
- const outPath = path.join(vendorDir, name);
75
-
76
- if (fs.existsSync(outPath)) {
77
- log(`Binary already present at vendor/${name}`);
78
- process.exit(0);
79
- }
80
-
81
- fs.mkdirSync(vendorDir, { recursive: true });
82
-
83
- function download(to, from, cb, redirects = 0) {
84
- const req = https.get(from, (res) => {
85
- if (
86
- [301, 302, 307, 308].includes(res.statusCode) &&
87
- res.headers.location &&
88
- redirects < 5
89
- ) {
90
- return download(to, res.headers.location, cb, redirects + 1);
91
- }
92
- if (res.statusCode !== 200) {
93
- return cb(new Error(`HTTP ${res.statusCode} for ${from}`));
94
- }
95
- const file = fs.createWriteStream(to, { mode: 0o755 });
96
- res.pipe(file);
97
- file.on("finish", () => file.close(cb));
98
- });
99
- req.on("error", (err) => cb(err));
100
- }
101
-
102
- log(`Fetching ${name} for v${version}...`);
103
- download(outPath, url, (err) => {
104
- if (err) {
105
- warn(`Could not download prebuilt binary: ${err.message}`);
106
- warn("CLI will fall back to running via Bun if available.");
107
- try {
108
- fs.unlinkSync(outPath);
109
- } catch {}
110
- process.exit(0);
111
- }
112
- if (process.platform !== "win32") {
113
- try {
114
- fs.chmodSync(outPath, 0o755);
115
- } catch {}
116
- }
117
- log(`Installed vendor/${name}`);
118
- });