@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,317 @@
1
+ /**
2
+ * Tests for command interpolation module
3
+ */
4
+
5
+ import { describe, expect, test } from "bun:test";
6
+ import {
7
+ getMissingParams,
8
+ interpolateCommand,
9
+ parseCommand,
10
+ parseCommandArgs,
11
+ prepareCommand,
12
+ shellEscape,
13
+ } from "../../src/execution/command";
14
+
15
+ describe("Command Interpolation", () => {
16
+ describe("parseCommand", () => {
17
+ test("parses command with no parameters", () => {
18
+ const result = parseCommand("echo hello");
19
+
20
+ expect(result.original).toBe("echo hello");
21
+ expect(result.tokens).toHaveLength(1);
22
+ expect(result.tokens[0]).toEqual({ type: "literal", value: "echo hello" });
23
+ expect(result.parameters).toHaveLength(0);
24
+ });
25
+
26
+ test("parses command with single parameter", () => {
27
+ const result = parseCommand("echo ${message}");
28
+
29
+ expect(result.original).toBe("echo ${message}");
30
+ expect(result.tokens).toHaveLength(2);
31
+ expect(result.tokens[0]).toEqual({ type: "literal", value: "echo " });
32
+ expect(result.tokens[1]).toEqual({ type: "parameter", name: "message" });
33
+ expect(result.parameters).toEqual(["message"]);
34
+ });
35
+
36
+ test("parses command with multiple parameters", () => {
37
+ const result = parseCommand("curl -X ${method} ${url} -d '${data}'");
38
+
39
+ expect(result.parameters).toContain("method");
40
+ expect(result.parameters).toContain("url");
41
+ expect(result.parameters).toContain("data");
42
+ expect(result.parameters).toHaveLength(3);
43
+ });
44
+
45
+ test("parses command with parameter at start", () => {
46
+ const result = parseCommand("${cmd} arg1 arg2");
47
+
48
+ expect(result.tokens[0]).toEqual({ type: "parameter", name: "cmd" });
49
+ expect(result.tokens[1]).toEqual({ type: "literal", value: " arg1 arg2" });
50
+ });
51
+
52
+ test("parses command with adjacent parameters", () => {
53
+ const result = parseCommand("${prefix}${suffix}");
54
+
55
+ expect(result.tokens).toHaveLength(2);
56
+ expect(result.tokens[0]).toEqual({ type: "parameter", name: "prefix" });
57
+ expect(result.tokens[1]).toEqual({ type: "parameter", name: "suffix" });
58
+ });
59
+
60
+ test("handles nested braces", () => {
61
+ const result = parseCommand("echo '${json}' | jq '.${field}'");
62
+
63
+ expect(result.parameters).toContain("json");
64
+ expect(result.parameters).toContain("field");
65
+ });
66
+
67
+ test("removes duplicate parameters", () => {
68
+ const result = parseCommand("echo ${name} ${name} ${name}");
69
+
70
+ expect(result.parameters).toEqual(["name"]);
71
+ });
72
+ });
73
+
74
+ describe("interpolateCommand", () => {
75
+ test("interpolates single parameter", () => {
76
+ // By default, escape=true, so values get shell-escaped
77
+ const result = interpolateCommand("echo ${message}", {
78
+ message: "hello world",
79
+ });
80
+
81
+ // "hello world" contains space, gets single-quoted
82
+ expect(result).toBe("echo 'hello world'");
83
+ });
84
+
85
+ test("interpolates multiple parameters", () => {
86
+ const result = interpolateCommand("curl -X ${method} ${url}", {
87
+ method: "POST",
88
+ url: "https://api.example.com",
89
+ });
90
+
91
+ // URL contains special chars, gets quoted
92
+ expect(result).toBe("curl -X POST 'https://api.example.com'");
93
+ });
94
+
95
+ test("handles missing parameters with keep option", () => {
96
+ const result = interpolateCommand("echo ${message}", {}, { onMissing: "keep" });
97
+
98
+ // Missing params are left as-is when onMissing is "keep"
99
+ expect(result).toBe("echo ${message}");
100
+ });
101
+
102
+ test("throws on missing parameters by default", () => {
103
+ expect(() => interpolateCommand("echo ${message}", {})).toThrow(
104
+ "Missing required parameter: message"
105
+ );
106
+ });
107
+
108
+ test("converts numbers to strings", () => {
109
+ const result = interpolateCommand("seq ${start} ${end}", {
110
+ start: 1,
111
+ end: 10,
112
+ });
113
+
114
+ expect(result).toBe("seq 1 10");
115
+ });
116
+
117
+ test("handles boolean values", () => {
118
+ const result = interpolateCommand("echo ${flag}", {
119
+ flag: true,
120
+ });
121
+
122
+ expect(result).toBe("echo true");
123
+ });
124
+
125
+ test("handles null values as empty string", () => {
126
+ const result = interpolateCommand("echo ${a}", {
127
+ a: null,
128
+ });
129
+
130
+ // null becomes empty string
131
+ expect(result).toBe("echo ''");
132
+ });
133
+
134
+ test("stringifies objects as JSON", () => {
135
+ const result = interpolateCommand(
136
+ "echo ${data}",
137
+ {
138
+ data: { key: "value" },
139
+ },
140
+ { escape: false }
141
+ );
142
+
143
+ expect(result).toBe('echo {"key":"value"}');
144
+ });
145
+
146
+ test("handles arrays", () => {
147
+ const result = interpolateCommand(
148
+ "echo ${items}",
149
+ {
150
+ items: [1, 2, 3],
151
+ },
152
+ { escape: false }
153
+ );
154
+
155
+ expect(result).toBe("echo [1,2,3]");
156
+ });
157
+ });
158
+
159
+ describe("shellEscape", () => {
160
+ test("escapes single quotes", () => {
161
+ // The implementation uses: 'it'"'"'s' pattern
162
+ expect(shellEscape("it's")).toBe("'it'\"'\"'s'");
163
+ });
164
+
165
+ test("wraps strings with spaces", () => {
166
+ expect(shellEscape("hello world")).toBe("'hello world'");
167
+ });
168
+
169
+ test("escapes special characters", () => {
170
+ expect(shellEscape("test$var")).toBe("'test$var'");
171
+ expect(shellEscape("test`cmd`")).toBe("'test`cmd`'");
172
+ });
173
+
174
+ test("returns safe strings as-is", () => {
175
+ expect(shellEscape("simple")).toBe("simple");
176
+ expect(shellEscape("path/to/file")).toBe("path/to/file");
177
+ expect(shellEscape("file.txt")).toBe("file.txt");
178
+ });
179
+
180
+ test("handles empty string", () => {
181
+ expect(shellEscape("")).toBe("''");
182
+ });
183
+
184
+ test("handles strings with newlines", () => {
185
+ expect(shellEscape("line1\nline2")).toBe("'line1\nline2'");
186
+ });
187
+
188
+ test("handles strings with backslashes", () => {
189
+ expect(shellEscape("path\\to\\file")).toBe("'path\\to\\file'");
190
+ });
191
+ });
192
+
193
+ describe("parseCommandArgs", () => {
194
+ test("parses simple arguments", () => {
195
+ const result = parseCommandArgs("arg1 arg2 arg3");
196
+
197
+ expect(result).toEqual(["arg1", "arg2", "arg3"]);
198
+ });
199
+
200
+ test("handles quoted strings", () => {
201
+ const result = parseCommandArgs('echo "hello world"');
202
+
203
+ expect(result).toEqual(["echo", "hello world"]);
204
+ });
205
+
206
+ test("handles single-quoted strings", () => {
207
+ const result = parseCommandArgs("echo 'hello world'");
208
+
209
+ expect(result).toEqual(["echo", "hello world"]);
210
+ });
211
+
212
+ test("handles mixed quotes", () => {
213
+ const result = parseCommandArgs("echo 'single' \"double\"");
214
+
215
+ expect(result).toEqual(["echo", "single", "double"]);
216
+ });
217
+
218
+ test("handles empty input", () => {
219
+ expect(parseCommandArgs("")).toEqual([]);
220
+ });
221
+
222
+ test("handles extra whitespace", () => {
223
+ const result = parseCommandArgs(" arg1 arg2 ");
224
+
225
+ expect(result).toEqual(["arg1", "arg2"]);
226
+ });
227
+
228
+ test("handles escaped quotes within strings", () => {
229
+ const result = parseCommandArgs('echo "say \\"hello\\""');
230
+
231
+ expect(result).toEqual(["echo", 'say "hello"']);
232
+ });
233
+ });
234
+
235
+ describe("prepareCommand", () => {
236
+ test("prepares simple command without shell wrap", () => {
237
+ const result = prepareCommand("echo hello", {});
238
+
239
+ expect(result).toEqual(["echo", "hello"]);
240
+ });
241
+
242
+ test("wraps command with pipes", () => {
243
+ const result = prepareCommand("echo hello | cat", {});
244
+
245
+ // Contains | which triggers shell wrap
246
+ expect(result).toEqual(["sh", "-c", "echo hello | cat"]);
247
+ });
248
+
249
+ test("interpolates parameters without shell wrap when no special chars", () => {
250
+ // After interpolation, "echo world" has no special chars
251
+ const result = prepareCommand("echo ${name}", { name: "world" });
252
+
253
+ expect(result).toEqual(["echo", "world"]);
254
+ });
255
+
256
+ test("handles commands with pipes", () => {
257
+ const result = prepareCommand("cat file | grep pattern", {});
258
+
259
+ expect(result).toEqual(["sh", "-c", "cat file | grep pattern"]);
260
+ });
261
+
262
+ test("parses simple args without shell features", () => {
263
+ // No special chars, so should parse as args
264
+ const result = prepareCommand("simple command here", {});
265
+
266
+ expect(result).toEqual(["simple", "command", "here"]);
267
+ });
268
+
269
+ test("shell wraps when escaped value contains quotes", () => {
270
+ // When value has spaces, it gets single-quoted, but parseCommandArgs
271
+ // handles quotes properly, so it still gets parsed as args
272
+ const result = prepareCommand("echo ${msg}", { msg: "hello world" });
273
+
274
+ // parseCommandArgs strips the quotes, so we get the unquoted value
275
+ expect(result).toEqual(["echo", "hello world"]);
276
+ });
277
+ });
278
+
279
+ describe("getMissingParams", () => {
280
+ test("returns empty array when all params present", () => {
281
+ const result = getMissingParams("echo ${a} ${b}", { a: "1", b: "2" });
282
+
283
+ expect(result).toEqual([]);
284
+ });
285
+
286
+ test("returns missing param names", () => {
287
+ const result = getMissingParams("echo ${a} ${b} ${c}", { a: "1" });
288
+
289
+ expect(result).toContain("b");
290
+ expect(result).toContain("c");
291
+ expect(result).not.toContain("a");
292
+ });
293
+
294
+ test("handles no parameters", () => {
295
+ const result = getMissingParams("echo hello", {});
296
+
297
+ expect(result).toEqual([]);
298
+ });
299
+
300
+ test("handles all parameters missing", () => {
301
+ const result = getMissingParams("echo ${x} ${y}", {});
302
+
303
+ expect(result).toEqual(["x", "y"]);
304
+ });
305
+
306
+ test("treats null as present but undefined as missing", () => {
307
+ // null is a value (present), undefined means not provided
308
+ const result = getMissingParams("echo ${a} ${b}", {
309
+ a: null,
310
+ b: undefined,
311
+ });
312
+
313
+ // Only b is missing since undefined is treated as not provided
314
+ expect(result).toEqual(["b"]);
315
+ });
316
+ });
317
+ });
@@ -0,0 +1,384 @@
1
+ /**
2
+ * Tests for input validation module
3
+ */
4
+
5
+ import { describe, expect, test } from "bun:test";
6
+ import type { JSONSchema7 } from "json-schema";
7
+ import {
8
+ applyDefaults,
9
+ getParamInfo,
10
+ getRequiredParams,
11
+ validateInputs,
12
+ } from "../../src/execution/validation";
13
+
14
+ describe("Input Validation", () => {
15
+ describe("validateInputs", () => {
16
+ const simpleSchema: JSONSchema7 = {
17
+ type: "object",
18
+ properties: {
19
+ name: { type: "string" },
20
+ count: { type: "number" },
21
+ },
22
+ required: ["name"],
23
+ };
24
+
25
+ test("validates correct inputs", () => {
26
+ const result = validateInputs({ name: "test", count: 5 }, simpleSchema);
27
+
28
+ expect(result.valid).toBe(true);
29
+ expect(result.errors).toHaveLength(0);
30
+ });
31
+
32
+ test("reports missing required fields", () => {
33
+ const result = validateInputs({ count: 5 }, simpleSchema);
34
+
35
+ expect(result.valid).toBe(false);
36
+ expect(result.errors.length).toBeGreaterThan(0);
37
+ expect(result.errors[0]?.path).toContain("name");
38
+ });
39
+
40
+ test("reports type errors", () => {
41
+ const result = validateInputs({ name: "test", count: "not a number" }, simpleSchema);
42
+
43
+ expect(result.valid).toBe(false);
44
+ expect(result.errors.some((e) => e.path.includes("count"))).toBe(true);
45
+ });
46
+
47
+ test("validates nested objects", () => {
48
+ const nestedSchema: JSONSchema7 = {
49
+ type: "object",
50
+ properties: {
51
+ config: {
52
+ type: "object",
53
+ properties: {
54
+ timeout: { type: "number" },
55
+ },
56
+ required: ["timeout"],
57
+ },
58
+ },
59
+ required: ["config"],
60
+ };
61
+
62
+ const result = validateInputs({ config: { timeout: 30 } }, nestedSchema);
63
+
64
+ expect(result.valid).toBe(true);
65
+ });
66
+
67
+ test("validates arrays", () => {
68
+ const arraySchema: JSONSchema7 = {
69
+ type: "object",
70
+ properties: {
71
+ items: {
72
+ type: "array",
73
+ items: { type: "string" },
74
+ },
75
+ },
76
+ };
77
+
78
+ const result = validateInputs({ items: ["a", "b", "c"] }, arraySchema);
79
+
80
+ expect(result.valid).toBe(true);
81
+ });
82
+
83
+ test("handles empty inputs", () => {
84
+ const result = validateInputs({}, simpleSchema);
85
+
86
+ // Missing required "name"
87
+ expect(result.valid).toBe(false);
88
+ });
89
+
90
+ test("validates string patterns", () => {
91
+ const patternSchema: JSONSchema7 = {
92
+ type: "object",
93
+ properties: {
94
+ email: {
95
+ type: "string",
96
+ pattern: "^[a-z]+@[a-z]+\\.[a-z]+$",
97
+ },
98
+ },
99
+ };
100
+
101
+ const validResult = validateInputs({ email: "test@example.com" }, patternSchema);
102
+ expect(validResult.valid).toBe(true);
103
+
104
+ const invalidResult = validateInputs({ email: "not-an-email" }, patternSchema);
105
+ expect(invalidResult.valid).toBe(false);
106
+ });
107
+
108
+ test("validates enums", () => {
109
+ const enumSchema: JSONSchema7 = {
110
+ type: "object",
111
+ properties: {
112
+ status: {
113
+ type: "string",
114
+ enum: ["active", "inactive", "pending"],
115
+ },
116
+ },
117
+ };
118
+
119
+ const validResult = validateInputs({ status: "active" }, enumSchema);
120
+ expect(validResult.valid).toBe(true);
121
+
122
+ const invalidResult = validateInputs({ status: "unknown" }, enumSchema);
123
+ expect(invalidResult.valid).toBe(false);
124
+ });
125
+
126
+ test("validates minimum and maximum", () => {
127
+ const rangeSchema: JSONSchema7 = {
128
+ type: "object",
129
+ properties: {
130
+ count: { type: "number", minimum: 0, maximum: 100 },
131
+ },
132
+ };
133
+
134
+ expect(validateInputs({ count: 50 }, rangeSchema).valid).toBe(true);
135
+ expect(validateInputs({ count: -1 }, rangeSchema).valid).toBe(false);
136
+ expect(validateInputs({ count: 101 }, rangeSchema).valid).toBe(false);
137
+ });
138
+
139
+ test("validates string lengths", () => {
140
+ const lengthSchema: JSONSchema7 = {
141
+ type: "object",
142
+ properties: {
143
+ name: { type: "string", minLength: 2, maxLength: 10 },
144
+ },
145
+ };
146
+
147
+ expect(validateInputs({ name: "test" }, lengthSchema).valid).toBe(true);
148
+ expect(validateInputs({ name: "x" }, lengthSchema).valid).toBe(false);
149
+ expect(validateInputs({ name: "this is too long" }, lengthSchema).valid).toBe(false);
150
+ });
151
+ });
152
+
153
+ describe("applyDefaults", () => {
154
+ test("applies default values", () => {
155
+ const schema: JSONSchema7 = {
156
+ type: "object",
157
+ properties: {
158
+ name: { type: "string", default: "default_name" },
159
+ count: { type: "number", default: 10 },
160
+ },
161
+ };
162
+
163
+ const result = applyDefaults({}, schema);
164
+
165
+ expect(result.name).toBe("default_name");
166
+ expect(result.count).toBe(10);
167
+ });
168
+
169
+ test("preserves provided values", () => {
170
+ const schema: JSONSchema7 = {
171
+ type: "object",
172
+ properties: {
173
+ name: { type: "string", default: "default_name" },
174
+ },
175
+ };
176
+
177
+ const result = applyDefaults({ name: "custom_name" }, schema);
178
+
179
+ expect(result.name).toBe("custom_name");
180
+ });
181
+
182
+ test("applies nested defaults", () => {
183
+ const schema: JSONSchema7 = {
184
+ type: "object",
185
+ properties: {
186
+ config: {
187
+ type: "object",
188
+ default: { timeout: 30, retries: 3 },
189
+ },
190
+ },
191
+ };
192
+
193
+ const result = applyDefaults({}, schema);
194
+
195
+ expect(result.config).toEqual({ timeout: 30, retries: 3 });
196
+ });
197
+
198
+ test("handles array defaults", () => {
199
+ const schema: JSONSchema7 = {
200
+ type: "object",
201
+ properties: {
202
+ tags: {
203
+ type: "array",
204
+ default: ["default", "tag"],
205
+ },
206
+ },
207
+ };
208
+
209
+ const result = applyDefaults({}, schema);
210
+
211
+ expect(result.tags).toEqual(["default", "tag"]);
212
+ });
213
+
214
+ test("handles boolean defaults", () => {
215
+ const schema: JSONSchema7 = {
216
+ type: "object",
217
+ properties: {
218
+ enabled: { type: "boolean", default: true },
219
+ },
220
+ };
221
+
222
+ const result = applyDefaults({}, schema);
223
+
224
+ expect(result.enabled).toBe(true);
225
+ });
226
+
227
+ test("does not apply defaults for explicit null", () => {
228
+ const schema: JSONSchema7 = {
229
+ type: "object",
230
+ properties: {
231
+ value: { type: "string", default: "default" },
232
+ },
233
+ };
234
+
235
+ // Explicitly passing null should not trigger default
236
+ const result = applyDefaults({ value: null }, schema);
237
+
238
+ expect(result.value).toBeNull();
239
+ });
240
+
241
+ test("applies defaults only for undefined", () => {
242
+ const schema: JSONSchema7 = {
243
+ type: "object",
244
+ properties: {
245
+ value: { type: "string", default: "default" },
246
+ count: { type: "number", default: 0 },
247
+ },
248
+ };
249
+
250
+ const result = applyDefaults({ value: undefined }, schema);
251
+
252
+ expect(result.value).toBe("default");
253
+ expect(result.count).toBe(0);
254
+ });
255
+ });
256
+
257
+ describe("getRequiredParams", () => {
258
+ test("returns required parameter names", () => {
259
+ const schema: JSONSchema7 = {
260
+ type: "object",
261
+ properties: {
262
+ name: { type: "string" },
263
+ email: { type: "string" },
264
+ age: { type: "number" },
265
+ },
266
+ required: ["name", "email"],
267
+ };
268
+
269
+ const result = getRequiredParams(schema);
270
+
271
+ expect(result).toContain("name");
272
+ expect(result).toContain("email");
273
+ expect(result).not.toContain("age");
274
+ });
275
+
276
+ test("returns empty array when no required params", () => {
277
+ const schema: JSONSchema7 = {
278
+ type: "object",
279
+ properties: {
280
+ name: { type: "string" },
281
+ },
282
+ };
283
+
284
+ const result = getRequiredParams(schema);
285
+
286
+ expect(result).toEqual([]);
287
+ });
288
+
289
+ test("handles schema without properties", () => {
290
+ const schema: JSONSchema7 = {
291
+ type: "object",
292
+ };
293
+
294
+ const result = getRequiredParams(schema);
295
+
296
+ expect(result).toEqual([]);
297
+ });
298
+ });
299
+
300
+ describe("getParamInfo", () => {
301
+ test("extracts parameter information", () => {
302
+ const schema: JSONSchema7 = {
303
+ type: "object",
304
+ properties: {
305
+ name: {
306
+ type: "string",
307
+ description: "User name",
308
+ default: "anonymous",
309
+ },
310
+ count: {
311
+ type: "number",
312
+ description: "Item count",
313
+ minimum: 0,
314
+ },
315
+ },
316
+ required: ["name"],
317
+ };
318
+
319
+ const result = getParamInfo(schema);
320
+
321
+ expect(result.size).toBe(2);
322
+
323
+ const nameParam = result.get("name");
324
+ expect(nameParam).toBeDefined();
325
+ expect(nameParam?.type).toBe("string");
326
+ expect(nameParam?.description).toBe("User name");
327
+ expect(nameParam?.required).toBe(true);
328
+ expect(nameParam?.default).toBe("anonymous");
329
+
330
+ const countParam = result.get("count");
331
+ expect(countParam).toBeDefined();
332
+ expect(countParam?.type).toBe("number");
333
+ expect(countParam?.required).toBe(false);
334
+ });
335
+
336
+ test("handles array types", () => {
337
+ const schema: JSONSchema7 = {
338
+ type: "object",
339
+ properties: {
340
+ tags: {
341
+ type: "array",
342
+ items: { type: "string" },
343
+ description: "Tags for categorization",
344
+ },
345
+ },
346
+ };
347
+
348
+ const result = getParamInfo(schema);
349
+ const tagsParam = result.get("tags");
350
+
351
+ expect(tagsParam?.type).toBe("array");
352
+ });
353
+
354
+ test("handles enum types", () => {
355
+ const schema: JSONSchema7 = {
356
+ type: "object",
357
+ properties: {
358
+ status: {
359
+ type: "string",
360
+ enum: ["active", "inactive"],
361
+ description: "Account status",
362
+ },
363
+ },
364
+ };
365
+
366
+ const result = getParamInfo(schema);
367
+ const statusParam = result.get("status");
368
+
369
+ // Note: getParamInfo doesn't extract enum values, just type
370
+ expect(statusParam).toBeDefined();
371
+ expect(statusParam?.type).toBe("string");
372
+ });
373
+
374
+ test("returns empty Map for schema without properties", () => {
375
+ const schema: JSONSchema7 = {
376
+ type: "object",
377
+ };
378
+
379
+ const result = getParamInfo(schema);
380
+
381
+ expect(result.size).toBe(0);
382
+ });
383
+ });
384
+ });
@@ -0,0 +1,4 @@
1
+ # Missing required fields
2
+ version: "1.0.0"
3
+ # name is missing
4
+ # description is missing