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

Commit 733e028

Browse files
mosmartinishantiw
andauthored
Make --output flag support all types of paths (#9169)
* Make --output flag support all types of paths * Update tests * Cleanup the comments * Update unit tests --------- Co-authored-by: !shan <[email protected]>
1 parent f641009 commit 733e028

File tree

16 files changed

+504
-152
lines changed

16 files changed

+504
-152
lines changed

Diff for: commander/src/bootstrapping/commands/config/create.ts

+6-9
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import { join, resolve } from 'path';
2020
import * as inquirer from 'inquirer';
2121
import { isHexString } from '@liskhq/lisk-validator';
2222
import { defaultConfig } from '../../../utils/config';
23-
import { OWNER_READ_WRITE } from '../../../constants';
23+
import { handleOutputFlag } from '../../../utils/output';
2424

2525
export class CreateCommand extends Command {
2626
static description = 'Creates network configuration file.';
@@ -81,17 +81,14 @@ export class CreateCommand extends Command {
8181
if (!userResponse.confirm) {
8282
this.error('Operation cancelled, config file already present at the desired location');
8383
} else {
84-
fs.writeJSONSync(resolve(configPath, 'config.json'), defaultConfig, {
85-
spaces: '\t',
86-
mode: OWNER_READ_WRITE,
87-
});
84+
const res = await handleOutputFlag(configPath, defaultConfig, 'config');
85+
this.log(res);
8886
}
8987
} else {
9088
fs.mkdirSync(configPath, { recursive: true });
91-
fs.writeJSONSync(resolve(configPath, 'config.json'), defaultConfig, {
92-
spaces: '\t',
93-
mode: OWNER_READ_WRITE,
94-
});
89+
90+
const res = await handleOutputFlag(configPath, defaultConfig, 'config');
91+
this.log(res);
9592
}
9693
}
9794
}

Diff for: commander/src/bootstrapping/commands/generator/export.ts

+3-9
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,10 @@
1414
*/
1515

1616
import { encrypt } from '@liskhq/lisk-cryptography';
17-
import * as fs from 'fs-extra';
1817
import * as path from 'path';
1918
import { flagsWithParser } from '../../../utils/flags';
2019
import { BaseIPCClientCommand } from '../base_ipc_client';
21-
import { OWNER_READ_WRITE } from '../../../constants';
20+
import { handleOutputFlag } from '../../../utils/output';
2221

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

89-
if (flags.output) {
90-
const { dir } = path.parse(flags.output);
91-
fs.ensureDirSync(dir);
92-
}
93-
9488
const allKeys = await this._client.invoke<GetKeysResponse>('generator_getAllKeys');
9589
const statusResponse = await this._client.invoke<GetStatusResponse>('generator_getStatus');
9690

@@ -120,7 +114,7 @@ export abstract class ExportCommand extends BaseIPCClientCommand {
120114
};
121115

122116
const filePath = flags.output ? flags.output : path.join(process.cwd(), 'generator_info.json');
123-
fs.writeJSONSync(filePath, output, { spaces: ' ', mode: OWNER_READ_WRITE });
124-
this.log(`Generator info is exported to ${filePath}`);
117+
const res = await handleOutputFlag(filePath, output, 'generator_info');
118+
this.log(res);
125119
}
126120
}

Diff for: commander/src/bootstrapping/commands/hash-onion.ts

+3-13
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,10 @@
1414
*/
1515

1616
import { Command, Flags as flagParser } from '@oclif/core';
17-
import * as fs from 'fs-extra';
18-
import * as path from 'path';
1917
import * as cryptography from '@liskhq/lisk-cryptography';
2018
import * as validator from '@liskhq/lisk-validator';
2119
import { flagsWithParser } from '../../utils/flags';
22-
import { OWNER_READ_WRITE } from '../../constants';
20+
import { handleOutputFlag } from '../../utils/output';
2321

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

63-
if (output) {
64-
const { dir } = path.parse(output);
65-
fs.ensureDirSync(dir);
66-
}
67-
6861
const seed = cryptography.utils.generateHashOnionSeed();
6962

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

7568
if (output) {
76-
if (pretty) {
77-
fs.writeJSONSync(output, result, { spaces: ' ', mode: OWNER_READ_WRITE });
78-
} else {
79-
fs.writeJSONSync(output, result, { mode: OWNER_READ_WRITE });
80-
}
69+
const res = await handleOutputFlag(output, result, 'hash-onion');
70+
this.log(res);
8171
} else {
8272
this.printJSON(result, pretty);
8373
}

Diff for: commander/src/bootstrapping/commands/keys/create.ts

