genlayer 0.6.0 → 0.8.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/.env.example CHANGED
@@ -69,3 +69,4 @@ VALIDATORS_CONFIG_JSON=''
69
69
 
70
70
 
71
71
  VSCODEDEBUG="false"
72
+ LOCALNETVERSION="latest"
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
 
2
2
 
3
+ ## 0.8.0 (2024-12-11)
4
+
5
+
6
+ ### Features
7
+
8
+ * implement config command ([#149](https://github.com/yeagerai/genlayer-cli/issues/149)) ([cc0f2ca](https://github.com/yeagerai/genlayer-cli/commit/cc0f2caee2c55f00efc7da0671663827a69be557))
9
+
10
+ ## 0.7.0 (2024-12-09)
11
+
12
+
13
+ ### Features
14
+
15
+ * adding localnet version option on init ([#151](https://github.com/yeagerai/genlayer-cli/issues/151)) ([a7bf419](https://github.com/yeagerai/genlayer-cli/commit/a7bf41986e89e8db95003df290d381e77dad127f))
16
+
3
17
  ## 0.6.0 (2024-12-03)
4
18
 
5
19
 
package/dist/index.js CHANGED
@@ -50858,7 +50858,7 @@ var {
50858
50858
  } = import_index.default;
50859
50859
 
50860
50860
  // package.json
50861
- var version = "0.6.0";
50861
+ var version = "0.8.0";
50862
50862
  var package_default = {
50863
50863
  name: "genlayer",
50864
50864
  version,
@@ -51817,6 +51817,18 @@ Run npm install -g genlayer to update
51817
51817
  return true;
51818
51818
  });
51819
51819
  }
51820
+ normalizeLocalnetVersion(version3) {
51821
+ if (!version3.startsWith("v")) {
51822
+ version3 = "v" + version3;
51823
+ }
51824
+ const versionRegex = /^v(\d+)\.(\d+)\.(\d+)(-.+)?$/;
51825
+ const match = version3.match(versionRegex);
51826
+ if (!match) {
51827
+ console.error("Invalid version format. Expected format: v0.0.0 or v0.0.0-suffix");
51828
+ process.exit(1);
51829
+ }
51830
+ return version3;
51831
+ }
51820
51832
  };
51821
51833
  var simulator_default = new SimulatorService();
51822
51834
 
@@ -54427,6 +54439,10 @@ function getVersionErrorMessage({ docker, node }) {
54427
54439
  function initAction(options, simulatorService) {
54428
54440
  return __async(this, null, function* () {
54429
54441
  simulatorService.setComposeOptions(options.headless);
54442
+ let localnetVersion = options.localnetVersion;
54443
+ if (localnetVersion !== "latest") {
54444
+ localnetVersion = simulatorService.normalizeLocalnetVersion(localnetVersion);
54445
+ }
54430
54446
  yield simulatorService.checkCliVersion();
54431
54447
  try {
54432
54448
  const requirementsInstalled = yield simulatorService.checkInstallRequirements();
@@ -54512,6 +54528,7 @@ function initAction(options, simulatorService) {
54512
54528
  }
54513
54529
  console.log("Configuring GenLayer Simulator environment...");
54514
54530
  simulatorService.addConfigToEnvFile(aiProvidersEnvVars);
54531
+ simulatorService.addConfigToEnvFile({ LOCALNETVERSION: localnetVersion });
54515
54532
  console.log("Running the GenLayer Simulator...");
54516
54533
  try {
54517
54534
  yield simulatorService.runSimulator();
@@ -54646,7 +54663,7 @@ function startAction(options, simulatorService) {
54646
54663
 
54647
54664
  // src/commands/general/index.ts
54648
54665
  function initializeGeneralCommands(program2) {
54649
- program2.command("init").description("Initialize the GenLayer Environment").option("--numValidators <numValidators>", "Number of validators", "5").option("--headless", "Headless mode", false).option("--reset-db", "Reset Database", false).action((options) => initAction(options, simulator_default));
54666
+ program2.command("init").description("Initialize the GenLayer Environment").option("--numValidators <numValidators>", "Number of validators", "5").option("--headless", "Headless mode", false).option("--reset-db", "Reset Database", false).option("--localnet-version <localnetVersion>", "Select a specific localnet version", "latest").action((options) => initAction(options, simulator_default));
54650
54667
  program2.command("up").description("Starts GenLayer's simulator").option("--reset-validators", "Remove all current validators and create new random ones", false).option("--numValidators <numValidators>", "Number of validators", "5").option("--headless", "Headless mode", false).option("--reset-db", "Reset Database", false).action((options) => startAction(options, simulator_default));
54651
54668
  return program2;
54652
54669
  }
@@ -78269,11 +78286,62 @@ function initializeKeygenCommands(program2) {
78269
78286
  return program2;
78270
78287
  }
78271
78288
 
78289
+ // src/commands/config/getSetReset.ts
78290
+ var ConfigActions = class {
78291
+ constructor() {
78292
+ __publicField(this, "configManager");
78293
+ this.configManager = new ConfigFileManager();
78294
+ }
78295
+ set(keyValue) {
78296
+ const [key, value] = keyValue.split("=");
78297
+ if (!key || value === void 0) {
78298
+ console.error("Invalid format. Use key=value.");
78299
+ process.exit(1);
78300
+ }
78301
+ this.configManager.writeConfig(key, value);
78302
+ console.log(`Configuration updated: ${key}=${value}`);
78303
+ }
78304
+ get(key) {
78305
+ if (key) {
78306
+ const value = this.configManager.getConfigByKey(key);
78307
+ if (value === null) {
78308
+ console.log(`No value set for key: ${key}`);
78309
+ } else {
78310
+ console.log(`${key}=${value}`);
78311
+ }
78312
+ } else {
78313
+ const config = this.configManager.getConfig();
78314
+ console.log("Current configuration:", JSON.stringify(config, null, 2));
78315
+ }
78316
+ }
78317
+ reset(key) {
78318
+ const config = this.configManager.getConfig();
78319
+ if (config[key] === void 0) {
78320
+ console.log(`Key does not exist in the configuration: ${key}`);
78321
+ return;
78322
+ }
78323
+ delete config[key];
78324
+ this.configManager.writeConfig(key, void 0);
78325
+ console.log(`Configuration key reset: ${key}`);
78326
+ }
78327
+ };
78328
+
78329
+ // src/commands/config/index.ts
78330
+ function initializeConfigCommands(program2) {
78331
+ const configActions = new ConfigActions();
78332
+ const configCommand = program2.command("config").description("Manage CLI configuration, including the default network");
78333
+ configCommand.command("set").description("Set a configuration value").argument("<key=value>", "Configuration key-value pair to set").action((keyValue) => configActions.set(keyValue));
78334
+ configCommand.command("get").description("Get the current configuration").argument("[key]", "Configuration key to retrieve").action((key) => configActions.get(key));
78335
+ configCommand.command("reset").description("Reset a configuration value to its default").argument("<key>", "Configuration key to reset").action((key) => configActions.reset(key));
78336
+ return program2;
78337
+ }
78338
+
78272
78339
  // src/index.ts
78273
78340
  function initializeCLI() {
78274
78341
  program.version(version).description(CLI_DESCRIPTION);
78275
78342
  initializeGeneralCommands(program);
78276
78343
  initializeKeygenCommands(program);
78344
+ initializeConfigCommands(program);
78277
78345
  program.parse(process.argv);
78278
78346
  }
78279
78347
  initializeCLI();
@@ -2,7 +2,7 @@ version: "3.8"
2
2
 
3
3
  services:
4
4
  frontend:
5
- image: yeagerai/simulator-frontend:latest
5
+ image: yeagerai/simulator-frontend:${LOCALNETVERSION:-latest}
6
6
  ports:
7
7
  - "${FRONTEND_PORT:-8080}:8080"
8
8
  environment:
@@ -25,7 +25,7 @@ services:
25
25
 
26
26
 
27
27
  jsonrpc:
28
- image: yeagerai/simulator-jsonrpc:latest
28
+ image: yeagerai/simulator-jsonrpc:${LOCALNETVERSION:-latest}
29
29
  environment:
30
30
  - FLASK_SERVER_PORT=${RPCPORT:-5000}
31
31
  - PYTHONUNBUFFERED=1
@@ -63,7 +63,7 @@ services:
63
63
  replicas: ${JSONRPC_REPLICAS:-1}
64
64
 
65
65
  webrequest:
66
- image: yeagerai/simulator-webrequest:latest
66
+ image: yeagerai/simulator-webrequest:${LOCALNETVERSION:-latest}
67
67
  shm_size: 2gb
68
68
  environment:
69
69
  - FLASK_SERVER_PORT=${WEBREQUESTPORT:-5002}
@@ -131,7 +131,7 @@ services:
131
131
  # - "./data/postgres:/var/lib/postgresql/data"
132
132
 
133
133
  database-migration:
134
- image: yeagerai/simulator-database-migration:latest
134
+ image: yeagerai/simulator-database-migration:${LOCALNETVERSION:-latest}
135
135
  environment:
136
136
  - DB_URL=postgresql://${DBUSER:-postgres}:${DBPASSWORD:-postgres}@postgres/${DBNAME:-simulator_db}
137
137
  - WEBREQUESTPORT=${WEBREQUESTPORT:-5002}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genlayer",
3
- "version": "0.6.0",
3
+ "version": "0.8.0",
4
4
  "description": "GenLayer Command Line Tool",
5
5
  "main": "src/index.ts",
6
6
  "bin": {
@@ -0,0 +1,44 @@
1
+ import { ConfigFileManager } from "../../lib/config/ConfigFileManager";
2
+
3
+ export class ConfigActions {
4
+ private configManager: ConfigFileManager;
5
+
6
+ constructor() {
7
+ this.configManager = new ConfigFileManager();
8
+ }
9
+
10
+ set(keyValue: string): void {
11
+ const [key, value] = keyValue.split("=");
12
+ if (!key || value === undefined) {
13
+ console.error("Invalid format. Use key=value.");
14
+ process.exit(1);
15
+ }
16
+ this.configManager.writeConfig(key, value);
17
+ console.log(`Configuration updated: ${key}=${value}`);
18
+ }
19
+
20
+ get(key?: string): void {
21
+ if (key) {
22
+ const value = this.configManager.getConfigByKey(key);
23
+ if (value === null) {
24
+ console.log(`No value set for key: ${key}`);
25
+ } else {
26
+ console.log(`${key}=${value}`);
27
+ }
28
+ } else {
29
+ const config = this.configManager.getConfig();
30
+ console.log("Current configuration:", JSON.stringify(config, null, 2));
31
+ }
32
+ }
33
+
34
+ reset(key: string): void {
35
+ const config = this.configManager.getConfig();
36
+ if (config[key] === undefined) {
37
+ console.log(`Key does not exist in the configuration: ${key}`);
38
+ return;
39
+ }
40
+ delete config[key];
41
+ this.configManager.writeConfig(key, undefined);
42
+ console.log(`Configuration key reset: ${key}`);
43
+ }
44
+ }
@@ -0,0 +1,30 @@
1
+ import { Command } from "commander";
2
+ import { ConfigActions } from "./getSetReset";
3
+
4
+ export function initializeConfigCommands(program: Command) {
5
+ const configActions = new ConfigActions();
6
+
7
+ const configCommand = program
8
+ .command("config")
9
+ .description("Manage CLI configuration, including the default network");
10
+
11
+ configCommand
12
+ .command("set")
13
+ .description("Set a configuration value")
14
+ .argument("<key=value>", "Configuration key-value pair to set")
15
+ .action((keyValue: string) => configActions.set(keyValue));
16
+
17
+ configCommand
18
+ .command("get")
19
+ .description("Get the current configuration")
20
+ .argument("[key]", "Configuration key to retrieve")
21
+ .action((key?: string) => configActions.get(key));
22
+
23
+ configCommand
24
+ .command("reset")
25
+ .description("Reset a configuration value to its default")
26
+ .argument("<key>", "Configuration key to reset")
27
+ .action((key: string) => configActions.reset(key));
28
+
29
+ return program;
30
+ }
@@ -11,6 +11,7 @@ export function initializeGeneralCommands(program: Command) {
11
11
  .option("--numValidators <numValidators>", "Number of validators", "5")
12
12
  .option("--headless", "Headless mode", false)
13
13
  .option("--reset-db", "Reset Database", false)
14
+ .option("--localnet-version <localnetVersion>", "Select a specific localnet version", 'latest')
14
15
  .action((options: InitActionOptions) => initAction(options, simulatorService));
15
16
 
16
17
  program
@@ -6,6 +6,7 @@ export interface InitActionOptions {
6
6
  numValidators: number;
7
7
  headless: boolean;
8
8
  resetDb: boolean;
9
+ localnetVersion: string;
9
10
  }
10
11
 
11
12
  function getRequirementsErrorMessage({docker}: Record<string, boolean>): string {
@@ -34,6 +35,11 @@ function getVersionErrorMessage({docker, node}: Record<string, string>): string
34
35
  export async function initAction(options: InitActionOptions, simulatorService: ISimulatorService) {
35
36
  simulatorService.setComposeOptions(options.headless);
36
37
 
38
+ let localnetVersion = options.localnetVersion;
39
+
40
+ if(localnetVersion !== 'latest'){
41
+ localnetVersion = simulatorService.normalizeLocalnetVersion(localnetVersion);
42
+ }
37
43
  await simulatorService.checkCliVersion();
38
44
 
39
45
  // Check if requirements are installed
@@ -139,6 +145,7 @@ export async function initAction(options: InitActionOptions, simulatorService: I
139
145
 
140
146
  console.log("Configuring GenLayer Simulator environment...");
141
147
  simulatorService.addConfigToEnvFile(aiProvidersEnvVars);
148
+ simulatorService.addConfigToEnvFile({LOCALNETVERSION: localnetVersion});
142
149
 
143
150
 
144
151
  // Run the GenLayer Simulator
package/src/index.ts CHANGED
@@ -4,11 +4,13 @@ import {version} from "../package.json";
4
4
  import {CLI_DESCRIPTION} from "../src/lib/config/text";
5
5
  import { initializeGeneralCommands } from "../src/commands/general";
6
6
  import { initializeKeygenCommands } from "../src/commands/keygen";
7
+ import { initializeConfigCommands } from "../src/commands/config";
7
8
 
8
9
  export function initializeCLI() {
9
10
  program.version(version).description(CLI_DESCRIPTION);
10
11
  initializeGeneralCommands(program);
11
12
  initializeKeygenCommands(program);
13
+ initializeConfigCommands(program);
12
14
  program.parse(process.argv);
13
15
  }
14
16
 
@@ -18,6 +18,7 @@ export interface ISimulatorService {
18
18
  checkCliVersion(): Promise<void>;
19
19
  cleanDatabase(): Promise<boolean>;
20
20
  addConfigToEnvFile(newConfig: Record<string, string>): void;
21
+ normalizeLocalnetVersion(version: string): string;
21
22
  }
22
23
 
23
24
 
@@ -295,6 +295,23 @@ export class SimulatorService implements ISimulatorService {
295
295
  return true;
296
296
  }
297
297
 
298
+ public normalizeLocalnetVersion(version: string) {
299
+
300
+ if (!version.startsWith('v')) {
301
+ version = 'v' + version;
302
+ }
303
+
304
+ const versionRegex = /^v(\d+)\.(\d+)\.(\d+)(-.+)?$/;
305
+ const match = version.match(versionRegex);
306
+
307
+ if (!match) {
308
+ console.error('Invalid version format. Expected format: v0.0.0 or v0.0.0-suffix');
309
+ process.exit(1);
310
+ }
311
+
312
+ return version
313
+ }
314
+
298
315
  }
299
316
 
300
317
  export default new SimulatorService();
@@ -89,7 +89,7 @@ describe("KeypairCreator", () => {
89
89
 
90
90
  test("overwrites the file if overwrite is true", () => {
91
91
  const consoleLogSpy = vi.spyOn(console, "log");
92
- vi.mocked(existsSync).mockReturnValue(true); // Simulate file exists
92
+ vi.mocked(existsSync).mockReturnValue(true);
93
93
  const options = { output: "keypair.json", overwrite: true };
94
94
 
95
95
  keypairCreator.createKeypairAction(options);
@@ -0,0 +1,101 @@
1
+ import { describe, test, vi, beforeEach, afterEach, expect } from "vitest";
2
+ import { ConfigActions } from "../../src/commands/config/getSetReset";
3
+ import { ConfigFileManager } from "../../src/lib/config/ConfigFileManager";
4
+
5
+ vi.mock("../../src/lib/config/ConfigFileManager");
6
+
7
+ describe("ConfigActions", () => {
8
+ let configActions: ConfigActions;
9
+
10
+ beforeEach(() => {
11
+ configActions = new ConfigActions();
12
+ vi.clearAllMocks();
13
+ });
14
+
15
+ new ConfigFileManager();
16
+
17
+ afterEach(() => {
18
+ vi.restoreAllMocks();
19
+ });
20
+
21
+ test("set method writes key-value pair to the configuration", () => {
22
+ const consoleLogSpy = vi.spyOn(console, "log");
23
+
24
+ configActions.set("defaultNetwork=testnet");
25
+
26
+ expect(configActions["configManager"].writeConfig).toHaveBeenCalledWith("defaultNetwork", "testnet");
27
+ expect(consoleLogSpy).toHaveBeenCalledWith("Configuration updated: defaultNetwork=testnet");
28
+ });
29
+
30
+ test("set method throws error for invalid format", () => {
31
+ const consoleErrorSpy = vi.spyOn(console, "error");
32
+ const processExitSpy = vi.spyOn(process, "exit").mockImplementation(() => {
33
+ throw new Error("process.exit");
34
+ });
35
+
36
+ expect(() => configActions.set("invalidFormat")).toThrowError("process.exit");
37
+
38
+ expect(consoleErrorSpy).toHaveBeenCalledWith("Invalid format. Use key=value.");
39
+ expect(processExitSpy).toHaveBeenCalledWith(1);
40
+ });
41
+
42
+ test("get method retrieves value for a specific key", () => {
43
+ vi.mocked(ConfigFileManager.prototype.getConfigByKey).mockReturnValue("testnet");
44
+
45
+ const consoleLogSpy = vi.spyOn(console, "log");
46
+
47
+ configActions.get("defaultNetwork");
48
+
49
+ expect(configActions["configManager"].getConfigByKey).toHaveBeenCalledWith("defaultNetwork");
50
+ expect(consoleLogSpy).toHaveBeenCalledWith("defaultNetwork=testnet");
51
+ });
52
+
53
+ test("get method prints message when key has no value", () => {
54
+ vi.mocked(ConfigFileManager.prototype.getConfigByKey).mockReturnValue(null);
55
+
56
+ const consoleLogSpy = vi.spyOn(console, "log");
57
+
58
+ configActions.get("nonexistentKey");
59
+
60
+ expect(configActions["configManager"].getConfigByKey).toHaveBeenCalledWith("nonexistentKey");
61
+ expect(consoleLogSpy).toHaveBeenCalledWith("No value set for key: nonexistentKey");
62
+ });
63
+
64
+ test("get method retrieves the entire configuration when no key is provided", () => {
65
+ const mockConfig = { defaultNetwork: "testnet" };
66
+ vi.mocked(ConfigFileManager.prototype.getConfig).mockReturnValue(mockConfig);
67
+
68
+ const consoleLogSpy = vi.spyOn(console, "log");
69
+
70
+ configActions.get();
71
+
72
+ expect(configActions["configManager"].getConfig).toHaveBeenCalledTimes(1);
73
+ expect(consoleLogSpy).toHaveBeenCalledWith("Current configuration:", JSON.stringify(mockConfig, null, 2));
74
+ });
75
+
76
+ test("reset method removes key from configuration", () => {
77
+ const mockConfig = { defaultNetwork: "testnet" };
78
+ vi.mocked(ConfigFileManager.prototype.getConfig).mockReturnValue(mockConfig);
79
+
80
+ const consoleLogSpy = vi.spyOn(console, "log");
81
+
82
+ configActions.reset("defaultNetwork");
83
+
84
+ expect(configActions["configManager"].getConfig).toHaveBeenCalledTimes(1);
85
+ expect(configActions["configManager"].writeConfig).toHaveBeenCalledWith("defaultNetwork", undefined);
86
+ expect(consoleLogSpy).toHaveBeenCalledWith("Configuration key reset: defaultNetwork");
87
+ });
88
+
89
+ test("reset method prints message when key does not exist", () => {
90
+ const mockConfig = {};
91
+ vi.mocked(ConfigFileManager.prototype.getConfig).mockReturnValue(mockConfig);
92
+
93
+ const consoleLogSpy = vi.spyOn(console, "log");
94
+
95
+ configActions.reset("nonexistentKey");
96
+
97
+ expect(configActions["configManager"].getConfig).toHaveBeenCalledTimes(1);
98
+ expect(configActions["configManager"].writeConfig).not.toHaveBeenCalled();
99
+ expect(consoleLogSpy).toHaveBeenCalledWith("Key does not exist in the configuration: nonexistentKey");
100
+ });
101
+ });
@@ -14,7 +14,7 @@ vi.mock("dotenv");
14
14
 
15
15
 
16
16
  const tempDir = mkdtempSync(join(tmpdir(), "test-initAction-"));
17
- const defaultActionOptions = { numValidators: 5, branch: "main", location: tempDir, headless: false, resetDb: false };
17
+ const defaultActionOptions = { numValidators: 5, branch: "main", location: tempDir, headless: false, resetDb: false, localnetVersion: 'latest' };
18
18
 
19
19
  describe("init action", () => {
20
20
  let error: ReturnType<any>;
@@ -201,7 +201,7 @@ describe("init action", () => {
201
201
  simServResetDockerContainers.mockResolvedValue(true);
202
202
  simServResetDockerImages.mockResolvedValue(true);
203
203
 
204
- await initAction({...defaultActionOptions, headless: true, resetDb: true}, simulatorService);
204
+ await initAction({...defaultActionOptions, headless: true, resetDb: true, localnetVersion: "v1.0.0"}, simulatorService);
205
205
 
206
206
  expect(log).toHaveBeenCalledWith(
207
207
  `GenLayer simulator initialized successfully! `
@@ -441,4 +441,5 @@ describe("init action", () => {
441
441
 
442
442
  expect(error).toHaveBeenCalledWith(errorMsg);
443
443
  });
444
+
444
445
  });
@@ -0,0 +1,54 @@
1
+ import { Command } from "commander";
2
+ import { vi, describe, beforeEach, afterEach, test, expect } from "vitest";
3
+ import { initializeConfigCommands } from "../../src/commands/config";
4
+ import { ConfigActions } from "../../src/commands/config/getSetReset";
5
+
6
+ vi.mock("../../src/commands/config/getSetReset");
7
+
8
+ describe("config commands", () => {
9
+ let program: Command;
10
+
11
+ beforeEach(() => {
12
+ program = new Command();
13
+ initializeConfigCommands(program);
14
+ });
15
+
16
+ afterEach(() => {
17
+ vi.restoreAllMocks();
18
+ });
19
+
20
+ test("ConfigActions.set is called with the correct key-value pair", async () => {
21
+ program.parse(["node", "test", "config", "set", "defaultNetwork=testnet"]);
22
+ expect(ConfigActions).toHaveBeenCalledTimes(1);
23
+ expect(ConfigActions.prototype.set).toHaveBeenCalledWith("defaultNetwork=testnet");
24
+ });
25
+
26
+ test("ConfigActions.get is called with a specific key", async () => {
27
+ program.parse(["node", "test", "config", "get", "defaultNetwork"]);
28
+ expect(ConfigActions).toHaveBeenCalledTimes(1);
29
+ expect(ConfigActions.prototype.get).toHaveBeenCalledWith("defaultNetwork");
30
+ });
31
+
32
+ test("ConfigActions.get is called without a key", async () => {
33
+ program.parse(["node", "test", "config", "get"]);
34
+ expect(ConfigActions).toHaveBeenCalledTimes(1);
35
+ expect(ConfigActions.prototype.get).toHaveBeenCalledWith(undefined);
36
+ });
37
+
38
+ test("ConfigActions.reset is called with the correct key", async () => {
39
+ program.parse(["node", "test", "config", "reset", "defaultNetwork"]);
40
+ expect(ConfigActions).toHaveBeenCalledTimes(1);
41
+ expect(ConfigActions.prototype.reset).toHaveBeenCalledWith("defaultNetwork");
42
+ });
43
+
44
+ test("ConfigActions is instantiated when the command is executed", async () => {
45
+ program.parse(["node", "test", "config", "set", "defaultNetwork=testnet"]);
46
+ expect(ConfigActions).toHaveBeenCalledTimes(1);
47
+ });
48
+
49
+ test("ConfigActions.set is called without throwing errors for valid input", async () => {
50
+ program.parse(["node", "test", "config", "set", "defaultNetwork=testnet"]);
51
+ vi.mocked(ConfigActions.prototype.set).mockReturnValue();
52
+ expect(() => program.parse(["node", "test", "config", "set", "defaultNetwork=testnet"])).not.toThrow();
53
+ });
54
+ });
@@ -8,7 +8,8 @@ const openFrontendSpy = vi.spyOn(simulatorService, "openFrontend");
8
8
  const defaultOptions = {
9
9
  numValidators: "5",
10
10
  headless: false,
11
- resetDb: false
11
+ resetDb: false,
12
+ localnetVersion: 'latest'
12
13
  }
13
14
 
14
15
  vi.mock("inquirer", () => ({
@@ -73,4 +74,11 @@ describe("init command", () => {
73
74
  expect(action).toHaveBeenCalledWith({...defaultOptions, headless: true});
74
75
  expect(openFrontendSpy).not.toHaveBeenCalled();
75
76
  });
77
+
78
+ test("option --localnet-version is accepted", async () => {
79
+ program.parse(["node", "test", "init", "--localnet-version", "v1.0.0"]);
80
+ expect(action).toHaveBeenCalledTimes(1);
81
+ expect(action).toHaveBeenCalledWith({...defaultOptions, localnetVersion: "v1.0.0"});
82
+ expect(openFrontendSpy).not.toHaveBeenCalled();
83
+ });
76
84
  });
@@ -17,6 +17,10 @@ vi.mock("../src/commands/keygen", () => ({
17
17
  initializeKeygenCommands: vi.fn(),
18
18
  }));
19
19
 
20
+ vi.mock("../src/commands/config", () => ({
21
+ initializeConfigCommands: vi.fn(),
22
+ }));
23
+
20
24
 
21
25
  describe("CLI", () => {
22
26
  it("should initialize CLI", () => {
@@ -505,3 +505,37 @@ describe("SimulatorService - Docker Tests", () => {
505
505
  consoleWarnSpy.mockRestore();
506
506
  });
507
507
  });
508
+
509
+ describe('normalizeLocalnetVersion', () => {
510
+ test('should add "v" if not present', () => {
511
+ expect(simulatorService.normalizeLocalnetVersion("0.26.0")).toBe("v0.26.0");
512
+ });
513
+
514
+ test('should preserve "v" if already present', () => {
515
+ expect(simulatorService.normalizeLocalnetVersion("v0.26.0")).toBe("v0.26.0");
516
+ });
517
+
518
+ test('should retain suffixes like "-test000"', () => {
519
+ expect(simulatorService.normalizeLocalnetVersion("0.25.0-test000")).toBe("v0.25.0-test000");
520
+ expect(simulatorService.normalizeLocalnetVersion("v1.0.0-alpha")).toBe("v1.0.0-alpha");
521
+ });
522
+
523
+ test('should handle versions with numbers only', () => {
524
+ expect(simulatorService.normalizeLocalnetVersion("1.0.0")).toBe("v1.0.0");
525
+ });
526
+
527
+ test('should throw an error and exit for invalid versions', () => {
528
+ const mockExit = vi.spyOn(process, 'exit').mockImplementation(() => { return undefined as never});
529
+ const mockConsoleError = vi.spyOn(console, 'error').mockImplementation(() => {});
530
+
531
+ simulatorService.normalizeLocalnetVersion("invalid-version");
532
+
533
+ expect(mockConsoleError).toHaveBeenCalledWith(
534
+ 'Invalid version format. Expected format: v0.0.0 or v0.0.0-suffix'
535
+ );
536
+ expect(mockExit).toHaveBeenCalledWith(1);
537
+
538
+ mockExit.mockRestore();
539
+ mockConsoleError.mockRestore();
540
+ });
541
+ });