From 91e2db4958c595c8059f2c25f2ed57de4574c368 Mon Sep 17 00:00:00 2001 From: SafraNako Date: Wed, 9 Sep 2026 20:14:09 +0300 Subject: [PATCH] fix: notarize --json no longer crashes with a TypeError `notarize` sets `const spinner = flags.json ? null : ora(...).start()` but then calls `spinner.succeed(...)` / `spinner.fail(...)` unconditionally (~20 sites), so `notarize -h -i -j` throws `TypeError: Cannot read properties of null (reading 'succeed')` at the first call and never reaches the JSON output path. `spinner3` / `spinner4` were also always-on `ora` instances, so their progress text would have corrupted the JSON even if `spinner` were guarded. Add a `makeSpinner(text)` helper: a real `ora` spinner normally, a chainable no-op object under `--json`. All three spinners go through it. Non-`--json` behaviour is unchanged. Adds `notarize #8b`: `notarize ... -j` against an unreachable endpoint must exit non-0 without a `TypeError` in its output (fails before this change). Co-Authored-By: Claude Sonnet 5 --- src/commands/notarize.js | 12 +++++++++--- test/commands/notarize.test.js | 33 ++++++++++++++++++++++++++++++++- 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/src/commands/notarize.js b/src/commands/notarize.js index 76ad2d3..2cc5820 100644 --- a/src/commands/notarize.js +++ b/src/commands/notarize.js @@ -81,8 +81,14 @@ class Notarise extends Command { if (!flags.json){ this.log(white().bgBlue(network)) } + // In --json mode use a spinner that swallows its output: the progress text + // would corrupt the JSON, and every spinner.succeed()/.fail() call below is + // unconditional (calling them on a bare `null` threw a TypeError). + const noop = function noop() { return this } + const nullSpinner = { start: noop, succeed: noop, fail: noop, info: noop, warn: noop, stop: noop } + const makeSpinner = (text) => (flags.json ? nullSpinner : ora({ text }).start()) // the data to notarise here, can be a file submitted (path) or a string passed on cli - const spinner = flags.json ? null : ora({ text: 'Notarising Data...\n', }).start() + const spinner = makeSpinner('Notarising Data...\n') if (args.dataHash) { const sha256regex = /^\b[A-Fa-f0-9]{64}\b/.test(args.dataHash) // is the passed data the correct length? should be a sha256 sum hash @@ -247,7 +253,7 @@ class Notarise extends Command { // send the message transaction with the notarise encoding to the node const message = await Qrlnetwork.api('GetMessageTxn', request) - const spinner3 = ora({ text: 'Signing transaction...' }).start() + const spinner3 = makeSpinner('Signing transaction...') // Preimage order is QRL core's MessageTransaction.get_data_bytes(): // master_addr || fee || message_hash || addr_to @@ -291,7 +297,7 @@ class Notarise extends Command { this.exit(1) } spinner3.succeed(`Node response matches the request. Transaction signed with OTS key ${flags.otsindex}. (nodes will reject this transaction if key reuse is detected)`) - const spinner4 = ora({ text: 'Pushing transaction to node...' }).start() + const spinner4 = makeSpinner('Pushing transaction to node...') // transaction sig and pub key into buffer returnedTx.signature = Buffer.from(signature) returnedTx.public_key = Buffer.from(xmssPK) // eslint-disable-line camelcase diff --git a/test/commands/notarize.test.js b/test/commands/notarize.test.js index 5b126c8..37bf5a6 100644 --- a/test/commands/notarize.test.js +++ b/test/commands/notarize.test.js @@ -198,9 +198,40 @@ describe('notarize #8', () => { }) }) +// --json mode used to throw "TypeError: Cannot read properties of null" on the +// first spinner.succeed() call. It should now run without that crash (here it +// still exits non-0 because the grpc endpoint is deliberately unreachable). +describe('notarize #8b --json does not crash', () => { + let exitCode + let output = '' + before(done => { + const args = [ + 'notarize', + sha256Hash, + '-w', setup.bobPTWalletLocation, + '-i', '0', + '-j', + '-g', 'https://brooklyn.theqrl.org/nottheapi/', + ] + const child = spawn('./bin/run', args, { detached: true, stdio: ['ignore', 'pipe', 'pipe'] }) + child.stdout.on('data', d => { output += d.toString() }) + child.stderr.on('data', d => { output += d.toString() }) + child.on('exit', code => { + exitCode = code + done() + }) + }) + it('does not throw a TypeError', () => { + assert.ok(!output.includes('TypeError'), output) + }) + it('exit code should be non-0 when the node is unreachable', () => { + assert.notStrictEqual(exitCode, 0) + }) +}) + // ////////////////////////// -// pass +// pass // ////////////////////////// describe('notarize #9 bobs plaintext wallet hexString', () => {