mindsim 0.1.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.
Files changed (79) hide show
  1. package/.github/workflows/publish.yml +32 -0
  2. package/.github/workflows/test.yml +28 -0
  3. package/.github/workflows/type-checks.yml +29 -0
  4. package/LICENSE +21 -0
  5. package/README.md +748 -0
  6. package/assets/mindsim-logo.svg +15 -0
  7. package/biome.jsonc +43 -0
  8. package/dist/auth.d.ts +5 -0
  9. package/dist/auth.d.ts.map +1 -0
  10. package/dist/auth.js +115 -0
  11. package/dist/auth.js.map +1 -0
  12. package/dist/cli.d.ts +3 -0
  13. package/dist/cli.d.ts.map +1 -0
  14. package/dist/cli.js +36 -0
  15. package/dist/cli.js.map +1 -0
  16. package/dist/config.d.ts +8 -0
  17. package/dist/config.d.ts.map +1 -0
  18. package/dist/config.js +63 -0
  19. package/dist/config.js.map +1 -0
  20. package/dist/index.d.ts +20 -0
  21. package/dist/index.d.ts.map +1 -0
  22. package/dist/index.js +71 -0
  23. package/dist/index.js.map +1 -0
  24. package/dist/resources/artifacts.d.ts +8 -0
  25. package/dist/resources/artifacts.d.ts.map +1 -0
  26. package/dist/resources/artifacts.js +17 -0
  27. package/dist/resources/artifacts.js.map +1 -0
  28. package/dist/resources/minds.d.ts +39 -0
  29. package/dist/resources/minds.d.ts.map +1 -0
  30. package/dist/resources/minds.js +85 -0
  31. package/dist/resources/minds.js.map +1 -0
  32. package/dist/resources/psychometrics.d.ts +11 -0
  33. package/dist/resources/psychometrics.d.ts.map +1 -0
  34. package/dist/resources/psychometrics.js +18 -0
  35. package/dist/resources/psychometrics.js.map +1 -0
  36. package/dist/resources/simulations.d.ts +26 -0
  37. package/dist/resources/simulations.d.ts.map +1 -0
  38. package/dist/resources/simulations.js +41 -0
  39. package/dist/resources/simulations.js.map +1 -0
  40. package/dist/resources/snapshots.d.ts +27 -0
  41. package/dist/resources/snapshots.d.ts.map +1 -0
  42. package/dist/resources/snapshots.js +101 -0
  43. package/dist/resources/snapshots.js.map +1 -0
  44. package/dist/resources/tags.d.ts +23 -0
  45. package/dist/resources/tags.d.ts.map +1 -0
  46. package/dist/resources/tags.js +32 -0
  47. package/dist/resources/tags.js.map +1 -0
  48. package/dist/types.d.ts +161 -0
  49. package/dist/types.d.ts.map +1 -0
  50. package/dist/types.js +3 -0
  51. package/dist/types.js.map +1 -0
  52. package/dist/version.d.ts +15 -0
  53. package/dist/version.d.ts.map +1 -0
  54. package/dist/version.js +105 -0
  55. package/dist/version.js.map +1 -0
  56. package/package.json +55 -0
  57. package/src/auth.ts +131 -0
  58. package/src/cli.ts +41 -0
  59. package/src/config.ts +60 -0
  60. package/src/index.ts +59 -0
  61. package/src/resources/artifacts.ts +13 -0
  62. package/src/resources/minds.ts +98 -0
  63. package/src/resources/psychometrics.ts +16 -0
  64. package/src/resources/simulations.ts +49 -0
  65. package/src/resources/snapshots.ts +126 -0
  66. package/src/resources/tags.ts +30 -0
  67. package/src/types.ts +185 -0
  68. package/src/version.ts +111 -0
  69. package/tests/auth.test.ts +41 -0
  70. package/tests/config.test.ts +129 -0
  71. package/tests/resources/minds.test.ts +119 -0
  72. package/tests/resources/psychometrics.test.ts +38 -0
  73. package/tests/resources/simulation.test.ts +94 -0
  74. package/tests/resources/snapshots.test.ts +135 -0
  75. package/tests/resources/tags.test.ts +87 -0
  76. package/tests/use-cases/quickstart.test.ts +84 -0
  77. package/tests/version.test.ts +221 -0
  78. package/tsconfig.json +29 -0
  79. package/vitest.config.ts +12 -0
