@enactprotocol/shared 2.2.2 → 2.2.4

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.
@@ -6,14 +6,19 @@ import { afterAll, beforeAll, describe, expect, test } from "bun:test";
6
6
  import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
7
7
  import { join } from "node:path";
8
8
  import {
9
+ addAlias,
9
10
  addToolToRegistry,
11
+ getAliasesForTool,
10
12
  getInstalledVersion,
11
13
  getToolCachePath,
12
14
  getToolsJsonPath,
13
15
  isToolInstalled,
14
16
  listInstalledTools,
15
17
  loadToolsRegistry,
18
+ removeAlias,
19
+ removeAliasesForTool,
16
20
  removeToolFromRegistry,
21
+ resolveAlias,
17
22
  saveToolsRegistry,
18
23
  } from "../src/registry";
19
24
 
@@ -228,4 +233,194 @@ describe("registry", () => {
228
233
  expect(tools.length).toBe(0);
229
234
  });
230
235
  });
236
+
237
+ describe("addAlias", () => {
238
+ test("adds alias to registry", () => {
239
+ addToolToRegistry("test/aliased-tool", "1.0.0", "project", PROJECT_DIR);
240
+ addAlias("mytool", "test/aliased-tool", "project", PROJECT_DIR);
241
+
242
+ const registry = loadToolsRegistry("project", PROJECT_DIR);
243
+ expect(registry.aliases?.mytool).toBe("test/aliased-tool");
244
+
245
+ // Clean up
246
+ rmSync(join(PROJECT_ENACT_DIR, "tools.json"));
247
+ });
248
+
249
+ test("throws error when alias already exists for different tool", () => {
250
+ addToolToRegistry("test/tool1", "1.0.0", "project", PROJECT_DIR);
251
+ addToolToRegistry("test/tool2", "1.0.0", "project", PROJECT_DIR);
252
+ addAlias("shared", "test/tool1", "project", PROJECT_DIR);
253
+
254
+ expect(() => {
255
+ addAlias("shared", "test/tool2", "project", PROJECT_DIR);
256
+ }).toThrow('Alias "shared" already exists for tool "test/tool1"');
257
+
258
+ // Clean up
259
+ rmSync(join(PROJECT_ENACT_DIR, "tools.json"));
260
+ });
261
+
262
+ test("allows adding same alias for same tool (idempotent)", () => {
263
+ addToolToRegistry("test/same-tool", "1.0.0", "project", PROJECT_DIR);
264
+ addAlias("same", "test/same-tool", "project", PROJECT_DIR);
265
+
266
+ // Should not throw
267
+ expect(() => {
268
+ addAlias("same", "test/same-tool", "project", PROJECT_DIR);
269
+ }).not.toThrow();
270
+
271
+ // Clean up
272
+ rmSync(join(PROJECT_ENACT_DIR, "tools.json"));
273
+ });
274
+ });
275
+
276
+ describe("removeAlias", () => {
277
+ test("removes existing alias", () => {
278
+ addToolToRegistry("test/removable", "1.0.0", "project", PROJECT_DIR);
279
+ addAlias("removeme", "test/removable", "project", PROJECT_DIR);
280
+
281
+ const removed = removeAlias("removeme", "project", PROJECT_DIR);
282
+ expect(removed).toBe(true);
283
+
284
+ const registry = loadToolsRegistry("project", PROJECT_DIR);
285
+ expect(registry.aliases?.removeme).toBeUndefined();
286
+
287
+ // Clean up
288
+ rmSync(join(PROJECT_ENACT_DIR, "tools.json"));
289
+ });
290
+
291
+ test("returns false for non-existent alias", () => {
292
+ const removed = removeAlias("nonexistent", "project", PROJECT_DIR);
293
+ expect(removed).toBe(false);
294
+ });
295
+ });
296
+
297
+ describe("resolveAlias", () => {
298
+ test("resolves existing alias to tool name", () => {
299
+ addToolToRegistry("org/category/full-name", "1.0.0", "project", PROJECT_DIR);
300
+ addAlias("short", "org/category/full-name", "project", PROJECT_DIR);
301
+
302
+ const resolved = resolveAlias("short", "project", PROJECT_DIR);
303
+ expect(resolved).toBe("org/category/full-name");
304
+
305
+ // Clean up
306
+ rmSync(join(PROJECT_ENACT_DIR, "tools.json"));
307
+ });
308
+
309
+ test("returns null for non-existent alias", () => {
310
+ const resolved = resolveAlias("unknown", "project", PROJECT_DIR);
311
+ expect(resolved).toBeNull();
312
+ });
313
+ });
314
+
315
+ describe("getAliasesForTool", () => {
316
+ test("returns all aliases for a tool", () => {
317
+ addToolToRegistry("test/multi-alias", "1.0.0", "project", PROJECT_DIR);
318
+ addAlias("alias1", "test/multi-alias", "project", PROJECT_DIR);
319
+ addAlias("alias2", "test/multi-alias", "project", PROJECT_DIR);
320
+
321
+ const aliases = getAliasesForTool("test/multi-alias", "project", PROJECT_DIR);
322
+ expect(aliases).toContain("alias1");
323
+ expect(aliases).toContain("alias2");
324
+ expect(aliases.length).toBe(2);
325
+
326
+ // Clean up
327
+ rmSync(join(PROJECT_ENACT_DIR, "tools.json"));
328
+ });
329
+
330
+ test("returns empty array for tool without aliases", () => {
331
+ addToolToRegistry("test/no-alias", "1.0.0", "project", PROJECT_DIR);
332
+
333
+ const aliases = getAliasesForTool("test/no-alias", "project", PROJECT_DIR);
334
+ expect(aliases).toEqual([]);
335
+
336
+ // Clean up
337
+ rmSync(join(PROJECT_ENACT_DIR, "tools.json"));
338
+ });
339
+ });
340
+
341
+ describe("removeAliasesForTool", () => {
342
+ test("removes all aliases for a tool", () => {
343
+ addToolToRegistry("test/cleanup", "1.0.0", "project", PROJECT_DIR);
344
+ addAlias("cleanup1", "test/cleanup", "project", PROJECT_DIR);
345
+ addAlias("cleanup2", "test/cleanup", "project", PROJECT_DIR);
346
+
347
+ const removed = removeAliasesForTool("test/cleanup", "project", PROJECT_DIR);
348
+ expect(removed).toBe(2);
349
+
350
+ const registry = loadToolsRegistry("project", PROJECT_DIR);
351
+ expect(registry.aliases?.cleanup1).toBeUndefined();
352
+ expect(registry.aliases?.cleanup2).toBeUndefined();
353
+
354
+ // Clean up
355
+ rmSync(join(PROJECT_ENACT_DIR, "tools.json"));
356
+ });
357
+
358
+ test("returns 0 for tool without aliases", () => {
359
+ addToolToRegistry("test/no-aliases-to-remove", "1.0.0", "project", PROJECT_DIR);
360
+
361
+ const removed = removeAliasesForTool("test/no-aliases-to-remove", "project", PROJECT_DIR);
362
+ expect(removed).toBe(0);
363
+
364
+ // Clean up
365
+ rmSync(join(PROJECT_ENACT_DIR, "tools.json"));
366
+ });
367
+
368
+ test("does not remove aliases for other tools", () => {
369
+ addToolToRegistry("test/keep", "1.0.0", "project", PROJECT_DIR);
370
+ addToolToRegistry("test/remove", "1.0.0", "project", PROJECT_DIR);
371
+ addAlias("keepme", "test/keep", "project", PROJECT_DIR);
372
+ addAlias("removeme", "test/remove", "project", PROJECT_DIR);
373
+
374
+ removeAliasesForTool("test/remove", "project", PROJECT_DIR);
375
+
376
+ const registry = loadToolsRegistry("project", PROJECT_DIR);
377
+ expect(registry.aliases?.keepme).toBe("test/keep");
378
+ expect(registry.aliases?.removeme).toBeUndefined();
379
+
380
+ // Clean up
381
+ rmSync(join(PROJECT_ENACT_DIR, "tools.json"));
382
+ });
383
+ });
384
+
385
+ describe("loadToolsRegistry with aliases", () => {
386
+ test("loads existing registry with aliases", () => {
387
+ const registryPath = join(PROJECT_ENACT_DIR, "tools.json");
388
+ writeFileSync(
389
+ registryPath,
390
+ JSON.stringify({
391
+ tools: {
392
+ "test/tool": "1.0.0",
393
+ },
394
+ aliases: {
395
+ t: "test/tool",
396
+ },
397
+ })
398
+ );
399
+
400
+ const registry = loadToolsRegistry("project", PROJECT_DIR);
401
+ expect(registry.tools["test/tool"]).toBe("1.0.0");
402
+ expect(registry.aliases?.t).toBe("test/tool");
403
+
404
+ // Clean up
405
+ rmSync(registryPath);
406
+ });
407
+
408
+ test("returns empty aliases when not present in file", () => {
409
+ const registryPath = join(PROJECT_ENACT_DIR, "tools.json");
410
+ writeFileSync(
411
+ registryPath,
412
+ JSON.stringify({
413
+ tools: {
414
+ "test/tool": "1.0.0",
415
+ },
416
+ })
417
+ );
418
+
419
+ const registry = loadToolsRegistry("project", PROJECT_DIR);
420
+ expect(registry.aliases).toEqual({});
421
+
422
+ // Clean up
423
+ rmSync(registryPath);
424
+ });
425
+ });
231
426
  });
