* Look up the display title for a resource from its JSON data file
( filePath: string, type: string )
| 74 | * Look up the display title for a resource from its JSON data file |
| 75 | */ |
| 76 | async function resolveResourceTitle( |
| 77 | filePath: string, |
| 78 | type: string |
| 79 | ): Promise<string> { |
| 80 | const fallback = filePath.split("/").pop() || filePath; |
| 81 | const jsonFile = RESOURCE_TYPE_TO_JSON[type]; |
| 82 | if (!jsonFile) return fallback; |
| 83 | |
| 84 | if (!(jsonFile in resourceDataCache)) { |
| 85 | resourceDataCache[jsonFile] = await fetchData<ResourceData>(jsonFile); |
| 86 | } |
| 87 | |
| 88 | const data = resourceDataCache[jsonFile]; |
| 89 | if (!data) return fallback; |
| 90 | |
| 91 | // Try exact path match first |
| 92 | const item = data.items.find((i) => i.path === filePath); |
| 93 | if (item) return item.title; |
| 94 | |
| 95 | // For skills/hooks, bundled files live under the resource folder while |
| 96 | // JSON stores the folder path itself (for example, skills/foo). |
| 97 | const collectionRootPath = |
| 98 | type === "skill" |
| 99 | ? getCollectionRootPath(filePath, "skills") |
| 100 | : type === "hook" |
| 101 | ? getCollectionRootPath(filePath, "hooks") |
| 102 | : filePath.substring(0, filePath.lastIndexOf("/")); |
| 103 | |
| 104 | if (collectionRootPath) { |
| 105 | const parentItem = data.items.find((i) => i.path === collectionRootPath); |
| 106 | if (parentItem) return parentItem.title; |
| 107 | } |
| 108 | |
| 109 | return fallback; |
| 110 | } |
| 111 | |
| 112 | function getFileName(filePath: string): string { |
| 113 | return filePath.split("/").pop() || filePath; |
no test coverage detected