diff --git a/biome.json b/biome.json new file mode 100644 index 000000000..d13d488f4 --- /dev/null +++ b/biome.json @@ -0,0 +1,36 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.5.3/schema.json", + "formatter": { + "enabled": true, + "formatWithErrors": false, + "indentStyle": "tab", + "indentWidth": 2, + "lineWidth": 120, + "lineEnding": "lf", + "ignore": [ + ".turbo", + ".vercel", + ".contentlayer", + ".next", + "coverage", + "dist", + "dist-docs", + "docs", + "CHANGELOG.md", + "tsup.config.bundled*" + ] + }, + "javascript": { + "formatter": { + "enabled": true, + "quoteStyle": "single", + "quoteProperties": "asNeeded", + "trailingComma": "all", + "semicolons": "always" + } + }, + "files": { + "ignoreUnknown": true, + "maxSize": 1000000 + } +} diff --git a/eslint.config.js b/eslint.config.js index 11eedddc1..4e7fd322b 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -6,6 +6,7 @@ import node from 'eslint-config-neon/flat/node.js'; import prettier from 'eslint-config-neon/flat/prettier.js'; import react from 'eslint-config-neon/flat/react.js'; import typescript from 'eslint-config-neon/flat/typescript.js'; +// import oxlint from 'eslint-plugin-oxlint'; import merge from 'lodash.merge'; const commonFiles = '{js,mjs,cjs,ts,mts,cts,jsx,tsx}'; @@ -63,6 +64,8 @@ const edgeRuleset = merge(...edge, { files: [`apps/**/*${commonFiles}`] }); const prettierRuleset = merge(...prettier, { files: [`**/*${commonFiles}`] }); +// const oxlintRuleset = merge({ rules: oxlint.rules }, { files: [`**/*${commonFiles}`] }); + /** @type {import('eslint').Linter.FlatConfig[]} */ export default [ { @@ -133,4 +136,5 @@ export default [ rules: { 'tsdoc/syntax': 0 }, }, prettierRuleset, + // oxlintRuleset, ]; diff --git a/packages/actions/src/uploadDocumentation/action.yml b/packages/actions/src/uploadDocumentation/action.yml index 6312a2173..d08a71a59 100644 --- a/packages/actions/src/uploadDocumentation/action.yml +++ b/packages/actions/src/uploadDocumentation/action.yml @@ -1,5 +1,5 @@ name: 'Upload documentation' -description: 'Uploads the docs.api.json file to a planetscale database' +description: 'Uploads the docs.api.json file to a neon postgresql database' inputs: package: description: 'The package string' diff --git a/packages/actions/src/uploadSplitDocumentation/action.yml b/packages/actions/src/uploadSplitDocumentation/action.yml new file mode 100644 index 000000000..c610c92b5 --- /dev/null +++ b/packages/actions/src/uploadSplitDocumentation/action.yml @@ -0,0 +1,10 @@ +name: 'Upload split documentation' +description: 'Splits and uploads the docs.api.json file into more fine-grained [item].api.json files' +inputs: + package: + description: 'The package string' + version: + description: 'The semver string' +runs: + using: node20 + main: ../../dist/uploadSplitDocumentation/index.js diff --git a/packages/actions/src/uploadSplitDocumentation/index.ts b/packages/actions/src/uploadSplitDocumentation/index.ts new file mode 100644 index 000000000..828fb08da --- /dev/null +++ b/packages/actions/src/uploadSplitDocumentation/index.ts @@ -0,0 +1,34 @@ +import { readFile } from 'node:fs/promises'; +import { basename } from 'node:path'; +import process from 'node:process'; +import { getInput, setFailed } from '@actions/core'; +import { create } from '@actions/glob'; +import { put } from '@vercel/blob'; +import { createPool } from '@vercel/postgres'; + +if (!process.env.DATABASE_URL) { + setFailed('DATABASE_URL is not set'); +} + +const pkg = getInput('package') || '*'; +const version = getInput('version') || 'main'; + +const pool = createPool({ + connectionString: process.env.DATABASE_URL, +}); + +const globber = await create(`packages/${pkg}/docs/split/main.*.*.api.json`); +for await (const file of globber.globGenerator()) { + const data = await readFile(file, 'utf8'); + try { + console.log(`Uploading ${file} with ${version}...`); + const name = basename(file).replace('main.', ''); + const { url } = await put(`${version}.${name}`, data, { + access: 'public', + addRandomSuffix: false, + }); + await pool.sql`insert into documentation (name, version, url) values (${name}, ${version}, ${url}) on conflict (name, version) do update set url = EXCLUDED.url`; + } catch (error) { + console.log(error); + } +} diff --git a/packages/actions/tsup.config.ts b/packages/actions/tsup.config.ts index a1e481343..7ee02ac15 100644 --- a/packages/actions/tsup.config.ts +++ b/packages/actions/tsup.config.ts @@ -2,6 +2,7 @@ import { createTsupConfig } from '../../tsup.config.js'; export default createTsupConfig({ entry: [ + 'src/buildSplitDocumentation/index.ts', 'src/index.ts', 'src/formatTag/index.ts', 'src/uploadDocumentation/index.ts', diff --git a/packages/brokers/package.json b/packages/brokers/package.json index 9a04a68e4..a8d41168d 100644 --- a/packages/brokers/package.json +++ b/packages/brokers/package.json @@ -10,7 +10,7 @@ "lint": "prettier --check . && cross-env TIMING=1 eslint --format=pretty src __tests__", "format": "prettier --write . && cross-env TIMING=1 eslint --fix --format=pretty src __tests__", "fmt": "pnpm run format", - "docs": "pnpm run build:docs && api-extractor run --local --minify", + "docs": "pnpm run build:docs && api-extractor run --local --minify && generate-split-documentation", "prepack": "pnpm run lint && pnpm run test && pnpm run build", "changelog": "git cliff --prepend ./CHANGELOG.md -u -c ./cliff.toml -r ../../ --include-path 'packages/brokers/*'", "release": "cliff-jumper" @@ -72,6 +72,7 @@ }, "devDependencies": { "@discordjs/api-extractor": "workspace:^", + "@discordjs/scripts": "workspace:^", "@favware/cliff-jumper": "^2.2.3", "@types/node": "18.18.8", "@vitest/coverage-v8": "^1.2.2", diff --git a/packages/builders/package.json b/packages/builders/package.json index 5d86867d6..842f72b44 100644 --- a/packages/builders/package.json +++ b/packages/builders/package.json @@ -10,7 +10,7 @@ "lint": "prettier --check . && cross-env TIMING=1 eslint --format=pretty src __tests__", "format": "prettier --write . && cross-env TIMING=1 eslint --fix --format=pretty src __tests__", "fmt": "pnpm run format", - "docs": "pnpm run build:docs && api-extractor run --local --minify", + "docs": "pnpm run build:docs && api-extractor run --local --minify && generate-split-documentation", "prepack": "pnpm run lint && pnpm run test && pnpm run build", "changelog": "git cliff --prepend ./CHANGELOG.md -u -c ./cliff.toml -r ../../ --include-path 'packages/builders/*'", "release": "cliff-jumper" @@ -74,6 +74,7 @@ }, "devDependencies": { "@discordjs/api-extractor": "workspace:^", + "@discordjs/scripts": "workspace:^", "@favware/cliff-jumper": "^2.2.3", "@types/node": "16.18.60", "@vitest/coverage-v8": "^1.2.2", diff --git a/packages/collection/package.json b/packages/collection/package.json index 5e6689516..2663f7bfb 100644 --- a/packages/collection/package.json +++ b/packages/collection/package.json @@ -10,7 +10,7 @@ "lint": "prettier --check . && cross-env TIMING=1 eslint --format=pretty src __tests__", "format": "prettier --write . && cross-env TIMING=1 eslint --fix --format=pretty src __tests__", "fmt": "pnpm run format", - "docs": "pnpm run build:docs && api-extractor run --local --minify", + "docs": "pnpm run build:docs && api-extractor run --local --minify && generate-split-documentation", "prepack": "pnpm run lint && pnpm run test && pnpm run build", "changelog": "git cliff --prepend ./CHANGELOG.md -u -c ./cliff.toml -r ../../ --include-path 'packages/collection/*'", "release": "cliff-jumper" @@ -61,6 +61,7 @@ "homepage": "https://discord.js.org", "devDependencies": { "@discordjs/api-extractor": "workspace:^", + "@discordjs/scripts": "workspace:^", "@favware/cliff-jumper": "^2.2.3", "@types/node": "18.18.8", "@vitest/coverage-v8": "^1.2.2", diff --git a/packages/core/package.json b/packages/core/package.json index bb3c10caa..f7acb92b4 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -9,7 +9,7 @@ "build:docs": "tsc -p tsconfig.docs.json", "lint": "prettier --check . && cross-env TIMING=1 eslint --format=pretty src", "format": "prettier --write . && cross-env TIMING=1 eslint --fix --format=pretty src", - "docs": "pnpm run build:docs && api-extractor run --local --minify", + "docs": "pnpm run build:docs && api-extractor run --local --minify && generate-split-documentation", "prepack": "pnpm run build && pnpm run lint", "changelog": "git cliff --prepend ./CHANGELOG.md -u -c ./cliff.toml -r ../../ --include-path 'packages/core/*'", "release": "cliff-jumper" @@ -73,6 +73,7 @@ }, "devDependencies": { "@discordjs/api-extractor": "workspace:^", + "@discordjs/scripts": "workspace:^", "@favware/cliff-jumper": "^2.2.3", "@types/node": "18.18.8", "@vitest/coverage-v8": "^1.2.2", diff --git a/packages/discord.js/package.json b/packages/discord.js/package.json index 763115ee0..d5927c3e1 100644 --- a/packages/discord.js/package.json +++ b/packages/discord.js/package.json @@ -11,7 +11,7 @@ "fmt": "pnpm run format", "docs": "docgen -i \"./src/*.js\" \"./src/**/*.js\" -c ./docs/index.json -r ../../ -o ./docs/docs.json && pnpm run docs:new", "docs:test": "docgen -i \"./src/*.js\" \"./src/**/*.js\" -c ./docs/index.json -r ../../", - "docs:new": "api-extractor run --local --minify", + "docs:new": "api-extractor run --local --minify && generate-split-documentation", "prepack": "pnpm run lint && pnpm run test && node ./scripts/esmDts.mjs", "changelog": "git cliff --prepend ./CHANGELOG.md -u -c ./cliff.toml -r ../../ --include-path 'packages/discord.js/*'", "release": "cliff-jumper" @@ -82,6 +82,7 @@ "devDependencies": { "@discordjs/api-extractor": "workspace:^", "@discordjs/docgen": "workspace:^", + "@discordjs/scripts": "workspace:^", "@favware/cliff-jumper": "2.2.3", "@types/node": "16.18.60", "@typescript-eslint/eslint-plugin": "^6.21.0", diff --git a/packages/docgen/package.json b/packages/docgen/package.json index e98463427..fa93d5a27 100644 --- a/packages/docgen/package.json +++ b/packages/docgen/package.json @@ -31,6 +31,7 @@ "lib": "src" }, "files": [ + "bin/index.js", "dist" ], "contributors": [ diff --git a/packages/formatters/package.json b/packages/formatters/package.json index 1debbf592..e2fc77938 100644 --- a/packages/formatters/package.json +++ b/packages/formatters/package.json @@ -9,7 +9,7 @@ "build:docs": "tsc -p tsconfig.docs.json", "lint": "prettier --check . && cross-env TIMING=1 eslint --format=pretty src __tests__", "format": "prettier --write . && cross-env TIMING=1 eslint --fix --format=pretty src __tests__", - "docs": "pnpm run build:docs && api-extractor run --local --minify", + "docs": "pnpm run build:docs && api-extractor run --local --minify && generate-split-documentation", "prepack": "pnpm run build && pnpm run lint", "changelog": "git cliff --prepend ./CHANGELOG.md -u -c ./cliff.toml -r ../../ --include-path 'packages/formatters/*'", "release": "cliff-jumper" @@ -58,6 +58,7 @@ }, "devDependencies": { "@discordjs/api-extractor": "workspace:^", + "@discordjs/scripts": "workspace:^", "@favware/cliff-jumper": "^2.2.3", "@types/node": "16.18.60", "@vitest/coverage-v8": "^1.2.2", diff --git a/packages/next/package.json b/packages/next/package.json index d972687cb..b8ade0dcb 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -9,7 +9,7 @@ "build:docs": "tsc -p tsconfig.docs.json", "lint": "prettier --check . && cross-env TIMING=1 eslint --format=pretty src __tests__", "format": "prettier --write . && cross-env TIMING=1 eslint --fix --format=pretty src __tests__", - "docs": "pnpm run build:docs && api-extractor run --local --minify", + "docs": "pnpm run build:docs && api-extractor run --local --minify && generate-split-documentation", "prepack": "pnpm run build && pnpm run lint", "changelog": "git cliff --prepend ./CHANGELOG.md -u -c ./cliff.toml -r ../../ --include-path 'packages/next/*'", "release": "cliff-jumper" @@ -75,6 +75,7 @@ }, "devDependencies": { "@discordjs/api-extractor": "workspace:^", + "@discordjs/scripts": "workspace:^", "@favware/cliff-jumper": "^2.2.3", "@types/node": "18.18.8", "@vitest/coverage-v8": "^1.2.2", diff --git a/packages/proxy/package.json b/packages/proxy/package.json index 729522927..96b88daf2 100644 --- a/packages/proxy/package.json +++ b/packages/proxy/package.json @@ -10,7 +10,7 @@ "lint": "prettier --check . && cross-env TIMING=1 eslint --format=pretty src __tests__", "format": "prettier --write . && cross-env TIMING=1 eslint --fix --format=pretty src __tests__", "fmt": "pnpm run format", - "docs": "pnpm run build:docs && api-extractor run --local --minify", + "docs": "pnpm run build:docs && api-extractor run --local --minify && generate-split-documentation", "prepack": "pnpm run lint && pnpm run test && pnpm run build", "changelog": "git cliff --prepend ./CHANGELOG.md -u -c ./cliff.toml -r ../../ --include-path 'packages/proxy/*'", "release": "cliff-jumper" @@ -71,6 +71,7 @@ }, "devDependencies": { "@discordjs/api-extractor": "workspace:^", + "@discordjs/scripts": "workspace:^", "@favware/cliff-jumper": "^2.2.3", "@types/node": "18.18.8", "@types/supertest": "^6.0.2", diff --git a/packages/rest/package.json b/packages/rest/package.json index a3e7bd8e0..20e4e3316 100644 --- a/packages/rest/package.json +++ b/packages/rest/package.json @@ -10,7 +10,7 @@ "lint": "prettier --check . && cross-env TIMING=1 eslint --format=pretty src __tests__", "format": "prettier --write . && cross-env TIMING=1 eslint --fix --format=pretty src __tests__", "fmt": "pnpm run format", - "docs": "pnpm run build:docs && api-extractor run --local --minify", + "docs": "pnpm run build:docs && api-extractor run --local --minify && generate-split-documentation", "prepack": "pnpm run lint && pnpm run test && pnpm run build", "changelog": "git cliff --prepend ./CHANGELOG.md -u -c ./cliff.toml -r ../../ --include-path 'packages/rest/*'", "release": "cliff-jumper" @@ -94,6 +94,7 @@ }, "devDependencies": { "@discordjs/api-extractor": "workspace:^", + "@discordjs/scripts": "workspace:^", "@favware/cliff-jumper": "^2.2.3", "@types/node": "18.17.9", "@vitest/coverage-v8": "^1.2.2", diff --git a/packages/scripts/bin/generateSplitDocumentation.js b/packages/scripts/bin/generateSplitDocumentation.js new file mode 100755 index 000000000..6d4028358 --- /dev/null +++ b/packages/scripts/bin/generateSplitDocumentation.js @@ -0,0 +1,2 @@ +#!/usr/bin/env node +require('../dist/bin/generateSplitDocumentation.js'); diff --git a/packages/scripts/bin/generateSplitDocumentation.ts b/packages/scripts/bin/generateSplitDocumentation.ts new file mode 100644 index 000000000..a9e151ee2 --- /dev/null +++ b/packages/scripts/bin/generateSplitDocumentation.ts @@ -0,0 +1,31 @@ +#!/usr/bin/env node +/* eslint-disable n/shebang */ +import { readFile } from 'node:fs/promises'; +import process from 'node:process'; +import { createCommand } from 'commander'; +import packageFile from '../package.json'; +import { generateSplitDocumentation } from '../src/index.js'; + +export interface CLIOptions { + custom: string; + input: string[]; + newOutput: string; + output: string; + root: string; + typescript: boolean; +} + +const command = createCommand().version(packageFile.version); + +const program = command.parse(process.argv); +program.opts(); + +console.log('Generating split documentation...'); +void generateSplitDocumentation({ + fetchPackageVersions: async (_) => { + return ['main']; + }, + fetchPackageVersionDocs: async (_, __) => { + return JSON.parse(await readFile(`${process.cwd()}/docs/docs.api.json`, 'utf8')); + }, +}).then(() => console.log('Generated split documentation.')); diff --git a/packages/scripts/package.json b/packages/scripts/package.json index 8f1ba4aaf..b041d89d0 100644 --- a/packages/scripts/package.json +++ b/packages/scripts/package.json @@ -10,26 +10,31 @@ "format": "prettier --write . && cross-env TIMING=1 eslint --fix --format=pretty src turbo/generators/config.ts", "fmt": "pnpm run format" }, + "bin": { + "generate-split-documentation": "./bin/generateSplitDocumentation.js" + }, "exports": { ".": { "require": { - "types": "./dist/index.d.ts", - "default": "./dist/index.js" + "types": "./dist/src/index.d.ts", + "default": "./dist/src/index.js" }, "import": { - "types": "./dist/index.d.mts", - "default": "./dist/index.mjs" + "types": "./dist/src/index.d.mts", + "default": "./dist/src/index.mjs" } } }, - "main": "./dist/index.js", - "module": "./dist/index.mjs", - "types": "./dist/index.d.ts", + "main": "./dist/src/index.js", + "module": "./dist/src/index.mjs", + "types": "./dist/src/index.d.ts", "directories": { + "bin": "bin", "lib": "src", "example": "turbo" }, "files": [ + "bin/generateSplitDocumentation.js", "dist" ], "contributors": [ @@ -60,6 +65,7 @@ "@microsoft/tsdoc-config": "0.16.2", "@vercel/blob": "^0.20.0", "@vercel/postgres": "^0.7.2", + "commander": "^11.1.0", "tslib": "^2.6.2", "undici": "6.6.1", "yaml": "2.3.4" diff --git a/packages/scripts/src/generateIndex.ts b/packages/scripts/src/generateIndex.ts index db952ab40..3fc71bc80 100644 --- a/packages/scripts/src/generateIndex.ts +++ b/packages/scripts/src/generateIndex.ts @@ -12,7 +12,7 @@ import { 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'; +import { PACKAGES, fetchVersionDocs, fetchVersions } from './shared.js'; export interface MemberJSON { kind: string; @@ -21,20 +21,6 @@ export interface MemberJSON { summary: string | null; } -export const PACKAGES = [ - 'discord.js', - 'brokers', - 'builders', - 'collection', - 'core', - 'formatters', - 'next', - 'proxy', - 'rest', - 'util', - 'voice', - 'ws', -]; let idx = 0; /** @@ -150,16 +136,6 @@ export async function writeIndexToFileSystem( ); } -export async function fetchVersions(pkg: string) { - const response = await request(`https://docs.discordjs.dev/api/info?package=${pkg}`); - return response.body.json() as Promise; -} - -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, diff --git a/packages/scripts/src/generateSplitDocumentation.ts b/packages/scripts/src/generateSplitDocumentation.ts new file mode 100644 index 000000000..5b46a997b --- /dev/null +++ b/packages/scripts/src/generateSplitDocumentation.ts @@ -0,0 +1,902 @@ +import { mkdir, stat, writeFile } from 'node:fs/promises'; +import { join } from 'node:path'; +import { cwd } from 'node:process'; +import type { + ApiClass, + ApiConstructor, + ApiDeclaredItem, + ApiDocumentedItem, + ApiEntryPoint, + ApiEnum, + ApiEnumMember, + ApiEvent, + ApiInterface, + ApiItem, + ApiItemContainerMixin, + ApiMethod, + ApiMethodSignature, + ApiProperty, + ApiPropertySignature, + ApiTypeAlias, + ApiTypeParameterListMixin, + ApiVariable, +} from '@discordjs/api-extractor-model'; +import { + Excerpt, + Meaning, + ApiAbstractMixin, + ApiFunction, + ApiItemKind, + ApiModel, + ApiPackage, + ApiParameterListMixin, + ApiProtectedMixin, + ApiReadonlyMixin, + ApiStaticMixin, + ExcerptTokenKind, + ExcerptToken, +} from '@discordjs/api-extractor-model'; +import { DocNodeKind, SelectorKind, StandardTags } from '@microsoft/tsdoc'; +import type { + DocNode, + DocNodeContainer, + DocDeclarationReference, + DocPlainText, + DocLinkTag, + DocFencedCode, + DocComment, +} from '@microsoft/tsdoc'; +import type { DeclarationReference } from '@microsoft/tsdoc/lib-commonjs/beta/DeclarationReference.js'; +import { PACKAGES, fetchVersionDocs, fetchVersions } from './shared.js'; + +function resolvePackageName(packageName: string) { + return packageName === 'discord.js' ? packageName : `@discordjs/${packageName}`; +} + +function findMemberByKey(model: ApiModel, packageName: string, containerKey: string) { + const pkg = model.tryGetPackageByName(resolvePackageName(packageName))!; + return (pkg.members[0] as ApiEntryPoint).tryGetMemberByKey(containerKey); +} + +function findMember(model: ApiModel, packageName: string, memberName: string | undefined) { + if (!memberName) { + return undefined; + } + + const pkg = model.tryGetPackageByName(resolvePackageName(packageName))!; + return pkg.entryPoints[0]?.findMembersByName(memberName)[0]; +} + +/** + * Resolves all inherited members (including merged members) of a given parent. + * + * @param parent - The parent to resolve the inherited members of. + * @param predicate - A predicate to filter the members by. + */ +export function resolveMembers( + parent: ApiItemContainerMixin, + predicate: (item: ApiItem) => item is WantedItem, +) { + const seenItems = new Set(); + const inheritedMembers = parent.findMembersWithInheritance().items.reduce((acc, item) => { + if (predicate(item) && !seenItems.has(item.displayName)) { + acc.push({ + item, + inherited: + item.parent?.containerKey === parent.containerKey + ? undefined + : (item.parent as ApiItemContainerMixin | undefined), + }); + + seenItems.add(item.displayName); + } + + return acc; + }, new Array<{ inherited?: ApiItemContainerMixin | undefined; item: WantedItem }>()); + + const mergedMembers = parent + .getMergedSiblings() + .filter((sibling) => sibling.containerKey !== parent.containerKey) + .flatMap((sibling) => (sibling as ApiItemContainerMixin).findMembersWithInheritance().items) + .filter((item) => predicate(item) && !seenItems.has(item.containerKey)) + .map((item) => ({ + item: item as WantedItem, + inherited: item.parent ? (item.parent as ApiItemContainerMixin) : undefined, + })); + + return [...inheritedMembers, ...mergedMembers]; +} + +const kindToMeaning = new Map([ + [ApiItemKind.CallSignature, Meaning.CallSignature], + [ApiItemKind.Class, Meaning.Class], + [ApiItemKind.ConstructSignature, Meaning.ConstructSignature], + [ApiItemKind.Constructor, Meaning.Constructor], + [ApiItemKind.Enum, Meaning.Enum], + [ApiItemKind.Event, Meaning.Event], + [ApiItemKind.Function, Meaning.Function], + [ApiItemKind.IndexSignature, Meaning.IndexSignature], + [ApiItemKind.Interface, Meaning.Interface], + [ApiItemKind.Property, Meaning.Member], + [ApiItemKind.Namespace, Meaning.Namespace], + [ApiItemKind.None, Meaning.ComplexType], + [ApiItemKind.TypeAlias, Meaning.TypeAlias], + [ApiItemKind.Variable, Meaning.Variable], +]); + +function mapMeaningToKind(meaning: Meaning): ApiItemKind { + return [...kindToMeaning.entries()].find((mapping) => mapping[1] === meaning)?.[0] ?? ApiItemKind.None; +} + +// function mapKindToMeaning(kind: ApiItemKind): Meaning { +// return kindToMeaning.get(kind) ?? Meaning.Variable; +// } + +function resolveCanonicalReference( + canonicalReference: DeclarationReference | DocDeclarationReference, + apiPackage: ApiPackage | undefined, +) { + if ( + 'source' in canonicalReference && + canonicalReference.source && + 'packageName' in canonicalReference.source && + canonicalReference.symbol?.componentPath && + canonicalReference.symbol.meaning + ) + return { + package: canonicalReference.source.packageName, + unscopedPackage: canonicalReference.source.unscopedPackageName, + item: { + kind: mapMeaningToKind(canonicalReference.symbol.meaning as unknown as Meaning), + displayName: canonicalReference.symbol.componentPath.component.toString(), + containerKey: `|${ + canonicalReference.symbol.meaning + }|${canonicalReference.symbol.componentPath.component.toString()}`, + }, + // eslint-disable-next-line unicorn/better-regex + version: apiPackage?.dependencies?.[canonicalReference.source.packageName]?.replace(/[~^]/, ''), + }; + else if ( + 'memberReferences' in canonicalReference && + canonicalReference.memberReferences.length && + canonicalReference.memberReferences[0]?.memberIdentifier && + canonicalReference.memberReferences[0]?.selector?.selectorKind === SelectorKind.System + ) { + const member = canonicalReference.memberReferences[0]!; + return { + package: canonicalReference.packageName?.replace('@discordjs/', ''), + item: { + kind: member.selector!.selector, + displayName: member.memberIdentifier!.identifier, + containerKey: `|${member.selector!.selector}|${member.memberIdentifier!.identifier}`, + members: canonicalReference.memberReferences + .slice(1) + .map((member) => ({ kind: member.kind, displayName: member.memberIdentifier!.identifier! })), + }, + // eslint-disable-next-line unicorn/better-regex + version: apiPackage?.dependencies?.[canonicalReference.packageName ?? '']?.replace(/[~^]/, ''), + }; + } + + return null; +} + +export function memberPredicate( + item: ApiItem, +): item is ApiEvent | ApiMethod | ApiMethodSignature | ApiProperty | ApiPropertySignature { + return ( + item.kind === ApiItemKind.Property || + item.kind === ApiItemKind.PropertySignature || + item.kind === ApiItemKind.Method || + item.kind === ApiItemKind.MethodSignature || + item.kind === ApiItemKind.Event + ); +} + +export function hasProperties(item: ApiItemContainerMixin) { + return resolveMembers(item, memberPredicate).some( + ({ item: member }) => member.kind === ApiItemKind.Property || member.kind === ApiItemKind.PropertySignature, + ); +} + +export function hasMethods(item: ApiItemContainerMixin) { + return resolveMembers(item, memberPredicate).some( + ({ item: member }) => member.kind === ApiItemKind.Method || member.kind === ApiItemKind.MethodSignature, + ); +} + +export function hasEvents(item: ApiItemContainerMixin) { + return resolveMembers(item, memberPredicate).some(({ item: member }) => member.kind === ApiItemKind.Event); +} + +interface ApiItemLike { + containerKey?: string; + displayName: string; + kind: string; + members?: readonly ApiItemLike[]; + parent?: ApiItemLike | undefined; +} + +function resolveItemURI(item: ApiItemLike): string { + return !item.parent || item.parent.kind === ApiItemKind.EntryPoint + ? `${item.displayName}:${item.kind}` + : `${item.parent.displayName}:${item.parent.kind}#${item.displayName}`; +} + +function itemExcerptText(excerpt: Excerpt, apiPackage: ApiPackage) { + 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) { + 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, + }; + } + + const resolved = token.canonicalReference + ? resolveCanonicalReference(token.canonicalReference, apiPackage) + : null; + + if (!resolved) { + return { + text: token.text, + }; + } + + return { + text: token.text, + resolvedItem: { + kind: resolved.item.kind, + displayName: resolved.item.displayName, + containerKey: resolved.item.containerKey, + uri: resolveItemURI(resolved.item), + packageName: resolved.package, + version: resolved.version, + }, + }; + } + + return { + text: token.text, + }; + }); +} + +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: + return { + kind: DocNodeKind.PlainText, + text: (node as DocPlainText).text, + }; + case DocNodeKind.Section: + case DocNodeKind.Paragraph: + return (node as DocNodeContainer).nodes.map((node) => createNode(node)); + case DocNodeKind.SoftBreak: + return { + kind: DocNodeKind.SoftBreak, + text: null, + }; + case DocNodeKind.LinkTag: { + const { codeDestination, urlDestination, linkText } = node as DocLinkTag; + + if (codeDestination) { + if ( + !codeDestination.importPath && + !codeDestination.packageName && + codeDestination.memberReferences.length === 1 && + codeDestination.memberReferences[0]!.memberIdentifier + ) { + const typeName = codeDestination.memberReferences[0]!.memberIdentifier.identifier; + + return { + kind: DocNodeKind.LinkTag, + text: typeName, + }; + } + + const declarationReference = apiItem + .getAssociatedModel() + ?.resolveDeclarationReference(codeDestination, apiItem); + const foundItem = declarationReference?.resolvedApiItem; + const resolved = resolveCanonicalReference(codeDestination, apiItem.getAssociatedPackage()); + + if (!foundItem && !resolved) { + return { + kind: DocNodeKind.LinkTag, + text: null, + }; + } + + 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, + uri: resolveItemURI(foundItem ?? resolved!.item), + resolvedPackage: { + packageName: resolved?.package ?? apiItem.getAssociatedPackage()?.displayName, + version: resolved?.package + ? apiItem.getAssociatedPackage()?.dependencies?.[resolved.package] ?? null + : null, + }, + }; + } + + if (urlDestination) { + return { + kind: DocNodeKind.LinkTag, + text: linkText ?? urlDestination, + uri: urlDestination, + }; + } + + return { + kind: DocNodeKind.LinkTag, + text: null, + }; + } + + case DocNodeKind.CodeSpan: { + const { code } = node as DocFencedCode; + + return { + kind: DocNodeKind.CodeSpan, + text: code, + }; + } + + case DocNodeKind.FencedCode: { + const { language, code } = node as DocFencedCode; + + return { + kind: DocNodeKind.FencedCode, + text: code, + language, + }; + } + + case DocNodeKind.Comment: { + const comment = node as DocComment; + + const exampleBlocks = comment.customBlocks.filter( + (block) => block.blockTag.tagName.toUpperCase() === StandardTags.example.tagNameWithUpperCase, + ); + + const defaultValueBlock = comment.customBlocks.find( + (block) => block.blockTag.tagName.toUpperCase() === StandardTags.defaultValue.tagNameWithUpperCase, + ); + + return { + kind: DocNodeKind.Comment, + deprecatedBlock: comment.deprecatedBlock ? createNode(comment.deprecatedBlock.content) : null, + summarySection: comment.summarySection ? createNode(comment.summarySection) : null, + remarksBlock: comment.remarksBlock ? createNode(comment.remarksBlock.content) : null, + defaultValueBlock: defaultValueBlock ? createNode(defaultValueBlock.content) : null, + returnsBlock: comment.returnsBlock ? createNode(comment.returnsBlock.content) : null, + exampleBlocks: exampleBlocks.map((block) => createNode(block.content)), + seeBlocks: comment.seeBlocks.map((block) => createNode(block.content)), + }; + } + + default: + return {}; + } + }; + + return item.kind === DocNodeKind.Paragraph || item.kind === DocNodeKind.Section + ? (item as DocNodeContainer).nodes.flatMap((node) => createNode(node)) + : createNode(item); +} + +function itemInfo(item: ApiDeclaredItem) { + const sourceExcerpt = item.excerpt.text.trim(); + + const isStatic = ApiStaticMixin.isBaseClassOf(item) && item.isStatic; + const isProtected = ApiProtectedMixin.isBaseClassOf(item) && item.isProtected; + const isReadonly = ApiReadonlyMixin.isBaseClassOf(item) && item.isReadonly; + const isAbstract = ApiAbstractMixin.isBaseClassOf(item) && item.isAbstract; + const isDeprecated = Boolean(item.tsdocComment?.deprecatedBlock); + + const hasSummary = Boolean(item.tsdocComment?.summarySection); + + return { + kind: item.kind, + displayName: item.displayName, + sourceURL: item.sourceLocation.fileUrl, + sourceLine: item.sourceLocation.fileLine, + sourceExcerpt, + summary: hasSummary ? itemTsDoc(item.tsdocComment!.summarySection!, item) : null, + isStatic, + isProtected, + isReadonly, + isAbstract, + isDeprecated, + }; +} + +/** + * This takes an api item with a parameter list and resolves the names and descriptions of all the parameters. + * + * @remarks + * This is different from accessing `Parameter#name` or `Parameter.tsdocBlockComment` as this method cross-references the associated tsdoc + * parameter names and descriptions and uses them as a higher precedence to the source code. + * @param item - The api item to resolve parameter data for + * @returns An array of parameters + */ +function resolveParameters(item: ApiDocumentedItem & ApiParameterListMixin) { + return item.parameters.map((param, idx) => { + const tsdocAnalog = + item.tsdocComment?.params.blocks[idx] ?? + item + .getMergedSiblings() + .find( + (paramList): paramList is ApiDocumentedItem & ApiParameterListMixin => + ApiParameterListMixin.isBaseClassOf(paramList) && paramList.overloadIndex === 1, + )?.tsdocComment?.params.blocks[idx]; + + return { + name: param.tsdocParamBlock?.parameterName ?? tsdocAnalog?.parameterName ?? param.name, + description: param.tsdocParamBlock?.content ?? tsdocAnalog?.content, + isOptional: param.isOptional, + isRest: param.isRest, + parameterTypeExcerpt: param.parameterTypeExcerpt, + }; + }); +} + +function itemTypeParameters(item: ApiTypeParameterListMixin) { + // { + // Name: typeParam.name, + // Constraints: , + // Optional: typeParam.isOptional ? 'Yes' : 'No', + // Default: , + // Description: typeParam.tsdocTypeParamBlock ? ( + // + // ) : ( + // 'None' + // ), + // } + + return item.typeParameters.map((typeParam) => ({ + name: typeParam.name, + constraintsExcerpt: itemExcerptText(typeParam.constraintExcerpt, item.getAssociatedPackage()!), + optional: typeParam.isOptional, + defaultExcerpt: itemExcerptText(typeParam.defaultTypeExcerpt, item.getAssociatedPackage()!), + description: typeParam.tsdocTypeParamBlock ? itemTsDoc(typeParam.tsdocTypeParamBlock.content, item) : null, + })); +} + +function itemParameters(item: ApiDocumentedItem & ApiParameterListMixin) { + const params = resolveParameters(item); + + // { + // Name: param.isRest ? `...${param.name}` : param.name, + // Type: , + // Optional: param.isOptional ? 'Yes' : 'No', + // Description: param.description ? : 'None', + // } + + return params.map((param) => ({ + name: param.isRest ? `...${param.name}` : param.name, + typeExcerpt: itemExcerptText(param.parameterTypeExcerpt, item.getAssociatedPackage()!), + optional: param.isOptional, + description: param.description ? itemTsDoc(param.description, item) : null, + })); +} + +function itemConstructor(item: ApiConstructor) { + return { + kind: item.kind, + name: item.displayName, + sourceURL: item.sourceLocation.fileUrl, + sourceLine: item.sourceLocation.fileLine, + summary: item.tsdocComment ? itemTsDoc(item.tsdocComment, item) : null, + parameters: itemParameters(item), + }; +} + +function isEventLike(item: ApiItem): item is ApiEvent { + return item.kind === ApiItemKind.Event; +} + +function itemEvent(item: ApiItemContainerMixin) { + const members = resolveMembers(item, isEventLike); + + return members.map((event) => { + const hasSummary = Boolean(event.item.tsdocComment?.summarySection); + + return { + ...itemInfo(event.item), + inheritedFrom: event.inherited?.displayName, + summary: hasSummary ? itemTsDoc(event.item.tsdocComment!, event.item) : null, + parameters: itemParameters(event.item), + }; + }); +} + +function isPropertyLike(item: ApiItem): item is ApiProperty | ApiPropertySignature { + return item.kind === ApiItemKind.Property || item.kind === ApiItemKind.PropertySignature; +} + +function itemProperty(item: ApiItemContainerMixin) { + const members = resolveMembers(item, isPropertyLike); + + return members.map((property) => { + const hasSummary = Boolean(property.item.tsdocComment?.summarySection); + + return { + ...itemInfo(property.item), + inheritedFrom: property.inherited?.displayName, + typeExcerpt: itemExcerptText(property.item.propertyTypeExcerpt, property.item.getAssociatedPackage()!), + summary: hasSummary ? itemTsDoc(property.item.tsdocComment!, property.item) : null, + }; + }); +} + +function parametersString(item: ApiDocumentedItem & ApiParameterListMixin) { + return resolveParameters(item).reduce((prev, cur, index) => { + if (index === 0) { + return `${prev}${cur.isRest ? '...' : ''}${cur.isOptional ? `${cur.name}?` : cur.name}`; + } + + return `${prev}, ${cur.isRest ? '...' : ''}${cur.isOptional ? `${cur.name}?` : cur.name}`; + }, ''); +} + +function isMethodLike(item: ApiItem): item is ApiMethod | ApiMethodSignature { + return ( + item.kind === ApiItemKind.Method || + (item.kind === ApiItemKind.MethodSignature && (item as ApiMethod).overloadIndex <= 1) + ); +} + +function itemMethod(item: ApiItemContainerMixin) { + const members = resolveMembers(item, isMethodLike); + + return members.map((method) => { + const parent = method.item.parent as ApiDeclaredItem; + const firstOverload = method.item + .getMergedSiblings() + .find( + (meth): meth is ApiMethod => meth.kind === ApiItemKind.Method && (meth as ApiMethod).overloadIndex === 1, + )?.tsdocComment; + + const hasSummary = Boolean(method.item.tsdocComment?.summarySection ?? firstOverload?.summarySection); + + return { + ...itemInfo(method.item), + parametersString: parametersString(method.item), + returnTypeExcerpt: itemExcerptText(method.item.returnTypeExcerpt, method.item.getAssociatedPackage()!), + inheritedFrom: parent ? method.inherited?.displayName : null, + typeParameters: itemTypeParameters(method.item), + parameters: itemParameters(method.item), + summary: hasSummary ? itemTsDoc(method.item.tsdocComment ?? firstOverload!, method.item) : null, + }; + }); +} + +function itemMembers(item: ApiDeclaredItem & ApiItemContainerMixin) { + const events = hasEvents(item) ? itemEvent(item) : []; + const properties = hasProperties(item) ? itemProperty(item) : []; + const methods = hasMethods(item) ? itemMethod(item) : []; + + return { + events, + properties, + methods, + }; +} + +export function itemHierarchyText({ + item, + type, +}: { + readonly item: ApiClass | ApiInterface; + readonly type: 'Extends' | 'Implements'; +}) { + if ( + (item.kind === ApiItemKind.Class && + (item as ApiClass).extendsType === undefined && + (item as ApiClass).implementsTypes.length === 0) || + (item.kind === ApiItemKind.Interface && !(item as ApiInterface).extendsTypes) + ) { + return null; + } + + let excerpts: Excerpt[]; + + if (item.kind === ApiItemKind.Class) { + if (type === 'Implements') { + if ((item as ApiClass).implementsTypes.length === 0) { + return null; + } + + excerpts = (item as ApiClass).implementsTypes.map((typeExcerpt) => typeExcerpt.excerpt); + } else { + if (!(item as ApiClass).extendsType) { + return null; + } + + excerpts = [(item as ApiClass).extendsType!.excerpt]; + } + } else { + if ((item as ApiInterface).extendsTypes.length === 0) { + return null; + } + + excerpts = (item as ApiInterface).extendsTypes.map((typeExcerpt) => typeExcerpt.excerpt); + } + + // return ( + //
+ // {excerpts.map((excerpt, idx) => ( + //
+ //

