mirror of
https://github.com/discordjs/discord-api-types.git
synced 2026-05-21 02:40:08 +00:00
docs: generate routes interfaces automatically on build (#1308)
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -66,3 +66,5 @@ docs/*
|
||||
|
||||
# djs repo clone
|
||||
djs
|
||||
|
||||
_generated_
|
||||
|
||||
@@ -38,3 +38,5 @@ utils/v8.ts
|
||||
v8.ts
|
||||
|
||||
.yarn/*
|
||||
djs/*
|
||||
_generated_
|
||||
|
||||
@@ -250,6 +250,8 @@ export default config([
|
||||
|
||||
'djs/**/*',
|
||||
'.yarn/*',
|
||||
|
||||
'_generated_/**/*',
|
||||
],
|
||||
},
|
||||
commonRuleset,
|
||||
|
||||
@@ -96,7 +96,8 @@
|
||||
"scripts": {
|
||||
"build:ci": "tsc --noEmit --incremental false",
|
||||
"build:deno": "node ./scripts/deno.mjs",
|
||||
"build:node": "tsc && run-p 'esm:*'",
|
||||
"build:generated": "tsx ./scripts/generate-prettier-routes-interface.ts",
|
||||
"build:node": "yarn build:generated && tsc && run-p 'esm:*'",
|
||||
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s",
|
||||
"ci:pr": "run-s changelog lint build:deno && node ./scripts/bump-website-version.mjs",
|
||||
"clean:deno": "rimraf deno/",
|
||||
@@ -113,7 +114,7 @@
|
||||
"lint": "prettier --write . && eslint --format=pretty --fix --ext mjs,ts \"{gateway,payloads,rest,rpc,voice,utils}/**/*.ts\" \"{globals,v*}.ts\" \"scripts/**/*.mjs\"",
|
||||
"postinstallDev": "is-ci || husky",
|
||||
"prepack": "run-s clean test:lint build:node",
|
||||
"postpack": "run-s clean:node build:deno",
|
||||
"postpack": "run-s clean:node build:deno && git checkout -- './deno/**/*.ts' './rest/**/*.ts'",
|
||||
"test:lint": "prettier --check . && eslint --format=pretty --ext mjs,ts \"{gateway,payloads,rest,rpc,voice,utils}/**/*.ts\" \"{globals,v*}.ts\" \"scripts/**/*.mjs\"",
|
||||
"test:types": "tsc -p tests"
|
||||
},
|
||||
@@ -126,6 +127,7 @@
|
||||
"author": "Vlad Frangu <me@vladfrangu.dev>",
|
||||
"license": "MIT",
|
||||
"files": [
|
||||
"_generated_/**/*.{js,js.map,d.ts,d.ts.map,mjs}",
|
||||
"{gateway,payloads,rest,rpc,voice,utils}/**/*.{js,js.map,d.ts,d.ts.map,mjs}",
|
||||
"{globals,v*}.{js,js.map,d.ts,d.ts.map,mjs}"
|
||||
],
|
||||
@@ -155,8 +157,9 @@
|
||||
"prettier": "^3.5.3",
|
||||
"pretty-quick": "^4.1.1",
|
||||
"rimraf": "^6.0.1",
|
||||
"ts-morph": "^26.0.0",
|
||||
"tsutils": "^3.21.0",
|
||||
"tsx": "^4.19.4",
|
||||
"tsx": "^4.20.3",
|
||||
"typescript": "^5.8.3",
|
||||
"typescript-eslint": "^8.33.0"
|
||||
},
|
||||
|
||||
232
scripts/generate-prettier-routes-interface.ts
Normal file
232
scripts/generate-prettier-routes-interface.ts
Normal file
@@ -0,0 +1,232 @@
|
||||
import { readdirSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import type {
|
||||
InterfaceDeclaration,
|
||||
ObjectLiteralExpression,
|
||||
ParameterDeclaration,
|
||||
ParameterDeclarationStructure,
|
||||
TypeParameterDeclaration,
|
||||
TypeParameterDeclarationStructure,
|
||||
} from 'ts-morph';
|
||||
import { Project, SyntaxKind } from 'ts-morph';
|
||||
|
||||
const RoutesInterfaceName = 'RoutesDeclarations';
|
||||
const CDNRoutesInterfaceName = 'CDNRoutesDeclarations';
|
||||
|
||||
const isInWebsite = __dirname.includes('website');
|
||||
|
||||
const extraPath = isInWebsite ? '../' : '';
|
||||
|
||||
const versions = readdirSync(join(__dirname, extraPath, '../rest')).filter((dir) => /^v\d+$/.exec(dir));
|
||||
|
||||
const generatedRestDirectory = join(__dirname, extraPath, '../_generated_/rest');
|
||||
const globalsFilePath = join(__dirname, extraPath, '../globals.ts');
|
||||
|
||||
function parameterDeclarationToStructure(parameter: ParameterDeclaration): ParameterDeclarationStructure {
|
||||
const obj = parameter.getStructure();
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
||||
obj.hasQuestionToken = obj.hasQuestionToken || Boolean(obj.initializer);
|
||||
|
||||
delete obj.initializer;
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
function typeParameterToStructure(typeParameter: TypeParameterDeclaration): TypeParameterDeclarationStructure {
|
||||
// eslint-disable-next-line sonarjs/prefer-immediate-return
|
||||
const obj = typeParameter.getStructure();
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
function handleTypeParameter(typeParameter: TypeParameterDeclaration, allSeen: Set<string>) {
|
||||
const extendsClause = typeParameter.getConstraint();
|
||||
|
||||
if (!extendsClause) {
|
||||
return;
|
||||
}
|
||||
|
||||
const type = extendsClause.getText();
|
||||
|
||||
if (allSeen.has(type)) {
|
||||
return;
|
||||
}
|
||||
|
||||
allSeen.add(type);
|
||||
|
||||
if (type === 'StorePageAssetFormat') {
|
||||
allSeen.add('ImageFormat');
|
||||
}
|
||||
}
|
||||
|
||||
function handleObject(object: ObjectLiteralExpression, interfaceToAddTo: InterfaceDeclaration) {
|
||||
const seenTypeParameters = new Set<string>();
|
||||
|
||||
for (const property of object.getPropertiesWithComments()) {
|
||||
const castedMethod = property.asKindOrThrow(SyntaxKind.MethodDeclaration);
|
||||
|
||||
const methodName = castedMethod.getName();
|
||||
const methodParameters = castedMethod.getParameters();
|
||||
let methodReturnType = castedMethod.getReturnType().getText();
|
||||
const methodDocs = castedMethod.getJsDocs();
|
||||
|
||||
const returnBody = castedMethod
|
||||
.getChildren()
|
||||
?.at(-1)
|
||||
?.getChildren()
|
||||
?.at(1)
|
||||
?.getChildren()
|
||||
?.at(-1)
|
||||
?.asKindOrThrow(SyntaxKind.ReturnStatement);
|
||||
|
||||
const asExpression = returnBody?.getChildrenOfKind(SyntaxKind.AsExpression)[0];
|
||||
|
||||
const unionType = asExpression?.getChildrenOfKind(SyntaxKind.UnionType)?.[0];
|
||||
|
||||
// Override with union if it exists in the cast
|
||||
if (unionType) {
|
||||
methodReturnType = unionType
|
||||
.getText()
|
||||
.split('\n')
|
||||
.map((line) => line.trim())
|
||||
.join(' ');
|
||||
}
|
||||
|
||||
if (methodReturnType.startsWith('| ')) {
|
||||
methodReturnType = methodReturnType.slice(2);
|
||||
}
|
||||
|
||||
const typeParameters = castedMethod.getTypeParameters();
|
||||
|
||||
for (const typeParameter of typeParameters) {
|
||||
handleTypeParameter(typeParameter, seenTypeParameters);
|
||||
}
|
||||
|
||||
interfaceToAddTo.addMethod({
|
||||
name: methodName,
|
||||
parameters: methodParameters.map(parameterDeclarationToStructure),
|
||||
typeParameters: typeParameters.map(typeParameterToStructure),
|
||||
returnType: methodReturnType,
|
||||
leadingTrivia:
|
||||
methodDocs
|
||||
.map((doc) => doc.getText())
|
||||
.join('\n')
|
||||
.replaceAll('\t', '') + '\n',
|
||||
});
|
||||
|
||||
for (const overload of castedMethod.getOverloads()) {
|
||||
const typeParameters = overload.getTypeParameters();
|
||||
|
||||
for (const typeParameter of typeParameters) {
|
||||
handleTypeParameter(typeParameter, seenTypeParameters);
|
||||
}
|
||||
|
||||
interfaceToAddTo.addMethod({
|
||||
name: overload.getName(),
|
||||
parameters: overload.getParameters().map(parameterDeclarationToStructure),
|
||||
typeParameters: typeParameters.map(typeParameterToStructure),
|
||||
returnType: overload.getReturnType().getText(),
|
||||
leadingTrivia:
|
||||
overload
|
||||
.getJsDocs()
|
||||
.map((doc) => doc.getText())
|
||||
.join('\n')
|
||||
.replaceAll('\t', '') + '\n',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return seenTypeParameters;
|
||||
}
|
||||
|
||||
for (const version of versions) {
|
||||
console.log(`Generating interfaces for ${version}...`);
|
||||
|
||||
const inputFilePath = join(__dirname, extraPath, `../rest/${version}/index.ts`);
|
||||
const generatedRestInterfacesFilePath = join(__dirname, extraPath, `../_generated_/rest/${version}/interfaces.ts`);
|
||||
|
||||
const project = new Project({});
|
||||
|
||||
project.addSourceFileAtPathIfExists(inputFilePath);
|
||||
project.addSourceFileAtPathIfExists(globalsFilePath);
|
||||
project.createDirectory(generatedRestDirectory);
|
||||
|
||||
const generatedRestInterfacesFile = project.createSourceFile(generatedRestInterfacesFilePath, undefined, {
|
||||
overwrite: true,
|
||||
});
|
||||
|
||||
const routesDeclarationInterface = generatedRestInterfacesFile.addInterface({
|
||||
name: RoutesInterfaceName,
|
||||
leadingTrivia: '// Automatically generated interface from the Routes object.\n',
|
||||
isExported: true,
|
||||
});
|
||||
|
||||
const cdnRoutesDeclarationInterface = generatedRestInterfacesFile.addInterface({
|
||||
name: CDNRoutesInterfaceName,
|
||||
leadingTrivia: '// Automatically generated interface from the CDN Routes object.\n',
|
||||
isExported: true,
|
||||
});
|
||||
|
||||
generatedRestInterfacesFile.addImportDeclaration({
|
||||
moduleSpecifier: '../../../globals',
|
||||
namedImports: ['Snowflake'],
|
||||
isTypeOnly: true,
|
||||
});
|
||||
|
||||
const routesObjectFile = project.getSourceFileOrThrow(inputFilePath);
|
||||
|
||||
routesObjectFile.addImportDeclaration({
|
||||
moduleSpecifier: `../../_generated_/rest/${version}/interfaces`,
|
||||
isTypeOnly: true,
|
||||
namedImports: [RoutesInterfaceName, CDNRoutesInterfaceName],
|
||||
});
|
||||
|
||||
routesObjectFile.addExportDeclaration({
|
||||
isTypeOnly: true,
|
||||
moduleSpecifier: `../../_generated_/rest/${version}/interfaces`,
|
||||
leadingTrivia: '// Exports all generated interfaces from the REST API.\n',
|
||||
});
|
||||
|
||||
const routesObject = routesObjectFile.getVariableDeclarationOrThrow('Routes');
|
||||
const cdnRoutesObject = routesObjectFile.getVariableDeclaration('CDNRoutes');
|
||||
|
||||
if (!cdnRoutesObject) {
|
||||
console.log('Skipping type generation for', version);
|
||||
continue;
|
||||
}
|
||||
|
||||
const routesObjectChildren = routesObject.getChildren();
|
||||
const cdnRoutesObjectChildren = cdnRoutesObject.getChildren();
|
||||
|
||||
const [routesIdentifier] = routesObjectChildren;
|
||||
|
||||
const routesObjectDeclaration = routesObject.getInitializerOrThrow();
|
||||
const [cdnRoutesIdentifier] = cdnRoutesObjectChildren;
|
||||
const cdnRoutesObjectDeclaration = cdnRoutesObject.getInitializerOrThrow();
|
||||
|
||||
const importsNeededForRoutes = handleObject(
|
||||
routesObjectDeclaration.asKindOrThrow(SyntaxKind.ObjectLiteralExpression),
|
||||
routesDeclarationInterface,
|
||||
);
|
||||
|
||||
const importsNeededForCDNRoutes = handleObject(
|
||||
cdnRoutesObjectDeclaration.asKindOrThrow(SyntaxKind.ObjectLiteralExpression),
|
||||
cdnRoutesDeclarationInterface,
|
||||
);
|
||||
|
||||
const typesToImportFromOriginalFile = new Set<string>([...importsNeededForRoutes, ...importsNeededForCDNRoutes]);
|
||||
|
||||
if (typesToImportFromOriginalFile.size > 0) {
|
||||
generatedRestInterfacesFile.addImportDeclaration({
|
||||
moduleSpecifier: `../../../rest/${version}/index`,
|
||||
isTypeOnly: true,
|
||||
namedImports: [...typesToImportFromOriginalFile].sort((a, b) => a.localeCompare(b)),
|
||||
});
|
||||
}
|
||||
|
||||
routesIdentifier.replaceWithText(`Routes: ${RoutesInterfaceName}`);
|
||||
cdnRoutesIdentifier.replaceWithText(`CDNRoutes: ${CDNRoutesInterfaceName}`);
|
||||
|
||||
project.saveSync();
|
||||
}
|
||||
886
website/package-lock.json
generated
886
website/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,7 @@
|
||||
"scripts": {
|
||||
"docusaurus": "docusaurus",
|
||||
"start": "npm run clean && docusaurus start",
|
||||
"build": "npm run clean && cross-env NODE_OPTIONS=\"--max_old_space_size=7500\" docusaurus build",
|
||||
"build": "npm run clean && cp ../scripts/generate-prettier-routes-interface.ts ./scripts/generate-prettier-routes-interface.ts && npx tsx ./scripts/generate-prettier-routes-interface.ts && cross-env NODE_OPTIONS=\"--max_old_space_size=7500\" docusaurus build",
|
||||
"swizzle": "docusaurus swizzle",
|
||||
"deploy": "docusaurus deploy",
|
||||
"clear": "docusaurus clear",
|
||||
@@ -47,7 +47,8 @@
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"sass": "^1.81.0",
|
||||
"swr": "^2.2.5"
|
||||
"swr": "^2.2.5",
|
||||
"ts-morph": "^26.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@apify/docusaurus-plugin-typedoc-api": "^4.3.1",
|
||||
@@ -66,6 +67,7 @@
|
||||
"patch-package": "^8.0.0",
|
||||
"prettier": "^3.4.1",
|
||||
"pretty-quick": "^4.0.0",
|
||||
"tsx": "^4.20.3",
|
||||
"typedoc": "^0.27.1",
|
||||
"typedoc-plugin-djs-links": "^2.2.1",
|
||||
"typedoc-plugin-markdown": "^4.3.0",
|
||||
|
||||
42
yarn.lock
42
yarn.lock
@@ -2535,6 +2535,17 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@ts-morph/common@npm:~0.27.0":
|
||||
version: 0.27.0
|
||||
resolution: "@ts-morph/common@npm:0.27.0"
|
||||
dependencies:
|
||||
fast-glob: "npm:^3.3.3"
|
||||
minimatch: "npm:^10.0.1"
|
||||
path-browserify: "npm:^1.0.1"
|
||||
checksum: 10c0/3daa267bd78114ff504eb064c5215da6e46589e775b781ec0da4998d999b0d7130eff287e70d6e13e0a0a897ea16e9387f4cd885b4b9d6d628f318cecb81d473
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tybys/wasm-util@npm:^0.10.0":
|
||||
version: 0.10.0
|
||||
resolution: "@tybys/wasm-util@npm:0.10.0"
|
||||
@@ -3772,6 +3783,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"code-block-writer@npm:^13.0.3":
|
||||
version: 13.0.3
|
||||
resolution: "code-block-writer@npm:13.0.3"
|
||||
checksum: 10c0/87db97b37583f71cfd7eced8bf3f0a0a0ca53af912751a734372b36c08cd27f3e8a4878ec05591c0cd9ae11bea8add1423e132d660edd86aab952656dd41fd66
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"color-convert@npm:^2.0.1":
|
||||
version: 2.0.1
|
||||
resolution: "color-convert@npm:2.0.1"
|
||||
@@ -4196,8 +4214,9 @@ __metadata:
|
||||
prettier: "npm:^3.5.3"
|
||||
pretty-quick: "npm:^4.1.1"
|
||||
rimraf: "npm:^6.0.1"
|
||||
ts-morph: "npm:^26.0.0"
|
||||
tsutils: "npm:^3.21.0"
|
||||
tsx: "npm:^4.19.4"
|
||||
tsx: "npm:^4.20.3"
|
||||
typescript: "npm:^5.8.3"
|
||||
typescript-eslint: "npm:^8.33.0"
|
||||
languageName: unknown
|
||||
@@ -7463,7 +7482,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"minimatch@npm:^10.0.3, minimatch@npm:^9.0.3 || ^10.0.1":
|
||||
"minimatch@npm:^10.0.1, minimatch@npm:^10.0.3, minimatch@npm:^9.0.3 || ^10.0.1":
|
||||
version: 10.0.3
|
||||
resolution: "minimatch@npm:10.0.3"
|
||||
dependencies:
|
||||
@@ -8103,6 +8122,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"path-browserify@npm:^1.0.1":
|
||||
version: 1.0.1
|
||||
resolution: "path-browserify@npm:1.0.1"
|
||||
checksum: 10c0/8b8c3fd5c66bd340272180590ae4ff139769e9ab79522e2eb82e3d571a89b8117c04147f65ad066dccfb42fcad902e5b7d794b3d35e0fd840491a8ddbedf8c66
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"path-exists@npm:^4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "path-exists@npm:4.0.0"
|
||||
@@ -9373,6 +9399,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ts-morph@npm:^26.0.0":
|
||||
version: 26.0.0
|
||||
resolution: "ts-morph@npm:26.0.0"
|
||||
dependencies:
|
||||
"@ts-morph/common": "npm:~0.27.0"
|
||||
code-block-writer: "npm:^13.0.3"
|
||||
checksum: 10c0/c6880d90a1eefe0ce6555bf8c11cc104b1f36f84bd36a37a82b9ae0b974f51fe6b1bc91bb0ec42550158dc1c812329d6433e1237cba64f1ef515c129b321dd5d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tslib@npm:^1.8.1":
|
||||
version: 1.14.1
|
||||
resolution: "tslib@npm:1.14.1"
|
||||
@@ -9414,7 +9450,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tsx@npm:^4.19.4":
|
||||
"tsx@npm:^4.20.3":
|
||||
version: 4.20.3
|
||||
resolution: "tsx@npm:4.20.3"
|
||||
dependencies:
|
||||
|
||||
Reference in New Issue
Block a user