-
Notifications
You must be signed in to change notification settings - Fork 7
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 1 commit
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 | ||||
|---|---|---|---|---|---|---|
|
|
@@ -17,6 +17,7 @@ import { genSystemScriptsJsonFile } from './scripts/gen'; | |||||
| import { CKBDebugger } from './tools/ckb-debugger'; | ||||||
| import { logger } from './util/logger'; | ||||||
| import { Network } from './type/base'; | ||||||
| import { status } from './cmd/status'; | ||||||
|
|
||||||
| const version = require('../package.json').version; | ||||||
| const description = require('../package.json').description; | ||||||
|
|
@@ -154,6 +155,14 @@ program | |||||
| return CKBDebugger.runWithArgs(process.argv.slice(2)); | ||||||
| }); | ||||||
|
|
||||||
| program | ||||||
| .command('status') | ||||||
| .description('Show ckb-tui status interface') | ||||||
| .option('--network <network>', 'Specify the network to deploy to', 'devnet') | ||||||
|
||||||
| .action(async (option) => { | ||||||
| status({ network: option.network }); | ||||||
|
||||||
| status({ network: option.network }); | |
| return status({ network: option.network }); |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,41 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { readSettings } from '../cfg/setting'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { CKBTui } from '../tools/ckb-tui'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { Network } from '../type/base'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { logger } from '../util/logger'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 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) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return logger.error( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| `RPC port ${port} is not listening. Please make sure the ${network} node is running and Proxy RPC is enabled.`, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return CKBTui.runWithArgs(['-r', url]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async function isRPCPortListening(port: number): Promise<boolean> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const net = require('net'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const client = new net.Socket(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return new Promise<boolean>((resolve) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| client.once('error', () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| resolve(false); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| client.connect(port, '127.0.0.1'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| client.once('connect', () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| client.end(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| resolve(true); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+42
to
+56
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| client.once('error', () => { | |
| resolve(false); | |
| }); | |
| client.connect(port, '127.0.0.1'); | |
| client.once('connect', () => { | |
| client.end(); | |
| resolve(true); | |
| }); | |
| 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,120 @@ | ||||||||||||||||||||||||||||||||||||||||||||||
| 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) { | ||||||||||||||||||||||||||||||||||||||||||||||
| 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 { | ||||||||||||||||||||||||||||||||||||||||||||||
| throw new Error(`Unsupported architecture for macOS: ${arch}`); | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+34
to
+41
|
||||||||||||||||||||||||||||||||||||||||||||||
| } 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' }); | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| // Assume the binary is extracted as 'ckb-tui' or 'ckb-tui.exe' | ||||||||||||||||||||||||||||||||||||||||||||||
| // todo: fix the bin name | ||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
| // todo: fix the bin name | |
| // TODO: fix the bin name |
Copilot
AI
Dec 5, 2025
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 extracted binary name ckb-tui-macos-amd64 doesn't match the asset name for macOS ARM64 (ckb-tui-with-node-macos-aarch64.tar.gz). When running on macOS ARM64, this will look for a file named ckb-tui-macos-amd64 which won't exist in the archive. The extracted binary name should match the architecture detected earlier (e.g., ckb-tui-macos-aarch64 for ARM64).
| // Assume the binary is extracted as 'ckb-tui' or 'ckb-tui.exe' | |
| // todo: fix the bin name | |
| const extractedBinary = platform === 'win32' ? 'ckb-tui.exe' : 'ckb-tui-macos-amd64'; | |
| // 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 { | |
| throw new Error(`Unsupported architecture for macOS: ${arch}`); | |
| } | |
| } 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}`); | |
| } |
Copilot
AI
Dec 5, 2025
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.
If the binary is not found after extraction (neither in the bin directory nor in subdirectories), the code silently continues without throwing an error or logging a warning. This could lead to confusing behavior later when trying to execute the binary. Consider adding an error check after the loop at line 84 to verify that the binary was successfully extracted and moved.
| // 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.'); | |
| } |
Copilot
AI
Dec 5, 2025
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 error message logs (error as Error).message but doesn't provide context about what might have gone wrong. Consider including more specific guidance, such as checking network connectivity, verifying the version exists in the releases, or checking file system permissions.
| logger.error('Failed to download/install ckb-tui:', (error as Error).message); | |
| logger.error( | |
| 'Failed to download/install ckb-tui:', | |
| (error as Error).message, | |
| '\nPlease check your network connectivity, verify that the specified version exists in the releases, and ensure you have sufficient file system permissions.' | |
| ); |
Copilot
AI
Dec 5, 2025
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.
If an error occurs during extraction or binary setup (lines 59-85), the downloaded archive file won't be cleaned up. Consider wrapping the cleanup in a finally block or moving it earlier to ensure cleanup happens even when errors occur.
| // Clean up archive | |
| fs.unlinkSync(archivePath); | |
| logger.info('ckb-tui installed successfully.'); | |
| } catch (error) { | |
| logger.error('Failed to download/install ckb-tui:', (error as Error).message); | |
| throw error; | |
| logger.info('ckb-tui installed successfully.'); | |
| } catch (error) { | |
| logger.error('Failed to download/install ckb-tui:', (error as Error).message); | |
| throw error; | |
| } finally { | |
| // Clean up archive even if error occurs | |
| if (fs.existsSync(archivePath)) { | |
| try { | |
| fs.unlinkSync(archivePath); | |
| } catch (cleanupError) { | |
| logger.warn('Failed to clean up archive file:', (cleanupError as Error).message); | |
| } | |
| } |
Copilot
AI
Dec 5, 2025
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 run method constructs a shell command by joining args without sanitization, which could allow command injection if any argument contains shell metacharacters (e.g., ;, |, &, etc.). Consider using spawnSync with the arguments array directly instead of constructing a shell command string: spawnSync(binaryPath, args, { stdio: 'inherit' }).
| const command = `"${binaryPath}" ${args.join(' ')}`; | |
| return spawnSync(command, { stdio: 'inherit', shell: true }); | |
| return spawnSync(binaryPath, args, { stdio: 'inherit' }); |
Copilot
AI
Dec 5, 2025
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 runWithArgs method is redundant and simply calls the run method. This doesn't add any value and can be removed. All callers should directly use the run method instead.
| static runWithArgs(args: string[]) { | |
| this.run(args); | |
| } |
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 heading "Watch Network With TUI" should use consistent capitalization. Consider "Watch Network with TUI" (lowercase "with") to match standard title case conventions.