genlayer 0.5.1-beta.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genlayer",
3
- "version": "0.5.1-beta.0",
3
+ "version": "0.6.0",
4
4
  "description": "GenLayer Command Line Tool",
5
5
  "main": "src/index.ts",
6
6
  "bin": {
@@ -59,6 +59,7 @@
59
59
  "commander": "^12.0.0",
60
60
  "dockerode": "^4.0.2",
61
61
  "dotenv": "^16.4.5",
62
+ "ethers": "^6.13.4",
62
63
  "inquirer": "^9.2.19",
63
64
  "node-fetch": "^2.7.0",
64
65
  "open": "^10.1.0",
@@ -1,9 +1,8 @@
1
- import {Command} from "commander";
1
+ import { Command } from "commander";
2
2
 
3
3
  import simulatorService from "../../lib/services/simulator";
4
-
5
- import {initAction, InitActionOptions} from "./init";
6
- import {startAction, StartActionOptions} from "./start";
4
+ import { initAction, InitActionOptions } from "./init";
5
+ import { startAction, StartActionOptions } from "./start";
7
6
 
8
7
  export function initializeGeneralCommands(program: Command) {
9
8
  program
@@ -0,0 +1,44 @@
1
+ import { writeFileSync, existsSync } from "fs";
2
+ import { ethers } from "ethers";
3
+ import { ConfigFileManager } from "../../lib/config/ConfigFileManager";
4
+
5
+ export interface CreateKeypairOptions {
6
+ output: string;
7
+ overwrite: boolean;
8
+ }
9
+
10
+ export class KeypairCreator {
11
+ private filePathManager: ConfigFileManager;
12
+
13
+ constructor() {
14
+ this.filePathManager = new ConfigFileManager();
15
+ }
16
+
17
+ createKeypairAction(options: CreateKeypairOptions) {
18
+ try {
19
+
20
+ const outputPath = this.filePathManager.getFilePath(options.output);
21
+
22
+ if(existsSync(outputPath) && !options.overwrite) {
23
+ console.warn(
24
+ `The file at ${outputPath} already exists. Use the '--overwrite' option to replace it.`
25
+ );
26
+ return;
27
+ }
28
+
29
+ const wallet = ethers.Wallet.createRandom();
30
+ const keypairData = {
31
+ address: wallet.address,
32
+ privateKey: wallet.privateKey,
33
+ };
34
+
35
+ writeFileSync(outputPath, JSON.stringify(keypairData, null, 2));
36
+
37
+ this.filePathManager.writeConfig('keyPairPath', outputPath);
38
+ console.log(`Keypair successfully created and saved to: ${outputPath}`);
39
+ } catch (error) {
40
+ console.error("Failed to generate keypair:", error);
41
+ process.exit(1);
42
+ }
43
+ }
44
+ }
@@ -0,0 +1,21 @@
1
+ import { Command } from "commander";
2
+ import { CreateKeypairOptions, KeypairCreator } from "./create";
3
+
4
+ export function initializeKeygenCommands(program: Command) {
5
+
6
+ const keygenCommand = program
7
+ .command("keygen")
8
+ .description("Manage keypair generation");
9
+
10
+ keygenCommand
11
+ .command("create")
12
+ .description("Generates a new keypair and saves it to a file")
13
+ .option("--output <path>", "Path to save the keypair", "./keypair.json")
14
+ .option("--overwrite", "Overwrite the existing file if it already exists", false)
15
+ .action((options: CreateKeypairOptions) => {
16
+ const keypairCreator = new KeypairCreator();
17
+ keypairCreator.createKeypairAction(options);
18
+ });
19
+
20
+ return program;
21
+ }
package/src/index.ts CHANGED
@@ -2,11 +2,13 @@
2
2
  import {program} from "commander";
3
3
  import {version} from "../package.json";
4
4
  import {CLI_DESCRIPTION} from "../src/lib/config/text";
5
- import {initializeGeneralCommands} from "../src/commands/general";
5
+ import { initializeGeneralCommands } from "../src/commands/general";
6
+ import { initializeKeygenCommands } from "../src/commands/keygen";
6
7
 
7
8
  export function initializeCLI() {
8
9
  program.version(version).description(CLI_DESCRIPTION);
9
10
  initializeGeneralCommands(program);
11
+ initializeKeygenCommands(program);
10
12
  program.parse(process.argv);
11
13
  }
12
14
 
@@ -0,0 +1,51 @@
1
+ import path from "path";
2
+ import os from "os";
3
+ import fs from "fs";
4
+
5
+ export class ConfigFileManager {
6
+ private folderPath: string;
7
+ private configFilePath: string;
8
+
9
+ constructor(baseFolder: string = ".genlayer/", configFileName: string = "genlayer-config.json") {
10
+ this.folderPath = path.resolve(os.homedir(), baseFolder);
11
+ this.configFilePath = path.resolve(this.folderPath, configFileName);
12
+ this.ensureFolderExists();
13
+ this.ensureConfigFileExists();
14
+ }
15
+
16
+ private ensureFolderExists(): void {
17
+ if (!fs.existsSync(this.folderPath)) {
18
+ fs.mkdirSync(this.folderPath, { recursive: true });
19
+ }
20
+ }
21
+
22
+ private ensureConfigFileExists(): void {
23
+ if (!fs.existsSync(this.configFilePath)) {
24
+ fs.writeFileSync(this.configFilePath, JSON.stringify({}, null, 2));
25
+ }
26
+ }
27
+
28
+ getFolderPath(): string {
29
+ return this.folderPath;
30
+ }
31
+
32
+ getFilePath(fileName: string): string {
33
+ return path.resolve(this.folderPath, fileName);
34
+ }
35
+
36
+ getConfig(): Record<string, any> {
37
+ const configContent = fs.readFileSync(this.configFilePath, "utf-8");
38
+ return JSON.parse(configContent);
39
+ }
40
+
41
+ getConfigByKey(key: string): any {
42
+ const config = this.getConfig();
43
+ return config[key] !== undefined ? config[key] : null;
44
+ }
45
+
46
+ writeConfig(key: string, value: any): void {
47
+ const config = this.getConfig();
48
+ config[key] = value;
49
+ fs.writeFileSync(this.configFilePath, JSON.stringify(config, null, 2));
50
+ }
51
+ }
@@ -0,0 +1,140 @@
1
+ import { describe, test, vi, beforeEach, afterEach, expect } from "vitest";
2
+ import { KeypairCreator } from "../../src/commands/keygen/create";
3
+ import { writeFileSync, existsSync } from "fs";
4
+ import { ethers } from "ethers";
5
+
6
+ vi.mock("fs");
7
+
8
+ vi.mock("ethers", () => ({
9
+ ethers: {
10
+ Wallet: {
11
+ createRandom: vi.fn(),
12
+ },
13
+ },
14
+ }));
15
+
16
+ vi.mock("../../src/lib/config/ConfigFileManager", () => ({
17
+ ConfigFileManager: vi.fn().mockImplementation(() => ({
18
+ getFilePath: vi.fn((fileName) => `/mocked/path/${fileName}`),
19
+ writeConfig: vi.fn(),
20
+ })),
21
+ }));
22
+
23
+ describe("KeypairCreator", () => {
24
+ let keypairCreator: KeypairCreator;
25
+
26
+ const mockWallet: any = {
27
+ address: "0xMockedAddress",
28
+ privateKey: "0xMockedPrivateKey",
29
+ };
30
+ keypairCreator = new KeypairCreator();
31
+
32
+ beforeEach(() => {
33
+ vi.clearAllMocks();
34
+ vi.mocked(ethers.Wallet.createRandom).mockReturnValue(mockWallet);
35
+ });
36
+
37
+ afterEach(() => {
38
+ vi.restoreAllMocks();
39
+ });
40
+
41
+ test("successfully creates and saves a keypair", () => {
42
+ const consoleLogSpy = vi.spyOn(console, "log");
43
+ vi.mocked(existsSync).mockReturnValue(false);
44
+ const options = { output: "keypair.json", overwrite: false };
45
+
46
+ keypairCreator.createKeypairAction(options);
47
+
48
+ expect(ethers.Wallet.createRandom).toHaveBeenCalledTimes(1);
49
+
50
+ expect(keypairCreator["filePathManager"].getFilePath).toHaveBeenCalledWith("keypair.json");
51
+
52
+ expect(writeFileSync).toHaveBeenCalledWith(
53
+ "/mocked/path/keypair.json",
54
+ JSON.stringify(
55
+ {
56
+ address: mockWallet.address,
57
+ privateKey: mockWallet.privateKey,
58
+ },
59
+ null,
60
+ 2
61
+ )
62
+ );
63
+
64
+ expect(keypairCreator["filePathManager"].writeConfig).toHaveBeenCalledWith(
65
+ "keyPairPath",
66
+ "/mocked/path/keypair.json"
67
+ );
68
+
69
+ expect(consoleLogSpy).toHaveBeenCalledWith(
70
+ "Keypair successfully created and saved to: /mocked/path/keypair.json"
71
+ );
72
+ });
73
+
74
+ test("skips creation if file exists and overwrite is false", () => {
75
+
76
+
77
+ const consoleWarnSpy = vi.spyOn(console, "warn");
78
+ vi.mocked(existsSync).mockReturnValue(true);
79
+ const options = { output: "keypair.json", overwrite: false };
80
+
81
+ keypairCreator.createKeypairAction(options);
82
+
83
+ expect(ethers.Wallet.createRandom).not.toHaveBeenCalled();
84
+ expect(writeFileSync).not.toHaveBeenCalled();
85
+ expect(consoleWarnSpy).toHaveBeenCalledWith(
86
+ "The file at /mocked/path/keypair.json already exists. Use the '--overwrite' option to replace it."
87
+ );
88
+ });
89
+
90
+ test("overwrites the file if overwrite is true", () => {
91
+ const consoleLogSpy = vi.spyOn(console, "log");
92
+ vi.mocked(existsSync).mockReturnValue(true); // Simulate file exists
93
+ const options = { output: "keypair.json", overwrite: true };
94
+
95
+ keypairCreator.createKeypairAction(options);
96
+
97
+ expect(ethers.Wallet.createRandom).toHaveBeenCalledTimes(1);
98
+
99
+ expect(keypairCreator["filePathManager"].getFilePath).toHaveBeenCalledWith("keypair.json");
100
+
101
+ expect(writeFileSync).toHaveBeenCalledWith(
102
+ "/mocked/path/keypair.json",
103
+ JSON.stringify(
104
+ {
105
+ address: mockWallet.address,
106
+ privateKey: mockWallet.privateKey,
107
+ },
108
+ null,
109
+ 2
110
+ )
111
+ );
112
+
113
+ expect(keypairCreator["filePathManager"].writeConfig).toHaveBeenCalledWith(
114
+ "keyPairPath",
115
+ "/mocked/path/keypair.json"
116
+ );
117
+
118
+ expect(consoleLogSpy).toHaveBeenCalledWith(
119
+ "Keypair successfully created and saved to: /mocked/path/keypair.json"
120
+ );
121
+ });
122
+
123
+ test("handles errors during keypair creation", () => {
124
+ const consoleErrorSpy = vi.spyOn(console, "error");
125
+ const processExitSpy = vi.spyOn(process, "exit").mockImplementation(() => {
126
+ throw new Error("process.exit");
127
+ });
128
+
129
+ vi.mocked(writeFileSync).mockImplementation(() => {
130
+ throw new Error("Mocked write error");
131
+ });
132
+
133
+ expect(() => {
134
+ keypairCreator.createKeypairAction({ output: "keypair.json", overwrite: true });
135
+ }).toThrowError("process.exit");
136
+
137
+ expect(consoleErrorSpy).toHaveBeenCalledWith("Failed to generate keypair:", expect.any(Error));
138
+ expect(processExitSpy).toHaveBeenCalledWith(1);
139
+ });
140
+ });
@@ -0,0 +1,77 @@
1
+ import { Command } from "commander";
2
+ import { vi, describe, beforeEach, afterEach, test, expect } from "vitest";
3
+ import { initializeKeygenCommands } from "../../src/commands/keygen";
4
+ import { KeypairCreator } from "../../src/commands/keygen/create";
5
+
6
+ vi.mock("../../src/commands/keygen/create");
7
+
8
+ describe("keygen create command", () => {
9
+ let program: Command;
10
+
11
+ beforeEach(() => {
12
+ program = new Command();
13
+ initializeKeygenCommands(program);
14
+ vi.clearAllMocks();
15
+ });
16
+
17
+ afterEach(() => {
18
+ vi.restoreAllMocks();
19
+ });
20
+
21
+ test("keypairCreator.createKeypairAction is called with default options", async () => {
22
+ program.parse(["node", "test", "keygen", "create"]);
23
+ expect(KeypairCreator).toHaveBeenCalledTimes(1);
24
+ expect(KeypairCreator.prototype.createKeypairAction).toHaveBeenCalledWith({
25
+ output: "./keypair.json",
26
+ overwrite: false,
27
+ });
28
+ });
29
+
30
+ test("keypairCreator.createKeypairAction is called with custom output option", async () => {
31
+ program.parse(["node", "test", "keygen", "create", "--output", "./custom.json"]);
32
+ expect(KeypairCreator).toHaveBeenCalledTimes(1);
33
+ expect(KeypairCreator.prototype.createKeypairAction).toHaveBeenCalledWith({
34
+ output: "./custom.json",
35
+ overwrite: false,
36
+ });
37
+ });
38
+
39
+ test("keypairCreator.createKeypairAction is called with overwrite enabled", async () => {
40
+ program.parse(["node", "test", "keygen", "create", "--overwrite"]);
41
+ expect(KeypairCreator).toHaveBeenCalledTimes(1);
42
+ expect(KeypairCreator.prototype.createKeypairAction).toHaveBeenCalledWith({
43
+ output: "./keypair.json",
44
+ overwrite: true,
45
+ });
46
+ });
47
+
48
+ test("keypairCreator.createKeypairAction is called with custom output and overwrite enabled", async () => {
49
+ program.parse(["node", "test", "keygen", "create", "--output", "./custom.json", "--overwrite"]);
50
+ expect(KeypairCreator).toHaveBeenCalledTimes(1);
51
+ expect(KeypairCreator.prototype.createKeypairAction).toHaveBeenCalledWith({
52
+ output: "./custom.json",
53
+ overwrite: true,
54
+ });
55
+ });
56
+
57
+ test("KeypairCreator is instantiated when the command is executed", async () => {
58
+ program.parse(["node", "test", "keygen", "create"]);
59
+ expect(KeypairCreator).toHaveBeenCalledTimes(1);
60
+ });
61
+
62
+ test("throws error for unrecognized options", async () => {
63
+ const keygenCommand = program.commands.find((cmd) => cmd.name() === "keygen");
64
+ const createCommand = keygenCommand?.commands.find((cmd) => cmd.name() === "create");
65
+
66
+ createCommand?.exitOverride();
67
+ expect(() => program.parse(["node", "test", "keygen", "create", "--unknown"])).toThrowError(
68
+ "error: unknown option '--unknown'"
69
+ );
70
+ });
71
+
72
+ test("keypairCreator.createKeypairAction is called without throwing errors for default options", async () => {
73
+ program.parse(["node", "test", "keygen", "create"]);
74
+ vi.mocked(KeypairCreator.prototype.createKeypairAction).mockReturnValue();
75
+ expect(() => program.parse(["node", "test", "keygen", "create"])).not.toThrow();
76
+ });
77
+ });
@@ -13,6 +13,10 @@ vi.mock("../src/commands/general", () => ({
13
13
  initializeGeneralCommands: vi.fn(),
14
14
  }));
15
15
 
16
+ vi.mock("../src/commands/keygen", () => ({
17
+ initializeKeygenCommands: vi.fn(),
18
+ }));
19
+
16
20
 
17
21
  describe("CLI", () => {
18
22
  it("should initialize CLI", () => {
@@ -0,0 +1,113 @@
1
+ import { describe, test, vi, beforeEach, afterEach, expect } from "vitest";
2
+ import { ConfigFileManager } from "../../src/lib/config/ConfigFileManager";
3
+ import fs from "fs";
4
+ import os from "os";
5
+ import path from "path";
6
+
7
+ vi.mock("fs");
8
+
9
+ vi.mock("os")
10
+
11
+ describe("ConfigFileManager", () => {
12
+ const mockFolderPath = "/mocked/home/.genlayer";
13
+ const mockConfigFilePath = `${mockFolderPath}/genlayer-config.json`;
14
+
15
+ let configFileManager: ConfigFileManager;
16
+
17
+ beforeEach(() => {
18
+ vi.clearAllMocks();
19
+ vi.mocked(os.homedir).mockReturnValue("/mocked/home");
20
+ configFileManager = new ConfigFileManager();
21
+ });
22
+
23
+ afterEach(() => {
24
+ vi.restoreAllMocks();
25
+ });
26
+
27
+ test("ensures folder and config file are created if they don't exist", () => {
28
+ vi.mocked(fs.existsSync).mockReturnValue(false);
29
+
30
+ new ConfigFileManager();
31
+
32
+ expect(fs.existsSync).toHaveBeenCalledWith(mockFolderPath);
33
+ expect(fs.mkdirSync).toHaveBeenCalledWith(mockFolderPath, { recursive: true });
34
+
35
+ expect(fs.existsSync).toHaveBeenCalledWith(mockConfigFilePath);
36
+ expect(fs.writeFileSync).toHaveBeenCalledWith(mockConfigFilePath, JSON.stringify({}, null, 2));
37
+ });
38
+
39
+ test("does not recreate folder or config file if they exist", () => {
40
+ vi.clearAllMocks();
41
+ vi.mocked(fs.existsSync).mockReturnValue(true);
42
+
43
+ new ConfigFileManager();
44
+
45
+ expect(fs.mkdirSync).not.toHaveBeenCalled();
46
+ expect(fs.writeFileSync).not.toHaveBeenCalled();
47
+ });
48
+
49
+ test("getFolderPath returns the correct folder path", () => {
50
+ expect(configFileManager.getFolderPath()).toBe(mockFolderPath);
51
+ });
52
+
53
+ test("getFilePath returns the correct file path for a given file name", () => {
54
+ const fileName = "example.json";
55
+ const expectedFilePath = path.resolve(mockFolderPath, fileName);
56
+
57
+ expect(configFileManager.getFilePath(fileName)).toBe(expectedFilePath);
58
+ });
59
+
60
+ test("getConfig returns the parsed content of the config file", () => {
61
+ const mockConfig = { key: "value" };
62
+ vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify(mockConfig));
63
+
64
+ const config = configFileManager.getConfig();
65
+
66
+ expect(fs.readFileSync).toHaveBeenCalledWith(mockConfigFilePath, "utf-8");
67
+ expect(config).toEqual(mockConfig);
68
+ });
69
+
70
+ test("getConfigByKey returns the value for a given key", () => {
71
+ const mockConfig = { key: "value" };
72
+ vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify(mockConfig));
73
+
74
+ const value = configFileManager.getConfigByKey("key");
75
+
76
+ expect(value).toBe("value");
77
+ });
78
+
79
+ test("getConfigByKey returns null for a non-existing key", () => {
80
+ const mockConfig = { key: "value" };
81
+ vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify(mockConfig));
82
+
83
+ const value = configFileManager.getConfigByKey("nonExistingKey");
84
+
85
+ expect(value).toBeNull();
86
+ });
87
+
88
+ test("writeConfig updates the config file with a new key-value pair", () => {
89
+ const mockConfig = { existingKey: "existingValue" };
90
+ vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify(mockConfig));
91
+
92
+ configFileManager.writeConfig("newKey", "newValue");
93
+
94
+ const expectedConfig = { existingKey: "existingValue", newKey: "newValue" };
95
+ expect(fs.writeFileSync).toHaveBeenCalledWith(
96
+ mockConfigFilePath,
97
+ JSON.stringify(expectedConfig, null, 2)
98
+ );
99
+ });
100
+
101
+ test("writeConfig overwrites an existing key in the config file", () => {
102
+ const mockConfig = { existingKey: "existingValue" };
103
+ vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify(mockConfig));
104
+
105
+ configFileManager.writeConfig("existingKey", "updatedValue");
106
+
107
+ const expectedConfig = { existingKey: "updatedValue" };
108
+ expect(fs.writeFileSync).toHaveBeenCalledWith(
109
+ mockConfigFilePath,
110
+ JSON.stringify(expectedConfig, null, 2)
111
+ );
112
+ });
113
+ });