+8-9
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,10 @@
1616
import { codec } from '@liskhq/lisk-codec';
1717
import { bls, address as addressUtil, ed, encrypt, legacy } from '@liskhq/lisk-cryptography';
1818
import { Command, Flags as flagParser } from '@oclif/core';
19-
import * as fs from 'fs-extra';
20-
import * as path from 'path';
2119
import { flagsWithParser } from '../../../utils/flags';
2220
import { getPassphraseFromPrompt, getPasswordFromPrompt } from '../../../utils/reader';
23-
import { OWNER_READ_WRITE, plainGeneratorKeysSchema } from '../../../constants';
21+
import { plainGeneratorKeysSchema } from '../../../constants';
22+
import { handleOutputFlag } from '../../../utils/output';
2423

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

3938
static flags = {
40-
output: flagsWithParser.output,
39+
output: flagParser.string({
40+
char: 'o',
41+
description: 'The output directory. Default will set to current working directory.',
42+
}),
4143
passphrase: flagsWithParser.passphrase,
4244
'no-encrypt': flagParser.boolean({
4345
char: 'n',
@@ -79,10 +81,6 @@ export class CreateCommand extends Command {
7981
},
8082
} = await this.parse(CreateCommand);
8183

82-
if (output) {
83-
const { dir } = path.parse(output);
84-
fs.ensureDirSync(dir);
85-
}
8684
const passphrase = passphraseSource ?? (await getPassphraseFromPrompt('passphrase', true));
8785
let password = '';
8886
if (!noEncrypt) {
@@ -156,7 +154,8 @@ export class CreateCommand extends Command {
156154
}
157155

158156
if (output) {
159-
fs.writeJSONSync(output, { keys }, { spaces: ' ', mode: OWNER_READ_WRITE });
157+
const res = await handleOutputFlag(output, { keys }, 'keys');
158+
this.log(res);
160159
} else {
161160
this.log(JSON.stringify({ keys }, undefined, ' '));
162161
}

Diff for: commander/src/bootstrapping/commands/keys/export.ts

+7-9
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,9 @@
1313
*
1414
*/
1515
import { encrypt } from '@liskhq/lisk-cryptography';
16-
import * as fs from 'fs-extra';
17-
import * as path from 'path';
1816
import { flagsWithParser } from '../../../utils/flags';
1917
import { BaseIPCClientCommand } from '../base_ipc_client';
20-
import { OWNER_READ_WRITE } from '../../../constants';
18+
import { handleOutputFlag } from '../../../utils/output';
2119

2220
interface EncryptedMessageObject {
2321
readonly version: string;
@@ -66,22 +64,19 @@ export abstract class ExportCommand extends BaseIPCClientCommand {
6664
...BaseIPCClientCommand.flags,
6765
output: {
6866
...flagsWithParser.output,
69-
required: true,
7067
},
7168
};
7269

7370
async run(): Promise<void> {
7471
const { flags } = await this.parse(ExportCommand);
72+
7573
if (!this._client) {
7674
this.error('APIClient is not initialized.');
7775
}
7876

79-
const { dir } = path.parse(flags.output as string);
80-
fs.ensureDirSync(dir);
81-
8277
const response = await this._client.invoke<GetKeysResponse>('generator_getAllKeys');
8378

84-
const keys = response.keys.map(k => {
79+
const keys = response?.keys.map(k => {
8580
if (k.type === 'encrypted') {
8681
return {
8782
address: k.address,
@@ -94,6 +89,9 @@ export abstract class ExportCommand extends BaseIPCClientCommand {
9489
};
9590
});
9691

97-
fs.writeJSONSync(flags.output as string, { keys }, { spaces: ' ', mode: OWNER_READ_WRITE });
92+
if (flags.output) {
93+
const res = await handleOutputFlag(flags.output, { keys }, 'keys');
94+
this.log(res);
95+
}
9896
}
9997
}

Diff for: commander/src/bootstrapping/commands/passphrase/create.ts

+8-12
Original file line numberDiff line numberDiff line change
@@ -14,33 +14,29 @@
1414
*/
1515

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

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

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

35-
if (output) {
36-
const { dir } = path.parse(output);
37-
fs.ensureDirSync(dir);
38-
}
39-
4035
const passphrase = Mnemonic.generateMnemonic(256);
4136

4237
if (output) {
43-
fs.writeJSONSync(output, { passphrase }, { spaces: ' ', mode: OWNER_READ_WRITE });
38+
const res = await handleOutputFlag(output, { passphrase }, 'passphrase');
39+
this.log(res);
4440
} else {
4541
this.log(JSON.stringify({ passphrase }, undefined, ' '));
4642
}

Diff for: commander/src/bootstrapping/commands/passphrase/encrypt.ts

+7-10
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,10 @@
1414
*/
1515

1616
import { Command, Flags as flagParser } from '@oclif/core';
17-
import * as fs from 'fs-extra';
18-
import * as path from 'path';
1917
import { encryptPassphrase } from '../../../utils/commons';
2018
import { flagsWithParser } from '../../../utils/flags';
2119
import { getPassphraseFromPrompt, getPasswordFromPrompt } from '../../../utils/reader';
22-
import { OWNER_READ_WRITE } from '../../../constants';
20+
import { handleOutputFlag } from '../../../utils/output';
2321

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

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

57-
if (output) {
58-
const { dir } = path.parse(output);
59-
fs.ensureDirSync(dir);
60-
}
61-
6258
const passphrase = passphraseSource ?? (await getPassphraseFromPrompt('passphrase', true));
6359
const password = passwordSource ?? (await getPasswordFromPrompt('password', true));
6460
const result = await encryptPassphrase(passphrase, password, outputPublicKey);
6561

6662
if (output) {
67-
fs.writeJSONSync(output, result, { spaces: ' ', mode: OWNER_READ_WRITE });
63+
const res = await handleOutputFlag(output, result, 'passphrase');
64+
this.log(res);
6865
} else {
6966
this.log(JSON.stringify(result, undefined, ' '));
7067
}

Diff for: commander/src/utils/flags.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,10 @@ export const flagsWithParser = {
155155
}),
156156
pretty: flagParser.boolean(flags.pretty),
157157
passphrase: flagParser.string(flags.passphrase),
158-
output: flagParser.string(flags.output),
158+
output: flagParser.string({
159+
...flags.output,
160+
default: process.cwd(),
161+
}),
159162
password: flagParser.string(flags.password),
160163
offline: flagParser.boolean({
161164
...flags.offline,

Diff for: commander/src/utils/output.ts

+91
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/*
2+
* LiskHQ/lisk-commander
3+
* Copyright © 2023 Lisk Foundation
4+
*
5+
* See the LICENSE file at the top-level directory of this distribution
6+
* for licensing information.
7+
*
8+
* Unless otherwise agreed in a custom licensing agreement with the Lisk Foundation,
9+
* no part of this software, including this file, may be copied, modified,
10+
* propagated, or distributed except according to the terms contained in the
11+
* LICENSE file.
12+
*
13+
* Removal or modification of this copyright notice is prohibited.
14+
*
15+
*/
16+
17+
import * as fs from 'fs-extra';
18+
import * as path from 'path';
19+
import { homedir } from 'os';
20+
import { OWNER_READ_WRITE } from '../constants';
21+
22+
interface OutputOptions {
23+
outputPath?: string;
24+
filename?: string;
25+
}
26+
27+
async function getDefaultFilename(namespace: string): Promise<string> {
28+
return `${namespace}.json`;
29+
}
30+
31+
function resolvePath(filePath: string): string {
32+
if (filePath.startsWith('~')) {
33+
return path.join(homedir(), filePath.slice(1));
34+
}
35+
36+
return path.resolve(filePath);
37+
}
38+
39+
async function handleOutput(options: OutputOptions, namespace: string): Promise<string> {
40+
const outputPath = options.outputPath ?? process.cwd();
41+
const filename = options.filename ?? (await getDefaultFilename(namespace));
42+
43+
const resolvedPath = resolvePath(outputPath);
44+
const outputPathWithFilename = path.join(resolvedPath, filename);
45+
46+
await fs.mkdir(resolvedPath, { recursive: true });
47+
48+
return outputPathWithFilename;
49+
}
50+
51+
export async function handleOutputFlag(
52+
outputPath: string,
53+
data: object,
54+
namespace: string,
55+
filename?: string,
56+
): Promise<string> {
57+
// if output path has an extension, then it is a file and write to current directory
58+
if (path.extname(outputPath)) {
59+
const resolvedPath = resolvePath(outputPath);
60+
const resolvedPathWithFilename = path.join(resolvedPath, filename ?? '');
61+
62+
try {
63+
fs.writeJSONSync(resolvedPathWithFilename, data, {
64+
spaces: ' ',
65+
mode: OWNER_READ_WRITE,
66+
});
67+
68+
return `Successfully written data to ${resolvedPathWithFilename}`;
69+
} catch (error) {
70+
throw new Error(`Error writing data to ${resolvedPathWithFilename}: ${error as string}`);
71+
}
72+
}
73+
74+
const options: OutputOptions = {
75+
outputPath,
76+
filename,
77+
};
78+
79+
const outputFilePath = await handleOutput(options, namespace);
80+
81+
try {
82+
fs.writeJSONSync(outputFilePath, data, {
83+
spaces: ' ',
84+
mode: OWNER_READ_WRITE,
85+
});
86+
87+
return `Successfully written data to ${outputFilePath}`;
88+
} catch (error) {
89+
throw new Error(`Error writing data to ${outputFilePath}: ${error as string}`);
90+
}
91+
}

0 commit comments

Comments
 (0)