diff --git a/apps/website/src/components/EntrypointSelect.tsx b/apps/website/src/components/EntrypointSelect.tsx index 95f783028..c2bf78c43 100644 --- a/apps/website/src/components/EntrypointSelect.tsx +++ b/apps/website/src/components/EntrypointSelect.tsx @@ -17,24 +17,21 @@ export function EntryPointSelect({ const { entryPoints: parsedEntrypoints } = parseDocsPathParams(params.item as string[] | undefined); return ( - {(item) => ( router.prefetch(`/docs/packages/${params.packageName}/${params.version}/${item.entryPoint}`) } - textValue={item.entryPoint || 'global'} + textValue={item.entryPoint} > - {item.entryPoint || 'global'} + {item.entryPoint} )} diff --git a/apps/website/src/middleware.ts b/apps/website/src/middleware.ts index c2d2880a8..ce5a289e8 100644 --- a/apps/website/src/middleware.ts +++ b/apps/website/src/middleware.ts @@ -1,6 +1,6 @@ import Cloudflare from 'cloudflare'; import { NextResponse, type NextRequest } from 'next/server'; -import { PACKAGES } from './util/constants'; +import { DEFAULT_ENTRY_POINT, PACKAGES, PACKAGES_WITH_ENTRY_POINTS } from './util/constants'; import { ENV } from './util/env'; const client = new Cloudflare({ @@ -8,7 +8,12 @@ const client = new Cloudflare({ }); async function fetchLatestVersion(packageName: string): Promise { + const hasEntryPoints = PACKAGES_WITH_ENTRY_POINTS.includes(packageName); if (ENV.IS_LOCAL_DEV) { + if (hasEntryPoints) { + return ['main', ...DEFAULT_ENTRY_POINT].join('/'); + } + return 'main'; } @@ -19,7 +24,7 @@ async function fetchLatestVersion(packageName: string): Promise { params: [packageName], }); - return (result[0]?.results as { version: string }[] | undefined)?.[0]?.version ?? 'main'; + return `${(result[0]?.results as { version: string }[] | undefined)?.[0]?.version ?? 'main'}${hasEntryPoints ? ['', ...DEFAULT_ENTRY_POINT].join('/') : ''}`; } catch { return ''; } diff --git a/apps/website/src/util/constants.ts b/apps/website/src/util/constants.ts index 9f7481b0a..46d17f043 100644 --- a/apps/website/src/util/constants.ts +++ b/apps/website/src/util/constants.ts @@ -16,5 +16,7 @@ export const PACKAGES = [ export const PACKAGES_WITH_ENTRY_POINTS = ['discord-api-types']; +export const DEFAULT_ENTRY_POINT = ['v10']; + export const DESCRIPTION = "discord.js is a powerful Node.js module that allows you to interact with the Discord API very easily. It takes a much more object-oriented approach than most other JS Discord libraries, making your bot's code significantly tidier and easier to comprehend."; diff --git a/apps/website/src/util/parseDocsPathParams.ts b/apps/website/src/util/parseDocsPathParams.ts index 8349a9d47..9f95ec4fb 100644 --- a/apps/website/src/util/parseDocsPathParams.ts +++ b/apps/website/src/util/parseDocsPathParams.ts @@ -1,3 +1,5 @@ +import { DEFAULT_ENTRY_POINT } from './constants'; + export function parseDocsPathParams(item: string[] | undefined): { entryPoints: string[]; foundItem: string | undefined; @@ -10,7 +12,7 @@ export function parseDocsPathParams(item: string[] | undefined): { const hasTypeMarker = lastElement?.includes('%3A'); return { - entryPoints: hasTypeMarker ? item.slice(0, -1) : item, + entryPoints: hasTypeMarker ? item.slice(0, -1) : lastElement?.length === 0 ? DEFAULT_ENTRY_POINT : item, foundItem: hasTypeMarker ? lastElement : undefined, }; } diff --git a/packages/api-extractor-model/src/model/ApiEntryPoint.ts b/packages/api-extractor-model/src/model/ApiEntryPoint.ts index b2fb34ac7..6f2a17a2c 100644 --- a/packages/api-extractor-model/src/model/ApiEntryPoint.ts +++ b/packages/api-extractor-model/src/model/ApiEntryPoint.ts @@ -15,16 +15,14 @@ import { ApiPackage } from './ApiPackage.js'; export interface IApiEntryPointOptions extends IApiItemContainerMixinOptions, IApiNameMixinOptions {} /** - * Represents the entry point for an NPM package. + * Represents an entry point for an NPM package. * * @remarks * * This is part of the {@link ApiModel} hierarchy of classes, which are serializable representations of * API declarations. * - * `ApiEntryPoint` represents the entry point to an NPM package. API Extractor does not currently support - * analysis of multiple entry points, but the `ApiEntryPoint` object is included to support a future feature. - * In the current implementation, `ApiEntryPoint.importPath` is always the empty string. + * `ApiEntryPoint` represents an entry point to an NPM package. * * For example, suppose the package.json file looks like this: * @@ -37,7 +35,7 @@ export interface IApiEntryPointOptions extends IApiItemContainerMixinOptions, IA * } * ``` * - * In this example, the `ApiEntryPoint` would represent the TypeScript module for `./lib/index.js`. + * In this example, the main `ApiEntryPoint` would represent the TypeScript module for `./lib/index.js`. * @public */ export class ApiEntryPoint extends ApiItemContainerMixin(ApiNameMixin(ApiItem)) { @@ -62,12 +60,7 @@ export class ApiEntryPoint extends ApiItemContainerMixin(ApiNameMixin(ApiItem)) /** * The module path for this entry point, relative to the parent `ApiPackage`. In the current implementation, - * this is always the empty string, indicating the default entry point. - * - * @remarks - * - * API Extractor does not currently support analysis of multiple entry points. If that feature is implemented - * in the future, then the `ApiEntryPoint.importPath` will be used to distinguish different entry points, + * this is used to distinguish different entry points, * for example: `controls/Button` in `import { Button } from "example-package/controls/Button";`. * * The `ApiEntryPoint.name` property stores the same value as `ApiEntryPoint.importPath`. diff --git a/packages/api-extractor/src/api/ExtractorConfig.ts b/packages/api-extractor/src/api/ExtractorConfig.ts index a2547d052..06594c7f1 100644 --- a/packages/api-extractor/src/api/ExtractorConfig.ts +++ b/packages/api-extractor/src/api/ExtractorConfig.ts @@ -209,6 +209,7 @@ interface IExtractorConfigParameters { docModelIncludeForgottenExports: boolean; enumMemberOrder: EnumMemberOrder; mainEntryPointFilePath: string; + mainEntryPointName: string; messages: IExtractorMessagesConfig; newlineKind: NewlineKind; omitTrimmingComments: boolean; @@ -474,6 +475,7 @@ export class ExtractorConfig { projectFolder, packageJson, packageFolder, + mainEntryPointName, mainEntryPointFilePath, additionalEntryPoints, bundledPackages, @@ -509,7 +511,7 @@ export class ExtractorConfig { this.packageJson = packageJson; this.packageFolder = packageFolder; this.mainEntryPointFilePath = { - modulePath: '', + modulePath: mainEntryPointName, filePath: mainEntryPointFilePath, }; this.additionalEntryPoints = additionalEntryPoints; @@ -1003,6 +1005,8 @@ export class ExtractorConfig { tokenContext, ); + const mainEntryPointName = configObject.mainEntryPointName ?? ''; + if (!ExtractorConfig.hasDtsFileExtension(mainEntryPointFilePath)) { throw new Error('The "mainEntryPointFilePath" value is not a declaration file: ' + mainEntryPointFilePath); } @@ -1289,6 +1293,7 @@ export class ExtractorConfig { packageJson, packageFolder, mainEntryPointFilePath, + mainEntryPointName, additionalEntryPoints, bundledPackages, tsconfigFilePath, diff --git a/packages/api-extractor/src/api/IConfigFile.ts b/packages/api-extractor/src/api/IConfigFile.ts index 24fe11a2c..3aad10d1b 100644 --- a/packages/api-extractor/src/api/IConfigFile.ts +++ b/packages/api-extractor/src/api/IConfigFile.ts @@ -491,6 +491,11 @@ export interface IConfigFile { */ mainEntryPointFilePath: string; + /** + * Specifies the import path of the entrypoint used as the starting point for analysis. + */ + mainEntryPointName: string; + /** * {@inheritDoc IExtractorMessagesConfig} */ diff --git a/packages/api-extractor/src/generators/ApiModelGenerator.ts b/packages/api-extractor/src/generators/ApiModelGenerator.ts index 304eb4c0f..e6950c1af 100644 --- a/packages/api-extractor/src/generators/ApiModelGenerator.ts +++ b/packages/api-extractor/src/generators/ApiModelGenerator.ts @@ -291,6 +291,7 @@ export class ApiModelGenerator { docComment: packageDocComment, tsdocConfiguration: this._collector.extractorConfig.tsdocConfiguration, projectFolderUrl: this._collector.extractorConfig.projectFolderUrl, + preserveMemberOrder: true, }); this._apiModel.addMember(apiPackage); diff --git a/packages/api-extractor/src/schemas/api-extractor-defaults.json b/packages/api-extractor/src/schemas/api-extractor-defaults.json index 5aaf8b9ce..f240cb7e7 100644 --- a/packages/api-extractor/src/schemas/api-extractor-defaults.json +++ b/packages/api-extractor/src/schemas/api-extractor-defaults.json @@ -3,6 +3,8 @@ // ("mainEntryPointFilePath" is required) + "mainEntryPointName": "", + "bundledPackages": [], "newlineKind": "crlf", diff --git a/packages/api-extractor/src/schemas/api-extractor-template.json b/packages/api-extractor/src/schemas/api-extractor-template.json index 63ace51d4..aac377e3b 100644 --- a/packages/api-extractor/src/schemas/api-extractor-template.json +++ b/packages/api-extractor/src/schemas/api-extractor-template.json @@ -47,6 +47,11 @@ */ "mainEntryPointFilePath": "/lib/index.d.ts", + /** + * Specifies the import path of the entrypoint used as the starting point for analysis. + */ + // "mainEntryPointName": "", + /** * A list of NPM package names whose exports should be treated as part of this package. * diff --git a/packages/api-extractor/src/schemas/api-extractor.schema.json b/packages/api-extractor/src/schemas/api-extractor.schema.json index 35f43f16b..ad133197b 100644 --- a/packages/api-extractor/src/schemas/api-extractor.schema.json +++ b/packages/api-extractor/src/schemas/api-extractor.schema.json @@ -23,6 +23,11 @@ "type": "string" }, + "mainEntryPointName": { + "description": "Specifies the import path of the entrypoint used as the starting point for analysis.", + "type": "string" + }, + "additionalEntryPoints": { "description": "Specifies the .d.ts files to be used as the starting points for analysis.", "type": "array", diff --git a/packages/scripts/src/generateSplitDocumentation.ts b/packages/scripts/src/generateSplitDocumentation.ts index d3682d5a8..42e705ed3 100644 --- a/packages/scripts/src/generateSplitDocumentation.ts +++ b/packages/scripts/src/generateSplitDocumentation.ts @@ -223,39 +223,8 @@ function resolveItemURI(item: ApiItemLike, entryPoint?: ApiEntryPoint): string { } function itemExcerptText(excerpt: Excerpt, apiPackage: ApiPackage, parent?: ApiTypeParameterListMixin) { - const DISCORD_API_TYPES_VERSION = 'v10'; - const DISCORD_API_TYPES_DOCS_URL = `https://discord-api-types.dev/api/discord-api-types-${DISCORD_API_TYPES_VERSION}`; - return excerpt.spannedTokens.map((token) => { if (token.kind === ExcerptTokenKind.Reference) { - const source = token.canonicalReference?.source; - const symbol = token.canonicalReference?.symbol; - - if (source && 'packageName' in source && source.packageName === 'discord-api-types' && symbol) { - const { meaning, componentPath: path } = symbol; - let href = DISCORD_API_TYPES_DOCS_URL; - - // dapi-types doesn't have routes for class members - // so we can assume this member is for an enum - if (meaning === 'member' && path && 'parent' in path) { - // unless it's a variable like FormattingPatterns.Role - if (path.parent.toString() === '__type') { - href += `#${token.text.split('.')[0]}`; - } else { - href += `/enum/${path.parent}#${path.component}`; - } - } else if (meaning === 'type' || meaning === 'var') { - href += `#${token.text}`; - } else { - href += `/${meaning}/${token.text}`; - } - - return { - text: token.text, - href, - }; - } - if (token.canonicalReference) { const resolved = resolveCanonicalReference(token.canonicalReference, apiPackage); @@ -316,9 +285,6 @@ function itemExcerptText(excerpt: Excerpt, apiPackage: ApiPackage, parent?: ApiT } function itemTsDoc(item: DocNode, apiItem: ApiItem) { - const DISCORD_API_TYPES_VERSION = 'v10'; - const DISCORD_API_TYPES_DOCS_URL = `https://discord-api-types.dev/api/discord-api-types-${DISCORD_API_TYPES_VERSION}`; - const createNode = (node: DocNode): any => { switch (node.kind) { case DocNodeKind.PlainText: @@ -380,29 +346,6 @@ function itemTsDoc(item: DocNode, apiItem: ApiItem) { }; } - if (resolved && resolved.package === 'discord-api-types') { - const { displayName, kind, members, containerKey } = resolved.item; - let href = DISCORD_API_TYPES_DOCS_URL; - - // dapi-types doesn't have routes for class members - // so we can assume this member is for an enum - if (kind === 'enum' && members?.[0]) { - href += `/enum/${displayName}#${members[0].displayName}`; - } else if (kind === 'type' || kind === 'var') { - href += `#${displayName}`; - } else { - href += `/${kind}/${displayName}`; - } - - return { - kind: DocNodeKind.LinkTag, - text: displayName, - containerKey, - uri: href, - members: members?.map((member) => `.${member.displayName}`).join('') ?? '', - }; - } - return { kind: DocNodeKind.LinkTag, text: linkText ?? foundItem?.displayName ?? resolved!.item.displayName, @@ -545,8 +488,6 @@ function itemInfo(item: ApiDeclaredItem) { function resolveFileUrl(item: ApiDeclaredItem) { const { - displayName, - kind, sourceLocation: { fileUrl, fileLine }, } = item; if (fileUrl?.includes('/node_modules/')) { @@ -571,20 +512,12 @@ function resolveFileUrl(item: ApiDeclaredItem) { // https://github.com/discordjs/discord.js/tree/main/node_modules/.pnpm/discord-api-types@0.37.97/node_modules/discord-api-types/payloads/v10/gateway.d.ts#L240 if (pkgName === 'discord-api-types') { - const DISCORD_API_TYPES_VERSION = 'v10'; - const DISCORD_API_TYPES_DOCS_URL = `https://discord-api-types.dev/api/discord-api-types-${DISCORD_API_TYPES_VERSION}`; - let href = DISCORD_API_TYPES_DOCS_URL; - - if (kind === ApiItemKind.EnumMember) { - href += `/enum/${item.parent!.displayName}#${displayName}`; - } else if (kind === ApiItemKind.TypeAlias || kind === ApiItemKind.Variable) { - href += `#${displayName}`; - } else { - href += `/${kindToMeaning.get(kind)}/${displayName}`; - } + let currentItem = item; + while (currentItem.parent && currentItem.parent.kind !== ApiItemKind.EntryPoint) + currentItem = currentItem.parent as ApiDeclaredItem; return { - sourceURL: href, + sourceURL: `/docs/packages/${pkgName}/${version}/${(currentItem.parent as ApiEntryPoint).importPath}/${currentItem.displayName}:${currentItem.kind}`, }; } } else if (fileUrl?.includes('/dist/') && fileUrl.includes('/main/packages/')) {