diff --git a/src/agent/CommandStatus.ts b/src/agent/CommandStatus.ts index c226c862..3805e4c7 100644 --- a/src/agent/CommandStatus.ts +++ b/src/agent/CommandStatus.ts @@ -81,13 +81,14 @@ class CommandStatus extends CommandPolykey { agentHost: response.agentHost, agentPort: response.agentPort, upTime: getUnixtime() - response.startTime, + startTime: response.startTime, connectionsActive: response.connectionsActive, nodesTotal: response.nodesTotal, version: response.version, sourceVersion: response.sourceVersion, stateVersion: response.stateVersion, networkVersion: response.networkVersion, - ...response.versionMetadata, + versionMetadata: response.versionMetadata, }, }), ); diff --git a/src/agent/CommandStop.ts b/src/agent/CommandStop.ts index 217ae237..de9b1da1 100644 --- a/src/agent/CommandStop.ts +++ b/src/agent/CommandStop.ts @@ -27,13 +27,13 @@ class CommandStop extends CommandPolykey { ); const statusInfo = clientStatus.statusInfo; if (statusInfo?.status === 'DEAD') { - this.logger.info('Agent is already dead'); + process.stderr.write('Agent is already dead\n'); return; } else if (statusInfo?.status === 'STOPPING') { - this.logger.info('Agent is already stopping'); + process.stderr.write('Agent is already stopping\n'); return; } else if (statusInfo?.status === 'STARTING') { - throw new errors.ErrorPolykeyCLIAgentStatus('agent is starting'); + throw new errors.ErrorPolykeyCLIAgentStatus('Agent is starting'); } const auth = await binProcessors.processAuthentication( options.passwordFile, @@ -62,7 +62,7 @@ class CommandStop extends CommandPolykey { }), auth, ); - this.logger.info('Stopping Agent'); + process.stderr.write('Stopping Agent\n'); } finally { if (pkClient! != null) await pkClient.stop(); } diff --git a/src/bootstrap/CommandBootstrap.ts b/src/bootstrap/CommandBootstrap.ts index 18dbd714..1662df4f 100644 --- a/src/bootstrap/CommandBootstrap.ts +++ b/src/bootstrap/CommandBootstrap.ts @@ -1,5 +1,6 @@ import process from 'process'; import CommandPolykey from '../CommandPolykey'; +import * as binUtils from '../utils'; import * as binOptions from '../utils/options'; import * as binProcessors from '../utils/processors'; @@ -36,7 +37,14 @@ class CommandBootstrap extends CommandPolykey { logger: this.logger, }); this.logger.info(`Bootstrapped ${options.nodePath}`); - if (recoveryCodeOut != null) process.stdout.write(recoveryCodeOut + '\n'); + process.stdout.write( + binUtils.outputFormatter({ + type: options.format === 'json' ? 'json' : 'dict', + data: { + recoveryCode: recoveryCodeOut, + }, + }), + ); }); } } diff --git a/src/identities/CommandAuthenticate.ts b/src/identities/CommandAuthenticate.ts index 77d9bedf..4064d995 100644 --- a/src/identities/CommandAuthenticate.ts +++ b/src/identities/CommandAuthenticate.ts @@ -68,9 +68,11 @@ class CommandAuthenticate extends CommandPolykey { ); for await (const message of genReadable) { if (message.request != null) { - this.logger.info(`Navigate to the URL in order to authenticate`); - this.logger.info( - 'Use any additional additional properties to complete authentication', + process.stderr.write( + `Navigate to the URL in order to authenticate\n`, + ); + process.stderr.write( + 'Use any additional additional properties to complete authentication\n', ); try { await identitiesUtils.browser(message.request.url); @@ -78,23 +80,35 @@ class CommandAuthenticate extends CommandPolykey { if (e.code !== 'ENOENT') throw e; // Otherwise we ignore `ENOENT` as a failure to spawn a browser } - process.stdout.write( - binUtils.outputFormatter({ - type: options.format === 'json' ? 'json' : 'dict', - data: { - url: message.request.url, - ...message.request.dataMap, - }, - }), - ); + if (options.format === 'json') { + process.stdout.write( + binUtils.outputFormatter({ + type: 'json', + data: { + url: message.request.url, + dataMap: message.request.dataMap, + }, + }), + ); + } else { + process.stdout.write( + binUtils.outputFormatter({ + type: 'dict', + data: { + url: message.request.url, + ...message.request.dataMap, + }, + }), + ); + } } else if (message.response != null) { - this.logger.info( - `Authenticated digital identity provider ${providerId}`, + process.stderr.write( + `Authenticated digital identity provider ${providerId}\n`, ); process.stdout.write( binUtils.outputFormatter({ - type: options.format === 'json' ? 'json' : 'list', - data: [message.response.identityId], + type: options.format === 'json' ? 'json' : 'dict', + data: { identityId: message.response.identityId }, }), ); } else { diff --git a/src/identities/CommandClaim.ts b/src/identities/CommandClaim.ts index 0c70f0ca..5f2679fc 100644 --- a/src/identities/CommandClaim.ts +++ b/src/identities/CommandClaim.ts @@ -60,14 +60,16 @@ class CommandClaim extends CommandPolykey { }), auth, ); - const output = [`Claim Id: ${claimMessage.claimId}`]; + const data: { claimId: string; url?: string } = { + claimId: claimMessage.claimId, + }; if (claimMessage.url) { - output.push(`Url: ${claimMessage.url}`); + data.url = claimMessage.url; } process.stdout.write( binUtils.outputFormatter({ - type: options.format === 'json' ? 'json' : 'list', - data: output, + type: options.format === 'json' ? 'json' : 'dict', + data, }), ); } finally { diff --git a/src/identities/CommandGet.ts b/src/identities/CommandGet.ts index e8b1e267..28514107 100644 --- a/src/identities/CommandGet.ts +++ b/src/identities/CommandGet.ts @@ -88,10 +88,17 @@ class CommandGet extends CommandPolykey { utils.never(); } const gestalt = res!.gestalt; - let output: any = gestalt; - if (options.format !== 'json') { - // Creating a list. - output = []; + if (options.format === 'json') { + process.stdout.write( + binUtils.outputFormatter({ + type: 'json', + data: { + gestalt, + }, + }), + ); + } else { + const output: Array = []; // Listing nodes. for (const nodeKey of Object.keys(gestalt.nodes)) { const node = gestalt.nodes[nodeKey]; @@ -102,13 +109,13 @@ class CommandGet extends CommandPolykey { const identity = gestalt.identities[identityKey]; output.push(`${identity.providerId}:${identity.identityId}`); } + process.stdout.write( + binUtils.outputFormatter({ + type: 'list', + data: output, + }), + ); } - process.stdout.write( - binUtils.outputFormatter({ - type: options.format === 'json' ? 'json' : 'list', - data: output, - }), - ); } finally { if (pkClient! != null) await pkClient.stop(); } diff --git a/src/identities/CommandInvite.ts b/src/identities/CommandInvite.ts index 0f1d9272..0d40a6ed 100644 --- a/src/identities/CommandInvite.ts +++ b/src/identities/CommandInvite.ts @@ -59,15 +59,10 @@ class CommandClaim extends CommandPolykey { }), auth, ); - process.stdout.write( - binUtils.outputFormatter({ - type: options.format === 'json' ? 'json' : 'list', - data: [ - `Successfully sent Gestalt Invite notification to Keynode with ID ${nodesUtils.encodeNodeId( - nodeId, - )}`, - ], - }), + process.stderr.write( + `Successfully sent Gestalt Invite notification to Keynode with ID ${nodesUtils.encodeNodeId( + nodeId, + )}\n`, ); } finally { if (pkClient! != null) await pkClient.stop(); diff --git a/src/identities/CommandList.ts b/src/identities/CommandList.ts index 7d7c7e43..c8905d49 100644 --- a/src/identities/CommandList.ts +++ b/src/identities/CommandList.ts @@ -1,4 +1,5 @@ import type PolykeyClient from 'polykey/dist/PolykeyClient'; +import type { GestaltMessage } from 'polykey/dist/client/types'; import CommandPolykey from '../CommandPolykey'; import * as binOptions from '../utils/options'; import * as binUtils from '../utils'; @@ -42,72 +43,71 @@ class CommandList extends CommandPolykey { }, logger: this.logger.getChild(PolykeyClient.name), }); - let output: any; - const gestalts = await binUtils.retryAuthentication(async (auth) => { - const gestalts: Array = []; - const stream = await pkClient.rpcClient.methods.gestaltsGestaltList({ - metadata: auth, - }); - for await (const gestaltMessage of stream) { - const gestalt = gestaltMessage.gestalt; - const newGestalt: any = { - permissions: [], - nodes: [], - identities: [], - }; - for (const node of Object.keys(gestalt.nodes)) { - const nodeInfo = gestalt.nodes[node]; - newGestalt.nodes.push({ nodeId: nodeInfo.nodeId }); - } - for (const identity of Object.keys(gestalt.identities)) { - const identityInfo = gestalt.identities[identity]; - newGestalt.identities.push({ - providerId: identityInfo.providerId, - identityId: identityInfo.identityId, + const gestaltMessages = await binUtils.retryAuthentication( + async (auth) => { + const gestaltMessages: Array< + GestaltMessage & { gestalt: { actionsList: Array } } + > = []; + const stream = await pkClient.rpcClient.methods.gestaltsGestaltList( + { + metadata: auth, + }, + ); + for await (const gestaltMessage of stream) { + // Getting the permissions for the gestalt. + const actionsMessage = await binUtils.retryAuthentication( + (auth) => + pkClient.rpcClient.methods.gestaltsActionsGetByNode({ + metadata: auth, + nodeIdEncoded: Object.values( + gestaltMessage.gestalt.nodes, + )[0].nodeId, + }), + auth, + ); + const actionsList = actionsMessage.actionsList; + gestaltMessages.push({ + gestalt: { + ...gestaltMessage.gestalt, + actionsList, + }, }); } - // Getting the permissions for the gestalt. - const actionsMessage = await binUtils.retryAuthentication( - (auth) => - pkClient.rpcClient.methods.gestaltsActionsGetByNode({ - metadata: auth, - nodeIdEncoded: newGestalt.nodes[0].nodeId, - }), - auth, - ); - const actionList = actionsMessage.actionsList; - if (actionList.length === 0) newGestalt.permissions = null; - else newGestalt.permissions = actionList; - gestalts.push(newGestalt); - } - return gestalts; - }, auth); - output = gestalts; - if (options.format !== 'json') { + return gestaltMessages; + }, + auth, + ); + if (options.format === 'json') { + process.stdout.write( + binUtils.outputFormatter({ + type: 'json', + data: gestaltMessages, + }), + ); + } else { // Convert to a human-readable list. - output = []; - let count = 1; - for (const gestalt of gestalts) { - output.push(`gestalt ${count}`); - output.push(`permissions: ${gestalt.permissions ?? 'None'}`); - // Listing nodes - for (const node of gestalt.nodes) { - output.push(`${node.nodeId}`); - } - // Listing identities - for (const identity of gestalt.identities) { - output.push(`${identity.providerId}:${identity.identityId}`); - } - output.push(''); - count++; + for (const gestaltMessage of gestaltMessages) { + const gestalt = gestaltMessage.gestalt; + const nodeIds = Object.values(gestalt.nodes).map( + (node) => node.nodeId as string, + ); + const identities = Object.values(gestalt.identities).map( + (identity) => `${identity.providerId}:${identity.identityId}`, + ); + process.stdout.write( + binUtils.outputFormatter({ + type: 'dict', + data: { + gestalt: { + actionsList: gestalt.actionsList.join(','), + identities, + nodeIds, + }, + }, + }), + ); } } - process.stdout.write( - binUtils.outputFormatter({ - type: options.format === 'json' ? 'json' : 'list', - data: output, - }), - ); } finally { if (pkClient! != null) await pkClient.stop(); } diff --git a/src/identities/CommandPermissions.ts b/src/identities/CommandPermissions.ts index 10622ebf..803ae521 100644 --- a/src/identities/CommandPermissions.ts +++ b/src/identities/CommandPermissions.ts @@ -52,7 +52,7 @@ class CommandPermissions extends CommandPolykey { logger: this.logger.getChild(PolykeyClient.name), }); const [type, id] = gestaltId; - let actions: Array = []; + let actionsList: Array = []; switch (type) { case 'node': { @@ -65,7 +65,7 @@ class CommandPermissions extends CommandPolykey { }), auth, ); - actions = res.actionsList; + actionsList = res.actionsList; } break; case 'identity': @@ -80,20 +80,31 @@ class CommandPermissions extends CommandPolykey { }), auth, ); - actions = res.actionsList; + actionsList = res.actionsList; } break; default: utils.never(); } - process.stdout.write( - binUtils.outputFormatter({ - type: options.format === 'json' ? 'json' : 'dict', - data: { - permissions: actions, - }, - }), - ); + if (options.format === 'json') { + process.stdout.write( + binUtils.outputFormatter({ + type: 'json', + data: { + actionsList, + }, + }), + ); + } else { + process.stdout.write( + binUtils.outputFormatter({ + type: 'dict', + data: { + actionsList: actionsList.join(','), + }, + }), + ); + } } finally { if (pkClient! != null) await pkClient.stop(); } diff --git a/src/identities/CommandSearch.ts b/src/identities/CommandSearch.ts index 56400305..b4489bd9 100644 --- a/src/identities/CommandSearch.ts +++ b/src/identities/CommandSearch.ts @@ -2,6 +2,7 @@ import type PolykeyClient from 'polykey/dist/PolykeyClient'; import type { IdentityInfoMessage } from 'polykey/dist/client/types'; import type { ReadableStream } from 'stream/web'; import type { ClientRPCResponseResult } from 'polykey/dist/client/types'; +import { TransformStream } from 'stream/web'; import CommandPolykey from '../CommandPolykey'; import * as binOptions from '../utils/options'; import * as binUtils from '../utils'; @@ -101,20 +102,37 @@ class CommandSearch extends CommandPolykey { limit: options.limit, }); } - for await (const identityInfoMessage of readableStream) { - const output = { - providerId: identityInfoMessage.providerId, - identityId: identityInfoMessage.identityId, - name: identityInfoMessage.name, - email: identityInfoMessage.email, - url: identityInfoMessage.url, - }; - process.stdout.write( - binUtils.outputFormatter({ - type: options.format === 'json' ? 'json' : 'dict', - data: output, - }), - ); + readableStream = readableStream.pipeThrough( + new TransformStream({ + transform: (chunk, controller) => { + controller.enqueue({ + providerId: chunk.providerId, + identityId: chunk.identityId, + name: chunk.name, + email: chunk.email, + url: chunk.url, + }); + }, + }), + ); + if (options.format === 'json') { + for await (const output of readableStream) { + process.stdout.write( + binUtils.outputFormatter({ + type: options.format === 'json' ? 'json' : 'dict', + data: output, + }), + ); + } + } else { + for await (const output of readableStream) { + process.stdout.write( + binUtils.outputFormatter({ + type: 'dict', + data: output, + }), + ); + } } }, auth); } finally { diff --git a/src/keys/CommandCert.ts b/src/keys/CommandCert.ts index 8c94ebee..11659553 100644 --- a/src/keys/CommandCert.ts +++ b/src/keys/CommandCert.ts @@ -49,19 +49,23 @@ class CommandCert extends CommandPolykey { }), auth, ); - const result = { - cert: response.cert, - }; - let output: any = result; - if (options.format === 'human') { - output = ['Root certificate:', result.cert]; + if (options.format === 'json') { + process.stdout.write( + binUtils.outputFormatter({ + type: 'json', + data: { + cert: response.cert, + }, + }), + ); + } else { + process.stdout.write( + binUtils.outputFormatter({ + type: 'raw', + data: response.cert, + }), + ); } - process.stdout.write( - binUtils.outputFormatter({ - type: options.format === 'json' ? 'json' : 'list', - data: output, - }), - ); } finally { if (pkClient! != null) await pkClient.stop(); } diff --git a/src/keys/CommandCertchain.ts b/src/keys/CommandCertchain.ts index ff212622..23ac4775 100644 --- a/src/keys/CommandCertchain.ts +++ b/src/keys/CommandCertchain.ts @@ -43,28 +43,30 @@ class CommandsCertchain extends CommandPolykey { logger: this.logger.getChild(PolykeyClient.name), }); const data = await binUtils.retryAuthentication(async (auth) => { - const data: Array = []; + const data: Array<{ cert: string }> = []; const stream = await pkClient.rpcClient.methods.keysCertsChainGet({ metadata: auth, }); - for await (const cert of stream) { - data.push(cert.cert); + for await (const certchainGetMessage of stream) { + data.push({ cert: certchainGetMessage.cert }); } return data; }, auth); - const result = { - certchain: data, - }; - let output: any = result; - if (options.format === 'human') { - output = ['Root Certificate Chain:', ...result.certchain]; + if (options.format === 'json') { + process.stdout.write( + binUtils.outputFormatter({ + type: 'json', + data: data, + }), + ); + } else { + process.stdout.write( + binUtils.outputFormatter({ + type: 'list', + data: data.map((certchainGetMessage) => certchainGetMessage.cert), + }), + ); } - process.stdout.write( - binUtils.outputFormatter({ - type: options.format === 'json' ? 'json' : 'list', - data: output, - }), - ); } finally { if (pkClient! != null) await pkClient.stop(); } diff --git a/src/keys/CommandDecrypt.ts b/src/keys/CommandDecrypt.ts index f6c8af78..06e2716c 100644 --- a/src/keys/CommandDecrypt.ts +++ b/src/keys/CommandDecrypt.ts @@ -71,19 +71,23 @@ class CommandDecrypt extends CommandPolykey { }), auth, ); - const result = { - decryptedData: response.data, - }; - let output: any = result; - if (options.format === 'human') { - output = { 'Decrypted data:': result.decryptedData }; + if (options.format === 'json') { + process.stdout.write( + binUtils.outputFormatter({ + type: 'json', + data: { + data: response.data, + }, + }), + ); + } else { + process.stdout.write( + binUtils.outputFormatter({ + type: 'raw', + data: response.data, + }), + ); } - process.stdout.write( - binUtils.outputFormatter({ - type: options.format === 'json' ? 'json' : 'dict', - data: output, - }), - ); } finally { if (pkClient! != null) await pkClient.stop(); } diff --git a/src/keys/CommandEncrypt.ts b/src/keys/CommandEncrypt.ts index bc124aee..7bb2d6bb 100644 --- a/src/keys/CommandEncrypt.ts +++ b/src/keys/CommandEncrypt.ts @@ -98,19 +98,23 @@ class CommandEncypt extends CommandPolykey { }), auth, ); - const result = { - encryptedData: response.data, - }; - let output: any = result; - if (options.format === 'human') { - output = { 'Encrypted data:': result.encryptedData }; + if (options.format === 'json') { + process.stdout.write( + binUtils.outputFormatter({ + type: 'json', + data: { + data: response.data, + }, + }), + ); + } else { + process.stdout.write( + binUtils.outputFormatter({ + type: 'raw', + data: response.data, + }), + ); } - process.stdout.write( - binUtils.outputFormatter({ - type: options.format === 'json' ? 'json' : 'dict', - data: output, - }), - ); } finally { if (pkClient! != null) await pkClient.stop(); } diff --git a/src/keys/CommandPair.ts b/src/keys/CommandPair.ts index a9cd9218..2c5dfb10 100644 --- a/src/keys/CommandPair.ts +++ b/src/keys/CommandPair.ts @@ -59,8 +59,8 @@ class CommandKeypair extends CommandPolykey { auth, ); const pair = { - publicKey: keyPairJWK.publicKeyJwk, - privateKey: keyPairJWK.privateKeyJwe, + publicKeyJwk: keyPairJWK.publicKeyJwk, + privateKeyJwe: keyPairJWK.privateKeyJwe, }; process.stdout.write( binUtils.outputFormatter({ diff --git a/src/keys/CommandSign.ts b/src/keys/CommandSign.ts index 554f6191..fbdd7c60 100644 --- a/src/keys/CommandSign.ts +++ b/src/keys/CommandSign.ts @@ -71,17 +71,12 @@ class CommandSign extends CommandPolykey { }), auth, ); - const result = { - signature: response.signature, - }; - let output: any = result; - if (options.format === 'human') { - output = { 'Signature:': result.signature }; - } process.stdout.write( binUtils.outputFormatter({ type: options.format === 'json' ? 'json' : 'dict', - data: output, + data: { + signature: response.signature, + }, }), ); } finally { diff --git a/src/keys/CommandVerify.ts b/src/keys/CommandVerify.ts index 31389af0..4703696d 100644 --- a/src/keys/CommandVerify.ts +++ b/src/keys/CommandVerify.ts @@ -107,17 +107,12 @@ class CommandVerify extends CommandPolykey { }), auth, ); - const result = { - signatureVerified: response.success, - }; - let output: any = result; - if (options.format === 'human') { - output = { 'Signature verified:': result.signatureVerified }; - } process.stdout.write( binUtils.outputFormatter({ type: options.format === 'json' ? 'json' : 'dict', - data: output, + data: { + success: response.success, + }, }), ); } finally { diff --git a/src/nodes/CommandClaim.ts b/src/nodes/CommandClaim.ts index 1d16b4c4..d5e8c7bf 100644 --- a/src/nodes/CommandClaim.ts +++ b/src/nodes/CommandClaim.ts @@ -63,28 +63,27 @@ class CommandClaim extends CommandPolykey { }), auth, ); - const claimed = response.success; - if (claimed) { - const outputFormatted = binUtils.outputFormatter({ - type: options.format === 'json' ? 'json' : 'list', - data: [ - `Successfully generated a cryptolink claim on Keynode with ID ${nodesUtils.encodeNodeId( - nodeId, - )}`, - ], - }); - process.stdout.write(outputFormatted); + if (response.success) { + process.stderr.write( + `Successfully generated a cryptolink claim on Keynode with ID ${nodesUtils.encodeNodeId( + nodeId, + )}\n`, + ); } else { - const outputFormatted = binUtils.outputFormatter({ - type: options.format === 'json' ? 'json' : 'list', - data: [ - `Successfully sent Gestalt Invite notification to Keynode with ID ${nodesUtils.encodeNodeId( - nodeId, - )}`, - ], - }); - process.stdout.write(outputFormatted); + process.stderr.write( + `Successfully sent Gestalt Invite notification to Keynode with ID ${nodesUtils.encodeNodeId( + nodeId, + )}\n`, + ); } + process.stdout.write( + binUtils.outputFormatter({ + type: options.format === 'json' ? 'json' : 'dict', + data: { + success: response.success, + }, + }), + ); } finally { if (pkClient! != null) await pkClient.stop(); } diff --git a/src/nodes/CommandConnections.ts b/src/nodes/CommandConnections.ts index 62ddece0..de8fc73d 100644 --- a/src/nodes/CommandConnections.ts +++ b/src/nodes/CommandConnections.ts @@ -57,7 +57,14 @@ class CommandAdd extends CommandPolykey { } return connectionEntries; }, auth); - if (options.format === 'human') { + if (options.format === 'json') { + // Wait for outputFormatter to complete and then write to stdout + const outputFormatted = binUtils.outputFormatter({ + type: 'json', + data: connections, + }); + process.stdout.write(outputFormatted); + } else { // Wait for outputFormatter to complete and then write to stdout const outputFormatted = binUtils.outputFormatter({ type: 'table', @@ -75,13 +82,6 @@ class CommandAdd extends CommandPolykey { }, }); process.stdout.write(outputFormatted); - } else { - // Wait for outputFormatter to complete and then write to stdout - const outputFormatted = binUtils.outputFormatter({ - type: 'json', - data: connections, - }); - process.stdout.write(outputFormatted); } } finally { if (pkClient! != null) await pkClient.stop(); diff --git a/src/nodes/CommandFind.ts b/src/nodes/CommandFind.ts index 83ea1d09..1af6a050 100644 --- a/src/nodes/CommandFind.ts +++ b/src/nodes/CommandFind.ts @@ -61,12 +61,6 @@ class CommandFind extends CommandPolykey { message: '', id: '', }; - let foundAddress: - | { - host: Host | Hostname; - port: Port; - } - | undefined; try { const response = await binUtils.retryAuthentication( (auth) => @@ -79,42 +73,42 @@ class CommandFind extends CommandPolykey { result.success = true; result.id = nodesUtils.encodeNodeId(nodeId); const [host, port] = response.nodeAddress; - foundAddress = { - host, - port, - }; - result.address = foundAddress; - result.message = `Found node at ${networkUtils.buildAddress( + const formattedNodeAddress = networkUtils.buildAddress( host as Host, port as Port, - )}`; + ); + process.stderr.write(`Found node at ${formattedNodeAddress}\n`); + if (options.format === 'json') { + process.stdout.write( + binUtils.outputFormatter({ + type: 'json', + data: { + nodeAddress: response.nodeAddress, + nodeContactAddressData: response.nodeContactAddressData, + }, + }), + ); + } else { + process.stdout.write( + binUtils.outputFormatter({ + type: 'dict', + data: { + nodeAddress: formattedNodeAddress, + ...response.nodeContactAddressData, + scopes: response.nodeContactAddressData.scopes.join(','), + }, + }), + ); + } } catch (err) { if ( !(err.cause instanceof nodesErrors.ErrorNodeGraphNodeIdNotFound) ) { throw err; } - // Else failed to find the node. - result.success = false; - result.id = nodesUtils.encodeNodeId(nodeId); - result.message = `Failed to find node ${result.id}`; - } - let outputFormatted: string | Uint8Array; - if (options.format === 'json') { - outputFormatted = binUtils.outputFormatter({ - type: 'json', - data: result, - }); - } else { - outputFormatted = binUtils.outputFormatter({ - type: 'list', - data: [`Found node at ${foundAddress}`], - }); - } - process.stdout.write(outputFormatted); - // Like ping, it should error when failing to find node for automation reasons. - if (!result.success) { - throw new errors.ErrorPolykeyCLINodeFindFailed(result.message); + throw new errors.ErrorPolykeyCLINodeFindFailed( + `Failed to find node ${nodesUtils.encodeNodeId(nodeId)}`, + ); } } finally { if (pkClient! != null) await pkClient.stop(); diff --git a/src/nodes/CommandGetAll.ts b/src/nodes/CommandGetAll.ts index 09177b60..1a7f43af 100644 --- a/src/nodes/CommandGetAll.ts +++ b/src/nodes/CommandGetAll.ts @@ -44,38 +44,44 @@ class CommandGetAll extends CommandPolykey { }, logger: this.logger.getChild(PolykeyClient.name), }); - const resultOutput = await binUtils.retryAuthentication( - async (auth) => { - const result = await pkClient.rpcClient.methods.nodesGetAll({ - metadata: auth, - }); - const output: Array = []; - for await (const nodesGetMessage of result) { - output.push(nodesGetMessage); - } - return output; - }, - auth, - ); - let output: Array = []; - if (options.format === 'human') { - for (const nodesGetMessage of resultOutput) { - const nodeIdEncoded = nodesGetMessage.nodeIdEncoded; - const bucketIndex = nodesGetMessage.bucketIndex; - for (const address of Object.keys(nodesGetMessage.nodeContact)) { - output.push( - `NodeId ${nodeIdEncoded}, Address ${address}, bucketIndex ${bucketIndex}`, - ); - } + const result = await binUtils.retryAuthentication(async (auth) => { + const result = await pkClient.rpcClient.methods.nodesGetAll({ + metadata: auth, + }); + const output: Array = []; + for await (const nodesGetMessage of result) { + output.push(nodesGetMessage); } + return output; + }, auth); + if (options.format === 'json') { + process.stdout.write( + binUtils.outputFormatter({ + type: 'json', + data: result.map((nodesGetMessage) => ({ + nodeIdEncoded: nodesGetMessage.nodeIdEncoded, + nodeContact: nodesGetMessage.nodeContact, + bucketIndex: nodesGetMessage.bucketIndex, + })), + }), + ); } else { - output = resultOutput; + process.stdout.write( + binUtils.outputFormatter({ + type: 'table', + options: { + columns: ['nodeIdEncoded', 'nodeAddress', 'bucketIndex'], + }, + data: result.flatMap((nodesGetMessage) => + Object.keys(nodesGetMessage.nodeContact).map((nodeAddress) => ({ + nodeIdEncoded: nodesGetMessage.nodeIdEncoded, + nodeAddress, + bucketIndex: nodesGetMessage.bucketIndex, + })), + ), + }), + ); } - const outputFormatted = binUtils.outputFormatter({ - type: options.format === 'json' ? 'json' : 'list', - data: output, - }); - process.stdout.write(outputFormatted); } finally { if (pkClient! != null) await pkClient.stop(); } diff --git a/src/nodes/CommandPing.ts b/src/nodes/CommandPing.ts index 258e64e5..88fee3ce 100644 --- a/src/nodes/CommandPing.ts +++ b/src/nodes/CommandPing.ts @@ -57,23 +57,19 @@ class CommandPing extends CommandPolykey { }), auth, ); - const status = { success: false, message: '' }; - status.success = statusMessage ? statusMessage.success : false; - if (!status.success && !error) { + if (!statusMessage.success) { error = new errors.ErrorPolykeyCLINodePingFailed( 'No response received', ); } - if (status.success) status.message = 'Node is Active.'; - else status.message = error.message; - const output: any = - options.format === 'json' ? status : [status.message]; + if (statusMessage.success) process.stderr.write('Node is Active\n'); const outputFormatted = binUtils.outputFormatter({ - type: options.format === 'json' ? 'json' : 'list', - data: output, + type: options.format === 'json' ? 'json' : 'dict', + data: { + success: statusMessage.success, + }, }); process.stdout.write(outputFormatted); - if (error != null) throw error; } finally { if (pkClient! != null) await pkClient.stop(); diff --git a/src/notifications/CommandRead.ts b/src/notifications/CommandRead.ts index 47b6c159..4b2811e9 100644 --- a/src/notifications/CommandRead.ts +++ b/src/notifications/CommandRead.ts @@ -61,7 +61,7 @@ class CommandRead extends CommandPolykey { }, logger: this.logger.getChild(PolykeyClient.name), }); - const notifications = await binUtils.retryAuthentication( + const notificationReadMessages = await binUtils.retryAuthentication( async (auth) => { const response = await pkClient.rpcClient.methods.notificationsRead( { @@ -71,23 +71,40 @@ class CommandRead extends CommandPolykey { order: options.order, }, ); - const notifications: Array = []; + const notificationReadMessages: Array<{ + notification: Notification; + }> = []; for await (const notificationMessage of response) { const notification = notificationsUtils.parseNotification( notificationMessage.notification, ); - notifications.push(notification); + notificationReadMessages.push({ notification }); } - return notifications; + return notificationReadMessages; }, meta, ); - for (const notification of notifications) { - const outputFormatted = binUtils.outputFormatter({ - type: options.format === 'json' ? 'json' : 'dict', - data: notification, - }); - process.stdout.write(outputFormatted); + if (notificationReadMessages.length === 0) { + process.stderr.write('No notifications received\n'); + } + if (options.format === 'json') { + process.stdout.write( + binUtils.outputFormatter({ + type: 'json', + data: notificationReadMessages, + }), + ); + } else { + for (const notificationReadMessage of notificationReadMessages) { + process.stdout.write( + binUtils.outputFormatter({ + type: 'dict', + data: { + notificiation: notificationReadMessage.notification, + }, + }), + ); + } } } finally { if (pkClient! != null) await pkClient.stop(); diff --git a/src/secrets/CommandGet.ts b/src/secrets/CommandGet.ts index 42040e53..334c1975 100644 --- a/src/secrets/CommandGet.ts +++ b/src/secrets/CommandGet.ts @@ -59,11 +59,23 @@ class CommandGet extends CommandPolykey { meta, ); const secretContent = Buffer.from(response.secretContent, 'binary'); - const outputFormatted = binUtils.outputFormatter({ - type: 'raw', - data: secretContent, - }); - process.stdout.write(outputFormatted); + if (options.format === 'json') { + process.stdout.write( + binUtils.outputFormatter({ + type: 'json', + data: { + secretContent: secretContent.toString(), + }, + }), + ); + } else { + process.stdout.write( + binUtils.outputFormatter({ + type: 'raw', + data: secretContent, + }), + ); + } } finally { if (pkClient! != null) await pkClient.stop(); } diff --git a/src/secrets/CommandList.ts b/src/secrets/CommandList.ts index 0dc2d3a7..641d071c 100644 --- a/src/secrets/CommandList.ts +++ b/src/secrets/CommandList.ts @@ -46,23 +46,36 @@ class CommandList extends CommandPolykey { logger: this.logger.getChild(PolykeyClient.name), }); const data = await binUtils.retryAuthentication(async (auth) => { - const data: Array = []; + const data: Array<{ secretName: string }> = []; const stream = await pkClient.rpcClient.methods.vaultsSecretsList({ metadata: auth, nameOrId: vaultName, }); for await (const secret of stream) { - data.push(secret.secretName); + data.push({ + secretName: secret.secretName, + }); } return data; }, auth); - const outputFormatted = binUtils.outputFormatter({ - type: options.format === 'json' ? 'json' : 'list', - data: data, - }); - - process.stdout.write(outputFormatted); + if (options.format === 'json') { + process.stdout.write( + binUtils.outputFormatter({ + type: 'json', + data: data, + }), + ); + } else { + process.stdout.write( + binUtils.outputFormatter({ + type: 'list', + data: data.map( + (secretsListMessage) => secretsListMessage.secretName, + ), + }), + ); + } } finally { if (pkClient! != null) await pkClient.stop(); } diff --git a/src/secrets/CommandStat.ts b/src/secrets/CommandStat.ts index 5cc6934d..78bd561e 100644 --- a/src/secrets/CommandStat.ts +++ b/src/secrets/CommandStat.ts @@ -60,18 +60,23 @@ class CommandStat extends CommandPolykey { meta, ); - const data: Array = [`Stats for "${secretPath[1]}"`]; - for (const [key, value] of Object.entries(response.stat)) { - data.push(`${key}: ${value}`); + if (options.format === 'json') { + process.stdout.write( + binUtils.outputFormatter({ + type: 'json', + data: { + stat: response.stat, + }, + }), + ); + } else { + process.stdout.write( + binUtils.outputFormatter({ + type: 'dict', + data: response.stat, + }), + ); } - - // Assuming the surrounding function is async - const outputFormatted = binUtils.outputFormatter({ - type: options.format === 'json' ? 'json' : 'list', - data, - }); - - process.stdout.write(outputFormatted); } finally { if (pkClient! != null) await pkClient.stop(); } diff --git a/src/types.ts b/src/types.ts index a6ded81e..cc9fe0f7 100644 --- a/src/types.ts +++ b/src/types.ts @@ -13,6 +13,10 @@ interface TableOptions { includeRowCount?: boolean; } +interface DictOptions { + padding?: number; +} + type AgentStatusLiveData = Omit & { nodeId: NodeIdEncoded; }; @@ -51,6 +55,7 @@ type AgentChildProcessOutput = export type { TableRow, TableOptions, + DictOptions, AgentStatusLiveData, AgentChildProcessInput, AgentChildProcessOutput, diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 8ff76bd5..bfefb68a 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -1,5 +1,5 @@ import type { POJO } from 'polykey/dist/types'; -import type { TableRow, TableOptions } from '../types'; +import type { TableRow, TableOptions, DictOptions } from '../types'; import process from 'process'; import { LogLevel } from '@matrixai/logger'; import ErrorPolykey from 'polykey/dist/ErrorPolykey'; @@ -40,6 +40,7 @@ type OutputObject = | { type: 'dict'; data: POJO; + options?: DictOptions; } | { type: 'json'; @@ -219,7 +220,7 @@ function outputFormatter(msg: OutputObject): string | Uint8Array { case 'table': return outputFormatterTable(msg.data, msg.options); case 'dict': - return outputFormatterDict(msg.data); + return outputFormatterDict(msg.data, msg.options); case 'json': return outputFormatterJson(msg.data); case 'error': @@ -351,35 +352,62 @@ function outputFormatterTable( return output; } -function outputFormatterDict(data: POJO): string { +function outputFormatterDict( + data: POJO, + { + padding = 0, + }: { + padding?: number; + } = {}, +): string { let output = ''; let maxKeyLength = 0; + const leftPadding = ' '.repeat(padding); // Array<[originalKey, encodedKey]> const keypairs: Array<[string, string]> = []; - for (const key in data) { - const encodedKey = encodeEscapedWrapped(key); - keypairs.push([key, encodedKey]); - if (encodedKey.length > maxKeyLength) { - maxKeyLength = encodedKey.length; + const dataIsArray = Array.isArray(data); + if (!dataIsArray) { + for (const key in data) { + const encodedKey = encodeEscapedWrapped(key); + keypairs.push([key, encodedKey]); + if (encodedKey.length > maxKeyLength) { + maxKeyLength = encodedKey.length; + } + } + } else { + for (const key of data) { + const encodedKey = encodeEscapedWrapped(key); + keypairs.push([key, encodedKey]); + if (encodedKey.length > maxKeyLength) { + maxKeyLength = encodedKey.length; + } } } for (const [originalKey, encodedKey] of keypairs) { + const rightPadding = ' '.repeat(maxKeyLength - encodedKey.length); + output += `${leftPadding}${encodedKey}${rightPadding}\t`; + + if (dataIsArray) { + output += '\n'; + continue; + } + let value = data[originalKey]; if (value == null) { value = ''; - } - - if (typeof value === 'string') { + } else if (typeof value == 'object') { + output += `\n${outputFormatterDict(value, { + padding: padding + 2, + })}`; + continue; + } else if (typeof value === 'string') { value = encodeEscapedWrapped(value); } else { value = JSON.stringify(value, encodeEscapedReplacer); } - value = value.replace(/(?:\r\n|\n)$/, ''); value = value.replace(/(\r\n|\n)/g, '$1\t'); - - const padding = ' '.repeat(maxKeyLength - encodedKey.length); - output += `${encodedKey}${padding}\t${value}\n`; + output += `${value}\n`; } return output; } diff --git a/src/vaults/CommandCreate.ts b/src/vaults/CommandCreate.ts index 456cb886..4624955b 100644 --- a/src/vaults/CommandCreate.ts +++ b/src/vaults/CommandCreate.ts @@ -54,11 +54,9 @@ class CommandCreate extends CommandPolykey { }), meta, ); - const outputFormatted = binUtils.outputFormatter({ - type: options.format === 'json' ? 'json' : 'list', - data: [`Vault ${response.vaultIdEncoded} created successfully`], - }); - process.stdout.write(outputFormatted); + process.stderr.write( + `Vault ${response.vaultIdEncoded} created successfully\n`, + ); } finally { if (pkClient! != null) await pkClient.stop(); } diff --git a/src/vaults/CommandLog.ts b/src/vaults/CommandLog.ts index 51852ce4..3137b172 100644 --- a/src/vaults/CommandLog.ts +++ b/src/vaults/CommandLog.ts @@ -48,7 +48,12 @@ class CommandLog extends CommandPolykey { logger: this.logger.getChild(PolykeyClient.name), }); const data = await binUtils.retryAuthentication(async (auth) => { - const data: Array = []; + const data: Array<{ + commitId: string; + committer: string; + timestamp: string; + message: string; + }> = []; const logStream = await pkClient.rpcClient.methods.vaultsLog({ metadata: auth, nameOrId: vault, @@ -56,18 +61,26 @@ class CommandLog extends CommandPolykey { commitId: options.commitId, }); for await (const logEntryMessage of logStream) { - data.push(`commit ${logEntryMessage.commitId}`); - data.push(`committer ${logEntryMessage.committer}`); - data.push(`Date: ${logEntryMessage.timestamp}`); - data.push(`${logEntryMessage.message}`); + data.push({ + commitId: logEntryMessage.commitId, + committer: logEntryMessage.committer, + timestamp: logEntryMessage.timestamp, + message: logEntryMessage.message, + }); } return data; }, meta); - const outputFormatted = binUtils.outputFormatter({ - type: options.format === 'json' ? 'json' : 'list', - data: data, - }); - process.stdout.write(outputFormatted); + if (options.format === 'json') { + process.stdout.write( + binUtils.outputFormatter({ type: 'json', data }), + ); + } else { + for (const entry of data) { + process.stdout.write( + binUtils.outputFormatter({ type: 'dict', data: entry }), + ); + } + } } finally { if (pkClient! != null) await pkClient.stop(); } diff --git a/src/vaults/CommandPermissions.ts b/src/vaults/CommandPermissions.ts index 69251bd2..add23da9 100644 --- a/src/vaults/CommandPermissions.ts +++ b/src/vaults/CommandPermissions.ts @@ -45,7 +45,11 @@ class CommandPermissions extends CommandPolykey { }, logger: this.logger.getChild(PolykeyClient.name), }); - const data: Array = []; + const data: Array<{ + vaultIdEncoded: string; + nodeIdEncoded: string; + vaultPermissionList: Array; + }> = []; await binUtils.retryAuthentication(async (auth) => { const permissionStream = await pkClient.rpcClient.methods.vaultsPermissionGet({ @@ -53,19 +57,37 @@ class CommandPermissions extends CommandPolykey { nameOrId: vaultName, }); for await (const permission of permissionStream) { - const nodeId = permission.nodeIdEncoded; - const actions = permission.vaultPermissionList.join(', '); - data.push(`${nodeId}: ${actions}`); + data.push({ + vaultIdEncoded: permission.vaultIdEncoded, + nodeIdEncoded: permission.nodeIdEncoded, + vaultPermissionList: permission.vaultPermissionList, + }); } return true; }, meta); - if (data.length === 0) data.push('No permissions were found'); - const outputFormatted = binUtils.outputFormatter({ - type: options.format === 'json' ? 'json' : 'list', - data: data, - }); - process.stdout.write(outputFormatted); + if (data.length === 0) { + process.stderr.write('No permissions were found\n'); + } + if (options.format === 'json') { + process.stdout.write( + binUtils.outputFormatter({ + type: 'json', + data: data, + }), + ); + } else { + for (const permission of data) { + permission.vaultPermissionList = + permission.vaultPermissionList.join(',') as any; + process.stdout.write( + binUtils.outputFormatter({ + type: 'dict', + data: permission, + }), + ); + } + } } finally { if (pkClient! != null) await pkClient.stop(); } diff --git a/tests/agent/stop.test.ts b/tests/agent/stop.test.ts index 5aa0c68f..a3e95e5d 100644 --- a/tests/agent/stop.test.ts +++ b/tests/agent/stop.test.ts @@ -220,7 +220,7 @@ describe('stop', () => { }, ); testUtils.expectProcessError(exitCode, stderr, [ - new binErrors.ErrorPolykeyCLIAgentStatus('agent is starting'), + new binErrors.ErrorPolykeyCLIAgentStatus('Agent is starting'), ]); await status.waitFor('LIVE'); await testUtils.pkStdio(['agent', 'stop'], { diff --git a/tests/identities/allowDisallowPermissions.test.ts b/tests/identities/allowDisallowPermissions.test.ts index f395f134..78e8806c 100644 --- a/tests/identities/allowDisallowPermissions.test.ts +++ b/tests/identities/allowDisallowPermissions.test.ts @@ -164,7 +164,7 @@ describe('allow/disallow/permissions', () => { )); expect(exitCode).toBe(0); expect(JSON.parse(stdout)).toEqual({ - permissions: ['notify', 'scan'], + actionsList: ['notify', 'scan'], }); // Disallow both permissions ({ exitCode } = await testUtils.pkStdio( @@ -208,7 +208,7 @@ describe('allow/disallow/permissions', () => { )); expect(exitCode).toBe(0); expect(JSON.parse(stdout)).toEqual({ - permissions: [], + actionsList: [], }); }); test('allows/disallows/gets gestalt permissions by identity', async () => { @@ -299,7 +299,7 @@ describe('allow/disallow/permissions', () => { )); expect(exitCode).toBe(0); expect(JSON.parse(stdout)).toEqual({ - permissions: ['notify', 'scan'], + actionsList: ['notify', 'scan'], }); // Disallow both permissions ({ exitCode } = await testUtils.pkStdio( @@ -337,7 +337,7 @@ describe('allow/disallow/permissions', () => { )); expect(exitCode).toBe(0); expect(JSON.parse(stdout)).toEqual({ - permissions: [], + actionsList: [], }); }); test('should fail on invalid inputs', async () => { diff --git a/tests/identities/claim.test.ts b/tests/identities/claim.test.ts index 11c6f9b3..c35c658b 100644 --- a/tests/identities/claim.test.ts +++ b/tests/identities/claim.test.ts @@ -92,7 +92,10 @@ describe('claim', () => { }, ); expect(exitCode).toBe(0); - expect(JSON.parse(stdout)).toEqual(['Claim Id: 0', 'Url: test.com']); + expect(JSON.parse(stdout)).toEqual({ + claimId: '0', + url: 'test.com', + }); // Check for claim on the provider const claim = await testProvider.getClaim( testToken.identityId, diff --git a/tests/identities/trustUntrustList.test.ts b/tests/identities/trustUntrustList.test.ts index 40952748..8dabd913 100644 --- a/tests/identities/trustUntrustList.test.ts +++ b/tests/identities/trustUntrustList.test.ts @@ -172,15 +172,21 @@ describe('trust/untrust/list', () => { )); expect(exitCode).toBe(0); expect(JSON.parse(stdout)).toHaveLength(2); - expect(JSON.parse(stdout)[0]).toEqual({ - permissions: ['notify'], - nodes: [{ nodeId: nodesUtils.encodeNodeId(nodeId) }], - identities: [ - { - providerId: provider.id, - identityId: identity, + expect(JSON.parse(stdout)[0]).toMatchObject({ + gestalt: { + actionsList: ['notify'], + nodes: { + ['node-' + nodesUtils.encodeNodeId(nodeId)]: { + nodeId: nodesUtils.encodeNodeId(nodeId), + }, }, - ], + identities: { + ['identity-' + JSON.stringify([provider.id, identity])]: { + providerId: provider.id, + identityId: identity, + }, + }, + }, }); // Untrust the gestalt by node // This should remove the permission, but not the gestalt (from the gestalt @@ -209,15 +215,21 @@ describe('trust/untrust/list', () => { )); expect(exitCode).toBe(0); expect(JSON.parse(stdout)).toHaveLength(2); - expect(JSON.parse(stdout)[0]).toEqual({ - permissions: null, - nodes: [{ nodeId: nodesUtils.encodeNodeId(nodeId) }], - identities: [ - { - providerId: provider.id, - identityId: identity, + expect(JSON.parse(stdout)[0]).toMatchObject({ + gestalt: { + actionsList: [], + nodes: { + ['node-' + nodesUtils.encodeNodeId(nodeId)]: { + nodeId: nodesUtils.encodeNodeId(nodeId), + }, }, - ], + identities: { + ['identity-' + JSON.stringify([provider.id, identity])]: { + providerId: provider.id, + identityId: identity, + }, + }, + }, }); // Revert side-effects await pkAgent.gestaltGraph.unsetNode(nodeId); @@ -316,15 +328,21 @@ describe('trust/untrust/list', () => { )); expect(exitCode).toBe(0); expect(JSON.parse(stdout)).toHaveLength(2); - expect(JSON.parse(stdout)[0]).toEqual({ - permissions: ['notify'], - nodes: [{ nodeId: nodesUtils.encodeNodeId(nodeId) }], - identities: [ - { - providerId: provider.id, - identityId: identity, + expect(JSON.parse(stdout)[0]).toMatchObject({ + gestalt: { + actionsList: ['notify'], + nodes: { + ['node-' + nodesUtils.encodeNodeId(nodeId)]: { + nodeId: nodesUtils.encodeNodeId(nodeId), + }, }, - ], + identities: { + ['identity-' + JSON.stringify([provider.id, identity])]: { + providerId: provider.id, + identityId: identity, + }, + }, + }, }); // Untrust the gestalt by node // This should remove the permission, but not the gestalt (from the gestalt @@ -353,15 +371,21 @@ describe('trust/untrust/list', () => { )); expect(exitCode).toBe(0); expect(JSON.parse(stdout)).toHaveLength(2); - expect(JSON.parse(stdout)[0]).toEqual({ - permissions: null, - nodes: [{ nodeId: nodesUtils.encodeNodeId(nodeId) }], - identities: [ - { - providerId: provider.id, - identityId: identity, + expect(JSON.parse(stdout)[0]).toMatchObject({ + gestalt: { + actionsList: [], + nodes: { + ['node-' + nodesUtils.encodeNodeId(nodeId)]: { + nodeId: nodesUtils.encodeNodeId(nodeId), + }, }, - ], + identities: { + ['identity-' + JSON.stringify([provider.id, identity])]: { + providerId: provider.id, + identityId: identity, + }, + }, + }, }); // Revert side-effects await pkAgent.gestaltGraph.unsetNode(nodeId); diff --git a/tests/keys/certchain.test.ts b/tests/keys/certchain.test.ts index dc53eec1..2dede7f2 100644 --- a/tests/keys/certchain.test.ts +++ b/tests/keys/certchain.test.ts @@ -27,8 +27,6 @@ describe('certchain', () => { }, ); expect(exitCode).toBe(0); - expect(JSON.parse(stdout)).toEqual({ - certchain: expect.any(Array), - }); + expect(JSON.parse(stdout)).toEqual(expect.any(Array)); }); }); diff --git a/tests/keys/encryptDecrypt.test.ts b/tests/keys/encryptDecrypt.test.ts index 7522901e..e1197d47 100644 --- a/tests/keys/encryptDecrypt.test.ts +++ b/tests/keys/encryptDecrypt.test.ts @@ -44,7 +44,7 @@ describe('encrypt-decrypt', () => { ); expect(exitCode).toBe(0); expect(JSON.parse(stdout)).toEqual({ - decryptedData: 'abc', + data: 'abc', }); }); test('encrypts data using NodeId', async () => { @@ -74,9 +74,9 @@ describe('encrypt-decrypt', () => { ); expect(exitCode).toBe(0); expect(JSON.parse(stdout)).toEqual({ - encryptedData: expect.any(String), + data: expect.any(String), }); - const encrypted = JSON.parse(stdout).encryptedData; + const encrypted = JSON.parse(stdout).data; const decrypted = keysUtils.decryptWithPrivateKey( targetKeyPair, Buffer.from(encrypted, 'binary'), @@ -105,9 +105,9 @@ describe('encrypt-decrypt', () => { ); expect(exitCode).toBe(0); expect(JSON.parse(stdout)).toEqual({ - encryptedData: expect.any(String), + data: expect.any(String), }); - const encrypted = JSON.parse(stdout).encryptedData; + const encrypted = JSON.parse(stdout).data; const decrypted = keysUtils.decryptWithPrivateKey( targetKeyPair, Buffer.from(encrypted, 'binary'), diff --git a/tests/keys/keypair.test.ts b/tests/keys/keypair.test.ts index 840877fd..18a8ef06 100644 --- a/tests/keys/keypair.test.ts +++ b/tests/keys/keypair.test.ts @@ -29,7 +29,7 @@ describe('keypair', () => { ); expect(exitCode).toBe(0); expect(JSON.parse(stdout)).toEqual({ - publicKey: { + publicKeyJwk: { alg: expect.any(String), crv: expect.any(String), ext: expect.any(Boolean), @@ -37,7 +37,7 @@ describe('keypair', () => { kty: expect.any(String), x: expect.any(String), }, - privateKey: { + privateKeyJwe: { ciphertext: expect.any(String), iv: expect.any(String), protected: expect.any(String), diff --git a/tests/keys/renew.test.ts b/tests/keys/renew.test.ts index a5500ee3..938da9a6 100644 --- a/tests/keys/renew.test.ts +++ b/tests/keys/renew.test.ts @@ -55,8 +55,8 @@ describe('renew', () => { }, ); expect(exitCode).toBe(0); - const prevPublicKey = JSON.parse(stdout).publicKey; - const prevPrivateKey = JSON.parse(stdout).privateKey; + const prevPublicKey = JSON.parse(stdout).publicKeyJwk; + const prevPrivateKey = JSON.parse(stdout).privateKeyJwe; ({ exitCode, stdout } = await testUtils.pkStdio( ['agent', 'status', '--format', 'json'], { @@ -103,8 +103,8 @@ describe('renew', () => { }, )); expect(exitCode).toBe(0); - const newPublicKey = JSON.parse(stdout).publicKey; - const newPrivateKey = JSON.parse(stdout).privateKey; + const newPublicKey = JSON.parse(stdout).publicKeyJwk; + const newPrivateKey = JSON.parse(stdout).privateKeyJwe; ({ exitCode, stdout } = await testUtils.pkStdio( ['agent', 'status', '--format', 'json'], { diff --git a/tests/keys/reset.test.ts b/tests/keys/reset.test.ts index 76aa9d7b..a37a099e 100644 --- a/tests/keys/reset.test.ts +++ b/tests/keys/reset.test.ts @@ -55,8 +55,8 @@ describe('reset', () => { }, ); expect(exitCode).toBe(0); - const prevPublicKey = JSON.parse(stdout).publicKey; - const prevPrivateKey = JSON.parse(stdout).privateKey; + const prevPublicKey = JSON.parse(stdout).publicKeyJwk; + const prevPrivateKey = JSON.parse(stdout).privateKeyJwe; ({ exitCode, stdout } = await testUtils.pkStdio( ['agent', 'status', '--format', 'json'], { @@ -104,8 +104,8 @@ describe('reset', () => { }, )); expect(exitCode).toBe(0); - const newPublicKey = JSON.parse(stdout).publicKey; - const newPrivateKey = JSON.parse(stdout).privateKey; + const newPublicKey = JSON.parse(stdout).publicKeyJwk; + const newPrivateKey = JSON.parse(stdout).privateKeyJwe; ({ exitCode, stdout } = await testUtils.pkStdio( ['agent', 'status', '--format', 'json'], { diff --git a/tests/keys/signVerify.test.ts b/tests/keys/signVerify.test.ts index 011b40a6..2de5ab6f 100644 --- a/tests/keys/signVerify.test.ts +++ b/tests/keys/signVerify.test.ts @@ -88,7 +88,7 @@ describe('sign-verify', () => { ); expect(exitCode).toBe(0); expect(JSON.parse(stdout)).toEqual({ - signatureVerified: true, + success: true, }); }); test('verifies a signature with JWK', async () => { @@ -122,7 +122,7 @@ describe('sign-verify', () => { ); expect(exitCode).toBe(0); expect(JSON.parse(stdout)).toEqual({ - signatureVerified: true, + success: true, }); }); test('verifies a signature fails with invalid JWK', async () => { diff --git a/tests/nodes/claim.test.ts b/tests/nodes/claim.test.ts index 531f2f7e..b33a0c12 100644 --- a/tests/nodes/claim.test.ts +++ b/tests/nodes/claim.test.ts @@ -81,8 +81,8 @@ describe('claim', () => { }); }); test('sends a gestalt invite', async () => { - const { exitCode, stdout } = await testUtils.pkStdio( - ['nodes', 'claim', remoteIdEncoded], + const { exitCode, stdout, stderr } = await testUtils.pkStdio( + ['nodes', 'claim', remoteIdEncoded, '--format', 'json'], { env: { PK_NODE_PATH: nodePath, @@ -92,15 +92,15 @@ describe('claim', () => { }, ); expect(exitCode).toBe(0); - expect(stdout).toContain('Successfully generated a cryptolink claim'); - expect(stdout).toContain(remoteIdEncoded); + expect(JSON.parse(stdout)).toMatchObject({ success: true }); + expect(stderr).toContain(remoteIdEncoded); }); test('sends a gestalt invite (force invite)', async () => { await remoteNode.notificationsManager.sendNotification(localId, { type: 'GestaltInvite', }); - const { exitCode, stdout } = await testUtils.pkStdio( - ['nodes', 'claim', remoteIdEncoded, '--force-invite'], + const { exitCode, stdout, stderr } = await testUtils.pkStdio( + ['nodes', 'claim', remoteIdEncoded, '--force-invite', '--format', 'json'], { env: { PK_NODE_PATH: nodePath, @@ -110,15 +110,15 @@ describe('claim', () => { }, ); expect(exitCode).toBe(0); - expect(stdout).toContain('Successfully generated a cryptolink'); - expect(stdout).toContain(nodesUtils.encodeNodeId(remoteId)); + expect(JSON.parse(stdout)).toMatchObject({ success: true }); + expect(stderr).toContain(nodesUtils.encodeNodeId(remoteId)); }); test('claims a node', async () => { await remoteNode.notificationsManager.sendNotification(localId, { type: 'GestaltInvite', }); - const { exitCode, stdout } = await testUtils.pkStdio( - ['nodes', 'claim', remoteIdEncoded], + const { exitCode, stdout, stderr } = await testUtils.pkStdio( + ['nodes', 'claim', remoteIdEncoded, '--format', 'json'], { env: { PK_NODE_PATH: nodePath, @@ -128,7 +128,7 @@ describe('claim', () => { }, ); expect(exitCode).toBe(0); - expect(stdout).toContain('cryptolink claim'); - expect(stdout).toContain(remoteIdEncoded); + expect(JSON.parse(stdout)).toMatchObject({ success: true }); + expect(stderr).toContain(remoteIdEncoded); }); }); diff --git a/tests/nodes/find.test.ts b/tests/nodes/find.test.ts index 7b4540be..ec5fce5f 100644 --- a/tests/nodes/find.test.ts +++ b/tests/nodes/find.test.ts @@ -98,7 +98,7 @@ describe('find', () => { }); }); test('finds an online node', async () => { - const { exitCode, stdout } = await testUtils.pkStdio( + const { exitCode, stdout, stderr } = await testUtils.pkStdio( [ 'nodes', 'find', @@ -117,14 +117,11 @@ describe('find', () => { expect(exitCode).toBe(0); const output = JSON.parse(stdout); expect(output).toMatchObject({ - success: true, - id: nodesUtils.encodeNodeId(remoteOnlineNodeId), + nodeAddress: expect.any(Object), + nodeContactAddressData: expect.any(Object), }); - expect(output.address).toMatchObject({ - host: remoteOnlineHost, - port: remoteOnlinePort, - }); - expect(output.message).toMatch( + expect(output.nodeAddress).toEqual([remoteOnlineHost, remoteOnlinePort]); + expect(stderr).toMatch( new RegExp(`Found node at .*?${remoteOnlineHost}:${remoteOnlinePort}.*?`), ); }); @@ -135,7 +132,7 @@ describe('find', () => { const unknownNodeId = nodesUtils.decodeNodeId( 'vrcacp9vsb4ht25hds6s4lpp2abfaso0mptcfnh499n35vfcn2gkg', ); - const { exitCode, stdout } = await testUtils.pkStdio( + const { exitCode, stderr } = await testUtils.pkExec( [ 'nodes', 'find', @@ -152,13 +149,7 @@ describe('find', () => { }, ); expect(exitCode).toBe(sysexits.GENERAL); - expect(JSON.parse(stdout)).toEqual({ - success: false, - message: `Failed to find node ${nodesUtils.encodeNodeId( - unknownNodeId!, - )}`, - id: nodesUtils.encodeNodeId(unknownNodeId!), - }); + expect(JSON.parse(stderr).type).toBe('ErrorPolykeyCLINodeFindFailed'); }, globalThis.failedConnectionTimeout, ); diff --git a/tests/nodes/ping.test.ts b/tests/nodes/ping.test.ts index c9e8cb56..8f252933 100644 --- a/tests/nodes/ping.test.ts +++ b/tests/nodes/ping.test.ts @@ -106,7 +106,6 @@ describe('ping', () => { expect(stderr).toContain('No response received'); expect(JSON.parse(stdout)).toEqual({ success: false, - message: 'No response received', }); }, globalThis.failedConnectionTimeout, @@ -136,13 +135,12 @@ describe('ping', () => { expect(exitCode).not.toBe(0); // Should fail if node doesn't exist. expect(JSON.parse(stdout)).toEqual({ success: false, - message: `No response received`, }); }, globalThis.failedConnectionTimeout, ); test('succeed when pinging a live node', async () => { - const { exitCode, stdout } = await testUtils.pkStdio( + const { exitCode, stdout, stderr } = await testUtils.pkStdio( [ 'nodes', 'ping', @@ -161,7 +159,7 @@ describe('ping', () => { expect(exitCode).toBe(0); expect(JSON.parse(stdout)).toEqual({ success: true, - message: 'Node is Active.', }); + expect(stderr).toContain('Node is Active'); }); }); diff --git a/tests/notifications/sendReadClear.test.ts b/tests/notifications/sendReadClear.test.ts index 5bf85e8b..17ecc18f 100644 --- a/tests/notifications/sendReadClear.test.ts +++ b/tests/notifications/sendReadClear.test.ts @@ -63,7 +63,7 @@ describe('send/read/claim', () => { 'sends, receives, and clears notifications', async () => { let exitCode: number, stdout: string; - let readNotifications: Array; + let readNotificationMessages: Array<{ notification: Notification }>; // Add receiver to sender's node graph, so it can be contacted ({ exitCode } = await testUtils.pkExec( [ @@ -173,12 +173,9 @@ describe('send/read/claim', () => { }, )); expect(exitCode).toBe(0); - readNotifications = stdout - .split('\n') - .slice(undefined, -1) - .map((v) => JSON.parse(v)); - expect(readNotifications).toHaveLength(3); - expect(readNotifications[0]).toMatchObject({ + readNotificationMessages = JSON.parse(stdout); + expect(readNotificationMessages).toHaveLength(3); + expect(readNotificationMessages[0].notification).toMatchObject({ data: { type: 'General', message: 'test message 3', @@ -187,7 +184,7 @@ describe('send/read/claim', () => { sub: nodesUtils.encodeNodeId(receiverId), isRead: true, }); - expect(readNotifications[1]).toMatchObject({ + expect(readNotificationMessages[1].notification).toMatchObject({ data: { type: 'General', message: 'test message 2', @@ -196,7 +193,7 @@ describe('send/read/claim', () => { sub: nodesUtils.encodeNodeId(receiverId), isRead: true, }); - expect(readNotifications[2]).toMatchObject({ + expect(readNotificationMessages[2].notification).toMatchObject({ data: { type: 'General', message: 'test message 1', @@ -217,11 +214,8 @@ describe('send/read/claim', () => { }, )); expect(exitCode).toBe(0); - readNotifications = stdout - .split('\n') - .slice(undefined, -1) - .map((v) => JSON.parse(v)); - expect(readNotifications).toHaveLength(0); + readNotificationMessages = JSON.parse(stdout); + expect(readNotificationMessages).toHaveLength(0); // Read notifications on reverse order ({ exitCode, stdout } = await testUtils.pkExec( ['notifications', 'read', '--order=oldest', '--format', 'json'], @@ -234,12 +228,9 @@ describe('send/read/claim', () => { }, )); expect(exitCode).toBe(0); - readNotifications = stdout - .split('\n') - .slice(undefined, -1) - .map((v) => JSON.parse(v)); - expect(readNotifications).toHaveLength(3); - expect(readNotifications[0]).toMatchObject({ + readNotificationMessages = JSON.parse(stdout); + expect(readNotificationMessages).toHaveLength(3); + expect(readNotificationMessages[0].notification).toMatchObject({ data: { type: 'General', message: 'test message 1', @@ -248,7 +239,7 @@ describe('send/read/claim', () => { sub: nodesUtils.encodeNodeId(receiverId), isRead: true, }); - expect(readNotifications[1]).toMatchObject({ + expect(readNotificationMessages[1].notification).toMatchObject({ data: { type: 'General', message: 'test message 2', @@ -257,7 +248,7 @@ describe('send/read/claim', () => { sub: nodesUtils.encodeNodeId(receiverId), isRead: true, }); - expect(readNotifications[2]).toMatchObject({ + expect(readNotificationMessages[2].notification).toMatchObject({ data: { type: 'General', message: 'test message 3', @@ -278,12 +269,9 @@ describe('send/read/claim', () => { }, )); expect(exitCode).toBe(0); - readNotifications = stdout - .split('\n') - .slice(undefined, -1) - .map((v) => JSON.parse(v)); - expect(readNotifications).toHaveLength(1); - expect(readNotifications[0]).toMatchObject({ + readNotificationMessages = JSON.parse(stdout); + expect(readNotificationMessages).toHaveLength(1); + expect(readNotificationMessages[0].notification).toMatchObject({ data: { type: 'General', message: 'test message 3', @@ -312,11 +300,8 @@ describe('send/read/claim', () => { }, )); expect(exitCode).toBe(0); - readNotifications = stdout - .split('\n') - .slice(undefined, -1) - .map((v) => JSON.parse(v)); - expect(readNotifications).toHaveLength(0); + readNotificationMessages = JSON.parse(stdout); + expect(readNotificationMessages).toHaveLength(0); }, globalThis.defaultTimeout * 3, ); diff --git a/tests/secrets/stat.test.ts b/tests/secrets/stat.test.ts index 95cbe51c..a6955336 100644 --- a/tests/secrets/stat.test.ts +++ b/tests/secrets/stat.test.ts @@ -59,16 +59,28 @@ describe('commandStat', () => { await vaultOps.addSecret(vault, 'MySecret', 'this is the secret'); }); - command = ['secrets', 'stat', '-np', dataDir, `${vaultName}:MySecret`]; + command = [ + 'secrets', + 'stat', + '-np', + dataDir, + `${vaultName}:MySecret`, + '--format', + 'json', + ]; const result = await testUtils.pkStdio([...command], { env: {}, cwd: dataDir, }); expect(result.exitCode).toBe(0); - expect(result.stdout).toContain('nlink: 1'); - expect(result.stdout).toContain('blocks: 1'); - expect(result.stdout).toContain('blksize: 4096'); - expect(result.stdout).toContain('size: 18'); + expect(JSON.parse(result.stdout)).toMatchObject({ + stat: { + nlink: 1, + blocks: 1, + blksize: 4096, + size: 18, + }, + }); }); }); diff --git a/tests/utils.test.ts b/tests/utils.test.ts index 8ddd69af..a6c1e857 100644 --- a/tests/utils.test.ts +++ b/tests/utils.test.ts @@ -119,6 +119,51 @@ describe('bin/utils', () => { }), ).toBe('{"key1":"value1","key2":"value2"}\n'); }); + test('dict nesting in human format', () => { + // Dict + expect( + binUtils.outputFormatter({ + type: 'dict', + data: { key1: {}, key2: {} }, + }), + ).toBe('key1\t\nkey2\t\n'); + expect( + binUtils.outputFormatter({ + type: 'dict', + data: { key1: ['value1', 'value2', 'value3'] }, + }), + ).toBe('key1\t\n value1\t\n value2\t\n value3\t\n'); + expect( + binUtils.outputFormatter({ + type: 'dict', + data: { + key1: { + key2: null, + key3: undefined, + key4: 'value', + }, + key5: 'value', + key6: { + key7: { + key8: { + key9: 'value', + }, + }, + }, + }, + }), + ).toBe( + 'key1\t\n' + + ' key2\t\n' + + ' key3\t\n' + + ' key4\tvalue\n' + + 'key5\tvalue\n' + + 'key6\t\n' + + ' key7\t\n' + + ' key8\t\n' + + ' key9\tvalue\n', + ); + }); test('outputFormatter should encode non-printable characters within a dict', () => { fc.assert( fc.property(