Skip to content
This repository has been archived by the owner on Jun 11, 2024. It is now read-only.

Commit

Permalink
Make --output flag support all types of paths (#9169)
Browse files Browse the repository at this point in the history
* Make --output flag support all types of paths

* Update tests

* Cleanup the comments

* Update unit tests

---------

Co-authored-by: !shan <ishantiw.quasar@gmail.com>
  • Loading branch information
mosmartin and ishantiw authored Dec 5, 2023
1 parent f641009 commit 733e028
Show file tree
Hide file tree
Showing 16 changed files with 504 additions and 152 deletions.
15 changes: 6 additions & 9 deletions commander/src/bootstrapping/commands/config/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { join, resolve } from 'path';
import * as inquirer from 'inquirer';
import { isHexString } from '@liskhq/lisk-validator';
import { defaultConfig } from '../../../utils/config';
import { OWNER_READ_WRITE } from '../../../constants';
import { handleOutputFlag } from '../../../utils/output';

export class CreateCommand extends Command {
static description = 'Creates network configuration file.';
Expand Down Expand Up @@ -81,17 +81,14 @@ export class CreateCommand extends Command {
if (!userResponse.confirm) {
this.error('Operation cancelled, config file already present at the desired location');
} else {
fs.writeJSONSync(resolve(configPath, 'config.json'), defaultConfig, {
spaces: '\t',
mode: OWNER_READ_WRITE,
});
const res = await handleOutputFlag(configPath, defaultConfig, 'config');
this.log(res);
}
} else {
fs.mkdirSync(configPath, { recursive: true });
fs.writeJSONSync(resolve(configPath, 'config.json'), defaultConfig, {
spaces: '\t',
mode: OWNER_READ_WRITE,
});

const res = await handleOutputFlag(configPath, defaultConfig, 'config');
this.log(res);
}
}
}
12 changes: 3 additions & 9 deletions commander/src/bootstrapping/commands/generator/export.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,10 @@
*/

import { encrypt } from '@liskhq/lisk-cryptography';
import * as fs from 'fs-extra';
import * as path from 'path';
import { flagsWithParser } from '../../../utils/flags';
import { BaseIPCClientCommand } from '../base_ipc_client';
import { OWNER_READ_WRITE } from '../../../constants';
import { handleOutputFlag } from '../../../utils/output';

interface EncryptedMessageObject {
readonly version: string;
Expand Down Expand Up @@ -86,11 +85,6 @@ export abstract class ExportCommand extends BaseIPCClientCommand {
this.error('APIClient is not initialized.');
}

if (flags.output) {
const { dir } = path.parse(flags.output);
fs.ensureDirSync(dir);
}

const allKeys = await this._client.invoke<GetKeysResponse>('generator_getAllKeys');
const statusResponse = await this._client.invoke<GetStatusResponse>('generator_getStatus');

Expand Down Expand Up @@ -120,7 +114,7 @@ export abstract class ExportCommand extends BaseIPCClientCommand {
};

const filePath = flags.output ? flags.output : path.join(process.cwd(), 'generator_info.json');
fs.writeJSONSync(filePath, output, { spaces: ' ', mode: OWNER_READ_WRITE });
this.log(`Generator info is exported to ${filePath}`);
const res = await handleOutputFlag(filePath, output, 'generator_info');
this.log(res);
}
}
16 changes: 3 additions & 13 deletions commander/src/bootstrapping/commands/hash-onion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,10 @@
*/

import { Command, Flags as flagParser } from '@oclif/core';
import * as fs from 'fs-extra';
import * as path from 'path';
import * as cryptography from '@liskhq/lisk-cryptography';
import * as validator from '@liskhq/lisk-validator';
import { flagsWithParser } from '../../utils/flags';
import { OWNER_READ_WRITE } from '../../constants';
import { handleOutputFlag } from '../../utils/output';

