(dir: string, isBuiltin: boolean)
| 808 | const trustedRepos = await fetchTrustedRepos() |
| 809 | |
| 810 | async function readExtensionsFromDir(dir: string, isBuiltin: boolean) { |
| 811 | if (!existsSync(dir)) return [] |
| 812 | try { |
| 813 | const entries = await readdir(dir, { withFileTypes: true }) |
| 814 | // On Windows, junction points are reported by Node.js as isSymbolicLink()=true, |
| 815 | // isDirectory()=false. Use statSync (which follows links) as the authoritative check. |
| 816 | const dirs = entries.filter(e => { |
| 817 | if (e.isDirectory()) return true |
| 818 | if (e.isSymbolicLink()) { |
| 819 | try { return statSync(join(dir, e.name)).isDirectory() } catch { return false } |
| 820 | } |
| 821 | return false |
| 822 | }) |
| 823 | return Promise.all(dirs.map(async (entry) => { |
| 824 | const base = { type: 'model' as const, id: entry.name, name: entry.name, trusted: isBuiltin, builtin: isBuiltin, nodes: [] } |
| 825 | const entryPath = join(dir, entry.name) |
| 826 | |
| 827 | // Detect local extensions: check for .modly-local sentinel |
| 828 | let localSourcePath: string | undefined |
| 829 | if (!isBuiltin) { |
| 830 | const sentinelPath = join(entryPath, '.modly-local') |
| 831 | if (existsSync(sentinelPath)) { |
| 832 | try { |
| 833 | localSourcePath = (await readFile(sentinelPath, 'utf-8')).trim() |
| 834 | } catch { /* ignore */ } |
| 835 | } |
| 836 | } |
| 837 | |
| 838 | for (const manifestFile of ['manifest.json', 'package.json']) { |
| 839 | const p = join(entryPath, manifestFile) |
| 840 | if (existsSync(p)) { |
| 841 | try { |
| 842 | const raw = await readFile(p, 'utf-8') |
| 843 | const parsed = JSON.parse(raw) as ParsedManifest |
| 844 | // Inject local:// source so the UI shows the Local badge |
| 845 | if (localSourcePath) parsed.source = `local://${localSourcePath}` |
| 846 | return parseExtensionManifest(parsed, entry.name, trustedRepos, isBuiltin) |
| 847 | } catch { /* ignore parse errors, fall through */ } |
| 848 | } |
| 849 | } |
| 850 | return base |
| 851 | })) |
| 852 | } catch { |
| 853 | return [] |
| 854 | } |
| 855 | } |
| 856 | |
| 857 | const [userExts, builtinExts] = await Promise.all([ |
| 858 | readExtensionsFromDir(extensionsDir, false), |
no test coverage detected