solforge 0.1.6 → 0.2.0

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 (194) hide show
  1. package/.agi/agi.sqlite +0 -0
  2. package/.claude/settings.local.json +9 -0
  3. package/.github/workflows/release-binaries.yml +133 -0
  4. package/.tmp/.787ebcdbf7b8fde8-00000000.hm +0 -0
  5. package/.tmp/.bffe6efebdf8aedc-00000000.hm +0 -0
  6. package/AGENTS.md +271 -0
  7. package/CLAUDE.md +106 -0
  8. package/PROJECT_STRUCTURE.md +124 -0
  9. package/README.md +367 -393
  10. package/SOLANA_KIT_GUIDE.md +251 -0
  11. package/SOLFORGE.md +119 -0
  12. package/biome.json +34 -0
  13. package/bun.lock +743 -0
  14. package/docs/bun-single-file-executable.md +585 -0
  15. package/docs/cli-plan.md +154 -0
  16. package/docs/data-indexing-plan.md +214 -0
  17. package/docs/gui-roadmap.md +202 -0
  18. package/drizzle/0000_friendly_millenium_guard.sql +53 -0
  19. package/drizzle/0001_stale_sentinels.sql +2 -0
  20. package/drizzle/meta/0000_snapshot.json +329 -0
  21. package/drizzle/meta/0001_snapshot.json +345 -0
  22. package/drizzle/meta/_journal.json +20 -0
  23. package/drizzle.config.ts +12 -0
  24. package/index.ts +21 -0
  25. package/mint.sh +47 -0
  26. package/package.json +45 -69
  27. package/postcss.config.js +6 -0
  28. package/rpc-server.ts.backup +519 -0
  29. package/server/index.ts +5 -0
  30. package/server/lib/base58.ts +33 -0
  31. package/server/lib/faucet.ts +110 -0
  32. package/server/lib/spl-token.ts +57 -0
  33. package/server/methods/TEMPLATE.md +117 -0
  34. package/server/methods/account/get-account-info.ts +90 -0
  35. package/server/methods/account/get-balance.ts +27 -0
  36. package/server/methods/account/get-multiple-accounts.ts +83 -0
  37. package/server/methods/account/get-parsed-account-info.ts +21 -0
  38. package/server/methods/account/index.ts +12 -0
  39. package/server/methods/account/parsers/index.ts +52 -0
  40. package/server/methods/account/parsers/loader-upgradeable.ts +66 -0
  41. package/server/methods/account/parsers/spl-token.ts +237 -0
  42. package/server/methods/account/parsers/system.ts +4 -0
  43. package/server/methods/account/request-airdrop.ts +219 -0
  44. package/server/methods/admin/adopt-mint-authority.ts +94 -0
  45. package/server/methods/admin/clone-program-accounts.ts +55 -0
  46. package/server/methods/admin/clone-program.ts +152 -0
  47. package/server/methods/admin/clone-token-accounts.ts +117 -0
  48. package/server/methods/admin/clone-token-mint.ts +82 -0
  49. package/server/methods/admin/create-mint.ts +114 -0
  50. package/server/methods/admin/create-token-account.ts +137 -0
  51. package/server/methods/admin/helpers.ts +70 -0
  52. package/server/methods/admin/index.ts +10 -0
  53. package/server/methods/admin/list-mints.ts +21 -0
  54. package/server/methods/admin/load-program.ts +52 -0
  55. package/server/methods/admin/mint-to.ts +278 -0
  56. package/server/methods/block/get-block-height.ts +5 -0
  57. package/server/methods/block/get-block.ts +35 -0
  58. package/server/methods/block/get-blocks-with-limit.ts +23 -0
  59. package/server/methods/block/get-latest-blockhash.ts +12 -0
  60. package/server/methods/block/get-slot.ts +5 -0
  61. package/server/methods/block/index.ts +6 -0
  62. package/server/methods/block/is-blockhash-valid.ts +23 -0
  63. package/server/methods/epoch/get-cluster-nodes.ts +17 -0
  64. package/server/methods/epoch/get-epoch-info.ts +16 -0
  65. package/server/methods/epoch/get-epoch-schedule.ts +15 -0
  66. package/server/methods/epoch/get-highest-snapshot-slot.ts +9 -0
  67. package/server/methods/epoch/get-leader-schedule.ts +8 -0
  68. package/server/methods/epoch/get-max-retransmit-slot.ts +9 -0
  69. package/server/methods/epoch/get-max-shred-insert-slot.ts +9 -0
  70. package/server/methods/epoch/get-slot-leader.ts +6 -0
  71. package/server/methods/epoch/get-slot-leaders.ts +9 -0
  72. package/server/methods/epoch/get-stake-activation.ts +9 -0
  73. package/server/methods/epoch/get-stake-minimum-delegation.ts +9 -0
  74. package/server/methods/epoch/get-vote-accounts.ts +19 -0
  75. package/server/methods/epoch/index.ts +13 -0
  76. package/server/methods/epoch/minimum-ledger-slot.ts +5 -0
  77. package/server/methods/fee/get-fee-calculator-for-blockhash.ts +12 -0
  78. package/server/methods/fee/get-fee-for-message.ts +8 -0
  79. package/server/methods/fee/get-fee-rate-governor.ts +16 -0
  80. package/server/methods/fee/get-fees.ts +14 -0
  81. package/server/methods/fee/get-recent-prioritization-fees.ts +22 -0
  82. package/server/methods/fee/index.ts +5 -0
  83. package/server/methods/get-address-lookup-table.ts +31 -0
  84. package/server/methods/index.ts +265 -0
  85. package/server/methods/performance/get-recent-performance-samples.ts +25 -0
  86. package/server/methods/performance/get-transaction-count.ts +5 -0
  87. package/server/methods/performance/index.ts +2 -0
  88. package/server/methods/program/get-block-commitment.ts +9 -0
  89. package/server/methods/program/get-block-production.ts +14 -0
  90. package/server/methods/program/get-block-time.ts +21 -0
  91. package/server/methods/program/get-blocks.ts +11 -0
  92. package/server/methods/program/get-first-available-block.ts +9 -0
  93. package/server/methods/program/get-genesis-hash.ts +6 -0
  94. package/server/methods/program/get-identity.ts +6 -0
  95. package/server/methods/program/get-inflation-governor.ts +15 -0
  96. package/server/methods/program/get-inflation-rate.ts +10 -0
  97. package/server/methods/program/get-inflation-reward.ts +12 -0
  98. package/server/methods/program/get-largest-accounts.ts +8 -0
  99. package/server/methods/program/get-parsed-program-accounts.ts +12 -0
  100. package/server/methods/program/get-parsed-token-accounts-by-delegate.ts +12 -0
  101. package/server/methods/program/get-parsed-token-accounts-by-owner.ts +12 -0
  102. package/server/methods/program/get-program-accounts.ts +221 -0
  103. package/server/methods/program/get-supply.ts +13 -0
  104. package/server/methods/program/get-token-account-balance.ts +64 -0
  105. package/server/methods/program/get-token-accounts-by-delegate.ts +81 -0
  106. package/server/methods/program/get-token-accounts-by-owner.ts +390 -0
  107. package/server/methods/program/get-token-largest-accounts.ts +80 -0
  108. package/server/methods/program/get-token-supply.ts +38 -0
  109. package/server/methods/program/index.ts +21 -0
  110. package/server/methods/solforge/index.ts +155 -0
  111. package/server/methods/system/get-health.ts +5 -0
  112. package/server/methods/system/get-minimum-balance-for-rent-exemption.ts +13 -0
  113. package/server/methods/system/get-version.ts +9 -0
  114. package/server/methods/system/index.ts +3 -0
  115. package/server/methods/transaction/get-confirmed-transaction.ts +11 -0
  116. package/server/methods/transaction/get-parsed-transaction.ts +21 -0
  117. package/server/methods/transaction/get-signature-statuses.ts +72 -0
  118. package/server/methods/transaction/get-signatures-for-address.ts +45 -0
  119. package/server/methods/transaction/get-transaction.ts +428 -0
  120. package/server/methods/transaction/index.ts +7 -0
  121. package/server/methods/transaction/send-transaction.ts +232 -0
  122. package/server/methods/transaction/simulate-transaction.ts +56 -0
  123. package/server/rpc-server.ts +474 -0
  124. package/server/types.ts +74 -0
  125. package/server/ws-server.ts +171 -0
  126. package/sf.config.json +38 -0
  127. package/src/cli/bootstrap.ts +67 -0
  128. package/src/cli/commands/airdrop.ts +37 -0
  129. package/src/cli/commands/config.ts +39 -0
  130. package/src/cli/commands/mint.ts +187 -0
  131. package/src/cli/commands/program-clone.ts +124 -0
  132. package/src/cli/commands/program-load.ts +64 -0
  133. package/src/cli/commands/rpc-start.ts +46 -0
  134. package/src/cli/commands/token-adopt-authority.ts +37 -0
  135. package/src/cli/commands/token-clone.ts +113 -0
  136. package/src/cli/commands/token-create.ts +81 -0
  137. package/src/cli/main.ts +130 -0
  138. package/src/cli/run-solforge.ts +98 -0
  139. package/src/cli/setup-utils.ts +54 -0
  140. package/src/cli/setup-wizard.ts +256 -0
  141. package/src/cli/utils/args.ts +15 -0
  142. package/src/config/index.ts +130 -0
  143. package/src/db/index.ts +83 -0
  144. package/src/db/schema/accounts.ts +23 -0
  145. package/src/db/schema/address-signatures.ts +31 -0
  146. package/src/db/schema/index.ts +5 -0
  147. package/src/db/schema/meta-kv.ts +9 -0
  148. package/src/db/schema/transactions.ts +29 -0
  149. package/src/db/schema/tx-accounts.ts +33 -0
  150. package/src/db/tx-store.ts +229 -0
  151. package/src/gui/public/app.css +1 -0
  152. package/src/gui/public/index.html +19 -0
  153. package/src/gui/server.ts +297 -0
  154. package/src/gui/src/api.ts +127 -0
  155. package/src/gui/src/app.tsx +390 -0
  156. package/src/gui/src/components/airdrop-mint-form.tsx +216 -0
  157. package/src/gui/src/components/clone-program-modal.tsx +183 -0
  158. package/src/gui/src/components/clone-token-modal.tsx +211 -0
  159. package/src/gui/src/components/modal.tsx +127 -0
  160. package/src/gui/src/components/programs-panel.tsx +112 -0
  161. package/src/gui/src/components/status-panel.tsx +122 -0
  162. package/src/gui/src/components/tokens-panel.tsx +116 -0
  163. package/src/gui/src/hooks/use-interval.ts +17 -0
  164. package/src/gui/src/index.css +529 -0
  165. package/src/gui/src/main.tsx +17 -0
  166. package/src/migrations-bundled.ts +17 -0
  167. package/src/rpc/start.ts +44 -0
  168. package/tailwind.config.js +27 -0
  169. package/test-client.ts +120 -0
  170. package/tmp/inspect-html.ts +4 -0
  171. package/tmp/response-test.ts +5 -0
  172. package/tmp/test-html.ts +5 -0
  173. package/tmp/test-server.ts +13 -0
  174. package/tsconfig.json +24 -23
  175. package/LICENSE +0 -21
  176. package/scripts/postinstall.cjs +0 -103
  177. package/src/api-server-entry.ts +0 -109
  178. package/src/commands/add-program.ts +0 -337
  179. package/src/commands/init.ts +0 -122
  180. package/src/commands/list.ts +0 -136
  181. package/src/commands/mint.ts +0 -336
  182. package/src/commands/start.ts +0 -878
  183. package/src/commands/status.ts +0 -99
  184. package/src/commands/stop.ts +0 -406
  185. package/src/config/manager.ts +0 -157
  186. package/src/index.ts +0 -188
  187. package/src/services/api-server.ts +0 -532
  188. package/src/services/port-manager.ts +0 -177
  189. package/src/services/process-registry.ts +0 -154
  190. package/src/services/program-cloner.ts +0 -317
  191. package/src/services/token-cloner.ts +0 -809
  192. package/src/services/validator.ts +0 -295
  193. package/src/types/config.ts +0 -110
  194. package/src/utils/shell.ts +0 -110
