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
@@ -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
- 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");
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
- 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
- }
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
- 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]!;
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
- // Load configuration
45
- await configManager.load(configPath);
46
- const config = configManager.getConfig();
44
+ // Load configuration
45
+ await configManager.load(configPath);
46
+ const config = configManager.getConfig();
47
47
 
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
- });
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
- const result = await apiServer.start();
58
+ const result = await apiServer.start();
59
59
 
60
- if (result.success) {
61
- console.log(chalk.green(`🚀 API Server started on port ${port}`));
60
+ if (result.success) {
61
+ console.log(chalk.green(`🚀 API Server started on port ${port}`));
62
62
 
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
- });
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
- 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
- });
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
- // 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
- }
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
- console.error(
102
- chalk.red(
103
- `❌ Fatal error: ${
104
- error instanceof Error ? error.message : String(error)
105
- }`
106
- )
107
- );
108
- process.exit(1);
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 = (flags["host"] as string) || "127.0.0.1";
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
- if (!cmd) {
25
- const { runSolforge } = await import("./run-solforge");
26
- await runSolforge();
27
- return;
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
- if (cmd === "help" || cmd === "-h" || cmd === "--help") {
31
- printHelp();
32
- return;
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
- const { rpcStartCommand } = await import("./commands/rpc-start");
38
- await rpcStartCommand(rest);
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
- console.log(`
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
- p.log.error(`Unknown command: ${parts.filter(Boolean).join(" ")}`);
127
- printHelp();
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();
@@ -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 config = await ensureConfig();
17
- await startWithConfig(config);
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 host = String(process.env.RPC_HOST || "127.0.0.1");
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;