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.
- package/LICENSE +2 -2
- package/README.md +323 -364
- package/cli.cjs +126 -69
- package/package.json +1 -1
- package/scripts/install.sh +112 -0
- package/scripts/postinstall.cjs +66 -58
- package/server/methods/program/get-token-accounts-by-owner.ts +7 -2
- package/server/ws-server.ts +4 -1
- package/src/api-server-entry.ts +91 -91
- package/src/cli/commands/rpc-start.ts +4 -1
- package/src/cli/main.ts +39 -14
- package/src/cli/run-solforge.ts +20 -6
- package/src/commands/add-program.ts +324 -328
- package/src/commands/init.ts +106 -106
- package/src/commands/list.ts +125 -125
- package/src/commands/mint.ts +246 -246
- package/src/commands/start.ts +834 -831
- package/src/commands/status.ts +80 -80
- package/src/commands/stop.ts +381 -382
- package/src/config/manager.ts +149 -149
- package/src/gui/public/app.css +1556 -1
- package/src/gui/public/build/main.css +1569 -1
- package/src/gui/server.ts +20 -21
- package/src/gui/src/app.tsx +56 -37
- package/src/gui/src/components/airdrop-mint-form.tsx +17 -11
- package/src/gui/src/components/clone-program-modal.tsx +6 -6
- package/src/gui/src/components/clone-token-modal.tsx +7 -7
- package/src/gui/src/components/modal.tsx +13 -11
- package/src/gui/src/components/programs-panel.tsx +27 -15
- package/src/gui/src/components/status-panel.tsx +31 -17
- package/src/gui/src/components/tokens-panel.tsx +25 -19
- package/src/gui/src/index.css +491 -463
- package/src/index.ts +161 -146
- package/src/rpc/start.ts +1 -1
- package/src/services/api-server.ts +470 -473
- package/src/services/port-manager.ts +167 -167
- package/src/services/process-registry.ts +143 -143
- package/src/services/program-cloner.ts +312 -312
- package/src/services/token-cloner.ts +799 -797
- package/src/services/validator.ts +288 -288
- package/src/types/config.ts +71 -71
- package/src/utils/shell.ts +75 -75
- package/src/utils/token-loader.ts +77 -77
package/src/api-server-entry.ts
CHANGED
|
@@ -1,109 +1,109 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import { APIServer } from "./services/api-server.js";
|
|
4
|
-
import { configManager } from "./config/manager.js";
|
|
5
3
|
import chalk from "chalk";
|
|
4
|
+
import { configManager } from "./config/manager.js";
|
|
5
|
+
import { APIServer } from "./services/api-server.js";
|
|
6
6
|
|
|
7
7
|
async function main() {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
8
|
+
try {
|
|
9
|
+
// Parse command line arguments
|
|
10
|
+
const args = process.argv.slice(2);
|
|
11
|
+
const portIndex = args.indexOf("--port");
|
|
12
|
+
const hostIndex = args.indexOf("--host");
|
|
13
|
+
const configIndex = args.indexOf("--config");
|
|
14
|
+
const rpcIndex = args.indexOf("--rpc-url");
|
|
15
|
+
const faucetIndex = args.indexOf("--faucet-url");
|
|
16
|
+
const workDirIndex = args.indexOf("--work-dir");
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
18
|
+
if (
|
|
19
|
+
portIndex === -1 ||
|
|
20
|
+
configIndex === -1 ||
|
|
21
|
+
rpcIndex === -1 ||
|
|
22
|
+
faucetIndex === -1 ||
|
|
23
|
+
workDirIndex === -1 ||
|
|
24
|
+
!args[portIndex + 1] ||
|
|
25
|
+
!args[configIndex + 1] ||
|
|
26
|
+
!args[rpcIndex + 1] ||
|
|
27
|
+
!args[faucetIndex + 1] ||
|
|
28
|
+
!args[workDirIndex + 1]
|
|
29
|
+
) {
|
|
30
|
+
console.error(
|
|
31
|
+
"Usage: api-server-entry --port <port> --config <config-path> --rpc-url <url> --faucet-url <url> --work-dir <dir> [--host <host>]",
|
|
32
|
+
);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
36
|
+
const port = parseInt(args[portIndex + 1]!);
|
|
37
|
+
const host =
|
|
38
|
+
hostIndex !== -1 && args[hostIndex + 1] ? args[hostIndex + 1] : undefined;
|
|
39
|
+
const configPath = args[configIndex + 1]!;
|
|
40
|
+
const rpcUrl = args[rpcIndex + 1]!;
|
|
41
|
+
const faucetUrl = args[faucetIndex + 1]!;
|
|
42
|
+
const workDir = args[workDirIndex + 1]!;
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
// Load configuration
|
|
45
|
+
await configManager.load(configPath);
|
|
46
|
+
const config = configManager.getConfig();
|
|
47
47
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
48
|
+
// Create and start API server
|
|
49
|
+
const apiServer = new APIServer({
|
|
50
|
+
port,
|
|
51
|
+
host,
|
|
52
|
+
validatorRpcUrl: rpcUrl,
|
|
53
|
+
validatorFaucetUrl: faucetUrl,
|
|
54
|
+
config,
|
|
55
|
+
workDir,
|
|
56
|
+
});
|
|
57
57
|
|
|
58
|
-
|
|
58
|
+
const result = await apiServer.start();
|
|
59
59
|
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
if (result.success) {
|
|
61
|
+
console.log(chalk.green(`🚀 API Server started on port ${port}`));
|
|
62
62
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
63
|
+
// Keep the process alive
|
|
64
|
+
process.on("SIGTERM", async () => {
|
|
65
|
+
console.log(
|
|
66
|
+
chalk.yellow("📡 API Server received SIGTERM, shutting down..."),
|
|
67
|
+
);
|
|
68
|
+
await apiServer.stop();
|
|
69
|
+
process.exit(0);
|
|
70
|
+
});
|
|
71
71
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
72
|
+
process.on("SIGINT", async () => {
|
|
73
|
+
console.log(
|
|
74
|
+
chalk.yellow("📡 API Server received SIGINT, shutting down..."),
|
|
75
|
+
);
|
|
76
|
+
await apiServer.stop();
|
|
77
|
+
process.exit(0);
|
|
78
|
+
});
|
|
79
79
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
80
|
+
// Keep process alive
|
|
81
|
+
setInterval(() => {}, 1000);
|
|
82
|
+
} else {
|
|
83
|
+
console.error(
|
|
84
|
+
chalk.red(`❌ Failed to start API server: ${result.error}`),
|
|
85
|
+
);
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
} catch (error) {
|
|
89
|
+
console.error(
|
|
90
|
+
chalk.red(
|
|
91
|
+
`❌ API Server error: ${
|
|
92
|
+
error instanceof Error ? error.message : String(error)
|
|
93
|
+
}`,
|
|
94
|
+
),
|
|
95
|
+
);
|
|
96
|
+
process.exit(1);
|
|
97
|
+
}
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
main().catch((error) => {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
101
|
+
console.error(
|
|
102
|
+
chalk.red(
|
|
103
|
+
`❌ Fatal error: ${
|
|
104
|
+
error instanceof Error ? error.message : String(error)
|
|
105
|
+
}`,
|
|
106
|
+
),
|
|
107
|
+
);
|
|
108
|
+
process.exit(1);
|
|
109
109
|
});
|
|
@@ -8,7 +8,10 @@ export async function rpcStartCommand(args: string[]) {
|
|
|
8
8
|
const cfg = await readConfig(flags["config"] as string | undefined);
|
|
9
9
|
const rpcPort = Number(flags["port"] ?? cfg.server.rpcPort ?? 8899);
|
|
10
10
|
const wsPort = Number(flags["ws-port"] ?? cfg.server.wsPort ?? rpcPort + 1);
|
|
11
|
-
const host =
|
|
11
|
+
const host =
|
|
12
|
+
flags["network"] === true
|
|
13
|
+
? "0.0.0.0"
|
|
14
|
+
: (flags["host"] as string) || "127.0.0.1";
|
|
12
15
|
const dbMode =
|
|
13
16
|
(flags["db-mode"] as string) || cfg.server.db.mode || "ephemeral";
|
|
14
17
|
const dbPath =
|
package/src/cli/main.ts
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
// Minimal, fast CLI router with @clack/prompts for UX
|
|
2
2
|
import * as p from "@clack/prompts";
|
|
3
|
+
// Load version for --version in both bun script and compiled binary
|
|
4
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
|
|
5
|
+
import pkg from "../../package.json" assert { type: "json" };
|
|
3
6
|
|
|
4
7
|
// Robust arg parsing for both bun script and compiled binary
|
|
5
8
|
const known = new Set([
|
|
6
9
|
"help",
|
|
7
10
|
"-h",
|
|
8
11
|
"--help",
|
|
12
|
+
"version",
|
|
13
|
+
"-v",
|
|
14
|
+
"--version",
|
|
9
15
|
"rpc",
|
|
10
16
|
"start",
|
|
11
17
|
"config",
|
|
@@ -21,21 +27,28 @@ const argv = firstIdx >= 0 ? raw.slice(firstIdx) : [];
|
|
|
21
27
|
async function main() {
|
|
22
28
|
const [cmd, sub, ...rest] = argv;
|
|
23
29
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
30
|
+
if (!cmd) {
|
|
31
|
+
const { runSolforge } = await import("./run-solforge");
|
|
32
|
+
// Pass through any flags provided when no explicit command was given
|
|
33
|
+
await runSolforge(raw);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
29
36
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
37
|
+
if (cmd === "help" || cmd === "-h" || cmd === "--help") {
|
|
38
|
+
printHelp();
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (cmd === "version" || cmd === "-v" || cmd === "--version") {
|
|
43
|
+
printVersion();
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
34
46
|
|
|
35
47
|
// Alias: solforge start -> solforge rpc start
|
|
36
48
|
if (cmd === "start") {
|
|
37
|
-
|
|
38
|
-
await
|
|
49
|
+
// Run the full Solforge flow (config ensure + bootstrap + servers)
|
|
50
|
+
const { runSolforge } = await import("./run-solforge");
|
|
51
|
+
await runSolforge(rest);
|
|
39
52
|
return;
|
|
40
53
|
}
|
|
41
54
|
|
|
@@ -104,7 +117,7 @@ async function main() {
|
|
|
104
117
|
}
|
|
105
118
|
|
|
106
119
|
function printHelp() {
|
|
107
|
-
|
|
120
|
+
console.log(`
|
|
108
121
|
solforge <command>
|
|
109
122
|
|
|
110
123
|
Commands:
|
|
@@ -119,12 +132,24 @@ Commands:
|
|
|
119
132
|
token clone <mint> Clone SPL token mint + accounts
|
|
120
133
|
program clone <programId> Clone program code (and optionally accounts)
|
|
121
134
|
program accounts clone <programId> Clone accounts owned by program
|
|
135
|
+
|
|
136
|
+
Options:
|
|
137
|
+
-h, --help Show help
|
|
138
|
+
-v, --version Show version
|
|
139
|
+
--network Bind servers to 0.0.0.0 (LAN access)
|
|
140
|
+
-y, --ci Non-interactive; auto-accept prompts (use existing config)
|
|
122
141
|
`);
|
|
123
142
|
}
|
|
124
143
|
|
|
125
144
|
async function unknownCommand(parts: (string | undefined)[]) {
|
|
126
|
-
|
|
127
|
-
|
|
145
|
+
p.log.error(`Unknown command: ${parts.filter(Boolean).join(" ")}`);
|
|
146
|
+
printHelp();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function printVersion() {
|
|
150
|
+
// Prefer package.json version if available
|
|
151
|
+
const v = (pkg as any)?.version ?? "";
|
|
152
|
+
console.log(String(v));
|
|
128
153
|
}
|
|
129
154
|
|
|
130
155
|
main();
|
package/src/cli/run-solforge.ts
CHANGED
|
@@ -9,17 +9,25 @@ import { startRpcServers } from "../rpc/start";
|
|
|
9
9
|
import { bootstrapEnvironment } from "./bootstrap";
|
|
10
10
|
import { cancelSetup } from "./setup-utils";
|
|
11
11
|
import { runSetupWizard } from "./setup-wizard";
|
|
12
|
+
import { parseFlags } from "./utils/args";
|
|
12
13
|
|
|
13
14
|
const CONFIG_PATH = "sf.config.json";
|
|
14
15
|
|
|
15
|
-
export async function runSolforge() {
|
|
16
|
-
const
|
|
17
|
-
|
|
16
|
+
export async function runSolforge(args: string[] = []) {
|
|
17
|
+
const { flags } = parseFlags(args);
|
|
18
|
+
const ci = flags["ci"] === true || flags["y"] === true;
|
|
19
|
+
const config = await ensureConfig(ci);
|
|
20
|
+
await startWithConfig(config, args);
|
|
18
21
|
}
|
|
19
22
|
|
|
20
|
-
async function ensureConfig(): Promise<SolforgeConfig> {
|
|
23
|
+
async function ensureConfig(ci = false): Promise<SolforgeConfig> {
|
|
21
24
|
const exists = await Bun.file(CONFIG_PATH).exists();
|
|
22
25
|
if (!exists) {
|
|
26
|
+
if (ci) {
|
|
27
|
+
// Non-interactive: write defaults and continue
|
|
28
|
+
await saveConfig(defaultConfig);
|
|
29
|
+
return defaultConfig;
|
|
30
|
+
}
|
|
23
31
|
p.intro("Solforge setup");
|
|
24
32
|
const config = await runSetupWizard();
|
|
25
33
|
await saveConfig(config);
|
|
@@ -28,6 +36,7 @@ async function ensureConfig(): Promise<SolforgeConfig> {
|
|
|
28
36
|
}
|
|
29
37
|
|
|
30
38
|
const current = await readConfig(CONFIG_PATH);
|
|
39
|
+
if (ci) return current; // Non-interactive: always reuse existing config
|
|
31
40
|
const reuse = await p.confirm({
|
|
32
41
|
message: `Use existing config at ${CONFIG_PATH}?`,
|
|
33
42
|
initialValue: true,
|
|
@@ -41,8 +50,13 @@ async function ensureConfig(): Promise<SolforgeConfig> {
|
|
|
41
50
|
return updated;
|
|
42
51
|
}
|
|
43
52
|
|
|
44
|
-
async function startWithConfig(config: SolforgeConfig) {
|
|
45
|
-
const
|
|
53
|
+
async function startWithConfig(config: SolforgeConfig, args: string[] = []) {
|
|
54
|
+
const { flags } = parseFlags(args);
|
|
55
|
+
const host = String(
|
|
56
|
+
flags["network"] === true
|
|
57
|
+
? "0.0.0.0"
|
|
58
|
+
: ((flags["host"] as string) ?? process.env.RPC_HOST ?? "127.0.0.1"),
|
|
59
|
+
);
|
|
46
60
|
const rpcPort = Number(config.server.rpcPort || defaultConfig.server.rpcPort);
|
|
47
61
|
const wsPort = Number(config.server.wsPort || rpcPort + 1);
|
|
48
62
|
const guiEnabled = config.gui?.enabled !== false;
|