solforge 0.1.4 ā 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 +1 -1
- package/src/api-server-entry.ts +5 -1
- package/src/commands/start.ts +28 -11
- package/src/index.ts +72 -1
- package/src/services/api-server.ts +5 -3
package/package.json
CHANGED
package/src/api-server-entry.ts
CHANGED
|
@@ -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,
|
package/src/commands/start.ts
CHANGED
|
@@ -21,7 +21,10 @@ function generateValidatorId(name: string): string {
|
|
|
21
21
|
return `${safeName}-${timestamp}-${randomSuffix}`;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
export async function startCommand(
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
601
|
+
` - POST http://${endpointHost}:${apiServerPort}/api/airdrop - Airdrop SOL`
|
|
585
602
|
)
|
|
586
603
|
);
|
|
587
604
|
console.log(
|
|
588
605
|
chalk.gray(
|
|
589
|
-
` - GET http
|
|
606
|
+
` - GET http://${endpointHost}:${apiServerPort}/api/wallet/{address}/balances - Get balances`
|
|
590
607
|
)
|
|
591
608
|
);
|
|
592
609
|
}
|
package/src/index.ts
CHANGED
|
@@ -47,6 +47,7 @@ program
|
|
|
47
47
|
.command("start")
|
|
48
48
|
.description("Start localnet with current sf.config.json")
|
|
49
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)")
|
|
50
51
|
.action(async (options) => {
|
|
51
52
|
const configPath = findConfig();
|
|
52
53
|
if (!configPath) {
|
|
@@ -57,7 +58,7 @@ program
|
|
|
57
58
|
process.exit(1);
|
|
58
59
|
}
|
|
59
60
|
|
|
60
|
-
await startCommand(options.debug || false);
|
|
61
|
+
await startCommand(options.debug || false, options.network || false);
|
|
61
62
|
});
|
|
62
63
|
|
|
63
64
|
program
|
|
@@ -86,6 +87,76 @@ program
|
|
|
86
87
|
await killCommand(validatorId, options);
|
|
87
88
|
});
|
|
88
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
|
+
|
|
89
160
|
program
|
|
90
161
|
.command("add-program")
|
|
91
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
|
-
|
|
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
|
|
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
|
|
475
|
+
` š Endpoints available at http://${host}:${this.config.port}/api`
|
|
474
476
|
)
|
|
475
477
|
);
|
|
476
478
|
resolve({ success: true });
|