Skip to main content
Glama
asset-plugin-virtual-source.mjs4.53 kB
import { existsSync, readFileSync } from 'node:fs'; import { basename, dirname, join, relative, resolve, sep } from 'node:path'; import { fileURLToPath } from 'node:url'; const hereDirname = () => { try { return dirname(fileURLToPath(import.meta.url)); } catch { return typeof __dirname !== 'undefined' ? __dirname : process.cwd(); } }; const findDistRoot = (startDir) => { let dir = startDir; for (let i = 0; i < 12; i++) { const base = basename(dir); if (base === 'dist') return dir; const parent = resolve(dir, '..'); if (parent === dir) break; dir = parent; } return null; }; // --- FIX 1: normalize stack frame filenames (handles 'file://...' properly) const normalizeFrameFile = (file) => { if (!file) return null; try { // If it's a file URL, convert to a filesystem path if (file.startsWith('file://')) return fileURLToPath(file); } catch {} return file; }; /** * Returns the directory of the *caller* module that invoked readAsset. * Prefers non-virtual frames; falls back to the first real frame. */ const getCallerDir = () => { const prev = Error.prepareStackTrace; try { Error.prepareStackTrace = (_, structured) => structured; const err = new Error(); Error.captureStackTrace(err, getCallerDir); /** @type {import('node:vm').CallSite[]} */ const frames = err.stack || []; const isVirtualPath = (p) => p.includes(`${sep}_virtual${sep}`) || p.includes('/_virtual/'); // First pass: real (non-virtual) frames for (const frame of frames) { const raw = typeof frame.getFileName === 'function' ? frame.getFileName() : null; const file = normalizeFrameFile(raw); if (!file) continue; if ( file.includes('node:internal') || file.includes(`${sep}internal${sep}modules${sep}`) ) continue; if (!isVirtualPath(file)) return dirname(file); } // Second pass: accept virtual if that's all we have for (const frame of frames) { const raw = typeof frame.getFileName === 'function' ? frame.getFileName() : null; const file = normalizeFrameFile(raw); if (file) return dirname(file); } } catch { // ignore } finally { Error.prepareStackTrace = prev; } return hereDirname(); }; /** * Read an asset copied from src/** to dist/assets/**. * - './' or '../' is resolved relative to the *caller module's* emitted directory. * - otherwise, treat as src-relative. * * @param {string} relPath - e.g. './PROMPT.md' or 'utils/AI/askDocQuestion/embeddings/<fileKey>.json' * @param {BufferEncoding} [encoding='utf8'] */ export const readAsset = (relPath, encoding = 'utf8') => { const here = hereDirname(); const distRoot = findDistRoot(here) ?? resolve(here, '..', '..', 'dist'); const assetsRoot = join(distRoot, 'assets'); const tried = []; // Determine caller subpath under dist/, stripping esm/ or cjs/ and _virtual/ const callerDirAbs = getCallerDir(); // --- FIX 2: ensure both sides are filesystem paths before relative() const callerSubpathRaw = relative(distRoot, callerDirAbs) .split('\\') .join('/'); /** * Transform dist/(esm|cjs)/... and _virtual/ prefix to clean subpath (Windows-safe) */ const callerSubpath = callerSubpathRaw .replace(/^(?:dist\/)?(?:esm|cjs)\//, '') .replace(/^_virtual\//, ''); // 1) Relative path from caller (./ or ../) const looksRelative = relPath.startsWith('./') || relPath.startsWith('../'); if (looksRelative) { // Use resolve to collapse any ../ correctly const fromCallerAbs = resolve(assetsRoot, callerSubpath, relPath); // 1.1) Path in assets source (/dist/assets/<caller-subpath>/<file>) tried.push(fromCallerAbs); if (existsSync(fromCallerAbs)) { return readFileSync(fromCallerAbs, encoding); } } // 2) Src-relative directly under assets (/dist/assets/my/file.txt) const directPath = join(assetsRoot, relPath); tried.push(directPath); if (existsSync(directPath)) { return readFileSync(directPath, encoding); } // 3) Fallback: caller subpath + src-relative (when virtual re-emits into subfolders) if (callerSubpath) { const nested = join(assetsRoot, callerSubpath, relPath); tried.push(nested); if (existsSync(nested)) { return readFileSync(nested, encoding); } } const msg = [ 'readAsset: file not found.', 'Searched:', ...tried.map((p) => `- ${p}`), ].join('\n'); throw new Error(msg); };

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/aymericzip/intlayer'

If you have feedback or need assistance with the MCP directory API, please join our Discord server