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

Make --output flag support all types of paths #9169

Merged
merged 5 commits into from
Dec 5, 2023
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
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