solforge 0.2.3 → 0.2.5

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 (43) hide show
  1. package/LICENSE +2 -2
  2. package/README.md +323 -364
  3. package/cli.cjs +126 -69
  4. package/package.json +1 -1
  5. package/scripts/install.sh +112 -0
  6. package/scripts/postinstall.cjs +66 -58
  7. package/server/methods/program/get-token-accounts-by-owner.ts +7 -2
  8. package/server/ws-server.ts +4 -1
  9. package/src/api-server-entry.ts +91 -91
  10. package/src/cli/commands/rpc-start.ts +4 -1
  11. package/src/cli/main.ts +39 -14
  12. package/src/cli/run-solforge.ts +20 -6
  13. package/src/commands/add-program.ts +324 -328
  14. package/src/commands/init.ts +106 -106
  15. package/src/commands/list.ts +125 -125
  16. package/src/commands/mint.ts +246 -246
  17. package/src/commands/start.ts +834 -831
  18. package/src/commands/status.ts +80 -80
  19. package/src/commands/stop.ts +381 -382
  20. package/src/config/manager.ts +149 -149
  21. package/src/gui/public/app.css +1556 -1
  22. package/src/gui/public/build/main.css +1569 -1
  23. package/src/gui/server.ts +20 -21
  24. package/src/gui/src/app.tsx +56 -37
  25. package/src/gui/src/components/airdrop-mint-form.tsx +17 -11
  26. package/src/gui/src/components/clone-program-modal.tsx +6 -6
  27. package/src/gui/src/components/clone-token-modal.tsx +7 -7
  28. package/src/gui/src/components/modal.tsx +13 -11
  29. package/src/gui/src/components/programs-panel.tsx +27 -15
  30. package/src/gui/src/components/status-panel.tsx +31 -17
  31. package/src/gui/src/components/tokens-panel.tsx +25 -19
  32. package/src/gui/src/index.css +491 -463
  33. package/src/index.ts +161 -146
  34. package/src/rpc/start.ts +1 -1
  35. package/src/services/api-server.ts +470 -473
  36. package/src/services/port-manager.ts +167 -167
  37. package/src/services/process-registry.ts +143 -143
  38. package/src/services/program-cloner.ts +312 -312
  39. package/src/services/token-cloner.ts +799 -797
  40. package/src/services/validator.ts +288 -288
  41. package/src/types/config.ts +71 -71
  42. package/src/utils/shell.ts +75 -75
  43. package/src/utils/token-loader.ts +77 -77
package/src/index.ts CHANGED
@@ -2,187 +2,202 @@
2
2
 
3
3
  // Suppress bigint-buffer warning
4
4
  const originalStderrWrite = process.stderr.write.bind(process.stderr);
