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 +6 -0
- package/dist/index.js +25 -4
- package/package.json +1 -1
- package/src/commands/general/init.ts +7 -4
- package/src/commands/general/start.ts +8 -0
- package/src/lib/config/simulator.ts +6 -0
- package/src/lib/interfaces/ISimulatorService.ts +1 -0
- package/src/lib/services/simulator.ts +13 -1
- package/tests/actions/init.test.ts +33 -0
- package/tests/actions/start.test.ts +23 -0
- package/tests/services/simulator.test.ts +30 -1
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.
|
|
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
|
-
|
|
28710
|
-
|
|
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
|
@@ -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
|
-
|
|
71
|
-
|
|
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";
|
|
@@ -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
|
{
|