genlayer 0.16.0 → 0.17.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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.17.0 (2025-05-05)
4
+
5
+ ### Features
6
+
7
+ * check if the simulator is running before launching it again ([#219](https://github.com/yeagerai/genlayer-cli/issues/219)) ([f45e1f7](https://github.com/yeagerai/genlayer-cli/commit/f45e1f7bcee9b1319cf60e658fa433fe5ec8c3eb))
8
+
3
9
  ## 0.16.0 (2025-05-02)
4
10
 
5
11
  ### Features
package/dist/index.js CHANGED
@@ -16853,7 +16853,7 @@ var require_semver2 = __commonJS({
16853
16853
  import { program } from "commander";
16854
16854
 
16855
16855
  // package.json
16856
- var version = "0.16.0";
16856
+ var version = "0.17.0";
16857
16857
  var package_default = {
16858
16858
  name: "genlayer",
16859
16859
  version,
@@ -16996,6 +16996,11 @@ var AI_PROVIDERS_CONFIG = {
16996
16996
  cliOptionValue: "xai"
16997
16997
  }
16998
16998
  };
16999
+ var GENLAYER_REQUIRED_CONTAINERS = [
17000
+ "genlayer-jsonrpc",
17001
+ "genlayer-webrequest",
17002
+ "genlayer-postgres"
17003
+ ];
16999
17004
 
17000
17005
  // src/commands/update/ollama.ts
17001
17006
  import Docker from "dockerode";
@@ -28654,6 +28659,15 @@ Run npm install -g genlayer to update
28654
28659
  }
28655
28660
  return version5;
28656
28661
  }
28662
+ async isLocalnetRunning() {
28663
+ const genlayerContainers = await this.getGenlayerContainers();
28664
+ const runningContainers = genlayerContainers.filter((container) => container.State === "running");
28665
+ return GENLAYER_REQUIRED_CONTAINERS.every(
28666
+ (requiredContainer) => runningContainers.some(
28667
+ (container) => container.Names.some((name) => name.includes(requiredContainer))
28668
+ )
28669
+ );
28670
+ }
28657
28671
  };
28658
28672
  var simulator_default = new SimulatorService();
28659
28673
 
@@ -28691,6 +28705,7 @@ var InitAction = class extends BaseAction {
28691
28705
  }
28692
28706
  this.startSpinner("Checking CLI version...");
28693
28707
  await this.simulatorService.checkCliVersion();
28708
+ const isRunning = await this.simulatorService.isLocalnetRunning();
28694
28709
  this.setSpinnerText("Checking installation requirements...");
28695
28710
  const requirementsInstalled = await this.simulatorService.checkInstallRequirements();
28696
28711
  const requirementErrorMessage = getRequirementsErrorMessage(requirementsInstalled);
@@ -28706,9 +28721,8 @@ var InitAction = class extends BaseAction {
28706
28721
  return;
28707
28722
  }
28708
28723
  this.stopSpinner();
28709
- await this.confirmPrompt(
28710
- `This command is going to reset GenLayer docker images and containers, providers API Keys, and GenLayer database (accounts, transactions, validators and logs). Contract code (gpy files) will be kept. Do you want to continue?`
28711
- );
28724
+ const confirmMessage = isRunning ? `GenLayer Localnet is already running and this command is going to reset GenLayer docker images and containers, providers API Keys, and GenLayer database (accounts, transactions, validators and logs). Contract code (gpy files) will be kept. Do you want to continue?` : `This command is going to reset GenLayer docker images and containers, providers API Keys, and GenLayer database (accounts, transactions, validators and logs). Contract code (gpy files) will be kept. Do you want to continue?`;
28725
+ await this.confirmPrompt(confirmMessage);
28712
28726
  this.logInfo(`Initializing GenLayer CLI with ${options.numValidators} validators`);
28713
28727
  this.startSpinner("Resetting Docker containers and images...");
28714
28728
  await this.simulatorService.resetDockerContainers();
@@ -28808,6 +28822,13 @@ var StartAction = class extends BaseAction {
28808
28822
  this.simulatorService.setComposeOptions(headless);
28809
28823
  this.startSpinner("Checking CLI version...");
28810
28824
  await this.simulatorService.checkCliVersion();
28825
+ const isRunning = await this.simulatorService.isLocalnetRunning();
28826
+ if (isRunning) {
28827
+ this.stopSpinner();
28828
+ await this.confirmPrompt("GenLayer Localnet is already running. Do you want to proceed?");
28829
+ this.startSpinner("Stopping Docker containers...");
28830
+ await this.simulatorService.stopDockerContainers();
28831
+ }
28811
28832
  const restartValidatorsHintText = resetValidators ? `creating new ${numValidators} random validators` : "keeping the existing validators";
28812
28833
  this.setSpinnerText(`Starting GenLayer Localnet (${restartValidatorsHintText})...`);
28813
28834
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genlayer",
3
- "version": "0.16.0",
3
+ "version": "0.17.0",
4
4
  "description": "GenLayer Command Line Tool",
5
5
  "main": "src/index.ts",
6
6
  "type": "module",
@@ -50,6 +50,8 @@ export class InitAction extends BaseAction {
50
50
  this.startSpinner("Checking CLI version...");
51
51
  await this.simulatorService.checkCliVersion();
52
52
 
53
+ const isRunning = await this.simulatorService.isLocalnetRunning();
54
+
53
55
  this.setSpinnerText("Checking installation requirements...");
54
56
  const requirementsInstalled = await this.simulatorService.checkInstallRequirements();
55
57
  const requirementErrorMessage = getRequirementsErrorMessage(requirementsInstalled);
@@ -67,10 +69,11 @@ export class InitAction extends BaseAction {
67
69
  }
68
70
  this.stopSpinner();
69
71
 
70
- // Confirm reset action with the user using BaseAction's confirm prompt
71
- await this.confirmPrompt(
72
- `This command is going to reset GenLayer docker images and containers, providers API Keys, and GenLayer database (accounts, transactions, validators and logs). Contract code (gpy files) will be kept. Do you want to continue?`
73
- );
72
+ const confirmMessage = isRunning
73
+ ? `GenLayer Localnet is already running and this command is going to reset GenLayer docker images and containers, providers API Keys, and GenLayer database (accounts, transactions, validators and logs). Contract code (gpy files) will be kept. Do you want to continue?`
74
+ : `This command is going to reset GenLayer docker images and containers, providers API Keys, and GenLayer database (accounts, transactions, validators and logs). Contract code (gpy files) will be kept. Do you want to continue?`;
75
+
76
+ await this.confirmPrompt(confirmMessage);
74
77
 
75
78
  this.logInfo(`Initializing GenLayer CLI with ${options.numValidators} validators`);
76
79
 
@@ -26,6 +26,14 @@ export class StartAction extends BaseAction {
26
26
  this.startSpinner("Checking CLI version...");
27
27
  await this.simulatorService.checkCliVersion();
28
28
 
29
+ const isRunning = await this.simulatorService.isLocalnetRunning();
30
+ if (isRunning) {
31
+ this.stopSpinner();
32
+ await this.confirmPrompt("GenLayer Localnet is already running. Do you want to proceed?");
33
+ this.startSpinner("Stopping Docker containers...");
34
+ await this.simulatorService.stopDockerContainers();
35
+ }
36
+
29
37
  const restartValidatorsHintText = resetValidators
30
38
  ? `creating new ${numValidators} random validators`
31
39
  : "keeping the existing validators";
@@ -60,3 +60,9 @@ export const AI_PROVIDERS_CONFIG: AiProvidersConfigType = {
60
60
  cliOptionValue: "xai",
61
61
  },
62
62
  };
63
+
64
+ export const GENLAYER_REQUIRED_CONTAINERS = [
65
+ "genlayer-jsonrpc",
66
+ "genlayer-webrequest",
67
+ "genlayer-postgres"
68
+ ]
@@ -19,6 +19,7 @@ export interface ISimulatorService {
19
19
  cleanDatabase(): Promise<boolean>;
20
20
  addConfigToEnvFile(newConfig: Record<string, string>): void;
21
21
  normalizeLocalnetVersion(version: string): string;
22
+ isLocalnetRunning(): Promise<boolean>;
22
23
  }
23
24
 
24
25
 
@@ -17,7 +17,8 @@ import {
17
17
  AiProviders,
18
18
  VERSION_REQUIREMENTS,
19
19
  CONTAINERS_NAME_PREFIX,
20
- IMAGES_NAME_PREFIX
20
+ IMAGES_NAME_PREFIX,
21
+ GENLAYER_REQUIRED_CONTAINERS
21
22
  } from "../config/simulator";
22
23
  import {
23
24
  checkCommand,
@@ -292,6 +293,17 @@ export class SimulatorService implements ISimulatorService {
292
293
  return version
293
294
  }
294
295
 
296
+ public async isLocalnetRunning(): Promise<boolean> {
297
+ const genlayerContainers = await this.getGenlayerContainers();
298
+ const runningContainers = genlayerContainers.filter(container => container.State === "running");
299
+
300
+ return GENLAYER_REQUIRED_CONTAINERS.every(requiredContainer =>
301
+ runningContainers.some(container =>
302
+ container.Names.some(name => name.includes(requiredContainer))
303
+ )
304
+ );
305
+ }
306
+
295
307
  }
296
308
 
297
309
  export default new SimulatorService();
@@ -56,6 +56,7 @@ describe("InitAction", () => {
56
56
  openFrontendSpy = vi.spyOn(SimulatorService.prototype, "openFrontend").mockResolvedValue(true);
57
57
  getFrontendUrlSpy = vi.spyOn(SimulatorService.prototype, "getFrontendUrl").mockReturnValue("http://localhost:8080");
58
58
  normalizeLocalnetVersionSpy = vi.spyOn(SimulatorService.prototype, "normalizeLocalnetVersion").mockImplementation((v: string) => v) as any;
59
+ vi.spyOn(SimulatorService.prototype, "isLocalnetRunning").mockResolvedValue(false);
59
60
  });
60
61
 
61
62
  afterEach(() => {
@@ -63,6 +64,38 @@ describe("InitAction", () => {
63
64
  });
64
65
 
65
66
  describe("Successful Execution", () => {
67
+ test("should show combined confirmation message when localnet is running", async () => {
68
+ vi.spyOn(SimulatorService.prototype, "isLocalnetRunning").mockResolvedValue(true);
69
+
70
+ const confirmPromptSpy = vi.spyOn(initAction as any, "confirmPrompt").mockResolvedValue(undefined);
71
+
72
+ inquirerPromptSpy
73
+ .mockResolvedValueOnce({ selectedLlmProviders: ["openai"] })
74
+ .mockResolvedValueOnce({ openai: "API_KEY_OPENAI" });
75
+
76
+ await initAction.execute(defaultOptions);
77
+
78
+ expect(confirmPromptSpy).toHaveBeenCalledWith(
79
+ "GenLayer Localnet is already running and this command is going to reset GenLayer docker images and containers, providers API Keys, and GenLayer database (accounts, transactions, validators and logs). Contract code (gpy files) will be kept. Do you want to continue?"
80
+ );
81
+ });
82
+
83
+ test("should show standard confirmation message when localnet is not running", async () => {
84
+ vi.spyOn(SimulatorService.prototype, "isLocalnetRunning").mockResolvedValue(false);
85
+
86
+ const confirmPromptSpy = vi.spyOn(initAction as any, "confirmPrompt").mockResolvedValue(undefined);
87
+
88
+ inquirerPromptSpy
89
+ .mockResolvedValueOnce({ selectedLlmProviders: ["openai"] })
90
+ .mockResolvedValueOnce({ openai: "API_KEY_OPENAI" });
91
+
92
+ await initAction.execute(defaultOptions);
93
+
94
+ expect(confirmPromptSpy).toHaveBeenCalledWith(
95
+ "This command is going to reset GenLayer docker images and containers, providers API Keys, and GenLayer database (accounts, transactions, validators and logs). Contract code (gpy files) will be kept. Do you want to continue?"
96
+ );
97
+ });
98
+
66
99
  test("executes the full flow in non-headless mode", async () => {
67
100
  inquirerPromptSpy
68
101
  .mockResolvedValueOnce({ confirmAction: true })
@@ -9,6 +9,7 @@ vi.mock("inquirer");
9
9
  describe("StartAction", () => {
10
10
  let startAction: StartAction;
11
11
  let mockSimulatorService: SimulatorService;
12
+ let mockConfirmPrompt: any;
12
13
 
13
14
  beforeEach(() => {
14
15
  vi.clearAllMocks();
@@ -18,7 +19,9 @@ describe("StartAction", () => {
18
19
  startAction["simulatorService"] = mockSimulatorService;
19
20
 
20
21
  mockSimulatorService.waitForSimulatorToBeReady = vi.fn().mockResolvedValue({ initialized: true });
22
+ mockSimulatorService.stopDockerContainers = vi.fn().mockResolvedValue(undefined);
21
23
 
24
+ mockConfirmPrompt = vi.spyOn(startAction as any, "confirmPrompt").mockResolvedValue(undefined);
22
25
  vi.spyOn(startAction as any, "startSpinner").mockImplementation(() => {});
23
26
  vi.spyOn(startAction as any, "setSpinnerText").mockImplementation(() => {});
24
27
  vi.spyOn(startAction as any, "succeedSpinner").mockImplementation(() => {});
@@ -36,6 +39,26 @@ describe("StartAction", () => {
36
39
  resetDb: false,
37
40
  };
38
41
 
42
+ test("should check if localnet is running and proceed without confirmation when not running", async () => {
43
+ mockSimulatorService.isLocalnetRunning = vi.fn().mockResolvedValue(false);
44
+
45
+ await startAction.execute(defaultOptions);
46
+
47
+ expect(mockSimulatorService.isLocalnetRunning).toHaveBeenCalled();
48
+ expect(mockConfirmPrompt).not.toHaveBeenCalled();
49
+ expect(mockSimulatorService.runSimulator).toHaveBeenCalled();
50
+ });
51
+
52
+ test("should prompt for confirmation when localnet is already running", async () => {
53
+ mockSimulatorService.isLocalnetRunning = vi.fn().mockResolvedValue(true);
54
+
55
+ await startAction.execute(defaultOptions);
56
+
57
+ expect(mockSimulatorService.isLocalnetRunning).toHaveBeenCalled();
58
+ expect(mockConfirmPrompt).toHaveBeenCalledWith("GenLayer Localnet is already running. Do you want to proceed?");
59
+ expect(mockSimulatorService.runSimulator).toHaveBeenCalled();
60
+ });
61
+
39
62
  test("should start the simulator successfully", async () => {
40
63
  mockSimulatorService.checkCliVersion = vi.fn().mockResolvedValue(undefined);
41
64
  mockSimulatorService.runSimulator = vi.fn().mockResolvedValue(undefined);
@@ -14,6 +14,7 @@ import {
14
14
  VERSION_REQUIREMENTS,
15
15
  STARTING_TIMEOUT_ATTEMPTS,
16
16
  DEFAULT_RUN_SIMULATOR_COMMAND, localnetCompatibleVersion, IMAGES_NAME_PREFIX,
17
+ AiProviders, GENLAYER_REQUIRED_CONTAINERS,
17
18
  } from "../../src/lib/config/simulator";
18
19
  import { rpcClient } from "../../src/lib/clients/jsonRpcClient";
19
20
  import * as semver from "semver";
@@ -284,7 +285,7 @@ describe("SimulatorService - Basic Tests", () => {
284
285
 
285
286
  test("should create random validators", async () => {
286
287
  const numValidators = 5;
287
- const llmProviders = ["openai", "ollama"];
288
+ const llmProviders = ["openai", "ollama"] as AiProviders[];
288
289
  const mockResponse = { success: true };
289
290
  vi.mocked(rpcClient.request).mockResolvedValue(mockResponse);
290
291
 
@@ -314,6 +315,34 @@ describe("SimulatorService - Docker Tests", () => {
314
315
  mockPing = vi.mocked(Docker.prototype.ping);
315
316
  });
316
317
 
318
+ test("isLocalnetRunning should return true when all required containers are running", async () => {
319
+ const mockContainers = [
320
+ { Id: "container1", Names: ["/genlayer-jsonrpc1"], State: "running" },
321
+ { Id: "container2", Names: ["/genlayer-webrequest1"], State: "running" },
322
+ { Id: "container3", Names: ["/genlayer-postgres1"], State: "running" },
323
+ { Id: "container4", Names: ["/genlayer-other-container1"], State: "running" },
324
+ { Id: "container5", Names: ["/genlayer-another-container1"], State: "exited" }
325
+ ];
326
+
327
+ mockListContainers.mockResolvedValue(mockContainers);
328
+ const result = await simulatorService.isLocalnetRunning();
329
+ expect(result).toBe(true);
330
+ });
331
+
332
+ test("isLocalnetRunning should return false when not all required containers are running", async () => {
333
+ const mockContainers = [
334
+ { Id: "container1", Names: ["/genlayer-jsonrpc2"], State: "running" },
335
+ { Id: "container2", Names: ["/genlayer-webrequest2"], State: "running" },
336
+ { Id: "container3", Names: ["/genlayer-postgres2"], State: "exited" },
337
+ { Id: "container4", Names: ["/genlayer-other-container2"], State: "running" },
338
+ { Id: "container5", Names: ["/unrelated-container2"], State: "running" }
339
+ ];
340
+
341
+ mockListContainers.mockResolvedValue(mockContainers);
342
+ const result = await simulatorService.isLocalnetRunning();
343
+ expect(result).toBe(false);
344
+ });
345
+
317
346
  test("should stop and remove Docker containers with the specified prefix", async () => {
318
347
  const mockContainers = [
319
348
  {