diff --git a/packages/site-kit/src/lib/markdown/renderer.ts b/packages/site-kit/src/lib/markdown/renderer.ts index f6d5b4c549..1b9f38419a 100644 --- a/packages/site-kit/src/lib/markdown/renderer.ts +++ b/packages/site-kit/src/lib/markdown/renderer.ts @@ -8,6 +8,7 @@ import { codeToHtml, createCssVariablesTheme } from 'shiki'; import { transformerTwoslash } from '@shikijs/twoslash'; import { SHIKI_LANGUAGE_MAP, escape, normalizeSlugify, smart_quotes, transform } from './utils'; import type { Modules } from './index'; +import { fileURLToPath } from 'node:url'; interface SnippetOptions { file: string | null; @@ -508,6 +509,37 @@ function find_nearest_node_modules(file: string): string | null { return null; } +/** + * Get the `mtime` of the most recently modified file in a dependency graph, + * excluding imports from `node_modules` + */ +function get_mtime(file: string, seen = new Set()) { + if (seen.has(file)) return -1; + seen.add(file); + + let mtime = fs.statSync(file).mtimeMs; + const content = fs.readFileSync(file, 'utf-8'); + + for (const [_, source] of content.matchAll(/^import(?:.+?\s+from\s+)?['"](.+)['"];?$/gm)) { + if (source[0] !== '.') continue; + + let resolved = path.resolve(file, '..', source); + if (!fs.existsSync(resolved)) resolved += '.ts'; + if (!fs.existsSync(resolved)) + throw new Error(`Could not resolve ${source} relative to ${file}`); + + mtime = Math.max(mtime, get_mtime(resolved, seen)); + } + + return mtime; +} + +const mtime = Math.max( + get_mtime(fileURLToPath(import.meta.url)), + fs.statSync('node_modules').mtimeMs, + fs.statSync('../../pnpm-lock.yaml').mtimeMs +); + /** * Utility function to work with code snippet caching. * @@ -526,12 +558,26 @@ async function create_snippet_cache(should: boolean) { const cache = new Map(); const directory = find_nearest_node_modules(import.meta.url) + '/.snippets'; + if (fs.existsSync(directory)) { + for (const dir of fs.readdirSync(directory)) { + if (!fs.statSync(`${directory}/${dir}`).isDirectory() || +dir < mtime) { + fs.rmSync(`${directory}/${dir}`, { force: true, recursive: true }); + } + } + } else { + fs.mkdirSync(directory); + } + + try { + fs.mkdirSync(`${directory}/${mtime}`); + } catch {} + function get_file(source: string) { const hash = createHash('sha256'); hash.update(source); const digest = hash.digest().toString('base64').replace(/\//g, '-'); - return `${directory}/${digest}.html`; + return `${directory}/${mtime}/${digest}.html`; } return { @@ -681,7 +727,15 @@ async function syntax_highlight({ html = await codeToHtml(source, { lang: 'ts', theme, - transformers: [transformerTwoslash({})] + transformers: [ + transformerTwoslash({ + twoslashOptions: { + compilerOptions: { + types: ['svelte', '@sveltejs/kit'] + } + } + }) + ] }); } catch (e) { console.error(`Error compiling snippet in ${filename}`);