{type}

+ // + // + // + //
+ // ))} + //
+ // ); + + return excerpts.map((excerpt) => { + return { + type, + excerpts: itemExcerptText(excerpt, item.getAssociatedPackage()!), + }; + }); +} + +function itemClass(item: ApiClass) { + const constructor = item.members.find((member) => member.kind === ApiItemKind.Constructor) as + | ApiConstructor + | undefined; + + return { + ...itemInfo(item), + extends: itemHierarchyText({ item, type: 'Extends' }), + implements: itemHierarchyText({ item, type: 'Implements' }), + typeParameters: itemTypeParameters(item), + constructor: constructor ? itemConstructor(constructor) : null, + members: itemMembers(item), + }; +} + +function itemFunction(item: ApiFunction) { + return { + ...itemInfo(item), + typeParameters: itemTypeParameters(item), + parameters: itemParameters(item), + }; +} + +function itemInterface(item: ApiInterface) { + return { + ...itemInfo(item), + extends: itemHierarchyText({ item, type: 'Extends' }), + typeParameters: itemTypeParameters(item), + members: itemMembers(item), + }; +} + +function itemUnion(item: ApiTypeAlias) { + const union: ExcerptToken[][] = []; + let currentUnionMember: ExcerptToken[] = []; + let depth = 0; + for (const token of item.typeExcerpt.spannedTokens) { + if (token.text.includes('?')) { + return [item.typeExcerpt.spannedTokens]; + } + + depth += token.text.split('<').length - token.text.split('>').length; + + if (token.text.trim() === '|' && depth === 0) { + if (currentUnionMember.length) { + union.push(currentUnionMember); + currentUnionMember = []; + } + } else if (depth === 0 && token.kind === ExcerptTokenKind.Content && token.text.includes('|')) { + for (const [idx, tokenpart] of token.text.split('|').entries()) { + if (currentUnionMember.length && depth === 0 && idx === 0) { + currentUnionMember.push(new ExcerptToken(ExcerptTokenKind.Content, tokenpart)); + union.push(currentUnionMember); + currentUnionMember = []; + } else if (currentUnionMember.length && depth === 0) { + union.push(currentUnionMember); + currentUnionMember = [new ExcerptToken(ExcerptTokenKind.Content, tokenpart)]; + } else if (tokenpart.length) { + currentUnionMember.push(new ExcerptToken(ExcerptTokenKind.Content, tokenpart)); + } + } + } else { + currentUnionMember.push(token); + } + } + + if (currentUnionMember.length) { + union.push(currentUnionMember); + } + + return union; +} + +function itemTypeAlias(item: ApiTypeAlias) { + return { + ...itemInfo(item), + unionMembers: itemUnion(item).map((member) => + itemExcerptText(new Excerpt(member, { startIndex: 0, endIndex: member.length }), item.getAssociatedPackage()!), + ), + }; +} + +function itemVariable(item: ApiVariable) { + return { + ...itemInfo(item), + }; +} + +function itemEnumMember(item: ApiEnumMember) { + return { + ...itemInfo(item), + name: item.name, + initializerExcerpt: item.initializerExcerpt + ? itemExcerptText(item.initializerExcerpt, item.getAssociatedPackage()!) + : null, + }; +} + +function itemEnum(item: ApiEnum) { + return { + ...itemInfo(item), + members: item.members.map((member) => itemEnumMember(member)), + }; +} + +function memberKind(member: ApiItem | null) { + switch (member?.kind) { + case 'Class': { + const classMember = member as ApiClass; + return itemClass(classMember); + } + + case 'Function': { + const functionMember = member as ApiFunction; + return itemFunction(functionMember); + } + + case 'Interface': { + const interfaceMember = member as ApiInterface; + return itemInterface(interfaceMember); + } + + case 'TypeAlias': { + const typeAliasMember = member as ApiTypeAlias; + return itemTypeAlias(typeAliasMember); + } + + case 'Variable': { + const variableMember = member as ApiVariable; + return itemVariable(variableMember); + } + + case 'Enum': { + const enumMember = member as ApiEnum; + return itemEnum(enumMember); + } + + default: + return null; + } +} + +async function writeSplitDocsToFileSystem(member: Record, tag = 'main') { + const dir = 'split'; + + try { + (await stat(join(cwd(), 'docs', dir))).isDirectory(); + } catch { + await mkdir(join(cwd(), 'docs', dir)); + } + + await writeFile( + join(cwd(), 'docs', dir, `${tag}.${member.displayName}.${member.kind}.api.json`), + JSON.stringify(member), + ); +} + +export async function generateSplitDocumentation({ + fetchPackageVersions = fetchVersions, + fetchPackageVersionDocs = fetchVersionDocs, +} = {}) { + for (const pkgName of PACKAGES) { + const versions = await fetchPackageVersions(pkgName); + + for (const version of versions) { + const data = await fetchPackageVersionDocs(pkgName, version); + const model = new ApiModel(); + model.addMember(ApiPackage.loadFromJson(data)); + const pkg = model.tryGetPackageByName(pkgName); + const entry = pkg?.entryPoints[0]; + + if (!entry) { + continue; + } + + const members = entry.members.map((item) => ({ + kind: item.kind, + name: item.displayName, + href: resolveItemURI(item), + overloadIndex: 'overloadIndex' in item ? (item.overloadIndex as number) : undefined, + })); + + // await writeSplitDocsToFileSystem(members, version); + + for (const member of members) { + const item = `${member.name}:${member.kind}`; + const [memberName, overloadIndex] = decodeURIComponent(item).split(':'); + + // eslint-disable-next-line prefer-const + let { containerKey, displayName: name } = findMember(model, pkgName, memberName) ?? {}; + if (name && overloadIndex && !Number.isNaN(Number.parseInt(overloadIndex, 10))) { + containerKey = ApiFunction.getContainerKey(name, Number.parseInt(overloadIndex, 10)); + } + + const foundMember = memberName && containerKey ? findMemberByKey(model, pkgName, containerKey) ?? null : null; + + const returnValue = memberKind(foundMember); + + if (!returnValue) { + continue; + } + + await writeSplitDocsToFileSystem(returnValue, version); + } + } + } +} diff --git a/packages/scripts/src/index.ts b/packages/scripts/src/index.ts index 353651a35..67674f622 100644 --- a/packages/scripts/src/index.ts +++ b/packages/scripts/src/index.ts @@ -1 +1,2 @@ export * from './generateIndex.js'; +export * from './generateSplitDocumentation.js'; diff --git a/packages/scripts/src/shared.ts b/packages/scripts/src/shared.ts new file mode 100644 index 000000000..3926b7724 --- /dev/null +++ b/packages/scripts/src/shared.ts @@ -0,0 +1,26 @@ +import { request } from 'undici'; + +export const PACKAGES = [ + 'discord.js', + 'brokers', + 'builders', + 'collection', + 'core', + 'formatters', + 'next', + 'proxy', + 'rest', + 'util', + 'voice', + 'ws', +]; + +export async function fetchVersions(pkg: string) { + const response = await request(`https://docs.discordjs.dev/api/info?package=${pkg}`); + return response.body.json() as Promise; +} + +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(); +} diff --git a/packages/scripts/tsup.config.ts b/packages/scripts/tsup.config.ts index 034e2ebc5..04b2d1bf8 100644 --- a/packages/scripts/tsup.config.ts +++ b/packages/scripts/tsup.config.ts @@ -2,6 +2,7 @@ import { createTsupConfig } from '../../tsup.config.js'; export default [ createTsupConfig({ + entry: ['src/index.ts', 'bin/generateSplitDocumentation.ts'], minify: 'terser', }), createTsupConfig({ diff --git a/packages/util/package.json b/packages/util/package.json index 3df059609..336ca9453 100644 --- a/packages/util/package.json +++ b/packages/util/package.json @@ -10,7 +10,7 @@ "lint": "prettier --check . && cross-env TIMING=1 eslint --format=pretty src", "format": "prettier --write . && cross-env TIMING=1 eslint --fix --format=pretty src", "fmt": "pnpm run format", - "docs": "pnpm run build:docs && api-extractor run --local --minify", + "docs": "pnpm run build:docs && api-extractor run --local --minify && generate-split-documentation", "prepack": "pnpm run lint && pnpm run test && pnpm run build", "changelog": "git cliff --prepend ./CHANGELOG.md -u -c ./cliff.toml -r ../../ --include-path 'packages/util/*'", "release": "cliff-jumper" @@ -62,6 +62,7 @@ "homepage": "https://discord.js.org", "devDependencies": { "@discordjs/api-extractor": "workspace:^", + "@discordjs/scripts": "workspace:^", "@favware/cliff-jumper": "^2.2.3", "@types/node": "16.18.60", "@vitest/coverage-v8": "^1.2.2", diff --git a/packages/voice/package.json b/packages/voice/package.json index 3a7c11f1a..c8e74b1e1 100644 --- a/packages/voice/package.json +++ b/packages/voice/package.json @@ -10,7 +10,7 @@ "lint": "prettier --check . && cross-env TIMING=1 eslint --format=pretty src __tests__", "format": "prettier --write . && cross-env TIMING=1 eslint --fix --format=pretty src __tests__", "fmt": "pnpm run format", - "docs": "pnpm run build:docs && api-extractor run --local --minify", + "docs": "pnpm run build:docs && api-extractor run --local --minify && generate-split-documentation", "prepack": "pnpm run lint && pnpm run test && pnpm run build", "changelog": "git cliff --prepend ./CHANGELOG.md -u -c ./cliff.toml -r ../../ --include-path 'packages/voice/*'", "release": "cliff-jumper" @@ -73,6 +73,7 @@ "@babel/preset-env": "^7.23.9", "@babel/preset-typescript": "^7.23.3", "@discordjs/api-extractor": "workspace:^", + "@discordjs/scripts": "workspace:^", "@favware/cliff-jumper": "^2.2.3", "@types/jest": "^29.5.12", "@types/node": "16.18.60", diff --git a/packages/ws/package.json b/packages/ws/package.json index c99f486e6..ae90a2e1f 100644 --- a/packages/ws/package.json +++ b/packages/ws/package.json @@ -9,7 +9,7 @@ "build:docs": "tsc -p tsconfig.docs.json", "lint": "prettier --check . && cross-env TIMING=1 eslint --format=pretty src __tests__", "format": "prettier --write . && cross-env TIMING=1 eslint --fix --format=pretty src __tests__", - "docs": "pnpm run build:docs && api-extractor run --local --minify", + "docs": "pnpm run build:docs && api-extractor run --local --minify && generate-split-documentation", "prepack": "pnpm run build && pnpm run lint", "changelog": "git cliff --prepend ./CHANGELOG.md -u -c ./cliff.toml -r ../../ --include-path 'packages/ws/*'", "release": "cliff-jumper" @@ -84,6 +84,7 @@ }, "devDependencies": { "@discordjs/api-extractor": "workspace:^", + "@discordjs/scripts": "workspace:^", "@favware/cliff-jumper": "^2.2.3", "@types/node": "18.17.9", "@vitest/coverage-v8": "^1.2.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ffeace1b5..05a9f4791 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -619,6 +619,9 @@ importers: '@discordjs/api-extractor': specifier: workspace:^ version: link:../api-extractor + '@discordjs/scripts': + specifier: workspace:^ + version: link:../scripts '@favware/cliff-jumper': specifier: ^2.2.3 version: 2.2.3 @@ -686,6 +689,9 @@ importers: '@discordjs/api-extractor': specifier: workspace:^ version: link:../api-extractor + '@discordjs/scripts': + specifier: workspace:^ + version: link:../scripts '@favware/cliff-jumper': specifier: ^2.2.3 version: 2.2.3 @@ -734,6 +740,9 @@ importers: '@discordjs/api-extractor': specifier: workspace:^ version: link:../api-extractor + '@discordjs/scripts': + specifier: workspace:^ + version: link:../scripts '@favware/cliff-jumper': specifier: ^2.2.3 version: 2.2.3 @@ -798,6 +807,9 @@ importers: '@discordjs/api-extractor': specifier: workspace:^ version: link:../api-extractor + '@discordjs/scripts': + specifier: workspace:^ + version: link:../scripts '@favware/cliff-jumper': specifier: ^2.2.3 version: 2.2.3 @@ -953,6 +965,9 @@ importers: '@discordjs/docgen': specifier: workspace:^ version: link:../docgen + '@discordjs/scripts': + specifier: workspace:^ + version: link:../scripts '@favware/cliff-jumper': specifier: 2.2.3 version: 2.2.3 @@ -1054,6 +1069,9 @@ importers: '@discordjs/api-extractor': specifier: workspace:^ version: link:../api-extractor + '@discordjs/scripts': + specifier: workspace:^ + version: link:../scripts '@favware/cliff-jumper': specifier: ^2.2.3 version: 2.2.3 @@ -1124,6 +1142,9 @@ importers: '@discordjs/api-extractor': specifier: workspace:^ version: link:../api-extractor + '@discordjs/scripts': + specifier: workspace:^ + version: link:../scripts '@favware/cliff-jumper': specifier: ^2.2.3 version: 2.2.3 @@ -1182,6 +1203,9 @@ importers: '@discordjs/api-extractor': specifier: workspace:^ version: link:../api-extractor + '@discordjs/scripts': + specifier: workspace:^ + version: link:../scripts '@favware/cliff-jumper': specifier: ^2.2.3 version: 2.2.3 @@ -1301,6 +1325,9 @@ importers: '@discordjs/api-extractor': specifier: workspace:^ version: link:../api-extractor + '@discordjs/scripts': + specifier: workspace:^ + version: link:../scripts '@favware/cliff-jumper': specifier: ^2.2.3 version: 2.2.3 @@ -1364,6 +1391,9 @@ importers: '@vercel/postgres': specifier: ^0.7.2 version: 0.7.2 + commander: + specifier: ^11.1.0 + version: 11.1.0 tslib: specifier: ^2.6.2 version: 2.6.2 @@ -1525,6 +1555,9 @@ importers: '@discordjs/api-extractor': specifier: workspace:^ version: link:../api-extractor + '@discordjs/scripts': + specifier: workspace:^ + version: link:../scripts '@favware/cliff-jumper': specifier: ^2.2.3 version: 2.2.3 @@ -1598,6 +1631,9 @@ importers: '@discordjs/api-extractor': specifier: workspace:^ version: link:../api-extractor + '@discordjs/scripts': + specifier: workspace:^ + version: link:../scripts '@favware/cliff-jumper': specifier: ^2.2.3 version: 2.2.3 @@ -1680,6 +1716,9 @@ importers: '@discordjs/api-extractor': specifier: workspace:^ version: link:../api-extractor + '@discordjs/scripts': + specifier: workspace:^ + version: link:../scripts '@favware/cliff-jumper': specifier: ^2.2.3 version: 2.2.3 @@ -4020,7 +4059,7 @@ packages: dependencies: '@definitelytyped/typescript-versions': 0.1.0 '@definitelytyped/utils': 0.1.1 - semver: 7.6.0 + semver: 7.5.4 dev: true /@definitelytyped/typescript-packages@0.1.0: @@ -4036,7 +4075,7 @@ packages: typescript-5.1: /typescript@5.1.6 typescript-5.2: /typescript@5.2.2 typescript-5.3: /typescript@5.3.3 - typescript-5.4: /typescript@5.4.0-dev.20240205 + typescript-5.4: /typescript@5.4.0-dev.20240206 dev: true /@definitelytyped/typescript-versions@0.1.0: @@ -5591,7 +5630,7 @@ packages: nopt: 7.2.0 proc-log: 3.0.0 read-package-json-fast: 3.0.2 - semver: 7.6.0 + semver: 7.5.4 walk-up-path: 3.0.1 dev: true @@ -5599,7 +5638,7 @@ packages: resolution: {integrity: sha512-7kZUAaLscfgbwBQRbvdMYaZOWyMEcPTH/tJjnyAWJ/dvvs9Ef+CERx/qJb9GExJpl1qipaDGn7KqHnFGGixd0w==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} dependencies: - semver: 7.6.0 + semver: 7.5.4 dev: true /@npmcli/map-workspaces@3.0.4: @@ -5947,7 +5986,7 @@ packages: request: 2.88.2 retry: 0.12.0 safe-buffer: 5.2.1 - semver: 7.6.0 + semver: 7.5.4 slide: 1.1.6 ssri: 8.0.1 optionalDependencies: @@ -7736,7 +7775,7 @@ packages: pretty-hrtime: 1.0.3 prompts: 2.4.2 read-pkg-up: 7.0.1 - semver: 7.6.0 + semver: 7.5.4 telejson: 7.2.0 tiny-invariant: 1.3.1 ts-dedent: 2.2.0 @@ -9028,7 +9067,7 @@ packages: debug: 4.3.4 globby: 11.1.0 is-glob: 4.0.3 - semver: 7.6.0 + semver: 7.5.4 tsutils: 3.21.0(typescript@5.3.3) typescript: 5.3.3 transitivePeerDependencies: @@ -9050,7 +9089,7 @@ packages: globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.3 - semver: 7.6.0 + semver: 7.5.4 ts-api-utils: 1.2.0(typescript@5.3.3) typescript: 5.3.3 transitivePeerDependencies: @@ -9096,7 +9135,7 @@ packages: '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.3.3) eslint: 8.56.0 eslint-scope: 5.1.1 - semver: 7.6.0 + semver: 7.5.4 transitivePeerDependencies: - supports-color - typescript @@ -9692,7 +9731,7 @@ packages: std-env: 3.7.0 test-exclude: 6.0.0 v8-to-istanbul: 9.2.0 - vitest: 1.2.2(@types/node@18.18.8)(happy-dom@13.3.8) + vitest: 1.2.2(@types/node@18.17.9) transitivePeerDependencies: - supports-color dev: true @@ -11757,7 +11796,7 @@ packages: handlebars: 4.7.8 json-stringify-safe: 5.0.1 meow: 12.1.1 - semver: 7.6.0 + semver: 7.5.4 split2: 4.2.0 dev: true @@ -12505,7 +12544,7 @@ packages: dependencies: semver: 7.5.4 shelljs: 0.8.5 - typescript: 5.4.0-dev.20240205 + typescript: 5.4.0-dev.20240206 dev: true /dts-critic@3.3.11(typescript@5.3.3): @@ -13445,7 +13484,7 @@ packages: get-tsconfig: 4.7.2 is-glob: 4.0.3 minimatch: 3.1.2 - semver: 7.6.0 + semver: 7.5.4 transitivePeerDependencies: - '@typescript-eslint/parser' - eslint-import-resolver-typescript @@ -19045,7 +19084,7 @@ packages: dependencies: hosted-git-info: 7.0.1 is-core-module: 2.13.1 - semver: 7.6.0 + semver: 7.5.4 validate-npm-package-license: 3.0.4 dev: true @@ -19073,7 +19112,7 @@ packages: engines: {node: '>=10'} dependencies: hosted-git-info: 4.1.0 - semver: 7.6.0 + semver: 7.5.4 validate-npm-package-name: 3.0.0 dev: true @@ -23016,8 +23055,8 @@ packages: engines: {node: '>=14.17'} hasBin: true - /typescript@5.4.0-dev.20240205: - resolution: {integrity: sha512-waxaX9QSNlTxyhaeS398h73fWzOr0DOq2rV6ZQSDywcabZjxeiCv18hYDIZXRk4dT0Q0DxhbNAL/THzjKjtDlQ==} + /typescript@5.4.0-dev.20240206: + resolution: {integrity: sha512-8P1XYxDbG/AyGE5tB8+JpeiQfS5ye1BTvIVDZaHhoK9nJuCn4nkB0L66lvfwYB+46hA4rLo3vE3WkIToSYtqQA==} engines: {node: '>=14.17'} hasBin: true dev: true