genlayer 0.11.0 → 0.12.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,14 @@
1
1
 
2
2
 
3
+ ## 0.12.0 (2025-02-05)
4
+
5
+
6
+ ### Features
7
+
8
+ * Add `stop` Command and Improve Docker Container Management ([#178](https://github.com/yeagerai/genlayer-cli/issues/178)) ([dbae62c](https://github.com/yeagerai/genlayer-cli/commit/dbae62cd6ea0c90ee7fb6953112006f9dff729c3))
9
+
10
+ ## 0.11.1 (2025-02-05)
11
+
3
12
  ## 0.11.0 (2025-01-31)
4
13
 
5
14
 
package/dist/index.js CHANGED
@@ -54537,7 +54537,7 @@ var {
54537
54537
  } = import_index.default;
54538
54538
 
54539
54539
  // package.json
54540
- var version = "0.11.0";
54540
+ var version = "0.12.0";
54541
54541
  var package_default = {
54542
54542
  name: "genlayer",
54543
54543
  version,
@@ -55272,6 +55272,32 @@ var SimulatorService = class {
55272
55272
  this.composeOptions = "";
55273
55273
  this.docker = new import_dockerode.default();
55274
55274
  }
55275
+ readEnvConfigValue(key) {
55276
+ const envFilePath = path2.join(this.location, ".env");
55277
+ const envConfig = dotenv.parse(fs5.readFileSync(envFilePath, "utf8"));
55278
+ return envConfig[key];
55279
+ }
55280
+ async getGenlayerContainers() {
55281
+ const containers = await this.docker.listContainers({ all: true });
55282
+ return containers.filter(
55283
+ (container) => container.Names.some(
55284
+ (name) => name.startsWith(CONTAINERS_NAME_PREFIX) || name.includes("ollama")
55285
+ )
55286
+ );
55287
+ }
55288
+ async stopAndRemoveContainers(remove = false) {
55289
+ const genlayerContainers = await this.getGenlayerContainers();
55290
+ for (const containerInfo of genlayerContainers) {
55291
+ const container = this.docker.getContainer(containerInfo.Id);
55292
+ if (containerInfo.State === "running") {
55293
+ await container.stop();
55294
+ }
55295
+ const isOllamaContainer = containerInfo.Names.some((name) => name.includes("ollama"));
55296
+ if (remove && !isOllamaContainer) {
55297
+ await container.remove();
55298
+ }
55299
+ }
55300
+ }
55275
55301
  addConfigToEnvFile(newConfig) {
55276
55302
  const envFilePath = path2.join(this.location, ".env");
55277
55303
  const envConfig = dotenv.parse(fs5.readFileSync(envFilePath, "utf8"));
@@ -55289,11 +55315,6 @@ var SimulatorService = class {
55289
55315
  getComposeOptions() {
55290
55316
  return this.composeOptions;
55291
55317
  }
55292
- readEnvConfigValue(key) {
55293
- const envFilePath = path2.join(this.location, ".env");
55294
- const envConfig = dotenv.parse(fs5.readFileSync(envFilePath, "utf8"));
55295
- return envConfig[key];
55296
- }
55297
55318
  async checkCliVersion() {
55298
55319
  const update = await (0, import_update_check.default)(package_default);
55299
55320
  if (update && update.latest !== package_default.version) {
@@ -55402,21 +55423,11 @@ Run npm install -g genlayer to update
55402
55423
  await openUrl(this.getFrontendUrl());
55403
55424
  return true;
55404
55425
  }
55426
+ async stopDockerContainers() {
55427
+ await this.stopAndRemoveContainers(false);
55428
+ }
55405
55429
  async resetDockerContainers() {
55406
- const containers = await this.docker.listContainers({ all: true });
55407
- const genlayerContainers = containers.filter(
55408
- (container) => container.Names.some(
55409
- (name) => name.startsWith(CONTAINERS_NAME_PREFIX)
55410
- )
55411
- );
55412
- for (const containerInfo of genlayerContainers) {
55413
- const container = this.docker.getContainer(containerInfo.Id);
55414
- if (containerInfo.State === "running") {
55415
- await container.stop();
55416
- }
55417
- await container.remove();
55418
- }
55419
- return true;
55430
+ await this.stopAndRemoveContainers(true);
55420
55431
  }
55421
55432
  async resetDockerImages() {
55422
55433
  const images = await this.docker.listImages();
@@ -55427,7 +55438,6 @@ Run npm install -g genlayer to update
55427
55438
  const image = this.docker.getImage(imageInfo.Id);
55428
55439
  await image.remove({ force: true });
55429
55440
  }
55430
- return true;
55431
55441
  }
55432
55442
  async cleanDatabase() {
55433
55443
  try {
@@ -57752,7 +57762,7 @@ async function initAction(options, simulatorService) {
57752
57762
  {
57753
57763
  type: "confirm",
57754
57764
  name: "confirmReset",
57755
- message: `This command is going to reset GenLayer docker images and containers, providers API Keys, and GenLayer database (accounts, transactions, and validators). Do you want to continue?`,
57765
+ message: `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?`,
57756
57766
  default: true
57757
57767
  }
57758
57768
  ]);
@@ -57948,10 +57958,53 @@ async function startAction(options, simulatorService) {
57948
57958
  }
57949
57959
  }
57950
57960
 
57961
+ // src/lib/actions/BaseAction.ts
57962
+ var BaseAction = class {
57963
+ async confirmPrompt(message) {
57964
+ const answer = await lib_default.prompt([
57965
+ {
57966
+ type: "confirm",
57967
+ name: "confirmAction",
57968
+ message,
57969
+ default: true
57970
+ }
57971
+ ]);
57972
+ if (!answer.confirmAction) {
57973
+ console.log("Operation aborted!");
57974
+ process.exit(0);
57975
+ }
57976
+ }
57977
+ };
57978
+
57979
+ // src/commands/general/stop.ts
57980
+ var StopAction = class extends BaseAction {
57981
+ constructor() {
57982
+ super();
57983
+ __publicField(this, "simulatorService");
57984
+ this.simulatorService = new SimulatorService();
57985
+ }
57986
+ async stop() {
57987
+ try {
57988
+ await this.confirmPrompt(
57989
+ "Are you sure you want to stop all running GenLayer containers? This will halt all active processes."
57990
+ );
57991
+ console.log(`Stopping Docker containers...`);
57992
+ await this.simulatorService.stopDockerContainers();
57993
+ console.log(`All running GenLayer containers have been successfully stopped.`);
57994
+ } catch (error) {
57995
+ console.error("An error occurred while stopping the containers:", error);
57996
+ }
57997
+ }
57998
+ };
57999
+
57951
58000
  // src/commands/general/index.ts
57952
58001
  function initializeGeneralCommands(program2) {
57953
58002
  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", localnetCompatibleVersion).action((options) => initAction(options, simulator_default));
57954
58003
  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));
58004
+ program2.command("stop").description("Stop all running localnet services.").action(async () => {
58005
+ const stopAction = new StopAction();
58006
+ await stopAction.stop();
58007
+ });
57955
58008
  return program2;
57956
58009
  }
57957
58010
 
@@ -86824,24 +86877,6 @@ function initializeConfigCommands(program2) {
86824
86877
  return program2;
86825
86878
  }
86826
86879
 
86827
- // src/lib/actions/BaseAction.ts
86828
- var BaseAction = class {
86829
- async confirmPrompt(message) {
86830
- const answer = await lib_default.prompt([
86831
- {
86832
- type: "confirm",
86833
- name: "confirmAction",
86834
- message,
86835
- default: true
86836
- }
86837
- ]);
86838
- if (!answer.confirmAction) {
86839
- console.log("Operation aborted!");
86840
- process.exit(0);
86841
- }
86842
- }
86843
- };
86844
-
86845
86880
  // src/commands/validators/validators.ts
86846
86881
  var ValidatorsAction = class extends BaseAction {
86847
86882
  async getValidator(options) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genlayer",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "description": "GenLayer Command Line Tool",
5
5
  "main": "src/index.ts",
6
6
  "bin": {
@@ -4,6 +4,7 @@ import simulatorService from "../../lib/services/simulator";
4
4
  import { initAction, InitActionOptions } from "./init";
5
5
  import { startAction, StartActionOptions } from "./start";
6
6
  import {localnetCompatibleVersion} from "../../lib/config/simulator";
7
+ import {StopAction} from "./stop";
7
8
 
8
9
  export function initializeGeneralCommands(program: Command) {
9
10
  program
@@ -24,5 +25,13 @@ export function initializeGeneralCommands(program: Command) {
24
25
  .option("--reset-db", "Reset Database", false)
25
26
  .action((options: StartActionOptions) => startAction(options, simulatorService));
26
27
 
28
+ program
29
+ .command("stop")
30
+ .description("Stop all running localnet services.")
31
+ .action(async () => {
32
+ const stopAction = new StopAction();
33
+ await stopAction.stop();
34
+ });
35
+
27
36
  return program;
28
37
  }
@@ -77,7 +77,7 @@ export async function initAction(options: InitActionOptions, simulatorService: I
77
77
  {
78
78
  type: "confirm",
79
79
  name: "confirmReset",
80
- message: `This command is going to reset GenLayer docker images and containers, providers API Keys, and GenLayer database (accounts, transactions, and validators). Do you want to continue?`,
80
+ message: `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?`,
81
81
  default: true,
82
82
  },
83
83
  ]);
@@ -0,0 +1,25 @@
1
+ import { BaseAction } from "../../lib/actions/BaseAction";
2
+ import { SimulatorService } from "../../lib/services/simulator";
3
+ import { ISimulatorService } from "../../lib/interfaces/ISimulatorService";
4
+
5
+ export class StopAction extends BaseAction {
6
+ private simulatorService: ISimulatorService;
7
+
8
+ constructor() {
9
+ super();
10
+ this.simulatorService = new SimulatorService();
11
+ }
12
+
13
+ public async stop(): Promise<void> {
14
+ try{
15
+ await this.confirmPrompt(
16
+ "Are you sure you want to stop all running GenLayer containers? This will halt all active processes."
17
+ );
18
+ console.log(`Stopping Docker containers...`);
19
+ await this.simulatorService.stopDockerContainers();
20
+ console.log(`All running GenLayer containers have been successfully stopped.`);
21
+ }catch (error) {
22
+ console.error("An error occurred while stopping the containers:", error)
23
+ }
24
+ }
25
+ }
@@ -12,8 +12,9 @@ export interface ISimulatorService {
12
12
  getAiProvidersOptions(withHint: boolean): Array<{name: string; value: string}>;
13
13
  getFrontendUrl(): string;
14
14
  openFrontend(): Promise<boolean>;
15
- resetDockerContainers(): Promise<boolean>;
16
- resetDockerImages(): Promise<boolean>;
15
+ stopDockerContainers(): Promise<void>;
16
+ resetDockerContainers(): Promise<void>;
17
+ resetDockerImages(): Promise<void>;
17
18
  checkCliVersion(): Promise<void>;
18
19
  cleanDatabase(): Promise<boolean>;
19
20
  addConfigToEnvFile(newConfig: Record<string, string>): void;
@@ -1,4 +1,4 @@
1
- import Docker from "dockerode"
1
+ import Docker, {ContainerInfo} from "dockerode";
2
2
  import * as fs from "fs";
3
3
  import * as dotenv from "dotenv";
4
4
  import * as path from "path";
@@ -48,6 +48,39 @@ export class SimulatorService implements ISimulatorService {
48
48
  this.docker = new Docker();
49
49
  }
50
50
 
51
+ private readEnvConfigValue(key: string): string {
52
+ const envFilePath = path.join(this.location, ".env");
53
+ // Transform the config string to object
54
+ const envConfig = dotenv.parse(fs.readFileSync(envFilePath, "utf8"));
55
+ return envConfig[key];
56
+ }
57
+
58
+ private async getGenlayerContainers(): Promise<ContainerInfo[]> {
59
+ const containers = await this.docker.listContainers({ all: true });
60
+ return containers.filter(container =>
61
+ container.Names.some(name =>
62
+ name.startsWith(CONTAINERS_NAME_PREFIX) || name.includes("ollama")
63
+ )
64
+ );
65
+ }
66
+
67
+ private async stopAndRemoveContainers(remove: boolean = false): Promise<void> {
68
+ const genlayerContainers = await this.getGenlayerContainers();
69
+
70
+ for (const containerInfo of genlayerContainers) {
71
+ const container = this.docker.getContainer(containerInfo.Id);
72
+ if (containerInfo.State === "running") {
73
+ await container.stop();
74
+ }
75
+
76
+ const isOllamaContainer = containerInfo.Names.some(name => name.includes("ollama"));
77
+
78
+ if (remove && !isOllamaContainer) {
79
+ await container.remove();
80
+ }
81
+ }
82
+ }
83
+
51
84
  public addConfigToEnvFile(newConfig: Record<string, string>): void {
52
85
  const envFilePath = path.join(this.location, ".env");
53
86
 
@@ -76,13 +109,6 @@ export class SimulatorService implements ISimulatorService {
76
109
  return this.composeOptions;
77
110
  }
78
111
 
79
- private readEnvConfigValue(key: string): string {
80
- const envFilePath = path.join(this.location, ".env");
81
- // Transform the config string to object
82
- const envConfig = dotenv.parse(fs.readFileSync(envFilePath, "utf8"));
83
- return envConfig[key];
84
- }
85
-
86
112
  public async checkCliVersion(): Promise<void> {
87
113
  const update = await updateCheck(pkg);
88
114
  if (update && update.latest !== pkg.version) {
@@ -218,25 +244,15 @@ export class SimulatorService implements ISimulatorService {
218
244
  return true;
219
245
  }
220
246
 
221
- public async resetDockerContainers(): Promise<boolean> {
222
- const containers = await this.docker.listContainers({ all: true });
223
- const genlayerContainers = containers.filter(container =>
224
- container.Names.some(name =>
225
- name.startsWith(CONTAINERS_NAME_PREFIX)
226
- )
227
- );
247
+ public async stopDockerContainers(): Promise<void> {
248
+ await this.stopAndRemoveContainers(false);
249
+ }
228
250
 
229
- for (const containerInfo of genlayerContainers) {
230
- const container = this.docker.getContainer(containerInfo.Id);
231
- if (containerInfo.State === "running") {
232
- await container.stop();
233
- }
234
- await container.remove();
235
- }
236
- return true;
251
+ public async resetDockerContainers(): Promise<void> {
252
+ await this.stopAndRemoveContainers(true);
237
253
  }
238
254
 
239
- public async resetDockerImages(): Promise<boolean> {
255
+ public async resetDockerImages(): Promise<void> {
240
256
  const images = await this.docker.listImages();
241
257
  const genlayerImages = images.filter(image =>
242
258
  image.RepoTags?.some(tag => tag.startsWith(IMAGES_NAME_PREFIX))
@@ -246,8 +262,6 @@ export class SimulatorService implements ISimulatorService {
246
262
  const image = this.docker.getImage(imageInfo.Id);
247
263
  await image.remove({force: true});
248
264
  }
249
-
250
- return true;
251
265
  }
252
266
 
253
267
  public async cleanDatabase(): Promise<boolean> {
@@ -0,0 +1,58 @@
1
+ import { describe, test, vi, beforeEach, afterEach, expect } from "vitest";
2
+ import { StopAction } from "../../src/commands/general/stop";
3
+ import { SimulatorService } from "../../src/lib/services/simulator";
4
+ import { ISimulatorService } from "../../src/lib/interfaces/ISimulatorService";
5
+ import inquirer from "inquirer";
6
+
7
+ vi.mock("../../src/lib/services/simulator");
8
+ vi.mock("inquirer");
9
+
10
+ describe("StopAction", () => {
11
+ let stopAction: StopAction;
12
+ let mockSimulatorService: ISimulatorService;
13
+
14
+ beforeEach(() => {
15
+ vi.clearAllMocks();
16
+
17
+ mockSimulatorService = {
18
+ stopDockerContainers: vi.fn(),
19
+ } as unknown as ISimulatorService;
20
+
21
+ SimulatorService.prototype.stopDockerContainers = mockSimulatorService.stopDockerContainers;
22
+
23
+ stopAction = new StopAction();
24
+ (stopAction as any).simulatorService = mockSimulatorService;
25
+ });
26
+
27
+ afterEach(() => {
28
+ vi.restoreAllMocks();
29
+ });
30
+
31
+ test("should stop containers if user confirms", async () => {
32
+ vi.mocked(inquirer.prompt).mockResolvedValue({ confirmAction: true });
33
+
34
+ await stopAction.stop();
35
+
36
+ expect(inquirer.prompt).toHaveBeenCalledWith([
37
+ {
38
+ type: "confirm",
39
+ name: "confirmAction",
40
+ message: "Are you sure you want to stop all running GenLayer containers? This will halt all active processes.",
41
+ default: true,
42
+ },
43
+ ]);
44
+ expect(mockSimulatorService.stopDockerContainers).toHaveBeenCalled();
45
+ });
46
+
47
+ test("should abort if user cancels", async () => {
48
+ vi.mocked(inquirer.prompt).mockResolvedValue({ confirmAction: false });
49
+
50
+ console.log = vi.fn();
51
+
52
+ await stopAction.stop();
53
+
54
+ expect(inquirer.prompt).toHaveBeenCalled();
55
+ expect(console.log).toHaveBeenCalledWith("Operation aborted!");
56
+ expect(mockSimulatorService.stopDockerContainers).not.toHaveBeenCalled();
57
+ });
58
+ });
@@ -0,0 +1,27 @@
1
+ import { Command } from "commander";
2
+ import { vi, describe, beforeEach, afterEach, test, expect } from "vitest";
3
+ import { initializeGeneralCommands } from "../../src/commands/general";
4
+ import { StopAction } from "../../src/commands/general/stop";
5
+
6
+ vi.mock("../../src/commands/general/stop");
7
+
8
+ describe("stop command", () => {
9
+ let program: Command;
10
+
11
+ beforeEach(() => {
12
+ program = new Command();
13
+ initializeGeneralCommands(program);
14
+
15
+ vi.clearAllMocks();
16
+ });
17
+
18
+ afterEach(() => {
19
+ vi.restoreAllMocks();
20
+ });
21
+
22
+ test("doesn't require arguments or options", async () => {
23
+ expect(() => program.parse(["node", "test", "stop"])).not.toThrow();
24
+ expect(StopAction).toHaveBeenCalledTimes(1);
25
+ expect(StopAction.prototype.stop).toHaveBeenCalledWith();
26
+ });
27
+ });
@@ -329,7 +329,7 @@ describe("SimulatorService - Docker Tests", () => {
329
329
 
330
330
  const result = await simulatorService.resetDockerContainers();
331
331
 
332
- expect(result).toBe(true);
332
+ expect(result).toBe(undefined);
333
333
  expect(mockListContainers).toHaveBeenCalledWith({ all: true });
334
334
 
335
335
  // Ensure only the relevant containers were stopped and removed
@@ -341,6 +341,36 @@ describe("SimulatorService - Docker Tests", () => {
341
341
  expect(mockRemove).toHaveBeenCalledTimes(2);
342
342
  });
343
343
 
344
+ test("should stop all running GenLayer containers", async () => {
345
+ const mockContainers = [
346
+ {
347
+ Id: "container1",
348
+ Names: [`${CONTAINERS_NAME_PREFIX}container1`],
349
+ State: "running",
350
+ },
351
+ {
352
+ Id: "container2",
353
+ Names: [`${CONTAINERS_NAME_PREFIX}container2`],
354
+ State: "exited",
355
+ },
356
+ ];
357
+
358
+ vi.mocked(Docker.prototype.listContainers).mockResolvedValue(mockContainers as any);
359
+
360
+ const mockStop = vi.fn().mockResolvedValue(undefined);
361
+ const mockGetContainer = vi.mocked(Docker.prototype.getContainer);
362
+ mockGetContainer.mockImplementation(() => ({
363
+ stop: mockStop,
364
+ } as unknown as Docker.Container));
365
+
366
+ await simulatorService.stopDockerContainers();
367
+
368
+ expect(mockGetContainer).toHaveBeenCalledWith("container1");
369
+ expect(mockGetContainer).toHaveBeenCalledWith("container2");
370
+ expect(mockStop).toHaveBeenCalledTimes(1);
371
+ });
372
+
373
+
344
374
  test("should remove Docker images with the specified prefix", async () => {
345
375
  const mockImages = [
346
376
  {
@@ -366,7 +396,7 @@ describe("SimulatorService - Docker Tests", () => {
366
396
 
367
397
  const result = await simulatorService.resetDockerImages();
368
398
 
369
- expect(result).toBe(true);
399
+ expect(result).toBe(undefined);
370
400
  expect(mockListImages).toHaveBeenCalled();
371
401
  expect(mockGetImage).toHaveBeenCalledWith("image1");
372
402
  expect(mockGetImage).toHaveBeenCalledWith("image2");
package/vitest.config.ts CHANGED
@@ -4,6 +4,7 @@ export default defineConfig({
4
4
  test: {
5
5
  globals: true,
6
6
  environment: 'jsdom',
7
+ testTimeout: 10000,
7
8
  coverage: {
8
9
  exclude: [...configDefaults.exclude, '*.js', 'tests/**/*.ts', 'src/types', 'scripts'],
9
10
  }