@@ -1,99 +0,0 @@
1
- import chalk from "chalk";
2
- import { runCommand, checkSolanaTools } from "../utils/shell.js";
3
- import { configManager } from "../config/manager.js";
4
- import { processRegistry } from "../services/process-registry.js";
5
-
6
- export async function statusCommand(): Promise<void> {
7
- console.log(chalk.blue("📊 Checking system status...\n"));
8
-
9
- // Check Solana CLI tools
10
- console.log(chalk.cyan("🔧 Solana CLI Tools:"));
11
- const tools = await checkSolanaTools();
12
-
13
- console.log(
14
- ` ${tools.solana ? "✅" : "❌"} solana CLI: ${
15
- tools.solana ? "Available" : "Not found"
16
- }`
17
- );
18
- console.log(
19
- ` ${tools.splToken ? "✅" : "❌"} spl-token CLI: ${
20
- tools.splToken ? "Available" : "Not found"
21
- }`
22
- );
23
-
24
- if (!tools.solana || !tools.splToken) {
25
- console.log(chalk.yellow("\n💡 Install Solana CLI tools:"));
26
- console.log(
27
- chalk.gray(
28
- ' sh -c "$(curl -sSfL https://release.solana.com/v1.18.4/install)"'
29
- )
30
- );
31
- console.log();
32
- }
33
-
34
- // Check running validators
35
- console.log(chalk.cyan("\n🏗️ Validator Status:"));
36
-
37
- // Clean up dead processes first
38
- await processRegistry.cleanup();
39
-
40
- const validators = processRegistry.getRunning();
41
-
42
- if (validators.length === 0) {
43
- console.log(` ❌ No validators running`);
44
- console.log(` 💡 Run 'solforge start' to launch a validator`);
45
- } else {
46
- console.log(
47
- ` ✅ ${validators.length} validator${
48
- validators.length > 1 ? "s" : ""
49
- } running`
50
- );
51
-
52
- for (const validator of validators) {
53
- const isRunning = await processRegistry.isProcessRunning(validator.pid);
54
- if (isRunning) {
55
- console.log(` 🔹 ${validator.name} (${validator.id}):`);
56
- console.log(` 🌐 RPC: ${validator.rpcUrl}`);
57
- console.log(` 💰 Faucet: ${validator.faucetUrl}`);
58
- console.log(` 🆔 PID: ${validator.pid}`);
59
-
60
- // Get current slot for this validator
61
- try {
62
- const slotResult = await runCommand(
63
- "solana",
64
- ["slot", "--url", validator.rpcUrl, "--output", "json"],
65
- { silent: true, jsonOutput: true }
66
- );
67
-
68
- if (slotResult.success && typeof slotResult.stdout === "object") {
69
- console.log(` 📊 Current slot: ${slotResult.stdout}`);
70
- }
71
- } catch {
72
- // Ignore slot check errors
73
- }
74
- } else {
75
- processRegistry.updateStatus(validator.id, "stopped");
76
- console.log(
77
- ` ⚠️ ${validator.name} (${validator.id}): Process stopped`
78
- );
79
- }
80
- }
81
-
82
- console.log(` 💡 Run 'solforge list' for detailed validator information`);
83
- }
84
-
85
- // Check config file
86
- console.log(chalk.cyan("\n📄 Configuration:"));
87
- try {
88
- const config = configManager.getConfig();
89
- console.log(` ✅ Config loaded: ${configManager.getConfigPath()}`);
90
- console.log(` 📝 Project: ${config.name}`);
91
- console.log(` 🪙 Tokens: ${config.tokens.length}`);
92
- console.log(` 📦 Programs: ${config.programs.length}`);
93
- } catch (error) {
94
- console.log(` ❌ No valid configuration found`);
95
- console.log(` 💡 Run 'solforge init' to create one`);
96
- }
97
-
98
- console.log();
99
- }
@@ -1,406 +0,0 @@
1
- import chalk from "chalk";
2
- import { select } from "@inquirer/prompts";
3
- import { processRegistry } from "../services/process-registry.js";
4
-
5
- import type { RunningValidator } from "../services/process-registry.js";
6
-
7
- export async function stopCommand(
8
- validatorId?: string,
9
- options: { all?: boolean; kill?: boolean } = {}
10
- ): Promise<void> {
11
- console.log(chalk.blue("🛑 Stopping validator(s)...\n"));
12
-
13
- // Clean up dead processes first
14
- await processRegistry.cleanup();
15
-
16
- const validators = processRegistry.getRunning();
17
-
18
- if (validators.length === 0) {
19
- console.log(chalk.yellow("⚠️ No running validators found"));
20
- return;
21
- }
22
-
23
- let validatorsToStop: RunningValidator[] = [];
24
-
25
- if (options.all) {
26
- // Stop all validators
27
- validatorsToStop = validators;
28
- console.log(
29
- chalk.cyan(`🔄 Stopping all ${validators.length} validator(s)...`)
30
- );
31
- } else if (validatorId) {
32
- // Stop specific validator
33
- const validator = processRegistry.getById(validatorId);
34
- if (!validator) {
35
- console.error(
36
- chalk.red(`❌ Validator with ID '${validatorId}' not found`)
37
- );
38
- console.log(
39
- chalk.gray("💡 Use `solforge list` to see running validators")
40
- );
41
- return;
42
- }
43
- validatorsToStop = [validator];
44
- console.log(
45
- chalk.cyan(
46
- `🔄 Stopping validator '${validator.name}' (${validatorId})...`
47
- )
48
- );
49
- } else {
50
- // No specific validator specified, show available options
51
- console.log(chalk.yellow("⚠️ Please specify which validator to stop:"));
52
- console.log(
53
- chalk.gray("💡 Use `solforge stop <id>` to stop a specific validator")
54
- );
55
- console.log(
56
- chalk.gray("💡 Use `solforge stop --all` to stop all validators")
57
- );
58
- console.log(chalk.gray("💡 Use `solforge list` to see running validators"));
59
- return;
60
- }
61
-
62
- // Stop each validator
63
- let stoppedCount = 0;
64
- let errorCount = 0;
65
-
66
- for (const validator of validatorsToStop) {
67
- try {
68
- const result = await stopValidator(validator, options.kill);
69
- if (result.success) {
70
- console.log(
71
- chalk.green(`✅ Stopped ${validator.name} (${validator.id})`)
72
- );
73
- stoppedCount++;
74
- } else {
75
- console.error(
76
- chalk.red(
77
- `❌ Failed to stop ${validator.name} (${validator.id}): ${result.error}`
78
- )
79
- );
80
- errorCount++;
81
- }
82
- } catch (error) {
83
- console.error(
84
- chalk.red(
85
- `❌ Error stopping ${validator.name} (${validator.id}): ${
86
- error instanceof Error ? error.message : String(error)
87
- }`
88
- )
89
- );
90
- errorCount++;
91
- }
92
- }
93
-
94
- // Summary
95
- console.log();
96
- if (stoppedCount > 0) {
97
- console.log(
98
- chalk.green(`✅ Successfully stopped ${stoppedCount} validator(s)`)
99
- );
100
- }
101
- if (errorCount > 0) {
102
- console.log(chalk.red(`❌ Failed to stop ${errorCount} validator(s)`));
103
- }
104
- }
105
-
106
- async function stopValidator(
107
- validator: RunningValidator,
108
- forceKill: boolean = false
109
- ): Promise<{ success: boolean; error?: string }> {
110
- try {
111
- // Stop the API server first if it has a PID
112
- if (validator.apiServerPid) {
113
- try {
114
- process.kill(validator.apiServerPid, "SIGTERM");
115
- console.log(
116
- chalk.gray(`📡 Stopped API server (PID: ${validator.apiServerPid})`)
117
- );
118
- } catch (error) {
119
- console.log(
120
- chalk.yellow(
121
- `⚠️ Warning: Failed to stop API server: ${
122
- error instanceof Error ? error.message : String(error)
123
- }`
124
- )
125
- );
126
- }
127
- }
128
-
129
- // Check if process is still running
130
- const isRunning = await processRegistry.isProcessRunning(validator.pid);
131
- if (!isRunning) {
132
- // Process already stopped, just clean up registry
133
- processRegistry.unregister(validator.id);
134
- return { success: true };
135
- }
136
-
137
- const signal = forceKill ? "SIGKILL" : "SIGTERM";
138
-
139
- // Send termination signal
140
- process.kill(validator.pid, signal);
141
-
142
- if (forceKill) {
143
- // For SIGKILL, process should stop immediately
144
- processRegistry.unregister(validator.id);
145
- return { success: true };
146
- } else {
147
- // For SIGTERM, wait for graceful shutdown
148
- const shutdownResult = await waitForProcessShutdown(validator.pid, 10000);
149
-
150
- if (shutdownResult.success) {
151
- processRegistry.unregister(validator.id);
152
- return { success: true };
153
- } else {
154
- // Graceful shutdown failed, try force kill
155
- console.log(
156
- chalk.yellow(
157
- `⚠️ Graceful shutdown failed for ${validator.name}, force killing...`
158
- )
159
- );
160
- process.kill(validator.pid, "SIGKILL");
161
- processRegistry.unregister(validator.id);
162
- return { success: true };
163
- }
164
- }
165
- } catch (error) {
166
- const errorMessage = error instanceof Error ? error.message : String(error);
167
-
168
- // If error is "ESRCH" (No such process), the process is already gone
169
- if (errorMessage.includes("ESRCH")) {
170
- processRegistry.unregister(validator.id);
171
- return { success: true };
172
- }
173
-
174
- return { success: false, error: errorMessage };
175
- }
176
- }
177
-
178
- async function waitForProcessShutdown(
179
- pid: number,
180
- timeoutMs: number = 10000
181
- ): Promise<{ success: boolean; error?: string }> {
182
- const startTime = Date.now();
183
-
184
- while (Date.now() - startTime < timeoutMs) {
185
- try {
186
- // Send signal 0 to check if process exists
187
- process.kill(pid, 0);
188
- // If no error thrown, process is still running
189
- await new Promise((resolve) => setTimeout(resolve, 500));
190
- } catch (error) {
191
- // Process is gone
192
- return { success: true };
193
- }
194
- }
195
-
196
- return { success: false, error: "Process shutdown timeout" };
197
- }
198
-
199
- export async function killCommand(
200
- validatorId?: string,
201
- options: { all?: boolean } = {}
202
- ): Promise<void> {
203
- console.log(chalk.red("💀 Force killing validator(s)...\n"));
204
-
205
- // Clean up dead processes first
206
- await processRegistry.cleanup();
207
-
208
- const validators = processRegistry.getRunning();
209
-
210
- if (validators.length === 0) {
211
- console.log(chalk.yellow("⚠️ No running validators found"));
212
- return;
213
- }
214
-
215
- let validatorsToKill: RunningValidator[] = [];
216
-
217
- if (options.all) {
218
- // Kill all validators
219
- validatorsToKill = validators;
220
- console.log(
221
- chalk.cyan(`🔄 Force killing all ${validators.length} validator(s)...`)
222
- );
223
- } else if (validatorId) {
224
- // Kill specific validator
225
- const validator = processRegistry.getById(validatorId);
226
- if (!validator) {
227
- console.error(
228
- chalk.red(`❌ Validator with ID '${validatorId}' not found`)
229
- );
230
- console.log(
231
- chalk.gray("💡 Use `solforge list` to see running validators")
232
- );
233
- return;
234
- }
235
- validatorsToKill = [validator];
236
- console.log(
237
- chalk.cyan(
238
- `🔄 Force killing validator '${validator.name}' (${validatorId})...`
239
- )
240
- );
241
- } else {
242
- // No specific validator specified, show interactive selection
243
- console.log(chalk.cyan("📋 Select validator(s) to force kill:\n"));
244
-
245
- // Display current validators
246
- displayValidatorsTable(validators);
247
-
248
- const choices = [
249
- ...validators.map((v) => ({
250
- name: `${v.name} (${v.id}) - PID: ${v.pid}`,
251
- value: v.id,
252
- })),
253
- {
254
- name: chalk.red("Kill ALL validators"),
255
- value: "all",
256
- },
257
- {
258
- name: chalk.gray("Cancel"),
259
- value: "cancel",
260
- },
261
- ];
262
-
263
- const selectedValidator = await select({
264
- message: "Which validator would you like to force kill?",
265
- choices,
266
- });
267
-
268
- if (selectedValidator === "cancel") {
269
- console.log(chalk.gray("Operation cancelled"));
270
- return;
271
- }
272
-
273
- if (selectedValidator === "all") {
274
- validatorsToKill = validators;
275
- console.log(
276
- chalk.cyan(`🔄 Force killing all ${validators.length} validator(s)...`)
277
- );
278
- } else {
279
- const validator = processRegistry.getById(selectedValidator);
280
- if (!validator) {
281
- console.error(chalk.red("❌ Selected validator not found"));
282
- return;
283
- }
284
- validatorsToKill = [validator];
285
- console.log(
286
- chalk.cyan(
287
- `🔄 Force killing validator '${validator.name}' (${selectedValidator})...`
288
- )
289
- );
290
- }
291
- }
292
-
293
- // Kill each validator
294
- let killedCount = 0;
295
- let errorCount = 0;
296
-
297
- for (const validator of validatorsToKill) {
298
- try {
299
- const result = await stopValidator(validator, true); // Force kill
300
- if (result.success) {
301
- console.log(
302
- chalk.green(`✅ Killed ${validator.name} (${validator.id})`)
303
- );
304
- killedCount++;
305
- } else {
306
- console.error(
307
- chalk.red(
308
- `❌ Failed to kill ${validator.name} (${validator.id}): ${result.error}`
309
- )
310
- );
311
- errorCount++;
312
- }
313
- } catch (error) {
314
- console.error(
315
- chalk.red(
316
- `❌ Error killing ${validator.name} (${validator.id}): ${
317
- error instanceof Error ? error.message : String(error)
318
- }`
319
- )
320
- );
321
- errorCount++;
322
- }
323
- }
324
-
325
- // Summary
326
- console.log();
327
- if (killedCount > 0) {
328
- console.log(
329
- chalk.green(`✅ Successfully killed ${killedCount} validator(s)`)
330
- );
331
- }
332
- if (errorCount > 0) {
333
- console.log(chalk.red(`❌ Failed to kill ${errorCount} validator(s)`));
334
- }
335
- }
336
-
337
- function displayValidatorsTable(validators: RunningValidator[]): void {
338
- // Calculate column widths
339
- const maxIdWidth = Math.max(2, ...validators.map((v) => v.id.length));
340
- const maxNameWidth = Math.max(4, ...validators.map((v) => v.name.length));
341
- const maxPidWidth = Math.max(
342
- 3,
343
- ...validators.map((v) => v.pid.toString().length)
344
- );
345
- const maxPortWidth = 9; // "8899/9900" format
346
- const maxUptimeWidth = 7;
347
-
348
- // Header
349
- const header =
350
- chalk.bold("ID".padEnd(maxIdWidth)) +
351
- " | " +
352
- chalk.bold("Name".padEnd(maxNameWidth)) +
353
- " | " +
354
- chalk.bold("PID".padEnd(maxPidWidth)) +
355
- " | " +
356
- chalk.bold("RPC/Faucet".padEnd(maxPortWidth)) +
357
- " | " +
358
- chalk.bold("Uptime".padEnd(maxUptimeWidth)) +
359
- " | " +
360
- chalk.bold("Status");
361
-
362
- console.log(header);
363
- console.log("-".repeat(header.length - 20)); // Subtract ANSI codes length
364
-
365
- // Rows
366
- validators.forEach((validator) => {
367
- const uptime = formatUptime(validator.startTime);
368
- const ports = `${validator.rpcPort}/${validator.faucetPort}`;
369
- const status =
370
- validator.status === "running" ? chalk.green("●") : chalk.red("●");
371
-
372
- const row =
373
- validator.id.padEnd(maxIdWidth) +
374
- " | " +
375
- validator.name.padEnd(maxNameWidth) +
376
- " | " +
377
- validator.pid.toString().padEnd(maxPidWidth) +
378
- " | " +
379
- ports.padEnd(maxPortWidth) +
380
- " | " +
381
- uptime.padEnd(maxUptimeWidth) +
382
- " | " +
383
- status;
384
-
385
- console.log(row);
386
- });
387
-
388
- console.log(); // Empty line
389
- }
390
-
391
- function formatUptime(startTime: Date): string {
392
- const now = new Date();
393
- const uptimeMs = now.getTime() - startTime.getTime();
394
- const uptimeSeconds = Math.floor(uptimeMs / 1000);
395
-
396
- if (uptimeSeconds < 60) {
397
- return `${uptimeSeconds}s`;
398
- } else if (uptimeSeconds < 3600) {
399
- const minutes = Math.floor(uptimeSeconds / 60);
400
- return `${minutes}m`;
401
- } else {
402
- const hours = Math.floor(uptimeSeconds / 3600);
403
- const minutes = Math.floor((uptimeSeconds % 3600) / 60);
404
- return `${hours}h${minutes}m`;
405
- }
406
- }
@@ -1,157 +0,0 @@
1
- import { readFileSync, writeFileSync, existsSync } from "fs";
2
- import { join, resolve } from "path";
3
- import { ConfigSchema } from "../types/config.js";
4
- import type { Config, ValidationResult } from "../types/config.js";
5
-
6
- export class ConfigManager {
7
- private config: Config | null = null;
8
- private configPath: string | null = null;
9
-
10
- /**
11
- * Load configuration from a file path
12
- */
13
- async load(configPath: string): Promise<Config> {
14
- try {
15
- const fullPath = resolve(configPath);
16
-
17
- if (!existsSync(fullPath)) {
18
- throw new Error(`Configuration file not found: ${fullPath}`);
19
- }
20
-
21
- const configContent = readFileSync(fullPath, "utf-8");
22
- const rawConfig = JSON.parse(configContent);
23
-
24
- // Validate and parse with Zod
25
- const result = ConfigSchema.safeParse(rawConfig);
26
-
27
- if (!result.success) {
28
- const errors = result.error.issues.map((issue) => ({
29
- path: issue.path.join("."),
30
- message: issue.message,
31
- }));
32
- throw new Error(
33
- `Configuration validation failed:\n${errors
34
- .map((e) => ` - ${e.path}: ${e.message}`)
35
- .join("\n")}`
36
- );
37
- }
38
-
39
- this.config = result.data;
40
- this.configPath = fullPath;
41
-
42
- return this.config;
43
- } catch (error) {
44
- if (error instanceof SyntaxError) {
45
- throw new Error(`Invalid JSON in configuration file: ${error.message}`);
46
- }
47
- throw error;
48
- }
49
- }
50
-
51
- /**
52
- * Save current configuration to file
53
- */
54
- async save(configPath?: string): Promise<void> {
55
- if (!this.config) {
56
- throw new Error("No configuration loaded");
57
- }
58
-
59
- const targetPath = configPath || this.configPath;
60
- if (!targetPath) {
61
- throw new Error("No configuration path specified");
62
- }
63
-
64
- try {
65
- const configContent = JSON.stringify(this.config, null, 2);
66
- writeFileSync(targetPath, configContent, "utf-8");
67
- this.configPath = targetPath;
68
- } catch (error) {
69
- throw new Error(
70
- `Failed to save configuration: ${
71
- error instanceof Error ? error.message : String(error)
72
- }`
73
- );
74
- }
75
- }
76
-
77
- /**
78
- * Validate a configuration object
79
- */
80
- validate(config: any): ValidationResult {
81
- const result = ConfigSchema.safeParse(config);
82
-
83
- if (result.success) {
84
- return { valid: true, errors: [] };
85
- }
86
-
87
- const errors = result.error.issues.map((issue) => ({
88
- path: issue.path.join("."),
89
- message: issue.message,
90
- }));
91
-
92
- return { valid: false, errors };
93
- }
94
-
95
- /**
96
- * Create a default configuration
97
- */
98
- createDefault(): Config {
99
- const defaultConfig = ConfigSchema.parse({});
100
- this.config = defaultConfig;
101
- return defaultConfig;
102
- }
103
-
104
- /**
105
- * Get current configuration
106
- */
107
- getConfig(): Config {
108
- if (!this.config) {
109
- throw new Error("No configuration loaded. Call load() first.");
110
- }
111
- return this.config;
112
- }
113
-
114
- /**
115
- * Update configuration
116
- */
117
- updateConfig(updates: Partial<Config>): Config {
118
- if (!this.config) {
119
- throw new Error("No configuration loaded. Call load() first.");
120
- }
121
-
122
- const updated = { ...this.config, ...updates };
123
- const result = ConfigSchema.safeParse(updated);
124
-
125
- if (!result.success) {
126
- const errors = result.error.issues.map((issue) => ({
127
- path: issue.path.join("."),
128
- message: issue.message,
129
- }));
130
- throw new Error(
131
- `Configuration update validation failed:\n${errors
132
- .map((e) => ` - ${e.path}: ${e.message}`)
133
- .join("\n")}`
134
- );
135
- }
136
-
137
- this.config = result.data;
138
- return this.config;
139
- }
140
-
141
- /**
142
- * Get configuration file path
143
- */
144
- getConfigPath(): string | null {
145
- return this.configPath;
146
- }
147
-
148
- /**
149
- * Check if configuration is loaded
150
- */
151
- isLoaded(): boolean {
152
- return this.config !== null;
153
- }
154
- }
155
-
156
- // Singleton instance
157
- export const configManager = new ConfigManager();