@@ -1,6 +1,7 @@
1
1
  import { afterAll, beforeAll, describe, expect, test } from "bun:test";
2
2
  import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
3
3
  import { join } from "node:path";
4
+ import { addAlias, addToolToRegistry, removeAlias } from "../src/registry";
4
5
  import {
5
6
  ToolResolveError,
6
7
  getToolPath,
@@ -269,4 +270,83 @@ Documentation here.
269
270
  expect(error.searchedLocations).toEqual(["/path/1", "/path/2"]);
270
271
  });
271
272
  });
273
+
274
+ describe("alias resolution", () => {
275
+ test("resolves tool via alias", () => {
276
+ // Set up an alias for the project tool
277
+ addToolToRegistry("test/project-tool", "1.0.0", "project", PROJECT_DIR);
278
+ addAlias("pt", "test/project-tool", "project", PROJECT_DIR);
279
+
280
+ try {
281
+ // Resolve using the alias (no slashes = potential alias)
282
+ const result = resolveTool("pt", { startDir: PROJECT_DIR });
283
+ expect(result.manifest.name).toBe("test/project-tool");
284
+ expect(result.location).toBe("project");
285
+ } finally {
286
+ // Clean up
287
+ removeAlias("pt", "project", PROJECT_DIR);
288
+ rmSync(join(PROJECT_ENACT_DIR, "tools.json"), { force: true });
289
+ }
290
+ });
291
+
292
+ test("alias resolution is case-insensitive (normalized to lowercase)", () => {
293
+ addToolToRegistry("test/project-tool", "1.0.0", "project", PROJECT_DIR);
294
+ addAlias("mytool", "test/project-tool", "project", PROJECT_DIR);
295
+
296
+ try {
297
+ // Lowercase alias should work
298
+ const result = resolveTool("mytool", { startDir: PROJECT_DIR });
299
+ expect(result.manifest.name).toBe("test/project-tool");
300
+
301
+ // Uppercase alias should also work (normalized to lowercase)
302
+ const upperResult = resolveTool("MYTOOL", { startDir: PROJECT_DIR });
303
+ expect(upperResult.manifest.name).toBe("test/project-tool");
304
+
305
+ // Mixed case should also work
306
+ const mixedResult = resolveTool("MyTool", { startDir: PROJECT_DIR });
307
+ expect(mixedResult.manifest.name).toBe("test/project-tool");
308
+ } finally {
309
+ removeAlias("mytool", "project", PROJECT_DIR);
310
+ rmSync(join(PROJECT_ENACT_DIR, "tools.json"), { force: true });
311
+ }
312
+ });
313
+
314
+ test("full tool names bypass alias resolution", () => {
315
+ addToolToRegistry("test/project-tool", "1.0.0", "project", PROJECT_DIR);
316
+ // Create an alias that would conflict if checked
317
+ addAlias("test/project-tool", "some/other/tool", "project", PROJECT_DIR);
318
+
319
+ try {
320
+ // Full name with slashes should resolve directly, not via alias
321
+ const result = resolveTool("test/project-tool", { startDir: PROJECT_DIR });
322
+ expect(result.manifest.name).toBe("test/project-tool");
323
+ } finally {
324
+ removeAlias("test/project-tool", "project", PROJECT_DIR);
325
+ rmSync(join(PROJECT_ENACT_DIR, "tools.json"), { force: true });
326
+ }
327
+ });
328
+
329
+ test("tryResolveTool works with aliases", () => {
330
+ addToolToRegistry("test/project-tool", "1.0.0", "project", PROJECT_DIR);
331
+ addAlias("try-alias", "test/project-tool", "project", PROJECT_DIR);
332
+
333
+ try {
334
+ const result = tryResolveTool("try-alias", { startDir: PROJECT_DIR });
335
+ expect(result).not.toBeNull();
336
+ expect(result?.manifest.name).toBe("test/project-tool");
337
+ } finally {
338
+ removeAlias("try-alias", "project", PROJECT_DIR);
339
+ rmSync(join(PROJECT_ENACT_DIR, "tools.json"), { force: true });
340
+ }
341
+ });
342
+
343
+ test("non-existent alias returns null from tryResolveTool", () => {
344
+ const result = tryResolveTool("nonexistent-alias", {
345
+ startDir: PROJECT_DIR,
346
+ skipUser: true,
347
+ skipCache: true,
348
+ });
349
+ expect(result).toBeNull();
350
+ });
351
+ });
272
352
  });