Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .changeset/balance-commander-errors.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'@offckb/cli': patch
---

Fix two CLI UX bugs reported in #498:

- `offckb balance` without an address no longer leaks the SDK's `Unknown address format undefined`. The address argument is now required, so commander prints a clear `error: missing required argument 'toAddress'`.
- Commander parameter/option errors (unknown option, invalid option value, missing argument) are printed exactly once on stderr instead of twice.
5 changes: 0 additions & 5 deletions .changeset/ckb-tui-org-repo-url.md

This file was deleted.

5 changes: 0 additions & 5 deletions .changeset/debug-tx-file-quotes.md

This file was deleted.

5 changes: 0 additions & 5 deletions .changeset/install-ckb-debugger.md

This file was deleted.

5 changes: 0 additions & 5 deletions .changeset/review-fixes-v0411.md

This file was deleted.

9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# @offckb/cli

## 0.4.12

### Patch Changes

- a591136: Update the ckb-tui download URL to the new `nervosnetwork/ckb-tui` org repository after ckb-tui was transferred out of the original personal repo. Release assets moved with the transfer, so the pinned SHA-256 digests still match byte-for-byte and verified installs are unaffected.
- e156a5a: Fix `offckb debug` failing with `ENOENT` on the tx-file path. The debugger now splits its command line quote-aware, so the quotes `encodeBinPathForTerminal` adds around space-containing paths are stripped instead of becoming part of the file name (a regression from the native ckb-debugger switch, which passes array argv to `execFileSync` instead of a shell string).
- 4f27ae9: Add `offckb install ckb-debugger` to install the native `ckb-debugger` binary. Instead of compiling from source with `cargo install` (which needs a Rust toolchain), offckb now downloads the prebuilt release asset for the current platform from the official ckb-standalone-debugger GitHub releases, verifies its SHA-256 digest, publishes it under the offckb data directory, and puts a `ckb-debugger` shim next to the offckb binary so generated projects can find it on PATH. `offckb create` installs the native debugger automatically when it is missing. The bundled WASM debugger and its fallback logic have been removed — `offckb debug` now requires the native binary. Version checks against the configured minimum now compare numerically, so an installed binary that satisfies `tools.ckbDebugger.minVersion` is correctly recognized.
- 852b68b: Fix review findings from the v0.4.11 release merge. `offckb logs script` now scans a wider window before filtering so sparse script entries are not missed, and an unknown `offckb logs` target is rejected instead of silently reading the node log. `offckb logs -f` detects log rotation by inode change, so a rotated-in file that is already larger than the old one is re-read from the start. `offckb logs --tail` rejects zero and negative values instead of printing the entire filtered log. CKB 0.205.0 prerelease binaries (rc builds), including prereleases of newer versions, are now correctly treated as Terminal-RPC capable during chain init. A ckb-tui binary that lost its execute bit is reinstalled instead of failing at spawn time. The RPC proxy keeps appending events when a proxy.log rollover fails (previously all further events were dropped) and preserves the previous archive when the rollover's rename fails, records batched JSON-RPC requests including batched `send_transaction`, skips malformed batch members without aborting the rest of the batch, and warns instead of misreporting a parse error when `send_transaction` has no usable params. `--verbose` on `offckb node` now also enables the proxy's per-request debug lines.

## 0.4.11

### Patch Changes
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.PHONY: all omnilock anyone-can-pay xudt spore ckb-js-vm nostr-lock
.PHONY: all omnilock anyone-can-pay xudt spore ckb-js-vm nostr-lock pw-lock secp256k1_multisig_v2

all: omnilock anyone-can-pay xudt spore ckb-js-vm nostr-lock pw-lock secp256k1_multisig_v2

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ Commands:
transfer [options] [toAddress] [amountInCKB] Transfer CKB tokens to address, only devnet and testnet
transfer-all [options] [toAddress] Transfer All CKB tokens to address, only devnet and testnet
balance [options] [toAddress] Check account balance, only devnet and testnet
install <tool> Install a tool binary used by offckb (e.g. the native ckb-debugger)
debugger Port of the raw CKB Standalone Debugger
status [options] Show ckb-tui status interface
logs [options] [target] Show devnet logs: node (default), contract script debug output, miner, or RPC proxy events
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@offckb/cli",
"version": "0.4.11",
"version": "0.4.12",
"description": "ckb development network for your first try",
"author": "CKB EcoFund",
"license": "MIT",
Expand Down
10 changes: 8 additions & 2 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ mainnetForkOverrideOption(
});

