@enactprotocol/shared 1.2.13 → 2.0.1

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 (207) hide show
  1. package/README.md +44 -0
  2. package/dist/config.d.ts +164 -0
  3. package/dist/config.d.ts.map +1 -0
  4. package/dist/config.js +386 -0
  5. package/dist/config.js.map +1 -0
  6. package/dist/constants.d.ts +15 -5
  7. package/dist/constants.d.ts.map +1 -0
  8. package/dist/constants.js +24 -8
  9. package/dist/constants.js.map +1 -0
  10. package/dist/execution/command.d.ts +102 -0
  11. package/dist/execution/command.d.ts.map +1 -0
  12. package/dist/execution/command.js +262 -0
  13. package/dist/execution/command.js.map +1 -0
  14. package/dist/execution/index.d.ts +12 -0
  15. package/dist/execution/index.d.ts.map +1 -0
  16. package/dist/execution/index.js +17 -0
  17. package/dist/execution/index.js.map +1 -0
  18. package/dist/execution/runtime.d.ts +82 -0
  19. package/dist/execution/runtime.d.ts.map +1 -0
  20. package/dist/execution/runtime.js +273 -0
  21. package/dist/execution/runtime.js.map +1 -0
  22. package/dist/execution/types.d.ts +306 -0
  23. package/dist/execution/types.d.ts.map +1 -0
  24. package/dist/execution/types.js +14 -0
  25. package/dist/execution/types.js.map +1 -0
  26. package/dist/execution/validation.d.ts +43 -0
  27. package/dist/execution/validation.d.ts.map +1 -0
  28. package/dist/execution/validation.js +430 -0
  29. package/dist/execution/validation.js.map +1 -0
  30. package/dist/index.d.ts +21 -21
  31. package/dist/index.d.ts.map +1 -0
  32. package/dist/index.js +49 -25
  33. package/dist/index.js.map +1 -0
  34. package/dist/manifest/index.d.ts +7 -0
  35. package/dist/manifest/index.d.ts.map +1 -0
  36. package/dist/manifest/index.js +10 -0
  37. package/dist/manifest/index.js.map +1 -0
  38. package/dist/manifest/loader.d.ts +76 -0
  39. package/dist/manifest/loader.d.ts.map +1 -0
  40. package/dist/manifest/loader.js +146 -0
  41. package/dist/manifest/loader.js.map +1 -0
  42. package/dist/manifest/parser.d.ts +64 -0
  43. package/dist/manifest/parser.d.ts.map +1 -0
  44. package/dist/manifest/parser.js +135 -0
  45. package/dist/manifest/parser.js.map +1 -0
  46. package/dist/manifest/validator.d.ts +95 -0
  47. package/dist/manifest/validator.d.ts.map +1 -0
  48. package/dist/manifest/validator.js +258 -0
  49. package/dist/manifest/validator.js.map +1 -0
  50. package/dist/paths.d.ts +57 -0
  51. package/dist/paths.d.ts.map +1 -0
  52. package/dist/paths.js +93 -0
  53. package/dist/paths.js.map +1 -0
  54. package/dist/registry.d.ts +73 -0
  55. package/dist/registry.d.ts.map +1 -0
  56. package/dist/registry.js +147 -0
  57. package/dist/registry.js.map +1 -0
  58. package/dist/resolver.d.ts +89 -0
  59. package/dist/resolver.d.ts.map +1 -0
  60. package/dist/resolver.js +282 -0
  61. package/dist/resolver.js.map +1 -0
  62. package/dist/types/index.d.ts +6 -0
  63. package/dist/types/index.d.ts.map +1 -0
  64. package/dist/types/index.js +5 -0
  65. package/dist/types/index.js.map +1 -0
  66. package/dist/types/manifest.d.ts +201 -0
  67. package/dist/types/manifest.d.ts.map +1 -0
  68. package/dist/types/manifest.js +13 -0
  69. package/dist/types/manifest.js.map +1 -0
  70. package/dist/types.d.ts +5 -132
  71. package/dist/types.d.ts.map +1 -0
  72. package/dist/types.js +5 -3
  73. package/dist/types.js.map +1 -0
  74. package/dist/utils/fs.d.ts +105 -0
  75. package/dist/utils/fs.d.ts.map +1 -0
  76. package/dist/utils/fs.js +233 -0
  77. package/dist/utils/fs.js.map +1 -0
  78. package/dist/utils/logger.d.ts +102 -25
  79. package/dist/utils/logger.d.ts.map +1 -0
  80. package/dist/utils/logger.js +214 -57
  81. package/dist/utils/logger.js.map +1 -0
  82. package/dist/utils/version.d.ts +60 -2
  83. package/dist/utils/version.d.ts.map +1 -0
  84. package/dist/utils/version.js +255 -31
  85. package/dist/utils/version.js.map +1 -0
  86. package/package.json +16 -58
  87. package/src/config.ts +510 -0
  88. package/src/constants.ts +36 -0
  89. package/src/execution/command.ts +314 -0
  90. package/src/execution/index.ts +73 -0
  91. package/src/execution/runtime.ts +308 -0
  92. package/src/execution/types.ts +379 -0
  93. package/src/execution/validation.ts +508 -0
  94. package/src/index.ts +238 -30
  95. package/src/manifest/index.ts +36 -0
  96. package/src/manifest/loader.ts +187 -0
  97. package/src/manifest/parser.ts +173 -0
  98. package/src/manifest/validator.ts +309 -0
  99. package/src/paths.ts +108 -0
  100. package/src/registry.ts +219 -0
  101. package/src/resolver.ts +345 -0
  102. package/src/types/index.ts +30 -0
  103. package/src/types/manifest.ts +255 -0
  104. package/src/types.ts +5 -188
  105. package/src/utils/fs.ts +281 -0
  106. package/src/utils/logger.ts +270 -59
  107. package/src/utils/version.ts +304 -36
  108. package/tests/config.test.ts +515 -0
  109. package/tests/execution/command.test.ts +317 -0
  110. package/tests/execution/validation.test.ts +384 -0
  111. package/tests/fixtures/invalid-tool.yaml +4 -0
  112. package/tests/fixtures/valid-tool.md +62 -0
  113. package/tests/fixtures/valid-tool.yaml +40 -0
  114. package/tests/index.test.ts +8 -0
  115. package/tests/manifest/loader.test.ts +291 -0
  116. package/tests/manifest/parser.test.ts +345 -0
  117. package/tests/manifest/validator.test.ts +394 -0
  118. package/tests/manifest-types.test.ts +358 -0
  119. package/tests/paths.test.ts +153 -0
  120. package/tests/registry.test.ts +231 -0
  121. package/tests/resolver.test.ts +272 -0
  122. package/tests/utils/fs.test.ts +388 -0
  123. package/tests/utils/logger.test.ts +480 -0
  124. package/tests/utils/version.test.ts +390 -0
  125. package/tsconfig.json +12 -0
  126. package/dist/LocalToolResolver.d.ts +0 -84
  127. package/dist/LocalToolResolver.js +0 -353
  128. package/dist/api/enact-api.d.ts +0 -130
  129. package/dist/api/enact-api.js +0 -428
  130. package/dist/api/index.d.ts +0 -2
  131. package/dist/api/index.js +0 -2
  132. package/dist/api/types.d.ts +0 -103
  133. package/dist/api/types.js +0 -1
  134. package/dist/core/DaggerExecutionProvider.d.ts +0 -169
  135. package/dist/core/DaggerExecutionProvider.js +0 -1029
  136. package/dist/core/DirectExecutionProvider.d.ts +0 -23
  137. package/dist/core/DirectExecutionProvider.js +0 -406
  138. package/dist/core/EnactCore.d.ts +0 -162
  139. package/dist/core/EnactCore.js +0 -597
  140. package/dist/core/NativeExecutionProvider.d.ts +0 -9
  141. package/dist/core/NativeExecutionProvider.js +0 -16
  142. package/dist/core/index.d.ts +0 -3
  143. package/dist/core/index.js +0 -3
  144. package/dist/exec/index.d.ts +0 -3
  145. package/dist/exec/index.js +0 -3
  146. package/dist/exec/logger.d.ts +0 -11
  147. package/dist/exec/logger.js +0 -57
  148. package/dist/exec/validate.d.ts +0 -5
  149. package/dist/exec/validate.js +0 -167
  150. package/dist/lib/enact-direct.d.ts +0 -150
  151. package/dist/lib/enact-direct.js +0 -159
  152. package/dist/lib/index.d.ts +0 -1
  153. package/dist/lib/index.js +0 -1
  154. package/dist/security/index.d.ts +0 -3
  155. package/dist/security/index.js +0 -3
  156. package/dist/security/security.d.ts +0 -23
  157. package/dist/security/security.js +0 -137
  158. package/dist/security/sign.d.ts +0 -103
  159. package/dist/security/sign.js +0 -666
  160. package/dist/security/verification-enforcer.d.ts +0 -53
  161. package/dist/security/verification-enforcer.js +0 -204
  162. package/dist/services/McpCoreService.d.ts +0 -98
  163. package/dist/services/McpCoreService.js +0 -124
  164. package/dist/services/index.d.ts +0 -1
  165. package/dist/services/index.js +0 -1
  166. package/dist/utils/config.d.ts +0 -111
  167. package/dist/utils/config.js +0 -342
  168. package/dist/utils/env-loader.d.ts +0 -54
  169. package/dist/utils/env-loader.js +0 -270
  170. package/dist/utils/help.d.ts +0 -36
  171. package/dist/utils/help.js +0 -248
  172. package/dist/utils/index.d.ts +0 -7
  173. package/dist/utils/index.js +0 -7
  174. package/dist/utils/silent-monitor.d.ts +0 -67
  175. package/dist/utils/silent-monitor.js +0 -242
  176. package/dist/utils/timeout.d.ts +0 -5
  177. package/dist/utils/timeout.js +0 -23
  178. package/dist/web/env-manager-server.d.ts +0 -29
  179. package/dist/web/env-manager-server.js +0 -367
  180. package/dist/web/index.d.ts +0 -1
  181. package/dist/web/index.js +0 -1
  182. package/src/LocalToolResolver.ts +0 -424
  183. package/src/api/enact-api.ts +0 -604
  184. package/src/api/index.ts +0 -2
  185. package/src/api/types.ts +0 -114
  186. package/src/core/DaggerExecutionProvider.ts +0 -1357
  187. package/src/core/DirectExecutionProvider.ts +0 -484
  188. package/src/core/EnactCore.ts +0 -847
  189. package/src/core/index.ts +0 -3
  190. package/src/exec/index.ts +0 -3
  191. package/src/exec/logger.ts +0 -63
  192. package/src/exec/validate.ts +0 -238
  193. package/src/lib/enact-direct.ts +0 -254
  194. package/src/lib/index.ts +0 -1
  195. package/src/services/McpCoreService.ts +0 -201
  196. package/src/services/index.ts +0 -1
  197. package/src/utils/config.ts +0 -438
  198. package/src/utils/env-loader.ts +0 -370
  199. package/src/utils/help.ts +0 -257
  200. package/src/utils/index.ts +0 -7
  201. package/src/utils/silent-monitor.ts +0 -328
  202. package/src/utils/timeout.ts +0 -26
  203. package/src/web/env-manager-server.ts +0 -465
  204. package/src/web/index.ts +0 -1
  205. package/src/web/static/app.js +0 -663
  206. package/src/web/static/index.html +0 -117
  207. package/src/web/static/style.css +0 -291
