genlayer 0.4.0 → 0.5.1-beta.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 +71 -0
- package/CHANGELOG.md +14 -0
- package/dist/index.js +1372 -412
- package/docker-compose.yml +144 -0
- package/package.json +4 -2
- package/scripts/postinstall.js +18 -0
- package/src/commands/general/index.ts +0 -4
- package/src/commands/general/init.ts +6 -43
- package/src/commands/general/start.ts +3 -16
- package/src/lib/config/simulator.ts +5 -6
- package/src/lib/interfaces/ISimulatorService.ts +2 -8
- package/src/lib/services/simulator.ts +57 -90
- package/tests/actions/init.test.ts +25 -84
- package/tests/actions/start.test.ts +2 -17
- package/tests/commands/init.test.ts +0 -17
- package/tests/commands/up.test.ts +1 -13
- package/tests/services/simulator.test.ts +159 -65
- package/vitest.config.ts +1 -1
|
@@ -12,8 +12,6 @@ import {
|
|
|
12
12
|
import {
|
|
13
13
|
DOCKER_IMAGES_AND_CONTAINERS_NAME_PREFIX,
|
|
14
14
|
VERSION_REQUIREMENTS,
|
|
15
|
-
} from "../../src/lib/config/simulator";
|
|
16
|
-
import {
|
|
17
15
|
STARTING_TIMEOUT_ATTEMPTS,
|
|
18
16
|
DEFAULT_RUN_SIMULATOR_COMMAND,
|
|
19
17
|
} from "../../src/lib/config/simulator";
|
|
@@ -21,7 +19,15 @@ import { rpcClient } from "../../src/lib/clients/jsonRpcClient";
|
|
|
21
19
|
import * as semver from "semver";
|
|
22
20
|
import Docker from "dockerode";
|
|
23
21
|
import {VersionRequiredError} from "../../src/lib/errors/versionRequired";
|
|
22
|
+
import updateCheck from "update-check";
|
|
24
23
|
|
|
24
|
+
vi.mock("../../package.json", () => ({
|
|
25
|
+
default: { version: "1.0.0", name: "genlayer" },
|
|
26
|
+
}));
|
|
27
|
+
|
|
28
|
+
vi.mock("update-check", () => ({
|
|
29
|
+
default: vi.fn(),
|
|
30
|
+
}));
|
|
25
31
|
vi.mock("dockerode");
|
|
26
32
|
vi.mock("fs");
|
|
27
33
|
vi.mock("path");
|
|
@@ -51,23 +57,14 @@ describe("SimulatorService - Basic Tests", () => {
|
|
|
51
57
|
vi.mocked(path.join).mockImplementation((...args) => args.join("/"));
|
|
52
58
|
});
|
|
53
59
|
|
|
54
|
-
test("should return the correct simulator location path", () => {
|
|
55
|
-
const expectedPath = "/mock/home/genlayer-simulator";
|
|
56
|
-
simulatorService.setSimulatorLocation("/mock/home/genlayer-simulator");
|
|
57
|
-
const simulatorLocation = simulatorService.getSimulatorLocation();
|
|
58
|
-
expect(simulatorLocation).toBe(expectedPath);
|
|
59
|
-
});
|
|
60
60
|
|
|
61
61
|
test("should read the correct frontend URL from .env config", () => {
|
|
62
|
-
const mockEnvFilePath = "/mock/home/genlayer-simulator/.env";
|
|
63
62
|
const mockEnvContent = "FRONTEND_PORT=8080";
|
|
64
63
|
const mockEnvConfig = { FRONTEND_PORT: "8080" };
|
|
65
64
|
vi.mocked(fs.readFileSync).mockReturnValue(mockEnvContent);
|
|
66
65
|
vi.mocked(dotenv.parse).mockReturnValue(mockEnvConfig);
|
|
67
|
-
simulatorService.setSimulatorLocation("/mock/home/genlayer-simulator");
|
|
68
66
|
const frontendUrl = simulatorService.getFrontendUrl();
|
|
69
67
|
expect(frontendUrl).toBe("http://localhost:8080");
|
|
70
|
-
expect(fs.readFileSync).toHaveBeenCalledWith(mockEnvFilePath, "utf8");
|
|
71
68
|
});
|
|
72
69
|
|
|
73
70
|
test("should check version requirements and return missing versions", async () => {
|
|
@@ -87,20 +84,6 @@ describe("SimulatorService - Basic Tests", () => {
|
|
|
87
84
|
await expect(simulatorService.checkVersion("14.0.0", "node")).rejects.toThrow();
|
|
88
85
|
});
|
|
89
86
|
|
|
90
|
-
test("should download simulator if not already installed", async () => {
|
|
91
|
-
const result = await simulatorService.downloadSimulator();
|
|
92
|
-
expect(result.wasInstalled).toBe(false);
|
|
93
|
-
expect(executeCommand).toHaveBeenCalled();
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
test("should skip download if simulator is already installed", async () => {
|
|
97
|
-
vi.mocked(executeCommand).mockRejectedValueOnce(new Error("Mocked command error"));
|
|
98
|
-
vi.spyOn(fs, "existsSync").mockReturnValue(true);
|
|
99
|
-
const result = await simulatorService.downloadSimulator();
|
|
100
|
-
expect(result.wasInstalled).toBe(true);
|
|
101
|
-
expect(executeCommand).toHaveBeenCalled();
|
|
102
|
-
expect(fs.existsSync).toHaveBeenCalled();
|
|
103
|
-
});
|
|
104
87
|
|
|
105
88
|
test("should return initialized true when simulator responds with OK (result.status = OK)", async () => {
|
|
106
89
|
vi.mocked(rpcClient.request).mockResolvedValueOnce({ result: {status: 'OK'} });
|
|
@@ -142,7 +125,7 @@ describe("SimulatorService - Basic Tests", () => {
|
|
|
142
125
|
stderr: "",
|
|
143
126
|
});
|
|
144
127
|
const result = await simulatorService.runSimulator();
|
|
145
|
-
const expectedCommand = DEFAULT_RUN_SIMULATOR_COMMAND(
|
|
128
|
+
const expectedCommand = DEFAULT_RUN_SIMULATOR_COMMAND(simulatorService.location, '');
|
|
146
129
|
expect(executeCommand).toHaveBeenCalledWith(expectedCommand);
|
|
147
130
|
expect(result).toEqual({ stdout: "Simulator started", stderr: "" });
|
|
148
131
|
});
|
|
@@ -155,11 +138,57 @@ describe("SimulatorService - Basic Tests", () => {
|
|
|
155
138
|
simulatorService.setComposeOptions(true)
|
|
156
139
|
const commandOption = simulatorService.getComposeOptions();
|
|
157
140
|
const result = await simulatorService.runSimulator();
|
|
158
|
-
const expectedCommand = DEFAULT_RUN_SIMULATOR_COMMAND(
|
|
141
|
+
const expectedCommand = DEFAULT_RUN_SIMULATOR_COMMAND(simulatorService.location, commandOption);
|
|
159
142
|
expect(executeCommand).toHaveBeenCalledWith(expectedCommand);
|
|
160
143
|
expect(result).toEqual({ stdout: "Simulator started", stderr: "" });
|
|
161
144
|
});
|
|
162
145
|
|
|
146
|
+
test("should create a backup of the .env file and add new config", () => {
|
|
147
|
+
const envFilePath = `/.env`;
|
|
148
|
+
const originalEnvContent = "KEY1=value1\nKEY2=value2";
|
|
149
|
+
const parsedEnvConfig = { KEY1: "value1", KEY2: "value2" };
|
|
150
|
+
const newConfig = { KEY3: "value3", KEY2: "newValue2" };
|
|
151
|
+
|
|
152
|
+
vi.mocked(fs.readFileSync).mockImplementation((filePath: any) => {
|
|
153
|
+
if (filePath === envFilePath) return originalEnvContent;
|
|
154
|
+
return "";
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
vi.mocked(dotenv.parse).mockReturnValue(parsedEnvConfig);
|
|
158
|
+
const writeFileSyncMock = vi.mocked(fs.writeFileSync);
|
|
159
|
+
|
|
160
|
+
simulatorService.addConfigToEnvFile(newConfig);
|
|
161
|
+
|
|
162
|
+
const expectedUpdatedContent = `KEY1=value1\nKEY2=newValue2\nKEY3=value3`;
|
|
163
|
+
expect(writeFileSyncMock).toHaveBeenCalledWith(envFilePath, expectedUpdatedContent);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test("should handle empty .env file and add new config", () => {
|
|
167
|
+
const envFilePath = `/.env`;
|
|
168
|
+
const newConfig = { NEW_KEY: "newValue" };
|
|
169
|
+
|
|
170
|
+
vi.mocked(fs.readFileSync).mockReturnValue("");
|
|
171
|
+
vi.mocked(dotenv.parse).mockReturnValue({});
|
|
172
|
+
const writeFileSyncMock = vi.mocked(fs.writeFileSync);
|
|
173
|
+
|
|
174
|
+
simulatorService.addConfigToEnvFile(newConfig);
|
|
175
|
+
|
|
176
|
+
expect(writeFileSyncMock).toHaveBeenCalledWith(`${envFilePath}.bak`, "");
|
|
177
|
+
const expectedUpdatedContent = `NEW_KEY=newValue`;
|
|
178
|
+
expect(writeFileSyncMock).toHaveBeenCalledWith(envFilePath, expectedUpdatedContent);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test("should throw error when .env file does not exist", () => {
|
|
182
|
+
vi.mocked(fs.readFileSync).mockImplementation(() => {
|
|
183
|
+
throw new Error("File not found");
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
expect(() => simulatorService.addConfigToEnvFile({ KEY: "value" })).toThrow(
|
|
187
|
+
"File not found"
|
|
188
|
+
);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
|
|
163
192
|
test("should open the frontend URL and return true", async () => {
|
|
164
193
|
vi.spyOn(simulatorService, "getFrontendUrl").mockReturnValue("http://localhost:8080");
|
|
165
194
|
const result = await simulatorService.openFrontend();
|
|
@@ -234,47 +263,14 @@ describe("SimulatorService - Basic Tests", () => {
|
|
|
234
263
|
expect(result).toEqual({ initialized: false, errorCode: "ERROR", errorMessage: fetchError.message });
|
|
235
264
|
});
|
|
236
265
|
|
|
237
|
-
test("should throw an error if executeCommand fails and simulator location does not exist", async () => {
|
|
238
|
-
const mockError = new Error("git clone failed");
|
|
239
|
-
vi.mocked(executeCommand).mockRejectedValueOnce(mockError);
|
|
240
|
-
vi.mocked(fs.existsSync).mockReturnValue(false);
|
|
241
|
-
await expect(simulatorService.downloadSimulator()).rejects.toThrow("git clone failed");
|
|
242
|
-
expect(executeCommand).toHaveBeenCalled();
|
|
243
|
-
expect(fs.existsSync).toHaveBeenCalledWith("/mock/home/genlayer-simulator");
|
|
244
|
-
});
|
|
245
|
-
|
|
246
266
|
test("should call executeCommand if docker ps command fails", async () => {
|
|
247
267
|
vi.mocked(checkCommand)
|
|
248
268
|
.mockResolvedValueOnce(undefined)
|
|
249
269
|
|
|
250
|
-
|
|
251
270
|
const result = await simulatorService.checkInstallRequirements();
|
|
252
271
|
expect(result.docker).toBe(true);
|
|
253
|
-
expect(result.git).toBe(true);
|
|
254
272
|
});
|
|
255
273
|
|
|
256
|
-
test("should update envConfig with newConfig values", () => {
|
|
257
|
-
const envFilePath = path.join("/mock/home/genlayer-simulator", ".env");
|
|
258
|
-
const originalEnvContent = "KEY1=value1\nKEY2=value2";
|
|
259
|
-
const envConfig = { KEY1: "value1", KEY2: "value2" };
|
|
260
|
-
const newConfig = { KEY2: "new_value2", KEY3: "value3" };
|
|
261
|
-
vi.mocked(fs.readFileSync)
|
|
262
|
-
.mockReturnValueOnce(originalEnvContent)
|
|
263
|
-
.mockReturnValueOnce(originalEnvContent);
|
|
264
|
-
vi.mocked(dotenv.parse).mockReturnValue(envConfig);
|
|
265
|
-
const writeFileSyncSpy = vi.spyOn(fs, "writeFileSync");
|
|
266
|
-
simulatorService["addConfigToEnvFile"](newConfig);
|
|
267
|
-
expect(envConfig).toEqual({
|
|
268
|
-
KEY1: "value1",
|
|
269
|
-
KEY2: "new_value2",
|
|
270
|
-
KEY3: "value3",
|
|
271
|
-
});
|
|
272
|
-
expect(writeFileSyncSpy).toHaveBeenCalledWith(`${envFilePath}.bak`, originalEnvContent);
|
|
273
|
-
expect(writeFileSyncSpy).toHaveBeenCalledWith(
|
|
274
|
-
envFilePath,
|
|
275
|
-
"KEY1=value1\nKEY2=new_value2\nKEY3=value3"
|
|
276
|
-
);
|
|
277
|
-
});
|
|
278
274
|
|
|
279
275
|
test("should return providers without errors", () => {
|
|
280
276
|
expect(simulatorService.getAiProvidersOptions(true)).toEqual(expect.any(Array));
|
|
@@ -306,8 +302,60 @@ describe("SimulatorService - Docker Tests", () => {
|
|
|
306
302
|
mockPing = vi.mocked(Docker.prototype.ping);
|
|
307
303
|
});
|
|
308
304
|
|
|
309
|
-
test("should
|
|
310
|
-
|
|
305
|
+
test("should handle errors during the execution of pullOllamaModel gracefully", async () => {
|
|
306
|
+
const mockExec = vi.fn();
|
|
307
|
+
const mockStart = vi.fn();
|
|
308
|
+
|
|
309
|
+
mockExec.mockResolvedValue({
|
|
310
|
+
start: mockStart,
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
const mockStream = {
|
|
314
|
+
on: vi.fn((event, callback) => {
|
|
315
|
+
if (event === "data") callback("Mock data chunk");
|
|
316
|
+
if (event === "error") callback(new Error("Mock error during stream"));
|
|
317
|
+
}),
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
mockStart.mockResolvedValue(mockStream);
|
|
321
|
+
|
|
322
|
+
mockGetContainer.mockReturnValueOnce({
|
|
323
|
+
exec: mockExec,
|
|
324
|
+
} as unknown as Docker.Container);
|
|
325
|
+
|
|
326
|
+
const result = await simulatorService.pullOllamaModel();
|
|
327
|
+
|
|
328
|
+
expect(result).toBe(false);
|
|
329
|
+
expect(mockGetContainer).toHaveBeenCalledWith("ollama");
|
|
330
|
+
expect(mockExec).toHaveBeenCalledWith({
|
|
331
|
+
Cmd: ["ollama", "pull", "llama3"],
|
|
332
|
+
AttachStdout: true,
|
|
333
|
+
AttachStderr: true,
|
|
334
|
+
});
|
|
335
|
+
expect(mockStart).toHaveBeenCalledWith({ Detach: false, Tty: false });
|
|
336
|
+
expect(mockStream.on).toHaveBeenCalledWith("error", expect.any(Function));
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
test("should successfully execute pullOllamaModel and return true", async () => {
|
|
340
|
+
const mockExec = vi.fn();
|
|
341
|
+
const mockStart = vi.fn();
|
|
342
|
+
|
|
343
|
+
mockExec.mockResolvedValue({
|
|
344
|
+
start: mockStart,
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
const mockStream = {
|
|
348
|
+
on: vi.fn((event, callback) => {
|
|
349
|
+
if (event === "data") callback("Mock data chunk");
|
|
350
|
+
if (event === "end") callback();
|
|
351
|
+
}),
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
mockStart.mockResolvedValue(mockStream);
|
|
355
|
+
|
|
356
|
+
mockGetContainer.mockReturnValueOnce({
|
|
357
|
+
exec: mockExec,
|
|
358
|
+
} as unknown as Docker.Container);
|
|
311
359
|
|
|
312
360
|
const result = await simulatorService.pullOllamaModel();
|
|
313
361
|
|
|
@@ -315,9 +363,15 @@ describe("SimulatorService - Docker Tests", () => {
|
|
|
315
363
|
expect(mockGetContainer).toHaveBeenCalledWith("ollama");
|
|
316
364
|
expect(mockExec).toHaveBeenCalledWith({
|
|
317
365
|
Cmd: ["ollama", "pull", "llama3"],
|
|
366
|
+
AttachStdout: true,
|
|
367
|
+
AttachStderr: true,
|
|
318
368
|
});
|
|
369
|
+
expect(mockStart).toHaveBeenCalledWith({ Detach: false, Tty: false });
|
|
370
|
+
expect(mockStream.on).toHaveBeenCalledWith("data", expect.any(Function));
|
|
371
|
+
expect(mockStream.on).toHaveBeenCalledWith("end", expect.any(Function));
|
|
319
372
|
});
|
|
320
373
|
|
|
374
|
+
|
|
321
375
|
test("should stop and remove Docker containers with the specified prefix", async () => {
|
|
322
376
|
const mockContainers = [
|
|
323
377
|
{
|
|
@@ -403,11 +457,51 @@ describe("SimulatorService - Docker Tests", () => {
|
|
|
403
457
|
expect(executeCommand).toHaveBeenCalledTimes(1);
|
|
404
458
|
});
|
|
405
459
|
|
|
406
|
-
test("should
|
|
460
|
+
test("should call execute command again to start docker service", async () => {
|
|
407
461
|
vi.mocked(checkCommand)
|
|
408
462
|
.mockResolvedValueOnce(undefined)
|
|
409
463
|
.mockRejectedValue(undefined);
|
|
410
|
-
mockPing.mockRejectedValueOnce("
|
|
411
|
-
await expect(simulatorService.checkInstallRequirements()).
|
|
464
|
+
mockPing.mockRejectedValueOnce("");
|
|
465
|
+
await expect(simulatorService.checkInstallRequirements()).resolves.toStrictEqual({ docker: true });
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
test("should warn the user when an update is available", async () => {
|
|
469
|
+
const update = { latest: "1.1.0" };
|
|
470
|
+
(updateCheck as any).mockResolvedValue(update);
|
|
471
|
+
|
|
472
|
+
const consoleWarnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
473
|
+
|
|
474
|
+
await simulatorService.checkCliVersion();
|
|
475
|
+
|
|
476
|
+
expect(consoleWarnSpy).toHaveBeenCalledWith(
|
|
477
|
+
`\nA new version (${update.latest}) is available! You're using version 1.0.0.\nRun npm install -g genlayer to update\n`
|
|
478
|
+
);
|
|
479
|
+
|
|
480
|
+
consoleWarnSpy.mockRestore();
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
test("should not warn the user when the CLI is up-to-date", async () => {
|
|
484
|
+
const update = { latest: "1.0.0" };
|
|
485
|
+
(updateCheck as any).mockResolvedValue(update);
|
|
486
|
+
|
|
487
|
+
const consoleWarnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
488
|
+
|
|
489
|
+
await simulatorService.checkCliVersion();
|
|
490
|
+
|
|
491
|
+
expect(consoleWarnSpy).not.toHaveBeenCalled();
|
|
492
|
+
|
|
493
|
+
consoleWarnSpy.mockRestore();
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
test("should handle update-check returning undefined", async () => {
|
|
497
|
+
(updateCheck as any).mockResolvedValue(undefined);
|
|
498
|
+
|
|
499
|
+
const consoleWarnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
500
|
+
|
|
501
|
+
await simulatorService.checkCliVersion();
|
|
502
|
+
|
|
503
|
+
expect(consoleWarnSpy).not.toHaveBeenCalled();
|
|
504
|
+
|
|
505
|
+
consoleWarnSpy.mockRestore();
|
|
412
506
|
});
|
|
413
507
|
});
|
package/vitest.config.ts
CHANGED
|
@@ -5,7 +5,7 @@ export default defineConfig({
|
|
|
5
5
|
globals: true,
|
|
6
6
|
environment: 'jsdom',
|
|
7
7
|
coverage: {
|
|
8
|
-
exclude: [...configDefaults.exclude, '*.js', 'tests/**/*.ts', 'src/types'],
|
|
8
|
+
exclude: [...configDefaults.exclude, '*.js', 'tests/**/*.ts', 'src/types', 'scripts'],
|
|
9
9
|
}
|
|
10
10
|
}
|
|
11
11
|
});
|