import { readFile } from 'fs/promises'; import { dirname, basename, resolve } from 'path'; import { glob } from 'glob'; import { minimatch } from 'minimatch'; export interface AmanuensisConfig { package: { searchPatterns: string | string[]; classifications: Record>; } } interface TypedocDataTextNode { kind: 'text'; text: string; } interface TypedocDataInlineTagNode { kind: 'inline-tag'; tag: string; text: string; target: number; tsLinkText: string; } interface SymbolIdMapEntry { sourceFileName: string; qualifiedName: string; } type TypedocDataNode = TypedocDataTextNode | TypedocDataInlineTagNode; export interface TypedocData { packages: any[]; typedocData: { readme: TypedocDataNode[]; symbolIdMap: Record; }; } export const getReadmeText = async (cwd = process.cwd()) => { const typedocDataJsonPath = resolve(cwd, '.amanuensis', 'data.json'); const typedocDataJson = await readFile(typedocDataJsonPath, 'utf-8'); const typedocData = JSON.parse(typedocDataJson) as { typedocData: { readme: { kind: string, text: string }[] } }; return typedocData.typedocData.readme.reduce( (theText, node) => { if (node.kind === 'text') { return `${theText}${node.text}`; } return theText; }, '', ); }; export const getPackages = async (cwd = process.cwd()) => { const configPath = resolve(cwd, '.amanuensis', 'config.json'); const configString = await readFile(configPath, 'utf-8'); const config = JSON.parse(configString) as AmanuensisConfig; const searchPatternsRaw = config.package.searchPatterns; const searchPatterns = Array.isArray(searchPatternsRaw) ? searchPatternsRaw : [searchPatternsRaw]; const patternPackagePaths = await Promise.all( searchPatterns.map(async (searchPattern) => glob( searchPattern === 'package.json' || searchPattern.endsWith('/package.json') ? searchPattern : `${searchPattern}/package.json`, { ignore: ['**/node_modules/**'], }, )), ); const packagePaths = patternPackagePaths.flat(); const markdownFilePaths = await glob( '**/*.{md,mdx}', { ignore: ['**/node_modules/**'], }, ); return Promise.all( packagePaths.map(async (packagePath) => { const packageString = await readFile(packagePath, 'utf-8'); const basePath = dirname(packagePath); const packageJson = JSON.parse(packageString) as { name: string }; const classifications = Object.fromEntries( Object.entries(config.package.classifications) .map(([classification, c]) => { const [thisClassifications] = Object.entries(c) .find(([, globRaw]) => { const globs = Array.isArray(globRaw) ? globRaw : [globRaw]; return globs.some((g) => minimatch(packageJson.name, g)); }) ?? []; return [classification, thisClassifications] as const; }), ); const markdownFiles = markdownFilePaths.filter((markdownFilePath) => ( markdownFilePath.startsWith(basePath) )); const markdown = await Promise.all( markdownFiles.map(async (markdownFilePath) => { const content = await readFile(markdownFilePath, 'utf-8'); const filePath = markdownFilePath.slice(basePath.length + 1); const file = filePath.split('/').at(-1) ?? ''; const name = filePath === 'README.md' ? 'index.md' : basename(file); return { name, filePath, content, }; }), ); return { name: packageJson.name, packageJson, basePath, markdown, classifications, }; }), ); }; export const getFileSources = async (cwd = process.cwd()) => { const typedocDataJsonPath = resolve(cwd, '.amanuensis', 'data.json'); const typedocDataJson = await readFile(typedocDataJsonPath, 'utf-8'); const typedocData = JSON.parse(typedocDataJson) as TypedocData; const symbolIdMapEntries = Object.values(typedocData.typedocData.symbolIdMap); const firstPartySources = symbolIdMapEntries.filter( ({ sourceFileName }) => !sourceFileName.startsWith('node_modules'), ); const firstPartySourceFiles = firstPartySources.map(({ sourceFileName }) => sourceFileName); const uniqueFirstPartySourceFiles = [...new Set(firstPartySourceFiles)]; return uniqueFirstPartySourceFiles; };