program
.command('balance [toAddress]')
.command('balance <toAddress>')
.description('Check account balance (CKB + detected SUDT/xUDT), only devnet and testnet')
.option('--network <network>', 'Specify the network to check', 'devnet')
.addOption(new Option('--udt-kind <kind>', 'Filter by UDT kind').choices(['sudt', 'xudt']))
Expand Down Expand Up @@ -387,7 +387,13 @@ export async function runCli(argv: string[] = process.argv): Promise<void> {
if (error instanceof CommanderError && error.exitCode === 0) return;
const message = error instanceof Error ? error.message : String(error);
const code = error instanceof CommanderError ? error.code : 'COMMAND_FAILED';
logger.failure(code, message);
// Commander errors were already written to stderr once by writeErr in
// configureCommanderErrors (non-JSON mode); re-emitting would duplicate
// the line. In JSON mode writeErr is suppressed, so logger.failure emits
// the single structured record instead.
if (!(error instanceof CommanderError) || logger.isJsonMode()) {
logger.failure(code, message);
}
process.exitCode = error instanceof CommanderError ? error.exitCode : 1;
}
}
Expand Down
15 changes: 11 additions & 4 deletions src/tools/ckb-debugger-install.ts
Original file line number Diff line number Diff line change
Expand Up @@ -351,9 +351,14 @@ export class CKBDebuggerInstaller {

const binName = isWindows ? 'ckb-debugger.cmd' : 'ckb-debugger';
const targetPath = path.join(path.dirname(offckbPath), binName);
// The marker distinguishes our own shim (and legacy v0.4.x fallback shims
// that `exec offckb debugger`) from a foreign file — e.g. a real binary a
// user installed via cargo — which must never be silently clobbered.
// The marker distinguishes our own shim from a foreign file — e.g. a real
// binary a user installed via cargo — which must never be silently
// clobbered. Legacy v0.4.x fallback shims are also upgraded in place, but
// only when the whole file is exactly the body offckb wrote back then (a
// shebang line plus `exec offckb debugger "$@"` on Unix, `@echo off` plus
// `offckb debugger %*` on Windows), line endings normalized and one
// trailing newline ignored; a mere mention of `offckb debugger`, or a
// legacy body with extra user content, is not enough to claim the file.
const marker = isWindows ? '@rem offckb-managed ckb-debugger shim' : '# offckb-managed ckb-debugger shim';
const content = isWindows
? `${marker}\r\n@echo off\r\n"${binaryPath}" %*\r\n`
Expand All @@ -362,7 +367,9 @@ export class CKBDebuggerInstaller {
try {
if (fs.existsSync(targetPath)) {
const existing = fs.readFileSync(targetPath, 'utf8');
if (!existing.includes(marker) && !existing.includes('offckb debugger')) {
const legacyBody = isWindows ? '@echo off\noffckb debugger %*' : '#!/bin/sh\nexec offckb debugger "$@"';
const isLegacyShim = existing.replace(/\r\n/g, '\n').replace(/\n$/, '') === legacyBody;
if (!existing.includes(marker) && !isLegacyShim) {
logger.warn(
`A file already exists at ${targetPath} that was not created by offckb; leaving it untouched. ` +
'Projects that call `ckb-debugger` directly will need it added to PATH manually.',
Expand Down
10 changes: 9 additions & 1 deletion src/tools/proxy-events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,15 @@ export function handleProxyRequestBody(reqData: string, ctx: ProxyEventContext):
ctx.sink.warn('skipping malformed JSON-RPC batch member');
continue;
}
handleOneRequest(jsonRpcContent, ctx);
try {
handleOneRequest(jsonRpcContent, ctx);
} catch (error) {
// Thrown values are not necessarily Error instances (user code can
// throw anything), so normalize before logging; reading .message off
// a non-Error (e.g. null) would itself throw and abort the batch.
const message = error instanceof Error ? error.message : String(error);
ctx.sink.warn(`skipping JSON-RPC request event: ${message}`);
}
}
} catch (err) {
ctx.sink.error('Error parsing JSON-RPC req content:', (err as Error).message);
Expand Down
87 changes: 87 additions & 0 deletions tests/ckb-debugger-install.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,27 @@ describe('CKBDebuggerInstaller', () => {
expect(fs.readFileSync(shimPath, 'utf-8')).toBe('#!/bin/sh\nexec /usr/bin/ckb-debugger "$@"\n');
});

it('does not overwrite a user file that merely mentions offckb debugger', async () => {
const buffer = await buildTarGz('0.208.0');
mockRelease('0.208.0', buffer);
const shimPath = path.join(root, 'bin', 'ckb-debugger');
mockSpawnSync.mockImplementation((cmd: string) => {
if (cmd === 'which') {
return { status: 0, stdout: `${path.join(root, 'bin', 'offckb')}\n`, stderr: '' };
}
return { status: 0, stdout: 'ckb-debugger 0.208.0\n', stderr: '' };
});
fs.mkdirSync(path.dirname(shimPath), { recursive: true });
// A user wrapper that documents offckb debugger without being the exact
// v0.4.x fallback body must stay untouched.
const userScript = '#!/bin/sh\n# How to call: offckb debugger --help\n/usr/bin/ckb-debugger "$@"\n';
fs.writeFileSync(shimPath, userScript);

await CKBDebuggerInstaller.install();

expect(fs.readFileSync(shimPath, 'utf-8')).toBe(userScript);
});

it('upgrades a legacy v0.4.x fallback shim', async () => {
const buffer = await buildTarGz('0.208.0');
mockRelease('0.208.0', buffer);
Expand All @@ -272,6 +293,72 @@ describe('CKBDebuggerInstaller', () => {
expect(fs.readFileSync(shimPath, 'utf-8')).toContain(`exec "${binaryPath}" "$@"`);
});

it('does not overwrite a legacy shim that has extra user content', async () => {
const buffer = await buildTarGz('0.208.0');
mockRelease('0.208.0', buffer);
const shimPath = path.join(root, 'bin', 'ckb-debugger');
mockSpawnSync.mockImplementation((cmd: string) => {
if (cmd === 'which') {
return { status: 0, stdout: `${path.join(root, 'bin', 'offckb')}\n`, stderr: '' };
}
return { status: 0, stdout: 'ckb-debugger 0.208.0\n', stderr: '' };
});
fs.mkdirSync(path.dirname(shimPath), { recursive: true });
// The complete legacy body plus one user line is not an offckb shim: the
// whole file must match exactly, so it stays untouched.
const userScript = '#!/bin/sh\nexec offckb debugger "$@"\n# custom wrapper\n';
fs.writeFileSync(shimPath, userScript);

await CKBDebuggerInstaller.install();

expect(fs.readFileSync(shimPath, 'utf-8')).toBe(userScript);
});

it('upgrades an exact legacy v0.4.x fallback shim on Windows', async () => {
mockHost('win32', 'x64');
const winBinaryPath = path.join(mockDirs.toolsRoot, 'ckb-debugger.exe');
fs.mkdirSync(path.dirname(winBinaryPath), { recursive: true });
fs.writeFileSync(winBinaryPath, fakeBinaryPayload('0.208.0'));
const shimPath = path.join(root, 'bin', 'ckb-debugger.cmd');
mockSpawnSync.mockImplementation((cmd: string) => {
if (cmd === 'where') {
return { status: 0, stdout: `${path.join(root, 'bin', 'offckb')}\n`, stderr: '' };
}
return { status: 0, stdout: 'ckb-debugger 0.208.0\n', stderr: '' };
});
fs.mkdirSync(path.dirname(shimPath), { recursive: true });
// CRLF line endings are normalized before the exact-body comparison.
fs.writeFileSync(shimPath, '@echo off\r\noffckb debugger %*\r\n');

const result = await CKBDebuggerInstaller.install();

expect(result.alreadyInstalled).toBe(true);
const upgraded = fs.readFileSync(shimPath, 'utf-8');
expect(upgraded).toContain('offckb-managed');
expect(upgraded).toContain(`"${winBinaryPath}" %*`);
});

it('does not overwrite a legacy shim with extra user content on Windows', async () => {
mockHost('win32', 'x64');
const winBinaryPath = path.join(mockDirs.toolsRoot, 'ckb-debugger.exe');
fs.mkdirSync(path.dirname(winBinaryPath), { recursive: true });
fs.writeFileSync(winBinaryPath, fakeBinaryPayload('0.208.0'));
const shimPath = path.join(root, 'bin', 'ckb-debugger.cmd');
mockSpawnSync.mockImplementation((cmd: string) => {
if (cmd === 'where') {
return { status: 0, stdout: `${path.join(root, 'bin', 'offckb')}\n`, stderr: '' };
}
return { status: 0, stdout: 'ckb-debugger 0.208.0\n', stderr: '' };
});
fs.mkdirSync(path.dirname(shimPath), { recursive: true });
const userScript = '@echo off\r\noffckb debugger %*\r\nREM custom wrapper\r\n';
fs.writeFileSync(shimPath, userScript);

await CKBDebuggerInstaller.install();

expect(fs.readFileSync(shimPath, 'utf-8')).toBe(userScript);
});

it('throws when the platform has no prebuilt asset', async () => {
mockHost('freebsd', 'x64');
await expect(CKBDebuggerInstaller.install()).rejects.toThrow(/no prebuilt binary/);
Expand Down
Loading
Loading