( filePath: string, )
| 812 | * Validate a manifest file or directory (auto-detects type) |
| 813 | */ |
| 814 | export async function validateManifest( |
| 815 | filePath: string, |
| 816 | ): Promise<ValidationResult> { |
| 817 | const absolutePath = path.resolve(filePath) |
| 818 | |
| 819 | // Stat path to check if it's a directory — handle ENOENT inline |
| 820 | let stats: Stats | null = null |
| 821 | try { |
| 822 | stats = await stat(absolutePath) |
| 823 | } catch (e: unknown) { |
| 824 | if (!isENOENT(e)) { |
| 825 | throw e |
| 826 | } |
| 827 | } |
| 828 | |
| 829 | if (stats?.isDirectory()) { |
| 830 | // Look for manifest files in .claude-plugin directory |
| 831 | // Prefer marketplace.json over plugin.json |
| 832 | const marketplacePath = path.join( |
| 833 | absolutePath, |
| 834 | '.claude-plugin', |
| 835 | 'marketplace.json', |
| 836 | ) |
| 837 | const marketplaceResult = await validateMarketplaceManifest(marketplacePath) |
| 838 | // Only fall through if the marketplace file was not found (ENOENT) |
| 839 | if (marketplaceResult.errors[0]?.code !== 'ENOENT') { |
| 840 | return marketplaceResult |
| 841 | } |
| 842 | |
| 843 | const pluginPath = path.join(absolutePath, '.claude-plugin', 'plugin.json') |
| 844 | const pluginResult = await validatePluginManifest(pluginPath) |
| 845 | if (pluginResult.errors[0]?.code !== 'ENOENT') { |
| 846 | return pluginResult |
| 847 | } |
| 848 | |
| 849 | return { |
| 850 | success: false, |
| 851 | errors: [ |
| 852 | { |
| 853 | path: 'directory', |
| 854 | message: `No manifest found in directory. Expected .claude-plugin/marketplace.json or .claude-plugin/plugin.json`, |
| 855 | }, |
| 856 | ], |
| 857 | warnings: [], |
| 858 | filePath: absolutePath, |
| 859 | fileType: 'plugin', |
| 860 | } |
| 861 | } |
| 862 | |
| 863 | const manifestType = detectManifestType(filePath) |
| 864 | |
| 865 | switch (manifestType) { |
| 866 | case 'plugin': |
| 867 | return validatePluginManifest(filePath) |
| 868 | case 'marketplace': |
| 869 | return validateMarketplaceManifest(filePath) |
| 870 | case 'unknown': { |
| 871 | // Try to parse and guess based on content |
nothing calls this directly
no test coverage detected