Files
discord.js/packages/scripts/src/generateIndex.ts
brynpttrsn 56943a72f4 fix(website): resolve linkTags in meta description (#10088)
* fix(website): resolve linkTags in summaries

* fix: case body as block

* fix: add discord-api-types support

* fix: remove urlDestination when undefined

* fix: breaks to if/else

---------

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2024-02-03 22:10:28 +00:00

194 lines
4.7 KiB
TypeScript

import { stat, mkdir, writeFile } from 'node:fs/promises';
import { join } from 'node:path';
import { cwd } from 'node:process';
import {
type ApiItem,
ApiPackage,
ApiModel,
ApiDeclaredItem,
ApiItemContainerMixin,
ApiItemKind,
} from '@discordjs/api-extractor-model';
import { generatePath } from '@discordjs/api-extractor-utils';
import { DocNodeKind } from '@microsoft/tsdoc';
import type { DocLinkTag, DocCodeSpan, DocNode, DocParagraph, DocPlainText } from '@microsoft/tsdoc';
import { request } from 'undici';
export interface MemberJSON {
kind: string;
name: string;
path: string;
summary: string | null;
}
export const PACKAGES = [
'discord.js',
'brokers',
'builders',
'collection',
'core',
'formatters',
'next',
'proxy',
'rest',
'util',
'voice',
'ws',
];
let idx = 0;
/**
* Attempts to resolve the summary text for the given item.
*
* @param item - The API item to resolve the summary text for.
*/
export function tryResolveSummaryText(item: ApiDeclaredItem): string | null {
if (!item?.tsdocComment) {
return null;
}
const { summarySection } = item.tsdocComment;
let retVal = '';
// Recursively visit the nodes in the summary section.
const visitTSDocNode = (node: DocNode) => {
switch (node.kind) {
case DocNodeKind.CodeSpan:
retVal += (node as DocCodeSpan).code;
break;
case DocNodeKind.PlainText:
retVal += (node as DocPlainText).text;
break;
case DocNodeKind.LinkTag: {
const { codeDestination, urlDestination, linkText } = node as DocLinkTag;
if (codeDestination) {
const declarationReference = item.getAssociatedModel()?.resolveDeclarationReference(codeDestination, item);
if (declarationReference?.resolvedApiItem) {
const foundItem = declarationReference.resolvedApiItem;
retVal += linkText ?? foundItem.displayName;
} else {
const typeName = codeDestination.memberReferences.map((ref) => ref.memberIdentifier?.identifier).join('.');
retVal += typeName;
}
} else {
retVal += linkText ?? urlDestination;
}
break;
}
case DocNodeKind.Section:
case DocNodeKind.Paragraph: {
for (const child of (node as DocParagraph).nodes) {
visitTSDocNode(child);
}
break;
}
default: // We'll ignore all other nodes.
break;
}
};
for (const node of summarySection.nodes) {
visitTSDocNode(node);
}
if (retVal === '') {
return null;
}
return retVal;
}
export function visitNodes(item: ApiItem, tag: string) {
const members: (MemberJSON & { id: number })[] = [];
for (const member of item.members) {
if (!(member instanceof ApiDeclaredItem)) {
continue;
}
if (member.kind === ApiItemKind.Constructor || member.kind === ApiItemKind.Namespace) {
continue;
}
if (ApiItemContainerMixin.isBaseClassOf(member)) {
members.push(...visitNodes(member, tag));
}
members.push({
id: idx++,
name: member.displayName,
kind: member.kind,
summary: tryResolveSummaryText(member) ?? '',
path: generatePath(member.getHierarchy(), tag),
});
}
return members;
}
export async function writeIndexToFileSystem(
members: ReturnType<typeof visitNodes>,
packageName: string,
tag = 'main',
) {
const dir = 'searchIndex';
try {
(await stat(join(cwd(), 'public', dir))).isDirectory();
} catch {
await mkdir(join(cwd(), 'public', dir));
}
await writeFile(
join(cwd(), 'public', dir, `${packageName}-${tag}-index.json`),
JSON.stringify(members, undefined, 2),
);
}
export async function fetchVersions(pkg: string) {
const response = await request(`https://docs.discordjs.dev/api/info?package=${pkg}`);
return response.body.json() as Promise<string[]>;
}
export async function fetchVersionDocs(pkg: string, version: string) {
const response = await request(`https://docs.discordjs.dev/docs/${pkg}/${version}.api.json`);
return response.body.json();
}
export async function generateAllIndices({
fetchPackageVersions = fetchVersions,
fetchPackageVersionDocs = fetchVersionDocs,
writeToFile = true,
} = {}) {
const indices: Record<any, any>[] = [];
for (const pkg of PACKAGES) {
const versions = await fetchPackageVersions(pkg);
for (const version of versions) {
idx = 0;
const data = await fetchPackageVersionDocs(pkg, version);
const model = new ApiModel();
model.addMember(ApiPackage.loadFromJson(data));
const members = visitNodes(model.tryGetPackageByName(pkg)!.entryPoints[0]!, version);
const sanitizePackageName = pkg.replaceAll('.', '-');
const sanitizeVersion = version.replaceAll('.', '-');
if (writeToFile) {
await writeIndexToFileSystem(members, sanitizePackageName, sanitizeVersion);
} else {
indices.push({ index: `${sanitizePackageName}-${sanitizeVersion}`, data: members });
}
}
}
return indices;
}