@enactprotocol/shared 2.2.1 → 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.
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/manifest/validator.d.ts +3 -0
- package/dist/manifest/validator.d.ts.map +1 -1
- package/dist/manifest/validator.js +8 -1
- package/dist/manifest/validator.js.map +1 -1
- package/dist/registry.d.ts +44 -0
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +95 -2
- package/dist/registry.js.map +1 -1
- package/dist/resolver.d.ts +1 -1
- package/dist/resolver.d.ts.map +1 -1
- package/dist/resolver.js +12 -3
- package/dist/resolver.js.map +1 -1
- package/dist/types/manifest.d.ts +6 -0
- package/dist/types/manifest.d.ts.map +1 -1
- package/dist/types/manifest.js.map +1 -1
- package/package.json +2 -2
- package/src/index.ts +6 -0
- package/src/manifest/validator.ts +9 -1
- package/src/registry.ts +131 -2
- package/src/resolver.ts +14 -3
- package/src/types/manifest.ts +11 -0
- package/tests/manifest/validator.test.ts +2 -2
- package/tests/registry.test.ts +195 -0
- package/tests/resolver.test.ts +80 -0
- package/tsconfig.tsbuildinfo +1 -1
package/src/registry.ts
CHANGED
|
@@ -19,6 +19,8 @@ import { getCacheDir, getEnactHome, getProjectEnactDir } from "./paths";
|
|
|
19
19
|
export interface ToolsRegistry {
|
|
20
20
|
/** Map of tool name to installed version */
|
|
21
21
|
tools: Record<string, string>;
|
|
22
|
+
/** Map of alias to full tool name */
|
|
23
|
+
aliases?: Record<string, string>;
|
|
22
24
|
}
|
|
23
25
|
|
|
24
26
|
/**
|
|
@@ -56,7 +58,7 @@ export function loadToolsRegistry(scope: RegistryScope, startDir?: string): Tool
|
|
|
56
58
|
const registryPath = getToolsJsonPath(scope, startDir);
|
|
57
59
|
|
|
58
60
|
if (!registryPath || !existsSync(registryPath)) {
|
|
59
|
-
return { tools: {} };
|
|
61
|
+
return { tools: {}, aliases: {} };
|
|
60
62
|
}
|
|
61
63
|
|
|
62
64
|
try {
|
|
@@ -64,10 +66,11 @@ export function loadToolsRegistry(scope: RegistryScope, startDir?: string): Tool
|
|
|
64
66
|
const parsed = JSON.parse(content);
|
|
65
67
|
return {
|
|
66
68
|
tools: parsed.tools ?? {},
|
|
69
|
+
aliases: parsed.aliases ?? {},
|
|
67
70
|
};
|
|
68
71
|
} catch {
|
|
69
72
|
// Return empty registry on parse error
|
|
70
|
-
return { tools: {} };
|
|
73
|
+
return { tools: {}, aliases: {} };
|
|
71
74
|
}
|
|
72
75
|
}
|
|
73
76
|
|
|
@@ -217,3 +220,129 @@ export function getInstalledToolInfo(
|
|
|
217
220
|
cachePath,
|
|
218
221
|
};
|
|
219
222
|
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Add an alias for a tool
|
|
226
|
+
* @param alias - Short name for the tool (e.g., "firebase")
|
|
227
|
+
* @param toolName - Full tool name (e.g., "user/api/firebase")
|
|
228
|
+
* @param scope - Registry scope (global or project)
|
|
229
|
+
* @param startDir - Starting directory for project scope
|
|
230
|
+
* @throws Error if alias already exists for a different tool
|
|
231
|
+
*/
|
|
232
|
+
export function addAlias(
|
|
233
|
+
alias: string,
|
|
234
|
+
toolName: string,
|
|
235
|
+
scope: RegistryScope,
|
|
236
|
+
startDir?: string
|
|
237
|
+
): void {
|
|
238
|
+
const registry = loadToolsRegistry(scope, startDir);
|
|
239
|
+
|
|
240
|
+
// Initialize aliases if not present
|
|
241
|
+
if (!registry.aliases) {
|
|
242
|
+
registry.aliases = {};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Check if alias already exists for a different tool
|
|
246
|
+
const existingTarget = registry.aliases[alias];
|
|
247
|
+
if (existingTarget && existingTarget !== toolName) {
|
|
248
|
+
throw new Error(
|
|
249
|
+
`Alias "${alias}" already exists for tool "${existingTarget}". Remove it first with 'enact alias --remove ${alias}'.`
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
registry.aliases[alias] = toolName;
|
|
254
|
+
saveToolsRegistry(registry, scope, startDir);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Remove an alias
|
|
259
|
+
* @param alias - Alias to remove
|
|
260
|
+
* @param scope - Registry scope
|
|
261
|
+
* @param startDir - Starting directory for project scope
|
|
262
|
+
* @returns true if alias was removed, false if it didn't exist
|
|
263
|
+
*/
|
|
264
|
+
export function removeAlias(alias: string, scope: RegistryScope, startDir?: string): boolean {
|
|
265
|
+
const registry = loadToolsRegistry(scope, startDir);
|
|
266
|
+
|
|
267
|
+
if (!registry.aliases || !(alias in registry.aliases)) {
|
|
268
|
+
return false;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
delete registry.aliases[alias];
|
|
272
|
+
saveToolsRegistry(registry, scope, startDir);
|
|
273
|
+
return true;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Resolve an alias to its full tool name
|
|
278
|
+
* @param alias - Alias to resolve
|
|
279
|
+
* @param scope - Registry scope
|
|
280
|
+
* @param startDir - Starting directory for project scope
|
|
281
|
+
* @returns Full tool name or null if alias doesn't exist
|
|
282
|
+
*/
|
|
283
|
+
export function resolveAlias(
|
|
284
|
+
alias: string,
|
|
285
|
+
scope: RegistryScope,
|
|
286
|
+
startDir?: string
|
|
287
|
+
): string | null {
|
|
288
|
+
const registry = loadToolsRegistry(scope, startDir);
|
|
289
|
+
return registry.aliases?.[alias] ?? null;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Get all aliases for a specific tool
|
|
294
|
+
* @param toolName - Full tool name
|
|
295
|
+
* @param scope - Registry scope
|
|
296
|
+
* @param startDir - Starting directory for project scope
|
|
297
|
+
* @returns Array of aliases for the tool
|
|
298
|
+
*/
|
|
299
|
+
export function getAliasesForTool(
|
|
300
|
+
toolName: string,
|
|
301
|
+
scope: RegistryScope,
|
|
302
|
+
startDir?: string
|
|
303
|
+
): string[] {
|
|
304
|
+
const registry = loadToolsRegistry(scope, startDir);
|
|
305
|
+
const aliases: string[] = [];
|
|
306
|
+
|
|
307
|
+
if (registry.aliases) {
|
|
308
|
+
for (const [alias, target] of Object.entries(registry.aliases)) {
|
|
309
|
+
if (target === toolName) {
|
|
310
|
+
aliases.push(alias);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
return aliases;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Remove all aliases for a specific tool
|
|
320
|
+
* Useful when uninstalling a tool
|
|
321
|
+
* @param toolName - Full tool name
|
|
322
|
+
* @param scope - Registry scope
|
|
323
|
+
* @param startDir - Starting directory for project scope
|
|
324
|
+
* @returns Number of aliases removed
|
|
325
|
+
*/
|
|
326
|
+
export function removeAliasesForTool(
|
|
327
|
+
toolName: string,
|
|
328
|
+
scope: RegistryScope,
|
|
329
|
+
startDir?: string
|
|
330
|
+
): number {
|
|
331
|
+
const registry = loadToolsRegistry(scope, startDir);
|
|
332
|
+
let removed = 0;
|
|
333
|
+
|
|
334
|
+
if (registry.aliases) {
|
|
335
|
+
for (const [alias, target] of Object.entries(registry.aliases)) {
|
|
336
|
+
if (target === toolName) {
|
|
337
|
+
delete registry.aliases[alias];
|
|
338
|
+
removed++;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if (removed > 0) {
|
|
343
|
+
saveToolsRegistry(registry, scope, startDir);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
return removed;
|
|
348
|
+
}
|
package/src/resolver.ts
CHANGED
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
loadManifest,
|
|
18
18
|
} from "./manifest/loader";
|
|
19
19
|
import { getCacheDir, getProjectEnactDir } from "./paths";
|
|
20
|
-
import { getInstalledVersion, getToolCachePath } from "./registry";
|
|
20
|
+
import { getInstalledVersion, getToolCachePath, resolveAlias } from "./registry";
|
|
21
21
|
import type { ToolLocation, ToolResolution } from "./types/manifest";
|
|
22
22
|
|
|
23
23
|
/**
|
|
@@ -174,15 +174,26 @@ export function resolveToolFromPath(filePath: string): ToolResolution {
|
|
|
174
174
|
/**
|
|
175
175
|
* Resolve a tool by name, searching through standard locations
|
|
176
176
|
*
|
|
177
|
-
* @param toolName - Tool name (e.g., "acme/utils/greeter")
|
|
177
|
+
* @param toolName - Tool name (e.g., "acme/utils/greeter") or alias (e.g., "firebase")
|
|
178
178
|
* @param options - Resolution options
|
|
179
179
|
* @returns ToolResolution
|
|
180
180
|
* @throws ToolResolveError if not found
|
|
181
181
|
*/
|
|
182
182
|
export function resolveTool(toolName: string, options: ResolveOptions = {}): ToolResolution {
|
|
183
|
-
|
|
183
|
+
let normalizedName = normalizeToolName(toolName);
|
|
184
184
|
const searchedLocations: string[] = [];
|
|
185
185
|
|
|
186
|
+
// Check if this might be an alias (no slashes = not a full tool name)
|
|
187
|
+
if (!normalizedName.includes("/")) {
|
|
188
|
+
// Try project-level alias first, then global
|
|
189
|
+
const aliasedName =
|
|
190
|
+
resolveAlias(normalizedName, "project", options.startDir) ??
|
|
191
|
+
resolveAlias(normalizedName, "global");
|
|
192
|
+
if (aliasedName) {
|
|
193
|
+
normalizedName = normalizeToolName(aliasedName);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
186
197
|
// 1. Try project tools (.enact/tools/{name}/)
|
|
187
198
|
if (!options.skipProject) {
|
|
188
199
|
const projectDir = getProjectEnactDir(options.startDir);
|
package/src/types/manifest.ts
CHANGED
|
@@ -134,6 +134,17 @@ export interface ToolManifest {
|
|
|
134
134
|
/** Resource limits and requirements */
|
|
135
135
|
resources?: ResourceRequirements;
|
|
136
136
|
|
|
137
|
+
// ==================== Agent Skills Spec Fields ====================
|
|
138
|
+
|
|
139
|
+
/** Environment requirements (intended product, system packages, network access, etc.) */
|
|
140
|
+
compatibility?: string;
|
|
141
|
+
|
|
142
|
+
/** Arbitrary key-value metadata for additional properties */
|
|
143
|
+
metadata?: Record<string, string>;
|
|
144
|
+
|
|
145
|
+
/** Space-delimited list of pre-approved tools the skill may use (experimental) */
|
|
146
|
+
allowedTools?: string;
|
|
147
|
+
|
|
137
148
|
// ==================== Documentation ====================
|
|
138
149
|
|
|
139
150
|
/** Extended documentation (Markdown) */
|
|
@@ -182,10 +182,10 @@ describe("manifest validator", () => {
|
|
|
182
182
|
}
|
|
183
183
|
});
|
|
184
184
|
|
|
185
|
-
test("fails for description over
|
|
185
|
+
test("fails for description over 1024 characters", () => {
|
|
186
186
|
const manifest = {
|
|
187
187
|
name: "org/tool",
|
|
188
|
-
description: "a".repeat(
|
|
188
|
+
description: "a".repeat(1025),
|
|
189
189
|
};
|
|
190
190
|
|
|
191
191
|
const result = validateManifest(manifest);
|
package/tests/registry.test.ts
CHANGED
|
@@ -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
|
});
|
package/tests/resolver.test.ts
CHANGED
|
@@ -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
|
});
|