export class HashOnionCommand extends Command {
static description = 'Create hash onions to be used by the forger.';
Expand Down Expand Up @@ -60,11 +58,6 @@ export class HashOnionCommand extends Command {
throw new Error('Count flag must be an integer and greater than 0.');
}

if (output) {
const { dir } = path.parse(output);
fs.ensureDirSync(dir);
}

const seed = cryptography.utils.generateHashOnionSeed();

const hashBuffers = cryptography.utils.hashOnion(seed, count, distance);
Expand All @@ -73,11 +66,8 @@ export class HashOnionCommand extends Command {
const result = { count, distance, hashes };

if (output) {
if (pretty) {
fs.writeJSONSync(output, result, { spaces: ' ', mode: OWNER_READ_WRITE });
} else {
fs.writeJSONSync(output, result, { mode: OWNER_READ_WRITE });
}
const res = await handleOutputFlag(output, result, 'hash-onion');
this.log(res);
} else {
this.printJSON(result, pretty);
}
Expand Down
17 changes: 8 additions & 9 deletions commander/src/bootstrapping/commands/keys/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,10 @@
import { codec } from '@liskhq/lisk-codec';
import { bls, address as addressUtil, ed, encrypt, legacy } from '@liskhq/lisk-cryptography';
import { Command, Flags as flagParser } from '@oclif/core';
import * as fs from 'fs-extra';
import * as path from 'path';
import { flagsWithParser } from '../../../utils/flags';
import { getPassphraseFromPrompt, getPasswordFromPrompt } from '../../../utils/reader';
import { OWNER_READ_WRITE, plainGeneratorKeysSchema } from '../../../constants';
import { plainGeneratorKeysSchema } from '../../../constants';
import { handleOutputFlag } from '../../../utils/output';

export class CreateCommand extends Command {
static description = 'Return keys corresponding to the given passphrase.';
Expand All @@ -37,7 +36,10 @@ export class CreateCommand extends Command {
];

static flags = {
output: flagsWithParser.output,
output: flagParser.string({
char: 'o',
description: 'The output directory. Default will set to current working directory.',
}),
passphrase: flagsWithParser.passphrase,
'no-encrypt': flagParser.boolean({
char: 'n',
Expand Down Expand Up @@ -79,10 +81,6 @@ export class CreateCommand extends Command {
},
} = await this.parse(CreateCommand);

if (output) {
const { dir } = path.parse(output);
fs.ensureDirSync(dir);
}
const passphrase = passphraseSource ?? (await getPassphraseFromPrompt('passphrase', true));
let password = '';
if (!noEncrypt) {
Expand Down Expand Up @@ -156,7 +154,8 @@ export class CreateCommand extends Command {
}

if (output) {
fs.writeJSONSync(output, { keys }, { spaces: ' ', mode: OWNER_READ_WRITE });
const res = await handleOutputFlag(output, { keys }, 'keys');
this.log(res);
} else {
this.log(JSON.stringify({ keys }, undefined, ' '));
}
Expand Down
16 changes: 7 additions & 9 deletions commander/src/bootstrapping/commands/keys/export.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,9 @@
*
*/
import { encrypt } from '@liskhq/lisk-cryptography';
import * as fs from 'fs-extra';
import * as path from 'path';
import { flagsWithParser } from '../../../utils/flags';
import { BaseIPCClientCommand } from '../base_ipc_client';
import { OWNER_READ_WRITE } from '../../../constants';
import { handleOutputFlag } from '../../../utils/output';

interface EncryptedMessageObject {
readonly version: string;
Expand Down Expand Up @@ -66,22 +64,19 @@ export abstract class ExportCommand extends BaseIPCClientCommand {
...BaseIPCClientCommand.flags,
output: {
...flagsWithParser.output,
required: true,
},
};

async run(): Promise<void> {
const { flags } = await this.parse(ExportCommand);

if (!this._client) {
this.error('APIClient is not initialized.');
}

const { dir } = path.parse(flags.output as string);
fs.ensureDirSync(dir);

const response = await this._client.invoke<GetKeysResponse>('generator_getAllKeys');

const keys = response.keys.map(k => {
const keys = response?.keys.map(k => {
if (k.type === 'encrypted') {
return {
address: k.address,
Expand All @@ -94,6 +89,9 @@ export abstract class ExportCommand extends BaseIPCClientCommand {
};
});

fs.writeJSONSync(flags.output as string, { keys }, { spaces: ' ', mode: OWNER_READ_WRITE });
if (flags.output) {
const res = await handleOutputFlag(flags.output, { keys }, 'keys');
this.log(res);
}
}
}
20 changes: 8 additions & 12 deletions commander/src/bootstrapping/commands/passphrase/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,33 +14,29 @@
*/

import { Mnemonic } from '@liskhq/lisk-passphrase';
import { Command } from '@oclif/core';
import * as fs from 'fs-extra';
import * as path from 'path';
import { flagsWithParser } from '../../../utils/flags';
import { OWNER_READ_WRITE } from '../../../constants';
import { Command, Flags as flagParser } from '@oclif/core';
import { handleOutputFlag } from '../../../utils/output';

export class CreateCommand extends Command {
static description = 'Returns a randomly generated 24 words mnemonic passphrase.';
static examples = ['passphrase:create', 'passphrase:create --output /mypath/passphrase.json'];
static flags = {
output: flagsWithParser.output,
output: flagParser.string({
char: 'o',
description: 'The output directory. Default will set to current working directory.',
}),
};

async run(): Promise<void> {
const {
flags: { output },
} = await this.parse(CreateCommand);

if (output) {
const { dir } = path.parse(output);
fs.ensureDirSync(dir);
}

const passphrase = Mnemonic.generateMnemonic(256);

if (output) {
fs.writeJSONSync(output, { passphrase }, { spaces: ' ', mode: OWNER_READ_WRITE });
const res = await handleOutputFlag(output, { passphrase }, 'passphrase');
this.log(res);
} else {
this.log(JSON.stringify({ passphrase }, undefined, ' '));
}
Expand Down
17 changes: 7 additions & 10 deletions commander/src/bootstrapping/commands/passphrase/encrypt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,10 @@
*/

import { Command, Flags as flagParser } from '@oclif/core';
import * as fs from 'fs-extra';
import * as path from 'path';
import { encryptPassphrase } from '../../../utils/commons';
import { flagsWithParser } from '../../../utils/flags';
import { getPassphraseFromPrompt, getPasswordFromPrompt } from '../../../utils/reader';
import { OWNER_READ_WRITE } from '../../../constants';
import { handleOutputFlag } from '../../../utils/output';

const outputPublicKeyOptionDescription =
'Includes the public key in the output. This option is provided for the convenience of node operators.';
Expand All @@ -41,7 +39,10 @@ export class EncryptCommand extends Command {
'output-public-key': flagParser.boolean({
description: outputPublicKeyOptionDescription,
}),
output: flagsWithParser.output,
output: flagParser.string({
char: 'o',
description: 'The output directory. Default will set to current working directory.',
}),
};

async run(): Promise<void> {
Expand All @@ -54,17 +55,13 @@ export class EncryptCommand extends Command {
},
} = await this.parse(EncryptCommand);

if (output) {
const { dir } = path.parse(output);
fs.ensureDirSync(dir);
}

const passphrase = passphraseSource ?? (await getPassphraseFromPrompt('passphrase', true));
const password = passwordSource ?? (await getPasswordFromPrompt('password', true));
const result = await encryptPassphrase(passphrase, password, outputPublicKey);

if (output) {
fs.writeJSONSync(output, result, { spaces: ' ', mode: OWNER_READ_WRITE });
const res = await handleOutputFlag(output, result, 'passphrase');
this.log(res);
} else {
this.log(JSON.stringify(result, undefined, ' '));
}
Expand Down
5 changes: 4 additions & 1 deletion commander/src/utils/flags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,10 @@ export const flagsWithParser = {
}),
pretty: flagParser.boolean(flags.pretty),
passphrase: flagParser.string(flags.passphrase),
output: flagParser.string(flags.output),
output: flagParser.string({
...flags.output,
default: process.cwd(),
}),
password: flagParser.string(flags.password),
offline: flagParser.boolean({
...flags.offline,
Expand Down
91 changes: 91 additions & 0 deletions commander/src/utils/output.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* LiskHQ/lisk-commander
* Copyright © 2023 Lisk Foundation
*
* See the LICENSE file at the top-level directory of this distribution
* for licensing information.
*
* Unless otherwise agreed in a custom licensing agreement with the Lisk Foundation,
* no part of this software, including this file, may be copied, modified,
* propagated, or distributed except according to the terms contained in the
* LICENSE file.
*
* Removal or modification of this copyright notice is prohibited.
*
*/

import * as fs from 'fs-extra';
import * as path from 'path';
import { homedir } from 'os';
import { OWNER_READ_WRITE } from '../constants';

interface OutputOptions {
outputPath?: string;
filename?: string;
}

async function getDefaultFilename(namespace: string): Promise<string> {
return `${namespace}.json`;
}

function resolvePath(filePath: string): string {
if (filePath.startsWith('~')) {
return path.join(homedir(), filePath.slice(1));
}

return path.resolve(filePath);
}

async function handleOutput(options: OutputOptions, namespace: string): Promise<string> {
const outputPath = options.outputPath ?? process.cwd();
const filename = options.filename ?? (await getDefaultFilename(namespace));

const resolvedPath = resolvePath(outputPath);
const outputPathWithFilename = path.join(resolvedPath, filename);

await fs.mkdir(resolvedPath, { recursive: true });

return outputPathWithFilename;
}

export async function handleOutputFlag(
outputPath: string,
data: object,
namespace: string,
filename?: string,
): Promise<string> {
// if output path has an extension, then it is a file and write to current directory
if (path.extname(outputPath)) {
const resolvedPath = resolvePath(outputPath);
const resolvedPathWithFilename = path.join(resolvedPath, filename ?? '');

try {
fs.writeJSONSync(resolvedPathWithFilename, data, {
spaces: ' ',
mode: OWNER_READ_WRITE,
});

return `Successfully written data to ${resolvedPathWithFilename}`;
} catch (error) {
throw new Error(`Error writing data to ${resolvedPathWithFilename}: ${error as string}`);
}
}

const options: OutputOptions = {
outputPath,
filename,
};

const outputFilePath = await handleOutput(options, namespace);

try {
fs.writeJSONSync(outputFilePath, data, {
spaces: ' ',
mode: OWNER_READ_WRITE,
});

return `Successfully written data to ${outputFilePath}`;
} catch (error) {
throw new Error(`Error writing data to ${outputFilePath}: ${error as string}`);
}
}
Loading

0 comments on commit 733e028

Please sign in to comment.