@@ -0,0 +1,94 @@
1
+ import axios from "axios";
2
+ import { beforeEach, describe, expect, it, type Mocked, vi } from "vitest";
3
+ import { MindSim } from "../../src/index";
4
+
5
+ vi.mock("axios");
6
+ const mockedAxios = axios as Mocked<typeof axios>;
7
+
8
+ describe("MindSim Simulations Resource", () => {
9
+ let mindsim: MindSim;
10
+ const MIND_ID = "368fcac9-27d8-4cc1-b479-5b9601110086";
11
+ const SIM_ID = "123e4567-e89b-12d3-a456-426614174000";
12
+
13
+ const mockApiClient = {
14
+ get: vi.fn(),
15
+ post: vi.fn(),
16
+ delete: vi.fn(),
17
+ defaults: { headers: { common: {} } },
18
+ interceptors: { request: { use: vi.fn() }, response: { use: vi.fn() } },
19
+ };
20
+
21
+ beforeEach(() => {
22
+ mockedAxios.create.mockReturnValue(mockApiClient as any);
23
+ mindsim = new MindSim("fake-key");
24
+ });
25
+
26
+ it("run() should post payload to simulations endpoint", async () => {
27
+ const mockResponse = { message: "I would use Python." };
28
+ mockApiClient.post.mockResolvedValue({ data: mockResponse });
29
+
30
+ const payload = {
31
+ mindId: MIND_ID,
32
+ scenario: { message: "Hello" },
33
+ };
34
+
35
+ const result = await mindsim.simulations.run(payload);
36
+
37
+ expect(mockApiClient.post).toHaveBeenCalledWith(expect.stringContaining("/simulate"), payload);
38
+ expect(result.message).toBe(mockResponse.message);
39
+ });
40
+
41
+ it("run() with background flag", async () => {
42
+ mockApiClient.post.mockResolvedValue({
43
+ data: { message: "Queued" },
44
+ });
45
+
46
+ const payload = {
47
+ mindId: MIND_ID,
48
+ scenario: { message: "Analyze this" },
49
+ runInBackground: true,
50
+ };
51
+
52
+ const result = await mindsim.simulations.run(payload);
53
+
54
+ expect(mockApiClient.post).toHaveBeenCalledWith(
55
+ expect.stringContaining("/simulate"),
56
+ expect.objectContaining({ runInBackground: true }),
57
+ );
58
+ expect(result.message).toBe("Queued");
59
+ });
60
+
61
+ it("list() should get simulations with pagination params", async () => {
62
+ mockApiClient.get.mockResolvedValue({
63
+ data: { simulations: [], count: 0 },
64
+ });
65
+
66
+ await mindsim.simulations.list({ offset: 10, limit: 5 });
67
+
68
+ expect(mockApiClient.get).toHaveBeenCalledWith("/simulations", {
69
+ params: { offset: 10, limit: 5 },
70
+ });
71
+ });
72
+
73
+ it("get() should retrieve specific simulation details", async () => {
74
+ mockApiClient.get.mockResolvedValue({
75
+ data: { simulation: { id: SIM_ID }, messages: [] },
76
+ });
77
+
78
+ const result = await mindsim.simulations.get(SIM_ID);
79
+
80
+ expect(mockApiClient.get).toHaveBeenCalledWith(`/simulations/${SIM_ID}`);
81
+ expect(result.simulation.id).toBe(SIM_ID);
82
+ });
83
+
84
+ it("delete() should remove simulation", async () => {
85
+ mockApiClient.delete.mockResolvedValue({
86
+ data: { message: "Deleted", simulationId: SIM_ID },
87
+ });
88
+
89
+ const result = await mindsim.simulations.delete(SIM_ID);
90
+
91
+ expect(mockApiClient.delete).toHaveBeenCalledWith(`/simulations/${SIM_ID}`);
92
+ expect(result.simulationId).toBe(SIM_ID);
93
+ });
94
+ });
@@ -0,0 +1,135 @@
1
+ import axios from "axios";
2
+ import FormData from "form-data";
3
+ import { beforeEach, describe, expect, it, type Mocked, vi } from "vitest";
4
+ import { MindSim } from "../../src/index";
5
+
6
+ vi.mock("axios");
7
+ const mockedAxios = axios as Mocked<typeof axios>;
8
+
9
+ describe("MindSim Snapshots Resource", () => {
10
+ let mindsim: MindSim;
11
+
12
+ const MIND_ID = "ac8ac8c4-05e9-4046-a919-4cbae2f49f22";
13
+ const ARTIFACT_ID = "f47ac10b-58cc-4372-a567-0e02b2c3d479";
14
+ const ASSESSMENT_ID = "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11";
15
+
16
+ const mockApiClient = {
17
+ get: vi.fn(),
18
+ post: vi.fn(),
19
+ put: vi.fn(),
20
+ defaults: { headers: { common: {} } },
21
+ interceptors: { request: { use: vi.fn() }, response: { use: vi.fn() } },
22
+ };
23
+
24
+ beforeEach(() => {
25
+ mockedAxios.create.mockReturnValue(mockApiClient as any);
26
+ mindsim = new MindSim("fake-key");
27
+ });
28
+
29
+ it("list() should retrieve snapshots for a mind", async () => {
30
+ const mockList = {
31
+ snapshots: [{ id: ASSESSMENT_ID, status: "completed" }],
32
+ count: 1,
33
+ };
34
+ mockApiClient.get.mockResolvedValue({ data: mockList });
35
+
36
+ const result = await mindsim.snapshots.list(MIND_ID);
37
+
38
+ expect(mockApiClient.get).toHaveBeenCalledWith(`/minds/${MIND_ID}/snapshots`);
39
+ expect(result.count).toBe(1);
40
+ expect(result.snapshots[0].id).toBe(ASSESSMENT_ID);
41
+ });
42
+
43
+ it("create() should execute the 3-step signed URL flow", async () => {
44
+ const fileBuffer = Buffer.from("fake-pdf-content");
45
+ const fileName = "test.pdf";
46
+
47
+ // 1. Mock Get Signed URL response
48
+ mockApiClient.get.mockResolvedValueOnce({
49
+ data: {
50
+ signedUrl: "https://s3.fake/upload",
51
+ artifactId: ARTIFACT_ID,
52
+ fileName: "test.pdf",
53
+ contentType: "application/pdf",
54
+ },
55
+ });
56
+
57
+ // 2. Mock the direct axios PUT to the signed URL
58
+ mockedAxios.put.mockResolvedValue({ status: 200 } as any);
59
+
60
+ // 3. Mock the final POST to create snapshot record
61
+ mockApiClient.post.mockResolvedValue({
62
+ data: {
63
+ message: "Success",
64
+ mindAssessmentId: ASSESSMENT_ID,
65
+ artifactId: ARTIFACT_ID,
66
+ },
67
+ });
68
+
69
+ const result = await mindsim.snapshots.create(MIND_ID, {
70
+ file: fileBuffer,
71
+ fileName: fileName,
72
+ contentType: "application/pdf",
73
+ mindsetDate: "2023-01-01",
74
+ });
75
+
76
+ // Verifications
77
+ expect(mockApiClient.get).toHaveBeenCalledWith(`/minds/${MIND_ID}/signed-url`, {
78
+ params: { fileName, contentType: "application/pdf" },
79
+ });
80
+
81
+ // Check global axios PUT was called for file upload
82
+ expect(mockedAxios.put).toHaveBeenCalledWith(
83
+ "https://s3.fake/upload",
84
+ fileBuffer,
85
+ expect.objectContaining({
86
+ headers: { "Content-Type": "application/pdf" },
87
+ }),
88
+ );
89
+
90
+ // Check final POST
91
+ expect(mockApiClient.post).toHaveBeenCalledWith(
92
+ `/minds/${MIND_ID}/snapshots`,
93
+ expect.any(FormData),
94
+ expect.objectContaining({
95
+ headers: expect.anything(), // FormData headers
96
+ }),
97
+ );
98
+
99
+ expect(result.mindAssessmentId).toBe(ASSESSMENT_ID);
100
+ });
101
+
102
+ it("createFromFile() should use FormData and return artifacts", async () => {
103
+ mockApiClient.post.mockResolvedValue({
104
+ data: { artifactId: ARTIFACT_ID },
105
+ });
106
+
107
+ await mindsim.snapshots.createFromFile(MIND_ID, {
108
+ file: Buffer.from("abc"),
109
+ fileName: "chat.txt",
110
+ });
111
+
112
+ expect(mockApiClient.post).toHaveBeenCalledWith(
113
+ `/minds/${MIND_ID}/snapshots`,
114
+ expect.any(FormData),
115
+ expect.objectContaining({ headers: expect.anything() }),
116
+ );
117
+ });
118
+
119
+ it("getStatus() should return status object with correct IDs", async () => {
120
+ mockApiClient.get.mockResolvedValue({
121
+ data: {
122
+ id: ASSESSMENT_ID,
123
+ mindId: MIND_ID,
124
+ status: "processing",
125
+ },
126
+ });
127
+
128
+ const status = await mindsim.snapshots.getStatus(MIND_ID, ASSESSMENT_ID);
129
+ expect(status.status).toBe("processing");
130
+ expect(status.id).toBe(ASSESSMENT_ID);
131
+ expect(mockApiClient.get).toHaveBeenCalledWith(
132
+ `/minds/${MIND_ID}/snapshots/${ASSESSMENT_ID}/status`,
133
+ );
134
+ });
135
+ });
@@ -0,0 +1,87 @@
1
+ import axios from "axios";
2
+ import { beforeEach, describe, expect, it, type Mocked, vi } from "vitest";
3
+ import { MindSim } from "../../src/index";
4
+ import type { Tag } from "../../src/types";
5
+
6
+ vi.mock("axios");
7
+ const mockedAxios = axios as Mocked<typeof axios>;
8
+
9
+ describe("MindSim Tags Resource", () => {
10
+ let mindsim: MindSim;
11
+
12
+ const TAG_ID_1 = "47a09167-3b9f-442c-a19c-96398cf238fb";
13
+ const TAG_ID_2 = "a1ca5e66-6f3a-4d92-ab25-8a86fdb111c1";
14
+
15
+ const mockClient = {
16
+ get: vi.fn(),
17
+ put: vi.fn(),
18
+ delete: vi.fn(),
19
+ post: vi.fn(),
20
+ defaults: { headers: { common: {} } },
21
+ interceptors: { request: { use: vi.fn() }, response: { use: vi.fn() } },
22
+ };
23
+
24
+ beforeEach(() => {
25
+ // setup axios.create to return our mock client
26
+ mockedAxios.create.mockReturnValue(mockClient as any);
27
+
28
+ mindsim = new MindSim("test-api-key");
29
+ });
30
+
31
+ describe("list", () => {
32
+ it("should fetch and return a list of tags via mindsim.tags.list()", async () => {
33
+ const mockData: Tag[] = [
34
+ { id: TAG_ID_1, name: "hero", mindCount: 5 },
35
+ { id: TAG_ID_2, name: "villain", mindCount: 2 },
36
+ ];
37
+
38
+ // Mock the API response
39
+ mockClient.get.mockResolvedValue({
40
+ data: { tags: mockData },
41
+ });
42
+
43
+ // Call via the main SDK instance
44
+ const result = await mindsim.tags.list();
45
+
46
+ // Assertions
47
+ expect(mockClient.get).toHaveBeenCalledWith("/tags");
48
+ expect(result).toEqual(mockData);
49
+ expect(result).toHaveLength(2);
50
+ expect(result?.[0]?.id).toBe(TAG_ID_1);
51
+ });
52
+ });
53
+
54
+ describe("update", () => {
55
+ it("should update a tag and return the updated object via mindsim.tags.update()", async () => {
56
+ const updatePayload = { name: "superhero" };
57
+ const mockResponseTag: Tag = {
58
+ id: TAG_ID_1,
59
+ name: "superhero",
60
+ mindCount: 5,
61
+ };
62
+
63
+ mockClient.put.mockResolvedValue({
64
+ data: { tag: mockResponseTag },
65
+ });
66
+
67
+ const result = await mindsim.tags.update(TAG_ID_1, updatePayload);
68
+
69
+ expect(mockClient.put).toHaveBeenCalledWith(`/tags/${TAG_ID_1}`, updatePayload);
70
+ expect(result).toEqual(mockResponseTag);
71
+ expect(result.name).toBe("superhero");
72
+ });
73
+ });
74
+
75
+ describe("delete", () => {
76
+ it("should delete a tag and return status via mindsim.tags.delete()", async () => {
77
+ mockClient.delete.mockResolvedValue({
78
+ data: { deleted: true },
79
+ });
80
+
81
+ const result = await mindsim.tags.delete(TAG_ID_2);
82
+
83
+ expect(mockClient.delete).toHaveBeenCalledWith(`/tags/${TAG_ID_2}`);
84
+ expect(result).toEqual({ deleted: true });
85
+ });
86
+ });
87
+ });
@@ -0,0 +1,84 @@
1
+ import fs from "node:fs";
2
+ import axios from "axios";
3
+ import { beforeEach, describe, expect, it, type Mocked, vi } from "vitest";
4
+ import { MindSim } from "../../src/index";
5
+
6
+ vi.mock("axios");
7
+ vi.mock("node:fs");
8
+
9
+ // Mock the version module to prevent file system access during instantiation
10
+ vi.mock("../../src/version", () => ({
11
+ getPackageVersion: () => "1.0.0",
12
+ checkForUpdates: vi.fn().mockResolvedValue(undefined),
13
+ }));
14
+
15
+ const mockedAxios = axios as Mocked<typeof axios>;
16
+ const mockedFs = fs as Mocked<typeof fs>;
17
+
18
+ describe("Quick Start Workflow Integration", () => {
19
+ let mindsim: MindSim;
20
+
21
+ const BOBBY_ID = "779e640c-67c2-44e5-bc91-b3b43eeaead8";
22
+ const ARTIFACT_ID = "c56a4180-65aa-42ec-a945-5fd21dec0538";
23
+ const ASSESSMENT_ID = "550e8400-e29b-41d4-a716-446655440000";
24
+
25
+ const mockApiClient = {
26
+ get: vi.fn(),
27
+ post: vi.fn(),
28
+ put: vi.fn(),
29
+ defaults: { headers: { common: {} } },
30
+ interceptors: { request: { use: vi.fn() }, response: { use: vi.fn() } },
31
+ };
32
+
33
+ beforeEach(() => {
34
+ mockedAxios.create.mockReturnValue(mockApiClient as any);
35
+ mockedAxios.put.mockResolvedValue({ status: 200 } as any); // For signed URL upload
36
+ mindsim = new MindSim("valid-key");
37
+ });
38
+
39
+ it("should execute the full Bobby Tables workflow", async () => {
40
+ // --- Step 1: Create Mind ---
41
+ mockApiClient.post.mockResolvedValueOnce({
42
+ data: {
43
+ mind: { id: BOBBY_ID, name: "Bobby Tables", email: "bobby@test.com", tags: [] },
44
+ },
45
+ });
46
+
47
+ const bobby = await mindsim.minds.create({
48
+ name: "Bobby Tables",
49
+ email: "bobby@test.com",
50
+ tags: ["developer"],
51
+ });
52
+
53
+ expect(bobby.id).toBe(BOBBY_ID);
54
+
55
+ // --- Step 2: Create Snapshot (Mocking File Read) ---
56
+ mockedFs.readFileSync.mockReturnValue(Buffer.from("fake-pdf"));
57
+
58
+ // Mocks for Snapshot creation flow
59
+ mockApiClient.get.mockResolvedValueOnce({
60
+ data: { signedUrl: "http://s3", artifactId: ARTIFACT_ID },
61
+ }); // signed-url
62
+
63
+ mockApiClient.post.mockResolvedValueOnce({
64
+ data: { mindAssessmentId: ASSESSMENT_ID, artifactId: ARTIFACT_ID },
65
+ }); // create snapshot record
66
+
67
+ const snapshotRes = await mindsim.snapshots.create(bobby.id, {
68
+ file: fs.readFileSync("dummy.pdf"), // Reads from mock
69
+ fileName: "dummy.pdf",
70
+ contentType: "application/pdf",
71
+ });
72
+
73
+ expect(snapshotRes.mindAssessmentId).toBe(ASSESSMENT_ID);
74
+
75
+ // --- Step 3: Check Status ---
76
+ mockApiClient.get.mockResolvedValueOnce({
77
+ data: { id: ASSESSMENT_ID, mindId: BOBBY_ID, status: "completed" },
78
+ });
79
+
80
+ const status = await mindsim.snapshots.getStatus(bobby.id, snapshotRes.mindAssessmentId);
81
+ expect(status.status).toBe("completed");
82
+ expect(status.mindId).toBe(BOBBY_ID);
83
+ });
84
+ });
@@ -0,0 +1,221 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import axios from "axios";
4
+ import { afterEach, beforeEach, describe, expect, it, type Mocked, vi } from "vitest";
5
+ import { checkForUpdates, getPackageVersion, updateSdk } from "../src/version";
6
+
7
+ // 1. Define the mock function using vi.hoisted so it's available to the mock factory
8
+ const { mockExecAsync } = vi.hoisted(() => {
9
+ return { mockExecAsync: vi.fn() };
10
+ });
11
+
12
+ // 2. Mock node:util to return our hoisted mock function
13
+ vi.mock("node:util", async () => {
14
+ return {
15
+ promisify: () => mockExecAsync,
16
+ };
17
+ });
18
+
19
+ // 3. Mock other dependencies
20
+ vi.mock("node:fs");
21
+ vi.mock("node:path");
22
+ vi.mock("axios");
23
+
24
+ const mockedFs = fs as Mocked<typeof fs>;
25
+ const mockedPath = path as Mocked<typeof path>;
26
+ const mockedAxios = axios as Mocked<typeof axios>;
27
+
28
+ describe("Version Module", () => {
29
+ const MOCK_PACKAGE_PATH = "/mock/project/package.json";
30
+ const MOCK_ALT_PATH = "/mock/package.json";
31
+
32
+ beforeEach(() => {
33
+ vi.resetModules();
34
+ vi.clearAllMocks();
35
+
36
+ // Reset the hoisted mock implementation
37
+ mockExecAsync.mockReset();
38
+
39
+ // Setup standard path mocks
40
+ mockedPath.resolve.mockReturnValue(MOCK_PACKAGE_PATH);
41
+ });
42
+
43
+ afterEach(() => {
44
+ vi.unstubAllEnvs();
45
+ });
46
+
47
+ describe("getPackageVersion", () => {
48
+ it("should return version from package.json if it exists at primary path", () => {
49
+ mockedFs.existsSync.mockReturnValue(true);
50
+ mockedFs.readFileSync.mockReturnValue(JSON.stringify({ version: "1.0.0" }));
51
+
52
+ const version = getPackageVersion();
53
+
54
+ expect(mockedPath.resolve).toHaveBeenCalled();
55
+ expect(mockedFs.readFileSync).toHaveBeenCalledWith(MOCK_PACKAGE_PATH, "utf-8");
56
+ expect(version).toBe("1.0.0");
57
+ });
58
+
59
+ it("should try fallback path if primary path is missing", () => {
60
+ // First check returns false (primary path)
61
+ // Second check returns true (alt path)
62
+ mockedFs.existsSync.mockReturnValueOnce(false).mockReturnValueOnce(true);
63
+ mockedFs.readFileSync.mockReturnValue(JSON.stringify({ version: "1.0.1" }));
64
+
65
+ mockedPath.resolve.mockReturnValueOnce(MOCK_PACKAGE_PATH).mockReturnValueOnce(MOCK_ALT_PATH);
66
+
67
+ const version = getPackageVersion();
68
+
69
+ expect(mockedFs.readFileSync).toHaveBeenCalledWith(MOCK_ALT_PATH, "utf-8");
70
+ expect(version).toBe("1.0.1");
71
+ });
72
+
73
+ it("should return '0.0.0' and log warning if file read fails", () => {
74
+ mockedFs.existsSync.mockReturnValue(true);
75
+ mockedFs.readFileSync.mockImplementation(() => {
76
+ throw new Error("Permission denied");
77
+ });
78
+
79
+ const consoleSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
80
+
81
+ const version = getPackageVersion();
82
+
83
+ expect(version).toBe("0.0.0");
84
+ expect(consoleSpy).toHaveBeenCalledWith(
85
+ expect.stringContaining("Unable to determine current package version"),
86
+ expect.any(Error),
87
+ );
88
+ });
89
+
90
+ it("should return '0.0.0' if JSON is invalid", () => {
91
+ mockedFs.existsSync.mockReturnValue(true);
92
+ mockedFs.readFileSync.mockReturnValue("INVALID_JSON");
93
+
94
+ const consoleSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
95
+
96
+ const version = getPackageVersion();
97
+ expect(version).toBe("0.0.0");
98
+ });
99
+ });
100
+
101
+ describe("checkForUpdates", () => {
102
+ beforeEach(() => {
103
+ // Default local version
104
+ mockedFs.existsSync.mockReturnValue(true);
105
+ mockedFs.readFileSync.mockReturnValue(JSON.stringify({ version: "1.0.0" }));
106
+ });
107
+
108
+ it("should log warning if remote version is greater than local", async () => {
109
+ mockedAxios.get.mockResolvedValue({
110
+ data: { "dist-tags": { latest: "1.1.0" } },
111
+ });
112
+
113
+ const consoleWarnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
114
+
115
+ await checkForUpdates();
116
+
117
+ expect(consoleWarnSpy).toHaveBeenCalledWith(expect.stringContaining("UPDATE AVAILABLE"));
118
+ });
119
+
120
+ it("should not log if versions match", async () => {
121
+ mockedAxios.get.mockResolvedValue({
122
+ data: { "dist-tags": { latest: "1.0.0" } },
123
+ });
124
+
125
+ const consoleWarnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
126
+ const consoleLogSpy = vi.spyOn(console, "log").mockImplementation(() => {});
127
+
128
+ await checkForUpdates(); // verbose defaults to false
129
+
130
+ expect(consoleWarnSpy).not.toHaveBeenCalled();
131
+ expect(consoleLogSpy).not.toHaveBeenCalled();
132
+ });
133
+
134
+ it("should log confirmation if verbose is true and versions match", async () => {
135
+ mockedAxios.get.mockResolvedValue({
136
+ data: { "dist-tags": { latest: "1.0.0" } },
137
+ });
138
+
139
+ const consoleLogSpy = vi.spyOn(console, "log").mockImplementation(() => {});
140
+
141
+ await checkForUpdates(true);
142
+
143
+ expect(consoleLogSpy).toHaveBeenCalledWith(
144
+ expect.stringContaining("MindSim SDK is up to date"),
145
+ );
146
+ });
147
+
148
+ it("should fail silently on network error (non-verbose)", async () => {
149
+ mockedAxios.get.mockRejectedValue(new Error("Network Error"));
150
+ const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
151
+
152
+ await checkForUpdates(false);
153
+
154
+ expect(consoleErrorSpy).not.toHaveBeenCalled();
155
+ });
156
+
157
+ it("should log error on failure if verbose is true", async () => {
158
+ mockedAxios.get.mockRejectedValue(new Error("Network Error"));
159
+ const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
160
+
161
+ await checkForUpdates(true);
162
+
163
+ expect(consoleErrorSpy).toHaveBeenCalledWith("Failed to check for updates:", "Network Error");
164
+ });
165
+ });
166
+
167
+ describe("updateSdk", () => {
168
+ beforeEach(() => {
169
+ // Default local version
170
+ mockedFs.existsSync.mockReturnValue(true);
171
+ mockedFs.readFileSync.mockReturnValue(JSON.stringify({ version: "1.0.0" }));
172
+ });
173
+
174
+ it("should run npm install if update available", async () => {
175
+ mockedAxios.get.mockResolvedValue({
176
+ data: { "dist-tags": { latest: "2.0.0" } },
177
+ });
178
+ // Mock the execAsync resolution
179
+ mockExecAsync.mockResolvedValue({ stdout: "ok", stderr: "" });
180
+
181
+ const consoleLogSpy = vi.spyOn(console, "log").mockImplementation(() => {});
182
+
183
+ await updateSdk();
184
+
185
+ expect(mockExecAsync).toHaveBeenCalledWith(
186
+ "npm install @mindsim/mindsim-sdk-typescript@latest --save",
187
+ );
188
+ expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining("Update complete"));
189
+ });
190
+
191
+ it("should do nothing if already on latest", async () => {
192
+ mockedAxios.get.mockResolvedValue({
193
+ data: { "dist-tags": { latest: "1.0.0" } },
194
+ });
195
+
196
+ const consoleLogSpy = vi.spyOn(console, "log").mockImplementation(() => {});
197
+
198
+ await updateSdk();
199
+
200
+ expect(mockExecAsync).not.toHaveBeenCalled();
201
+ expect(consoleLogSpy).toHaveBeenCalledWith(
202
+ expect.stringContaining("already on the latest version"),
203
+ );
204
+ });
205
+
206
+ it("should handle exec errors", async () => {
207
+ mockedAxios.get.mockResolvedValue({
208
+ data: { "dist-tags": { latest: "2.0.0" } },
209
+ });
210
+ // Mock execAsync rejection
211
+ mockExecAsync.mockRejectedValue(new Error("Permission denied"));
212
+
213
+ const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
214
+
215
+ await updateSdk();
216
+
217
+ expect(consoleErrorSpy).toHaveBeenCalledWith("❌ Failed to update MindSim SDK.");
218
+ expect(consoleErrorSpy).toHaveBeenCalledWith("Permission denied");
219
+ });
220
+ });
221
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "compilerOptions": {
3
+ "module": "nodenext",
4
+ "target": "esnext",
5
+ "lib": ["esnext"],
6
+ "types": ["node"],
7
+ "rootDir": "./src",
8
+ "outDir": "./dist",
9
+ "sourceMap": true,
10
+ "declaration": true,
11
+ "declarationMap": true,
12
+ "noUncheckedIndexedAccess": true,
13
+ "exactOptionalPropertyTypes": true,
14
+ "strict": true,
15
+ "jsx": "react-jsx",
16
+ "verbatimModuleSyntax": false,
17
+ "isolatedModules": true,
18
+ "noUncheckedSideEffectImports": true,
19
+ "moduleDetection": "force",
20
+ "skipLibCheck": true,
21
+ "esModuleInterop": true
22
+ },
23
+ "ts-node": {
24
+ "esm": true,
25
+ "experimentalSpecifierResolution": "node"
26
+ },
27
+ "include": ["src/**/*.ts"],
28
+ "exclude": ["node_modules", "tests/**/*.ts"]
29
+ }
@@ -0,0 +1,12 @@
1
+ // vitest.config.ts
2
+ import { defineConfig } from "vitest/config";
3
+
4
+ export default defineConfig({
5
+ test: {
6
+ globals: true, // Allows using describe, expect, it without imports
7
+ environment: "node",
8
+ clearMocks: true,
9
+ include: ["tests", "**/*.test.ts", "**/*.spec.ts"],
10
+ exclude: ["node_modules", "dist"],
11
+ },
12
+ });