solforge 0.1.3 → 0.1.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "solforge",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Solana localnet orchestration tool for cloning mainnet programs and tokens",
5
5
  "module": "index.ts",
6
6
  "type": "module",
@@ -9,6 +9,7 @@ async function main() {
9
9
  // Parse command line arguments
10
10
  const args = process.argv.slice(2);
11
11
  const portIndex = args.indexOf("--port");
12
+ const hostIndex = args.indexOf("--host");
12
13
  const configIndex = args.indexOf("--config");
13
14
  const rpcIndex = args.indexOf("--rpc-url");
14
15
  const faucetIndex = args.indexOf("--faucet-url");
@@ -27,12 +28,14 @@ async function main() {
27
28
  !args[workDirIndex + 1]
28
29
  ) {
29
30
  console.error(
30
- "Usage: api-server-entry --port <port> --config <config-path> --rpc-url <url> --faucet-url <url> --work-dir <dir>"
31
+ "Usage: api-server-entry --port <port> --config <config-path> --rpc-url <url> --faucet-url <url> --work-dir <dir> [--host <host>]"
31
32
  );
32
33
  process.exit(1);
33
34
  }
34
35
 
35
36
  const port = parseInt(args[portIndex + 1]!);
37
+ const host =
38
+ hostIndex !== -1 && args[hostIndex + 1] ? args[hostIndex + 1] : undefined;
36
39
  const configPath = args[configIndex + 1]!;
37
40
  const rpcUrl = args[rpcIndex + 1]!;
38
41
  const faucetUrl = args[faucetIndex + 1]!;
@@ -45,6 +48,7 @@ async function main() {
45
48
  // Create and start API server
46
49
  const apiServer = new APIServer({
47
50
  port,
51
+ host,
48
52
  validatorRpcUrl: rpcUrl,
49
53
  validatorFaucetUrl: faucetUrl,
50
54
  config,
@@ -21,7 +21,10 @@ function generateValidatorId(name: string): string {
21
21
  return `${safeName}-${timestamp}-${randomSuffix}`;
22
22
  }
23
23
 
24
- export async function startCommand(debug: boolean = false): Promise<void> {
24
+ export async function startCommand(
25
+ debug: boolean = false,
26
+ network: boolean = false
27
+ ): Promise<void> {
25
28
  // Check prerequisites
26
29
  const tools = await checkSolanaTools();
27
30
  if (!tools.solana) {
@@ -360,7 +363,8 @@ export async function startCommand(debug: boolean = false): Promise<void> {
360
363
  const workDir = join(currentDir, ".solforge");
361
364
 
362
365
  // Start API server in background using runCommand with nohup
363
- const apiServerCommand = `nohup bun run "${apiServerScript}" --port ${apiServerPort} --config "${configPath}" --rpc-url "http://127.0.0.1:${config.localnet.port}" --faucet-url "http://127.0.0.1:${config.localnet.faucetPort}" --work-dir "${workDir}" > /dev/null 2>&1 &`;
366
+ const hostFlag = network ? ` --host "0.0.0.0"` : "";
367
+ const apiServerCommand = `nohup bun run "${apiServerScript}" --port ${apiServerPort} --config "${configPath}" --rpc-url "http://127.0.0.1:${config.localnet.port}" --faucet-url "http://127.0.0.1:${config.localnet.faucetPort}" --work-dir "${workDir}"${hostFlag} > /dev/null 2>&1 &`;
364
368
 
365
369
  const startResult = await runCommand("sh", ["-c", apiServerCommand], {
366
370
  silent: !debug,
@@ -373,8 +377,9 @@ export async function startCommand(debug: boolean = false): Promise<void> {
373
377
 
374
378
  // Test if the API server is responding
375
379
  try {
380
+ const healthCheckHost = network ? "0.0.0.0" : "127.0.0.1";
376
381
  const response = await fetch(
377
- `http://127.0.0.1:${apiServerPort}/api/health`
382
+ `http://${healthCheckHost}:${apiServerPort}/api/health`
378
383
  );
379
384
  if (response.ok) {
380
385
  apiResult = { success: true };
@@ -385,7 +390,10 @@ export async function startCommand(debug: boolean = false): Promise<void> {
385
390
  { silent: true, debug: false }
386
391
  );
387
392
  if (pidResult.success && pidResult.stdout.trim()) {
388
- apiServerPid = parseInt(pidResult.stdout.trim().split("\n")[0]);
393
+ const pidLine = pidResult.stdout.trim().split("\n")[0];
394
+ if (pidLine) {
395
+ apiServerPid = parseInt(pidLine);
396
+ }
389
397
  }
390
398
  } else {
391
399
  apiResult = {
@@ -436,7 +444,7 @@ export async function startCommand(debug: boolean = false): Promise<void> {
436
444
  status: "running",
437
445
  apiServerPort: apiResult.success ? apiServerPort : undefined,
438
446
  apiServerUrl: apiResult.success
439
- ? `http://127.0.0.1:${apiServerPort}`
447
+ ? `http://${network ? "0.0.0.0" : "127.0.0.1"}:${apiServerPort}`
440
448
  : undefined,
441
449
  apiServerPid: apiResult.success ? apiServerPid : undefined,
442
450
  };
@@ -457,9 +465,17 @@ export async function startCommand(debug: boolean = false): Promise<void> {
457
465
  )
458
466
  );
459
467
  if (apiResult.success) {
468
+ const displayHost = network ? "0.0.0.0" : "127.0.0.1";
460
469
  console.log(
461
- chalk.cyan(`šŸš€ API Server: http://127.0.0.1:${apiServerPort}/api`)
470
+ chalk.cyan(`šŸš€ API Server: http://${displayHost}:${apiServerPort}/api`)
462
471
  );
472
+ if (network) {
473
+ console.log(
474
+ chalk.yellow(
475
+ " 🌐 Network mode enabled - API server accessible from other devices"
476
+ )
477
+ );
478
+ }
463
479
  }
464
480
 
465
481
  // Airdrop SOL to mint authority if tokens were cloned
@@ -563,30 +579,31 @@ export async function startCommand(debug: boolean = false): Promise<void> {
563
579
  chalk.gray(" - Run `solforge stop --all` to stop all validators")
564
580
  );
565
581
  if (apiResult.success) {
582
+ const endpointHost = network ? "0.0.0.0" : "127.0.0.1";
566
583
  console.log(chalk.blue("\nšŸ”Œ API Endpoints:"));
567
584
  console.log(
568
585
  chalk.gray(
569
- ` - GET http://127.0.0.1:${apiServerPort}/api/tokens - List cloned tokens`
586
+ ` - GET http://${endpointHost}:${apiServerPort}/api/tokens - List cloned tokens`
570
587
  )
571
588
  );
572
589
  console.log(
573
590
  chalk.gray(
574
- ` - GET http://127.0.0.1:${apiServerPort}/api/programs - List cloned programs`
591
+ ` - GET http://${endpointHost}:${apiServerPort}/api/programs - List cloned programs`
575
592
  )
576
593
  );
577
594
  console.log(
578
595
  chalk.gray(
579
- ` - POST http://127.0.0.1:${apiServerPort}/api/tokens/{symbol}/mint - Mint tokens`
596
+ ` - POST http://${endpointHost}:${apiServerPort}/api/tokens/{symbol}/mint - Mint tokens`
580
597
  )
581
598
  );
582
599
  console.log(
583
600
  chalk.gray(
584
- ` - POST http://127.0.0.1:${apiServerPort}/api/airdrop - Airdrop SOL`
601
+ ` - POST http://${endpointHost}:${apiServerPort}/api/airdrop - Airdrop SOL`
585
602
  )
586
603
  );
587
604
  console.log(
588
605
  chalk.gray(
589
- ` - GET http://127.0.0.1:${apiServerPort}/api/wallet/{address}/balances - Get balances`
606
+ ` - GET http://${endpointHost}:${apiServerPort}/api/wallet/{address}/balances - Get balances`
590
607
  )
591
608
  );
592
609
  }
package/src/index.ts CHANGED
@@ -1,5 +1,14 @@
1
1
  #!/usr/bin/env bun
2
2
 
3
+ // Suppress bigint-buffer warning
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);
10
+ };
11
+
3
12
  import { Command } from "commander";
4
13
  import chalk from "chalk";
5
14
  import { existsSync } from "fs";
@@ -38,6 +47,7 @@ program
38
47
  .command("start")
39
48
  .description("Start localnet with current sf.config.json")
40
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)")
41
51
  .action(async (options) => {
42
52
  const configPath = findConfig();
43
53
  if (!configPath) {
@@ -48,7 +58,7 @@ program
48
58
  process.exit(1);
49
59
  }
50
60
 
51
- await startCommand(options.debug || false);
61
+ await startCommand(options.debug || false, options.network || false);
52
62
  });
53
63
 
54
64
  program
@@ -77,6 +87,76 @@ program
77
87
  await killCommand(validatorId, options);
78
88
  });
79
89
 
90
+ 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
+ });
159
+
80
160
  program
81
161
  .command("add-program")
82
162
  .description("Add a program to sf.config.json")
@@ -16,6 +16,7 @@ import type { ClonedToken } from "./token-cloner.js";
16
16
 
17
17
  export interface APIServerConfig {
18
18
  port: number;
19
+ host?: string;
19
20
  validatorRpcUrl: string;
20
21
  validatorFaucetUrl: string;
21
22
  config: Config;
@@ -462,15 +463,16 @@ export class APIServer {
462
463
  async start(): Promise<{ success: boolean; error?: string }> {
463
464
  return new Promise((resolve) => {
464
465
  try {
465
- this.server = this.app.listen(this.config.port, "127.0.0.1", () => {
466
+ const host = this.config.host || "127.0.0.1";
467
+ this.server = this.app.listen(this.config.port, host, () => {
466
468
  console.log(
467
469
  chalk.green(
468
- `šŸš€ API Server started on http://127.0.0.1:${this.config.port}`
470
+ `šŸš€ API Server started on http://${host}:${this.config.port}`
469
471
  )
470
472
  );
471
473
  console.log(
472
474
  chalk.gray(
473
- ` šŸ“‹ Endpoints available at http://127.0.0.1:${this.config.port}/api`
475
+ ` šŸ“‹ Endpoints available at http://${host}:${this.config.port}/api`
474
476
  )
475
477
  );
476
478
  resolve({ success: true });