UNPKG

docusaurus-plugin-typedoc-api

Version:

Docusaurus plugin that provides source code API documentation powered by TypeDoc.

413 lines (392 loc) 17.5 kB
'use strict'; const _excluded = ["changelogPath", "readmePath"]; function _objectWithoutProperties(e, t) { if (null == e) return {}; var o, r, i = _objectWithoutPropertiesLoose(e, t); if (Object.getOwnPropertySymbols) { var s = Object.getOwnPropertySymbols(e); for (r = 0; r < s.length; r++) o = s[r], t.includes(o) || {}.propertyIsEnumerable.call(e, o) && (i[o] = e[o]); } return i; } function _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t = {}; for (var n in r) if ({}.hasOwnProperty.call(r, n)) { if (e.includes(n)) continue; t[n] = r[n]; } return t; } function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; } function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; } function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } const fs = require('node:fs'); const path = require('node:path'); const TypeDoc = require('typedoc'); const ts = require('typescript'); const utils = require('@docusaurus/utils'); const _0_23 = require('./structure/0.23.js'); const url = require('./url.js'); const _interopDefault = e => e && e.__esModule ? e : { default: e }; function _interopNamespace(e) { if (e && e.__esModule) return e; const n = Object.create(null); if (e) { for (const k in e) { if (k !== 'default') { const d = Object.getOwnPropertyDescriptor(e, k); Object.defineProperty(n, k, d.get ? d : { enumerable: true, get: () => e[k] }); } } } n.default = e; return Object.freeze(n); } const fs__default = /*#__PURE__*/_interopDefault(fs); const path__default = /*#__PURE__*/_interopDefault(path); const TypeDoc__namespace = /*#__PURE__*/_interopNamespace(TypeDoc); const ts__default = /*#__PURE__*/_interopDefault(ts); function shouldEmit(projectRoot, tsconfigPath) { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const _ts__default$default$ = ts__default.default.readConfigFile(tsconfigPath, name => fs__default.default.readFileSync(name, 'utf8')), config = _ts__default$default$.config, error = _ts__default$default$.error; if (error) { throw new Error(`Failed to load ${tsconfigPath}`); } const result = ts__default.default.parseJsonConfigFileContent(config, ts__default.default.sys, projectRoot, {}, tsconfigPath); if (result.errors.length > 0) { throw new Error(`Failed to parse ${tsconfigPath}`); } return result.projectReferences && result.projectReferences.length > 0 ? 'docs' : 'none'; } // Persist build state as a global, since the plugin is re-evaluated every hot reload. // Because of this, we can't use state in the plugin or module scope. if (!global.typedocBuild) { global.typedocBuild = { count: 0 }; } async function generateJson(projectRoot, entryPoints, outFile, options) { /* eslint-disable sort-keys */ // Running the TypeDoc compiler is pretty slow... // We should only load on the 1st build, and use cache for subsequent reloads. if (global.typedocBuild.count > 0 && fs__default.default.existsSync(outFile)) { return true; } const tsconfig = path__default.default.join(projectRoot, options.tsconfigName ?? 'tsconfig.json'); const app = await TypeDoc__namespace.Application.bootstrapWithPlugins(_objectSpread(_objectSpread({ gitRevision: options.gitRefName, includeVersion: true, skipErrorChecking: true, // stripYamlFrontmatter: true, // Only emit when using project references emit: shouldEmit(projectRoot, tsconfig), // Only document the public API by default excludeExternals: true, excludeInternal: true, excludePrivate: true, excludeProtected: true, // Enable verbose logging when debugging logLevel: options.debug ? 'Verbose' : 'Info', inlineTags: ['@link', '@inheritDoc', '@label', '@linkcode', '@linkplain', '@apilink', '@doclink'] }, options.typedocOptions), {}, { // Control how config and packages are detected tsconfig, entryPoints: entryPoints.map(ep => path__default.default.join(projectRoot, ep)), entryPointStrategy: 'expand', exclude: options.exclude, // We use a fake category title so that we can fallback to the parent group defaultCategory: '__CATEGORY__' }), [new TypeDoc__namespace.TSConfigReader(), new TypeDoc__namespace.TypeDocReader()]); const project = await app.convert(); if (project) { await app.generateJson(project, outFile); global.typedocBuild.count += 1; return true; } return false; } function createReflectionMap(items = []) { const map = {}; // eslint-disable-next-line complexity items.forEach(item => { // Add @reference categories to reflection. const referenceCategories = {}; for (const tag of item.comment?.blockTags ?? []) { if (tag.tag === '@reference' && tag.content.length >= 2 && tag.content[0].kind === 'text') { const categoryName = tag.content[0].text.trim(); const ref = tag.content.find(t => t.tag === '@link'); if (ref && typeof ref.target === 'number') { if (!(categoryName in referenceCategories)) { referenceCategories[categoryName] = { title: categoryName, children: [] }; } if (!referenceCategories[categoryName].children.includes(ref.target)) { referenceCategories[categoryName].children.push(ref.target); } } } } // Update categories with reference categories. if (!item.categories) { // eslint-disable-next-line no-param-reassign item.categories = []; } for (const category of Object.values(referenceCategories)) { if (category.children.length > 0) { const index = item.categories.findIndex(c => c.title === category.title); if (index === -1) { item.categories.push(category); } } } // Add item. map[item.id] = item; }); return map; } function loadPackageJsonAndDocs(initialDir, pkgFileName = 'package.json', readmeFileName = 'README.md', changelogFileName = 'CHANGELOG.md') { let currentDir = initialDir; while (!fs__default.default.existsSync(path__default.default.join(currentDir, pkgFileName))) { currentDir = path__default.default.dirname(currentDir); } const readmePath = path__default.default.join(currentDir, readmeFileName); const changelogPath = path__default.default.join(currentDir, changelogFileName); return { packageJson: JSON.parse(fs__default.default.readFileSync(path__default.default.join(currentDir, pkgFileName), 'utf8')), readmePath: fs__default.default.existsSync(readmePath) ? readmePath : '', changelogPath: fs__default.default.existsSync(changelogPath) ? changelogPath : '' }; } function addMetadataToReflections(project, packageSlug, urlPrefix) { const permalink = `/${url.joinUrl(urlPrefix, packageSlug)}`; if (project.children) { // eslint-disable-next-line no-param-reassign project.children = project.children.map(child => { _0_23.migrateToVersion0230(child); const kindSlugPart = url.getKindSlug(child); const childSlug = kindSlugPart ? `/${kindSlugPart}/${child.name}` : `#${child.name}`; const childPermalink = permalink + childSlug; // We need to go another level deeper and only use fragments if (child.kind === TypeDoc.ReflectionKind.Namespace && child.children) { // eslint-disable-next-line no-param-reassign child.children = child.children.map(grandChild => _objectSpread(_objectSpread({}, grandChild), {}, { permalink: utils.normalizeUrl([`${childPermalink}#${grandChild.name}`]) })); } return _objectSpread(_objectSpread({}, child), {}, { permalink: utils.normalizeUrl([childPermalink]) }); }); } // @ts-expect-error Not sure why this fails return _objectSpread(_objectSpread({}, project), {}, { permalink: utils.normalizeUrl([permalink]) }); } function mergeReflections(base, next) { if (Array.isArray(base.children) && Array.isArray(next.children)) { base.children.push(...next.children); } if (Array.isArray(base.groups) && Array.isArray(next.groups)) { next.groups.forEach(group => { const baseGroup = base.groups?.find(g => g.title === group.title); if (baseGroup) { baseGroup.children?.push(...(group.children ?? [])); } else { base.groups?.push(group); } }); // We can remove refs since were merging all reflections into one // eslint-disable-next-line no-param-reassign base.groups = base.groups.filter(group => group.title !== 'References'); } } function sortReflectionGroups(reflections) { reflections.forEach(reflection => { const map = createReflectionMap(reflection.children); const sort = (a, b) => map[a].name < map[b].name ? -1 : 1; reflection.categories?.forEach(category => { category.children?.sort(sort); }); reflection.groups?.forEach(group => { group.children?.sort(sort); group.categories?.forEach(category => { category.children?.sort(sort); }); }); }); } function sourceFileMatchesEntryPoint(sourceFile, entryPoint, { deep, single }) { // Single package if (single) { return ( // src/index.ts === src/index.ts !deep && sourceFile === entryPoint || // index.ts === src/index.ts !deep && sourceFile === path__default.default.basename(entryPoint) || // some/deep/file.ts === ... deep ); } // Multiple packages return ( // packages/foo/src/index.ts === packages/foo/src/index.ts // foo/src/index.ts ~ packages/foo/src/index.ts !deep && (sourceFile === entryPoint || entryPoint.endsWith(sourceFile)) || // packages/foo/src/some/deep/file.ts === packages/foo/src/ deep && sourceFile.startsWith(entryPoint) ); } function modContainsEntryPoint(mod, entry, meta) { const relModSources = mod.sources ?? []; const relModSourceFile = relModSources.find(sf => !!sf.fileName)?.fileName ?? ''; const relEntryPoint = url.joinUrl(meta.packagePath, entry.path); // Monorepos of 1 package don't have sources, so use the child sources. // They also don't use full paths like "package/src/index.ts" and simply use "index.ts", // so account for those entry points also. if (!relModSourceFile) { const absEntryPoint = path__default.default.normalize(path__default.default.join(meta.packageRoot, entry.path)); const relEntryPointName = path__default.default.basename(relEntryPoint); const entryPointInSourceFiles = !!meta.allSourceFiles[absEntryPoint] || !!meta.allSourceFiles[relEntryPoint] || relEntryPointName.startsWith('index.') && !!meta.allSourceFiles[relEntryPointName]; if (entryPointInSourceFiles) { return sourceFileMatchesEntryPoint(relEntryPoint, relEntryPoint, { deep: meta.isUsingDeepImports, single: meta.isSinglePackage }); } } return sourceFileMatchesEntryPoint(relModSourceFile, relEntryPoint, { deep: meta.isUsingDeepImports, single: meta.isSinglePackage }); } function extractReflectionModules(project, isSinglePackage) { const modules = []; const inheritChildren = () => { project.children?.forEach(child => { if (child.kind === TypeDoc.ReflectionKind.Module) { modules.push(child); } }); }; // Single packages are extremely difficult, as the TypeDoc structure is // different for every kind of package entry point pattern if (isSinglePackage) { const hasNoModules = project.children?.every(child => child.kind !== TypeDoc.ReflectionKind.Module); if (hasNoModules) { // No "module" children: // - Polyrepos // - Monorepos with 1 package modules.push(project); } else { // Has "module" children: // - Polyrepos with deep imports // - Polyrepos with multi-imports // - Monorepos inheritChildren(); } // Multiple packages are extremely simple, as every package is a module reflection // as a child on the top-level project reflection } else { inheritChildren(); } return modules; } function buildSourceFileNameMap(project, modChildren) { const map = {}; const cwd = process.cwd(); Object.values(project.symbolIdMap).forEach(symbol => { // absolute map[path__default.default.normalize(path__default.default.join(cwd, symbol.sourceFileName))] = true; }); modChildren.forEach(child => { child.sources?.forEach(sf => { // relative map[sf.fileName] = true; }); }); return map; } function flattenAndGroupPackages(packageConfigs, project, urlPrefix, options, versioned = false) { const isSinglePackage = packageConfigs.length === 1; const modules = extractReflectionModules(project, isSinglePackage); // Loop through every TypeDoc module and group based on package and entry point const packages = {}; const packagesWithDeepImports = []; modules.forEach(mod => { const allSourceFiles = buildSourceFileNameMap(project, mod.children ?? []); packageConfigs.some(cfg => Object.entries(cfg.entryPoints).some(([importPath, entry]) => { const isUsingDeepImports = !entry.path.match(/\.tsx?$/); if (!modContainsEntryPoint(mod, entry, { allSourceFiles, isSinglePackage, isUsingDeepImports, packagePath: cfg.packagePath, packageRoot: cfg.packageRoot })) { return false; } // We have a matching entry point, so store the record if (!packages[cfg.packagePath]) { const _loadPackageJsonAndDo = loadPackageJsonAndDocs(path__default.default.join(options.projectRoot, cfg.packagePath), options.packageJsonName, options.readmeName, options.changelogName), packageJson = _loadPackageJsonAndDo.packageJson, readmePath = _loadPackageJsonAndDo.readmePath, changelogPath = _loadPackageJsonAndDo.changelogPath; packages[cfg.packagePath] = { entryPoints: [], packageName: versioned && cfg.packageName || packageJson.name, packageVersion: versioned && cfg.packageVersion || packageJson.version, readmePath, changelogPath }; // eslint-disable-next-line no-param-reassign cfg.packageName = packages[cfg.packagePath].packageName; // eslint-disable-next-line no-param-reassign cfg.packageVersion = packages[cfg.packagePath].packageVersion; } // Add metadata to package and children reflections const urlSlug = url.getPackageSlug(cfg, importPath, isSinglePackage); const reflection = addMetadataToReflections(mod, urlSlug, urlPrefix); const existingEntry = packages[cfg.packagePath].entryPoints.find(ep => ep.urlSlug === urlSlug); if (existingEntry) { if (isUsingDeepImports) { mergeReflections(existingEntry.reflection, reflection); } else { // eslint-disable-next-line no-console console.error(`Entry point ${urlSlug} already defined. How did you get here?`); } } else { packages[cfg.packagePath].entryPoints.push({ index: importPath === 'index', label: entry.label, reflection, urlSlug }); if (isUsingDeepImports) { packagesWithDeepImports.push(reflection); } } // Update the reflection name since its useless reflection.name = importPath === 'index' ? packages[cfg.packagePath].packageName : url.joinUrl(packages[cfg.packagePath].packageName, importPath); return true; })); }); // Since we merged multiple reflections together, we'll need to sort groups manually sortReflectionGroups(packagesWithDeepImports); // Sort packages by name return Object.values(packages).sort((a, b) => a.packageName.localeCompare(b.packageName)); } function formatPackagesWithoutHostInfo(packages) { return packages.map(_ref => { let changelogPath = _ref.changelogPath, readmePath = _ref.readmePath, pkg = _objectWithoutProperties(_ref, _excluded); return pkg; }); } exports.addMetadataToReflections = addMetadataToReflections; exports.createReflectionMap = createReflectionMap; exports.flattenAndGroupPackages = flattenAndGroupPackages; exports.formatPackagesWithoutHostInfo = formatPackagesWithoutHostInfo; exports.generateJson = generateJson; exports.loadPackageJsonAndDocs = loadPackageJsonAndDocs; //# sourceMappingURL=data.js.map