-
Notifications
You must be signed in to change notification settings - Fork 5
feat(cmd): add status command with ckb-tui #323
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,3 +7,4 @@ dist/ | |
| .vscode | ||
| templates/temp-clone-folder | ||
| build/ | ||
| package-lock.json | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| import { readSettings } from '../cfg/setting'; | ||
| import { CKBTui } from '../tools/ckb-tui'; | ||
| import { Network } from '../type/base'; | ||
| import { logger } from '../util/logger'; | ||
| import * as net from 'net'; | ||
|
|
||
| export interface StatusOptions { | ||
| network?: Network; | ||
| } | ||
|
|
||
| export async function status({ network }: StatusOptions) { | ||
| const settings = readSettings(); | ||
| const port = | ||
| network === Network.devnet | ||
| ? settings.devnet.rpcProxyPort | ||
| : network === Network.testnet | ||
| ? settings.testnet.rpcProxyPort | ||
| : settings.mainnet.rpcProxyPort; | ||
| const url = `http://127.0.0.1:${port}`; | ||
| const isListening = await isRPCPortListening(port); | ||
| if (!isListening) { | ||
| logger.error( | ||
| `RPC port ${port} is not listening. Please make sure the ${network} node is running and Proxy RPC is enabled.`, | ||
| ); | ||
| return; | ||
| } | ||
| CKBTui.run(['-r', url]); | ||
| } | ||
|
|
||
| async function isRPCPortListening(port: number): Promise<boolean> { | ||
| const client = new net.Socket(); | ||
| return new Promise<boolean>((resolve) => { | ||
| let settled = false; | ||
| const TIMEOUT_MS = 5000; | ||
| const timeout = setTimeout(() => { | ||
| if (!settled) { | ||
| settled = true; | ||
| client.destroy(); | ||
| resolve(false); | ||
| } | ||
| }, TIMEOUT_MS); | ||
| client.once('error', () => { | ||
| if (!settled) { | ||
| settled = true; | ||
| clearTimeout(timeout); | ||
| resolve(false); | ||
| } | ||
| }); | ||
| client.once('connect', () => { | ||
| if (!settled) { | ||
| settled = true; | ||
| clearTimeout(timeout); | ||
| client.end(); | ||
| resolve(true); | ||
| } | ||
| }); | ||
| client.connect(port, '127.0.0.1'); | ||
| }); | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,154 @@ | ||||||||||||||||
| import { spawnSync, execSync } from 'child_process'; | ||||||||||||||||
| import * as path from 'path'; | ||||||||||||||||
| import * as fs from 'fs'; | ||||||||||||||||
| import { readSettings } from '../cfg/setting'; | ||||||||||||||||
| import { logger } from '../util/logger'; | ||||||||||||||||
|
|
||||||||||||||||
| export class CKBTui { | ||||||||||||||||
| private static binaryPath: string | null = null; | ||||||||||||||||
|
|
||||||||||||||||
| private static getBinaryPath(): string { | ||||||||||||||||
| if (!this.binaryPath) { | ||||||||||||||||
| const settings = readSettings(); | ||||||||||||||||
| const binDir = settings.tools.rootFolder; | ||||||||||||||||
| const version = settings.tools.ckbTui.version; | ||||||||||||||||
| this.binaryPath = path.join(binDir, 'ckb-tui'); | ||||||||||||||||
|
|
||||||||||||||||
| if (!fs.existsSync(this.binaryPath)) { | ||||||||||||||||
| this.downloadBinary(version); | ||||||||||||||||
| } | ||||||||||||||||
| } | ||||||||||||||||
| return this.binaryPath; | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| private static downloadBinary(version: string) { | ||||||||||||||||
| // Validate version format to prevent URL manipulation | ||||||||||||||||
| if (!/^v\d+\.\d+\.\d+$/.test(version)) { | ||||||||||||||||
| throw new Error(`Invalid version format: ${version}. Expected format: vX.Y.Z`); | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| const platform = process.platform; | ||||||||||||||||
| const arch = process.arch; | ||||||||||||||||
| let assetName: string; | ||||||||||||||||
|
|
||||||||||||||||
| if (platform === 'darwin') { | ||||||||||||||||
| if (arch === 'arm64') { | ||||||||||||||||
| assetName = `ckb-tui-with-node-macos-aarch64.tar.gz`; | ||||||||||||||||
| } else if (arch === 'x64') { | ||||||||||||||||
| assetName = `ckb-tui-with-node-macos-amd64.tar.gz`; | ||||||||||||||||
| } else { | ||||||||||||||||
| throw new Error(`Unsupported architecture for macOS: ${arch}`); | ||||||||||||||||
| } | ||||||||||||||||
| } else if (platform === 'linux') { | ||||||||||||||||
| if (arch === 'x64') { | ||||||||||||||||
| assetName = `ckb-tui-with-node-linux-amd64.tar.gz`; | ||||||||||||||||
| } else { | ||||||||||||||||
| throw new Error(`Unsupported architecture for Linux: ${arch}`); | ||||||||||||||||
| } | ||||||||||||||||
| } else if (platform === 'win32') { | ||||||||||||||||
| if (arch === 'x64') { | ||||||||||||||||
| assetName = `ckb-tui-with-node-windows-amd64.zip`; | ||||||||||||||||
| } else { | ||||||||||||||||
| throw new Error(`Unsupported architecture for Windows: ${arch}`); | ||||||||||||||||
| } | ||||||||||||||||
| } else { | ||||||||||||||||
| throw new Error(`Unsupported platform: ${platform}`); | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| const downloadUrl = `https://github.com/Officeyutong/ckb-tui/releases/download/${version}/${assetName}`; | ||||||||||||||||
|
||||||||||||||||
| const binDir = path.dirname(this.binaryPath!); | ||||||||||||||||
| const archivePath = path.join(binDir, assetName); | ||||||||||||||||
|
|
||||||||||||||||
| try { | ||||||||||||||||
| logger.info(`Downloading ckb-tui from ${downloadUrl}...`); | ||||||||||||||||
| execSync(`curl -L -o "${archivePath}" "${downloadUrl}"`, { stdio: 'inherit' }); | ||||||||||||||||
|
|
||||||||||||||||
| logger.info('Extracting...'); | ||||||||||||||||
| if (assetName.endsWith('.tar.gz')) { | ||||||||||||||||
| execSync(`tar -xzf "${archivePath}" -C "${binDir}"`, { stdio: 'inherit' }); | ||||||||||||||||
| } else if (assetName.endsWith('.zip')) { | ||||||||||||||||
| execSync(`unzip "${archivePath}" -d "${binDir}"`, { stdio: 'inherit' }); | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| // Set the correct binary name based on platform and architecture | ||||||||||||||||
| let extractedBinary: string; | ||||||||||||||||
| if (platform === 'win32') { | ||||||||||||||||
| extractedBinary = 'ckb-tui.exe'; | ||||||||||||||||
| } else if (platform === 'darwin') { | ||||||||||||||||
| if (arch === 'arm64') { | ||||||||||||||||
| extractedBinary = 'ckb-tui-macos-aarch64'; | ||||||||||||||||
| } else { | ||||||||||||||||
| extractedBinary = 'ckb-tui-macos-amd64'; | ||||||||||||||||
| } | ||||||||||||||||
| } else if (platform === 'linux') { | ||||||||||||||||
| if (arch === 'x64') { | ||||||||||||||||
| extractedBinary = 'ckb-tui-linux-amd64'; | ||||||||||||||||
| } else { | ||||||||||||||||
| throw new Error(`Unsupported architecture for Linux: ${arch}`); | ||||||||||||||||
| } | ||||||||||||||||
| } else { | ||||||||||||||||
| throw new Error(`Unsupported platform: ${platform}`); | ||||||||||||||||
| } | ||||||||||||||||
| const extractedPath = path.join(binDir, extractedBinary); | ||||||||||||||||
| if (fs.existsSync(extractedPath)) { | ||||||||||||||||
| fs.renameSync(extractedPath, this.binaryPath!); | ||||||||||||||||
| } else { | ||||||||||||||||
| // If in a subfolder, find it | ||||||||||||||||
| const files = fs.readdirSync(binDir); | ||||||||||||||||
| for (const file of files) { | ||||||||||||||||
| const filePath = path.join(binDir, file); | ||||||||||||||||
| if (fs.statSync(filePath).isDirectory()) { | ||||||||||||||||
| const candidate = path.join(filePath, extractedBinary); | ||||||||||||||||
| if (fs.existsSync(candidate)) { | ||||||||||||||||
| fs.renameSync(candidate, this.binaryPath!); | ||||||||||||||||
| break; | ||||||||||||||||
| } | ||||||||||||||||
| } | ||||||||||||||||
| } | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
|
||||||||||||||||
| // Check that the binary was successfully extracted and moved | |
| if (!fs.existsSync(this.binaryPath!)) { | |
| logger.error(`ckb-tui binary was not found after extraction. Expected at: ${this.binaryPath}`); | |
| throw new Error('Failed to extract and locate ckb-tui binary.'); | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The network option accepts any string value without validation. If a user provides an invalid network name (e.g., "production" or "dev"), it would be passed through without error checking. Consider validating that the network value is one of the valid Network enum values ('devnet', 'testnet', 'mainnet') and providing a helpful error message if it's not.