@@ -0,0 +1,231 @@
1
+ /**
2
+ * Tests for the local tool registry (tools.json management)
3
+ */
4
+
5
+ import { afterAll, beforeAll, describe, expect, test } from "bun:test";
6
+ import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
7
+ import { join } from "node:path";
8
+ import {
9
+ addToolToRegistry,
10
+ getInstalledVersion,
11
+ getToolCachePath,
12
+ getToolsJsonPath,
13
+ isToolInstalled,
14
+ listInstalledTools,
15
+ loadToolsRegistry,
16
+ removeToolFromRegistry,
17
+ saveToolsRegistry,
18
+ } from "../src/registry";
19
+
20
+ const TEST_DIR = join(import.meta.dir, "temp-registry-test");
21
+ const PROJECT_DIR = join(TEST_DIR, "project");
22
+ const PROJECT_ENACT_DIR = join(PROJECT_DIR, ".enact");
23
+
24
+ describe("registry", () => {
25
+ beforeAll(() => {
26
+ // Create test directory structure
27
+ mkdirSync(PROJECT_ENACT_DIR, { recursive: true });
28
+ });
29
+
30
+ afterAll(() => {
31
+ // Clean up test directories
32
+ if (existsSync(TEST_DIR)) {
33
+ rmSync(TEST_DIR, { recursive: true, force: true });
34
+ }
35
+ });
36
+
37
+ describe("getToolsJsonPath", () => {
38
+ test("returns path for global scope", () => {
39
+ const path = getToolsJsonPath("global");
40
+ expect(path).not.toBeNull();
41
+ expect(path).toContain(".enact");
42
+ expect(path).toEndWith("tools.json");
43
+ });
44
+
45
+ test("returns path for project scope when .enact exists", () => {
46
+ const path = getToolsJsonPath("project", PROJECT_DIR);
47
+ expect(path).not.toBeNull();
48
+ expect(path).toContain(PROJECT_ENACT_DIR);
49
+ expect(path).toEndWith("tools.json");
50
+ });
51
+
52
+ test("returns null for project scope when no .enact", () => {
53
+ const path = getToolsJsonPath("project", "/tmp/nonexistent-test-path");
54
+ expect(path).toBeNull();
55
+ });
56
+ });
57
+
58
+ describe("loadToolsRegistry", () => {
59
+ test("returns empty registry when file does not exist", () => {
60
+ // Ensure tools.json doesn't exist from previous tests
61
+ const toolsJsonPath = join(PROJECT_ENACT_DIR, "tools.json");
62
+ if (existsSync(toolsJsonPath)) {
63
+ rmSync(toolsJsonPath);
64
+ }
65
+ const registry = loadToolsRegistry("project", PROJECT_DIR);
66
+ expect(registry.tools).toEqual({});
67
+ });
68
+
69
+ test("loads existing registry", () => {
70
+ // Create a test registry file
71
+ const registryPath = join(PROJECT_ENACT_DIR, "tools.json");
72
+ writeFileSync(
73
+ registryPath,
74
+ JSON.stringify({
75
+ tools: {
76
+ "test/tool": "1.0.0",
77
+ "other/tool": "2.0.0",
78
+ },
79
+ })
80
+ );
81
+
82
+ const registry = loadToolsRegistry("project", PROJECT_DIR);
83
+ expect(registry.tools["test/tool"]).toBe("1.0.0");
84
+ expect(registry.tools["other/tool"]).toBe("2.0.0");
85
+
86
+ // Clean up
87
+ rmSync(registryPath);
88
+ });
89
+
90
+ test("returns empty registry on parse error", () => {
91
+ const registryPath = join(PROJECT_ENACT_DIR, "tools.json");
92
+ writeFileSync(registryPath, "invalid json");
93
+
94
+ const registry = loadToolsRegistry("project", PROJECT_DIR);
95
+ expect(registry.tools).toEqual({});
96
+
97
+ // Clean up
98
+ rmSync(registryPath);
99
+ });
100
+ });
101
+
102
+ describe("saveToolsRegistry", () => {
103
+ test("saves registry to file", () => {
104
+ const registry = {
105
+ tools: {
106
+ "test/save": "1.0.0",
107
+ },
108
+ };
109
+
110
+ saveToolsRegistry(registry, "project", PROJECT_DIR);
111
+
112
+ const registryPath = join(PROJECT_ENACT_DIR, "tools.json");
113
+ expect(existsSync(registryPath)).toBe(true);
114
+
115
+ const loaded = loadToolsRegistry("project", PROJECT_DIR);
116
+ expect(loaded.tools["test/save"]).toBe("1.0.0");
117
+
118
+ // Clean up
119
+ rmSync(registryPath);
120
+ });
121
+ });
122
+
123
+ describe("addToolToRegistry", () => {
124
+ test("adds tool to registry", () => {
125
+ addToolToRegistry("test/add", "1.0.0", "project", PROJECT_DIR);
126
+
127
+ const registry = loadToolsRegistry("project", PROJECT_DIR);
128
+ expect(registry.tools["test/add"]).toBe("1.0.0");
129
+
130
+ // Clean up
131
+ rmSync(join(PROJECT_ENACT_DIR, "tools.json"));
132
+ });
133
+
134
+ test("updates existing tool version", () => {
135
+ addToolToRegistry("test/update", "1.0.0", "project", PROJECT_DIR);
136
+ addToolToRegistry("test/update", "2.0.0", "project", PROJECT_DIR);
137
+
138
+ const registry = loadToolsRegistry("project", PROJECT_DIR);
139
+ expect(registry.tools["test/update"]).toBe("2.0.0");
140
+
141
+ // Clean up
142
+ rmSync(join(PROJECT_ENACT_DIR, "tools.json"));
143
+ });
144
+ });
145
+
146
+ describe("removeToolFromRegistry", () => {
147
+ test("removes tool from registry", () => {
148
+ addToolToRegistry("test/remove", "1.0.0", "project", PROJECT_DIR);
149
+ const removed = removeToolFromRegistry("test/remove", "project", PROJECT_DIR);
150
+
151
+ expect(removed).toBe(true);
152
+
153
+ const registry = loadToolsRegistry("project", PROJECT_DIR);
154
+ expect(registry.tools["test/remove"]).toBeUndefined();
155
+
156
+ // Clean up
157
+ rmSync(join(PROJECT_ENACT_DIR, "tools.json"));
158
+ });
159
+
160
+ test("returns false for non-existent tool", () => {
161
+ const removed = removeToolFromRegistry("nonexistent/tool", "project", PROJECT_DIR);
162
+ expect(removed).toBe(false);
163
+ });
164
+ });
165
+
166
+ describe("isToolInstalled", () => {
167
+ test("returns true for installed tool", () => {
168
+ addToolToRegistry("test/installed", "1.0.0", "project", PROJECT_DIR);
169
+ expect(isToolInstalled("test/installed", "project", PROJECT_DIR)).toBe(true);
170
+
171
+ // Clean up
172
+ rmSync(join(PROJECT_ENACT_DIR, "tools.json"));
173
+ });
174
+
175
+ test("returns false for non-installed tool", () => {
176
+ expect(isToolInstalled("not/installed", "project", PROJECT_DIR)).toBe(false);
177
+ });
178
+ });
179
+
180
+ describe("getInstalledVersion", () => {
181
+ test("returns version for installed tool", () => {
182
+ addToolToRegistry("test/version", "3.0.0", "project", PROJECT_DIR);
183
+ const version = getInstalledVersion("test/version", "project", PROJECT_DIR);
184
+ expect(version).toBe("3.0.0");
185
+
186
+ // Clean up
187
+ rmSync(join(PROJECT_ENACT_DIR, "tools.json"));
188
+ });
189
+
190
+ test("returns null for non-installed tool", () => {
191
+ const version = getInstalledVersion("not/installed", "project", PROJECT_DIR);
192
+ expect(version).toBeNull();
193
+ });
194
+ });
195
+
196
+ describe("getToolCachePath", () => {
197
+ test("returns cache path with version", () => {
198
+ const path = getToolCachePath("org/tool", "1.0.0");
199
+ expect(path).toContain(".enact");
200
+ expect(path).toContain("cache");
201
+ expect(path).toContain("org/tool");
202
+ expect(path).toContain("v1.0.0");
203
+ });
204
+
205
+ test("normalizes version prefix", () => {
206
+ const pathWithV = getToolCachePath("org/tool", "v1.0.0");
207
+ const pathWithoutV = getToolCachePath("org/tool", "1.0.0");
208
+ expect(pathWithV).toBe(pathWithoutV);
209
+ });
210
+ });
211
+
212
+ describe("listInstalledTools", () => {
213
+ test("returns list of installed tools", () => {
214
+ addToolToRegistry("test/list1", "1.0.0", "project", PROJECT_DIR);
215
+ addToolToRegistry("test/list2", "2.0.0", "project", PROJECT_DIR);
216
+
217
+ const tools = listInstalledTools("project", PROJECT_DIR);
218
+ expect(tools.length).toBe(2);
219
+ expect(tools.find((t) => t.name === "test/list1")).toBeTruthy();
220
+ expect(tools.find((t) => t.name === "test/list2")).toBeTruthy();
221
+
222
+ // Clean up
223
+ rmSync(join(PROJECT_ENACT_DIR, "tools.json"));
224
+ });
225
+
226
+ test("returns empty list when no tools installed", () => {
227
+ const tools = listInstalledTools("project", PROJECT_DIR);
228
+ expect(tools.length).toBe(0);
229
+ });
230
+ });
231
+ });
@@ -0,0 +1,272 @@
1
+ import { afterAll, beforeAll, describe, expect, test } from "bun:test";
2
+ import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import {
5
+ ToolResolveError,
6
+ getToolPath,
7
+ getToolSearchPaths,
8
+ normalizeToolName,
9
+ resolveTool,
10
+ resolveToolAuto,
11
+ resolveToolFromPath,
12
+ toolNameToPath,
13
+ tryResolveTool,
14
+ } from "../src/resolver";
15
+
16
+ const TEST_DIR = join(import.meta.dir, "temp-resolver-test");
17
+ const PROJECT_DIR = join(TEST_DIR, "project");
18
+ const PROJECT_ENACT_DIR = join(PROJECT_DIR, ".enact");
19
+
20
+ describe("tool resolver", () => {
21
+ beforeAll(() => {
22
+ // Create test directories
23
+ mkdirSync(join(PROJECT_ENACT_DIR, "tools", "test", "project-tool"), { recursive: true });
24
+
25
+ // Create a project-level tool
26
+ writeFileSync(
27
+ join(PROJECT_ENACT_DIR, "tools", "test", "project-tool", "enact.yaml"),
28
+ `
29
+ name: test/project-tool
30
+ description: A project-level test tool
31
+ version: "1.0.0"
32
+ `,
33
+ "utf-8"
34
+ );
35
+
36
+ // Create a direct tool directory for path-based resolution
37
+ mkdirSync(join(TEST_DIR, "direct-tool"), { recursive: true });
38
+ writeFileSync(
39
+ join(TEST_DIR, "direct-tool", "enact.yaml"),
40
+ `
41
+ name: test/direct-tool
42
+ description: A directly referenced tool
43
+ version: "2.0.0"
44
+ `,
45
+ "utf-8"
46
+ );
47
+
48
+ // Create a tool with enact.md
49
+ mkdirSync(join(TEST_DIR, "md-tool"), { recursive: true });
50
+ writeFileSync(
51
+ join(TEST_DIR, "md-tool", "enact.md"),
52
+ `---
53
+ name: test/md-tool
54
+ description: A markdown tool
55
+ version: "3.0.0"
56
+ ---
57
+
58
+ # MD Tool
59
+
60
+ Documentation here.
61
+ `,
62
+ "utf-8"
63
+ );
64
+ });
65
+
66
+ afterAll(() => {
67
+ // Cleanup
68
+ if (existsSync(TEST_DIR)) {
69
+ rmSync(TEST_DIR, { recursive: true, force: true });
70
+ }
71
+ });
72
+
73
+ describe("utility functions", () => {
74
+ describe("normalizeToolName", () => {
75
+ test("lowercases name", () => {
76
+ expect(normalizeToolName("Acme/Utils/Greeter")).toBe("acme/utils/greeter");
77
+ });
78
+
79
+ test("converts backslashes to forward slashes", () => {
80
+ expect(normalizeToolName("acme\\utils\\greeter")).toBe("acme/utils/greeter");
81
+ });
82
+
83
+ test("trims whitespace", () => {
84
+ expect(normalizeToolName(" acme/tool ")).toBe("acme/tool");
85
+ });
86
+ });
87
+
88
+ describe("toolNameToPath", () => {
89
+ test("returns path-like string", () => {
90
+ expect(toolNameToPath("acme/utils/greeter")).toBe("acme/utils/greeter");
91
+ });
92
+
93
+ test("normalizes backslashes", () => {
94
+ expect(toolNameToPath("acme\\utils")).toBe("acme/utils");
95
+ });
96
+ });
97
+
98
+ describe("getToolPath", () => {
99
+ test("joins tools dir and tool name", () => {
100
+ const result = getToolPath("/home/user/.enact/tools", "acme/greeter");
101
+ expect(result).toContain("acme");
102
+ expect(result).toContain("greeter");
103
+ });
104
+ });
105
+
106
+ describe("getToolSearchPaths", () => {
107
+ test("returns array of paths", () => {
108
+ const paths = getToolSearchPaths("test/tool", { startDir: PROJECT_DIR });
109
+ expect(Array.isArray(paths)).toBe(true);
110
+ expect(paths.length).toBeGreaterThan(0);
111
+ });
112
+
113
+ test("respects skipProject option", () => {
114
+ const withProject = getToolSearchPaths("test/tool", { startDir: PROJECT_DIR });
115
+ const withoutProject = getToolSearchPaths("test/tool", {
116
+ startDir: PROJECT_DIR,
117
+ skipProject: true,
118
+ });
119
+ expect(withoutProject.length).toBeLessThan(withProject.length);
120
+ });
121
+
122
+ test("respects skipUser option", () => {
123
+ // skipUser only affects global tools that are registered in tools.json
124
+ // Since no tools are installed globally, both should return the same paths
125
+ // This tests that skipUser doesn't add extra paths when no global tools exist
126
+ const withUser = getToolSearchPaths("test/tool", { skipCache: true });
127
+ const withoutUser = getToolSearchPaths("test/tool", { skipUser: true, skipCache: true });
128
+ expect(withoutUser.length).toBeLessThanOrEqual(withUser.length);
129
+ });
130
+ });
131
+ });
132
+
133
+ describe("resolveToolFromPath", () => {
134
+ test("resolves tool from directory", () => {
135
+ const toolDir = join(TEST_DIR, "direct-tool");
136
+ const result = resolveToolFromPath(toolDir);
137
+
138
+ expect(result.manifest.name).toBe("test/direct-tool");
139
+ expect(result.location).toBe("file");
140
+ expect(result.sourceDir).toBe(toolDir);
141
+ });
142
+
143
+ test("resolves tool from manifest file directly", () => {
144
+ const manifestPath = join(TEST_DIR, "direct-tool", "enact.yaml");
145
+ const result = resolveToolFromPath(manifestPath);
146
+
147
+ expect(result.manifest.name).toBe("test/direct-tool");
148
+ expect(result.manifestPath).toBe(manifestPath);
149
+ });
150
+
151
+ test("resolves markdown tool", () => {
152
+ const toolDir = join(TEST_DIR, "md-tool");
153
+ const result = resolveToolFromPath(toolDir);
154
+
155
+ expect(result.manifest.name).toBe("test/md-tool");
156
+ });
157
+
158
+ test("throws ToolResolveError for non-existent path", () => {
159
+ expect(() => resolveToolFromPath("/non/existent/path")).toThrow(ToolResolveError);
160
+ });
161
+
162
+ test("throws ToolResolveError for directory without manifest", () => {
163
+ const emptyDir = join(TEST_DIR, "empty-dir");
164
+ mkdirSync(emptyDir, { recursive: true });
165
+
166
+ expect(() => resolveToolFromPath(emptyDir)).toThrow(ToolResolveError);
167
+ });
168
+ });
169
+
170
+ describe("resolveTool", () => {
171
+ test("resolves tool from project", () => {
172
+ const result = resolveTool("test/project-tool", { startDir: PROJECT_DIR });
173
+
174
+ expect(result.manifest.name).toBe("test/project-tool");
175
+ expect(result.location).toBe("project");
176
+ });
177
+
178
+ test("throws ToolResolveError for non-existent tool", () => {
179
+ expect(() =>
180
+ resolveTool("non-existent/tool", { startDir: PROJECT_DIR, skipUser: true, skipCache: true })
181
+ ).toThrow(ToolResolveError);
182
+ });
183
+
184
+ test("error includes searched locations", () => {
185
+ try {
186
+ resolveTool("non-existent/tool", { startDir: PROJECT_DIR });
187
+ expect.unreachable("Should have thrown");
188
+ } catch (error) {
189
+ expect(error).toBeInstanceOf(ToolResolveError);
190
+ expect((error as ToolResolveError).searchedLocations).toBeDefined();
191
+ expect((error as ToolResolveError).searchedLocations?.length).toBeGreaterThan(0);
192
+ }
193
+ });
194
+
195
+ test("normalizes tool name", () => {
196
+ const result = resolveTool("TEST/PROJECT-TOOL", { startDir: PROJECT_DIR });
197
+ expect(result.manifest.name).toBe("test/project-tool");
198
+ });
199
+ });
200
+
201
+ describe("tryResolveTool", () => {
202
+ test("returns result on success", () => {
203
+ const result = tryResolveTool("test/project-tool", { startDir: PROJECT_DIR });
204
+ expect(result).not.toBeNull();
205
+ expect(result?.manifest.name).toBe("test/project-tool");
206
+ });
207
+
208
+ test("returns null on failure", () => {
209
+ const result = tryResolveTool("non-existent/tool", {
210
+ startDir: PROJECT_DIR,
211
+ skipUser: true,
212
+ skipCache: true,
213
+ });
214
+ expect(result).toBeNull();
215
+ });
216
+
217
+ test("handles path input", () => {
218
+ const toolDir = join(TEST_DIR, "direct-tool");
219
+ const result = tryResolveTool(toolDir);
220
+ expect(result).not.toBeNull();
221
+ expect(result?.manifest.name).toBe("test/direct-tool");
222
+ });
223
+
224
+ test("handles relative path with ./", () => {
225
+ // This would need the cwd to be set appropriately
226
+ // For now, just verify it doesn't crash
227
+ const result = tryResolveTool("./non-existent");
228
+ expect(result).toBeNull();
229
+ });
230
+ });
231
+
232
+ describe("resolveToolAuto", () => {
233
+ test("resolves path starting with /", () => {
234
+ const toolDir = join(TEST_DIR, "direct-tool");
235
+ const result = resolveToolAuto(toolDir);
236
+ expect(result.manifest.name).toBe("test/direct-tool");
237
+ });
238
+
239
+ test("resolves path starting with ./", () => {
240
+ // Use an absolute path that exists
241
+ const toolDir = join(TEST_DIR, "direct-tool");
242
+ const result = resolveToolAuto(toolDir);
243
+ expect(result.manifest.name).toBe("test/direct-tool");
244
+ });
245
+
246
+ test("resolves tool name", () => {
247
+ const result = resolveToolAuto("test/project-tool", { startDir: PROJECT_DIR });
248
+ expect(result.manifest.name).toBe("test/project-tool");
249
+ });
250
+
251
+ test("throws for non-existent", () => {
252
+ expect(() =>
253
+ resolveToolAuto("completely/non-existent", {
254
+ startDir: PROJECT_DIR,
255
+ skipUser: true,
256
+ skipCache: true,
257
+ })
258
+ ).toThrow(ToolResolveError);
259
+ });
260
+ });
261
+
262
+ describe("ToolResolveError", () => {
263
+ test("has correct properties", () => {
264
+ const error = new ToolResolveError("Test error", "test/tool", ["/path/1", "/path/2"]);
265
+
266
+ expect(error.name).toBe("ToolResolveError");
267
+ expect(error.message).toBe("Test error");
268
+ expect(error.toolPath).toBe("test/tool");
269
+ expect(error.searchedLocations).toEqual(["/path/1", "/path/2"]);
270
+ });
271
+ });
272
+ });