5
- process.stderr.write = function(chunk: any, encoding?: any, callback?: any) {
6
- if (typeof chunk === 'string' && chunk.includes('bigint: Failed to load bindings')) {
7
- return true; // Suppress this specific warning
8
- }
9
- return originalStderrWrite(chunk, encoding, callback);
5
+ process.stderr.write = (chunk: any, encoding?: any, callback?: any) => {
6
+ if (
7
+ typeof chunk === "string" &&
8
+ chunk.includes("bigint: Failed to load bindings")
9
+ ) {
10
+ return true; // Suppress this specific warning
11
+ }
12
+ return originalStderrWrite(chunk, encoding, callback);
10
13
  };
11
14
 
12
- import { Command } from "commander";
13
15
  import chalk from "chalk";
16
+ import { Command } from "commander";
14
17
  import { existsSync } from "fs";
15
18
  import { resolve } from "path";
19
+ import packageJson from "../package.json" with { type: "json" };
20
+ import { addProgramCommand } from "./commands/add-program.js";
16
21
  import { initCommand } from "./commands/init.js";
17
- import { statusCommand } from "./commands/status.js";
18
- import { startCommand } from "./commands/start.js";
19
- import { mintCommand } from "./commands/mint.js";
20
22
  import { listCommand } from "./commands/list.js";
21
- import { stopCommand, killCommand } from "./commands/stop.js";
22
- import { addProgramCommand } from "./commands/add-program.js";
23
- import packageJson from "../package.json" with { type: "json" };
23
+ import { mintCommand } from "./commands/mint.js";
24
+ import { startCommand } from "./commands/start.js";
25
+ import { statusCommand } from "./commands/status.js";
26
+ import { killCommand, stopCommand } from "./commands/stop.js";
24
27
 
25
28
  const program = new Command();
26
29
 
27
30
  program
28
- .name("solforge")
29
- .description("Solana localnet orchestration tool")
30
- .version(packageJson.version);
31
+ .name("solforge")
32
+ .description("Solana localnet orchestration tool")
33
+ .version(packageJson.version);
31
34
 
32
35
  // Check for sf.config.json in current directory
33
36
  function findConfig(): string | null {
34
- const configPath = resolve(process.cwd(), "sf.config.json");
35
- return existsSync(configPath) ? configPath : null;
37
+ const configPath = resolve(process.cwd(), "sf.config.json");
38
+ return existsSync(configPath) ? configPath : null;
36
39
  }
37
40
 
38
41
  program
39
- .command("init")
40
- .description("Initialize a new sf.config.json in current directory")
41
- .action(async () => {
42
- console.log(chalk.blue("🚀 Initializing SolForge configuration..."));
43
- await initCommand();
44
- });
42
+ .command("init")
43
+ .description("Initialize a new sf.config.json in current directory")
44
+ .action(async () => {
45
+ console.log(chalk.blue("🚀 Initializing SolForge configuration..."));
46
+ await initCommand();
47
+ });
45
48
 
46
49
  program
47
- .command("start")
48
- .description("Start localnet with current sf.config.json")
49
- .option("--debug", "Enable debug logging to see commands and detailed output")
50
- .option("--network", "Make API server accessible over network (binds to 0.0.0.0 instead of 127.0.0.1)")
51
- .action(async (options) => {
52
- const configPath = findConfig();
53
- if (!configPath) {
54
- console.error(
55
- chalk.red("❌ No sf.config.json found in current directory")
56
- );
57
- console.log(chalk.yellow("💡 Run `solforge init` to create one"));
58
- process.exit(1);
59
- }
60
-
61
- await startCommand(options.debug || false, options.network || false);
62
- });
50
+ .command("start")
51
+ .description("Start localnet with current sf.config.json")
52
+ .option("--debug", "Enable debug logging to see commands and detailed output")
53
+ .option(
54
+ "--network",
55
+ "Make API server accessible over network (binds to 0.0.0.0 instead of 127.0.0.1)",
56
+ )
57
+ .action(async (options) => {
58
+ const configPath = findConfig();
59
+ if (!configPath) {
60
+ console.error(
61
+ chalk.red("❌ No sf.config.json found in current directory"),
62
+ );
63
+ console.log(chalk.yellow("💡 Run `solforge init` to create one"));
64
+ process.exit(1);
65
+ }
66
+
67
+ await startCommand(options.debug || false, options.network || false);
68
+ });
63
69
 
64
70
  program
65
- .command("list")
66
- .description("List all running validators")
67
- .action(async () => {
68
- await listCommand();
69
- });
71
+ .command("list")
72
+ .description("List all running validators")
73
+ .action(async () => {
74
+ await listCommand();
75
+ });
70
76
 
71
77
  program
72
- .command("stop")
73
- .description("Stop running validator(s)")
74
- .argument("[validator-id]", "ID of validator to stop")
75
- .option("--all", "Stop all running validators")
76
- .option("--kill", "Force kill the validator (SIGKILL instead of SIGTERM)")
77
- .action(async (validatorId, options) => {
78
- await stopCommand(validatorId, options);
79
- });
78
+ .command("stop")
79
+ .description("Stop running validator(s)")
80
+ .argument("[validator-id]", "ID of validator to stop")
81
+ .option("--all", "Stop all running validators")
82
+ .option("--kill", "Force kill the validator (SIGKILL instead of SIGTERM)")
83
+ .action(async (validatorId, options) => {
84
+ await stopCommand(validatorId, options);
85
+ });
80
86
 
81
87
  program
82
- .command("kill")
83
- .description("Force kill running validator(s)")
84
- .argument("[validator-id]", "ID of validator to kill")
85
- .option("--all", "Kill all running validators")
86
- .action(async (validatorId, options) => {
87
- await killCommand(validatorId, options);
88
- });
88
+ .command("kill")
89
+ .description("Force kill running validator(s)")
90
+ .argument("[validator-id]", "ID of validator to kill")
91
+ .option("--all", "Kill all running validators")
92
+ .action(async (validatorId, options) => {
93
+ await killCommand(validatorId, options);
94
+ });
89
95
 
90
96
  program
91
- .command("api-server")
92
- .description("Start API server standalone")
93
- .option("-p, --port <port>", "Port for API server", "3000")
94
- .option("--host <host>", "Host to bind to (default: 127.0.0.1, use 0.0.0.0 for network access)")
95
- .option("--rpc-url <url>", "Validator RPC URL", "http://127.0.0.1:8899")
96
- .option("--faucet-url <url>", "Validator faucet URL", "http://127.0.0.1:9900")
97
- .option("--work-dir <dir>", "Work directory", "./.solforge")
98
- .action(async (options) => {
99
- const configPath = findConfig();
100
- if (!configPath) {
101
- console.error(
102
- chalk.red("❌ No sf.config.json found in current directory")
103
- );
104
- console.log(chalk.yellow("💡 Run `solforge init` to create one"));
105
- process.exit(1);
106
- }
107
-
108
- // Import API server components
109
- const { APIServer } = await import("./services/api-server.js");
110
- const { configManager } = await import("./config/manager.js");
111
-
112
- try {
113
- await configManager.load(configPath);
114
- const config = configManager.getConfig();
115
-
116
- const apiServer = new APIServer({
117
- port: parseInt(options.port),
118
- host: options.host,
119
- validatorRpcUrl: options.rpcUrl,
120
- validatorFaucetUrl: options.faucetUrl,
121
- config,
122
- workDir: options.workDir,
123
- });
124
-
125
- const result = await apiServer.start();
126
- if (result.success) {
127
- console.log(chalk.green("✅ API Server started successfully!"));
128
-
129
- // Keep the process alive
130
- process.on("SIGTERM", async () => {
131
- console.log(chalk.yellow("📡 API Server received SIGTERM, shutting down..."));
132
- await apiServer.stop();
133
- process.exit(0);
134
- });
135
-
136
- process.on("SIGINT", async () => {
137
- console.log(chalk.yellow("📡 API Server received SIGINT, shutting down..."));
138
- await apiServer.stop();
139
- process.exit(0);
140
- });
141
-
142
- // Keep process alive
143
- setInterval(() => {}, 1000);
144
- } else {
145
- console.error(chalk.red(`❌ Failed to start API server: ${result.error}`));
146
- process.exit(1);
147
- }
148
- } catch (error) {
149
- console.error(
150
- chalk.red(
151
- `❌ API Server error: ${
152
- error instanceof Error ? error.message : String(error)
153
- }`
154
- )
155
- );
156
- process.exit(1);
157
- }
158
- });
97
+ .command("api-server")
98
+ .description("Start API server standalone")
99
+ .option("-p, --port <port>", "Port for API server", "3000")
100
+ .option(
101
+ "--host <host>",
102
+ "Host to bind to (default: 127.0.0.1, use 0.0.0.0 for network access)",
103
+ )
104
+ .option("--rpc-url <url>", "Validator RPC URL", "http://127.0.0.1:8899")
105
+ .option("--faucet-url <url>", "Validator faucet URL", "http://127.0.0.1:9900")
106
+ .option("--work-dir <dir>", "Work directory", "./.solforge")
107
+ .action(async (options) => {
108
+ const configPath = findConfig();
109
+ if (!configPath) {
110
+ console.error(
111
+ chalk.red("❌ No sf.config.json found in current directory"),
112
+ );
113
+ console.log(chalk.yellow("💡 Run `solforge init` to create one"));
114
+ process.exit(1);
115
+ }
116
+
117
+ // Import API server components
118
+ const { APIServer } = await import("./services/api-server.js");
119
+ const { configManager } = await import("./config/manager.js");
120
+
121
+ try {
122
+ await configManager.load(configPath);
123
+ const config = configManager.getConfig();
124
+
125
+ const apiServer = new APIServer({
126
+ port: parseInt(options.port),
127
+ host: options.host,
128
+ validatorRpcUrl: options.rpcUrl,
129
+ validatorFaucetUrl: options.faucetUrl,
130
+ config,
131
+ workDir: options.workDir,
132
+ });
133
+
134
+ const result = await apiServer.start();
135
+ if (result.success) {
136
+ console.log(chalk.green(" API Server started successfully!"));
137
+
138
+ // Keep the process alive
139
+ process.on("SIGTERM", async () => {
140
+ console.log(
141
+ chalk.yellow("📡 API Server received SIGTERM, shutting down..."),
142
+ );
143
+ await apiServer.stop();
144
+ process.exit(0);
145
+ });
146
+
147
+ process.on("SIGINT", async () => {
148
+ console.log(
149
+ chalk.yellow("📡 API Server received SIGINT, shutting down..."),
150
+ );
151
+ await apiServer.stop();
152
+ process.exit(0);
153
+ });
154
+
155
+ // Keep process alive
156
+ setInterval(() => {}, 1000);
157
+ } else {
158
+ console.error(
159
+ chalk.red(`❌ Failed to start API server: ${result.error}`),
160
+ );
161
+ process.exit(1);
162
+ }
163
+ } catch (error) {
164
+ console.error(
165
+ chalk.red(
166
+ `❌ API Server error: ${
167
+ error instanceof Error ? error.message : String(error)
168
+ }`,
169
+ ),
170
+ );
171
+ process.exit(1);
172
+ }
173
+ });
159
174
 
160
175
  program
161
- .command("add-program")
162
- .description("Add a program to sf.config.json")
176
+ .command("add-program")
177
+ .description("Add a program to sf.config.json")
163
178
 
164
- .option("--program-id <address>", "Mainnet program ID to clone and deploy")
165
- .option("--name <name>", "Friendly name for the program")
166
- .option("--no-interactive", "Run in non-interactive mode")
167
- .action(async (options) => {
168
- await addProgramCommand(options);
169
- });
179
+ .option("--program-id <address>", "Mainnet program ID to clone and deploy")
180
+ .option("--name <name>", "Friendly name for the program")
181
+ .option("--no-interactive", "Run in non-interactive mode")
182
+ .action(async (options) => {
183
+ await addProgramCommand(options);
184
+ });
170
185
 
171
186
  program
172
- .command("status")
173
- .description("Show localnet status")
174
- .action(async () => {
175
- await statusCommand();
176
- });
187
+ .command("status")
188
+ .description("Show localnet status")
189
+ .action(async () => {
190
+ await statusCommand();
191
+ });
177
192
 
178
193
  program.addCommand(mintCommand);
179
194
 
180
195
  program
181
- .command("reset")
182
- .description("Reset localnet ledger")
183
- .action(async () => {
184
- console.log(chalk.blue("🔄 Resetting localnet..."));
185
- // TODO: Implement reset
186
- });
196
+ .command("reset")
197
+ .description("Reset localnet ledger")
198
+ .action(async () => {
199
+ console.log(chalk.blue("🔄 Resetting localnet..."));
200
+ // TODO: Implement reset
201
+ });
187
202
 
188
203
  program.parse();
package/src/rpc/start.ts CHANGED
@@ -27,7 +27,7 @@ export function startRpcServers(opts: RpcStartOptions = {}) {
27
27
  if (opts.dbPath) process.env.SOLFORGE_DB_PATH = opts.dbPath;
28
28
 
29
29
  const { httpServer, rpcServer } = createLiteSVMRpcServer(rpcPort, host);
30
- const { wsServer } = createLiteSVMWebSocketServer(rpcServer, wsPort);
30
+ const { wsServer } = createLiteSVMWebSocketServer(rpcServer, wsPort, host);
31
31
  const guiServer = guiEnabled
32
32
  ? startGuiServer({ port: guiPort, host, rpcPort, rpcServer })
33
33
  : null;