mirror of
https://github.com/discordjs/discord.js.git
synced 2026-03-10 00:23:30 +01:00
feat(api-extractor): support multiple entrypoints (#10829)
* feat(api-extractor): support multiple entrypoints * chore: initial support in generateSplitDocumentation * chore: bring in line with upstream * refactor: multiple entrypoints in scripts * fix: split docs * feat: website * fix: docs failing on next * fix: don't include dtypes for now * refactor: don't fetch entrypoint if there is none --------- Co-authored-by: iCrawl <buechler.noel@outlook.com>
This commit is contained in:
@@ -66,9 +66,12 @@ export class CompilerState {
|
||||
);
|
||||
}
|
||||
|
||||
const inputFilePaths: string[] = commandLine.fileNames.concat(extractorConfig.mainEntryPointFilePath);
|
||||
const inputFilePaths: string[] = commandLine.fileNames.concat(
|
||||
extractorConfig.mainEntryPointFilePath.filePath,
|
||||
extractorConfig.additionalEntryPoints.map((ep) => ep.filePath),
|
||||
);
|
||||
if (options?.additionalEntryPoints) {
|
||||
inputFilePaths.push(...options.additionalEntryPoints);
|
||||
inputFilePaths.push(...options.additionalEntryPoints.map((entryPoint) => entryPoint));
|
||||
}
|
||||
|
||||
// Append the entry points and remove any non-declaration files from the list
|
||||
|
||||
@@ -70,6 +70,11 @@ export const enum ConsoleMessageId {
|
||||
*/
|
||||
UsingCustomTSDocConfig = 'console-using-custom-tsdoc-config',
|
||||
|
||||
/**
|
||||
* "Generating ___ API report: ___"
|
||||
*/
|
||||
WritingApiReport = 'console-writing-api-report',
|
||||
|
||||
/**
|
||||
* "Writing: ___"
|
||||
*/
|
||||
|
||||
@@ -26,7 +26,7 @@ import { ApiReportGenerator } from '../generators/ApiReportGenerator.js';
|
||||
import { DtsRollupGenerator, DtsRollupKind } from '../generators/DtsRollupGenerator.js';
|
||||
import { CompilerState } from './CompilerState.js';
|
||||
import { ConsoleMessageId } from './ConsoleMessageId.js';
|
||||
import { ExtractorConfig } from './ExtractorConfig.js';
|
||||
import { ExtractorConfig, type IExtractorConfigApiReport } from './ExtractorConfig.js';
|
||||
import type { ExtractorMessage } from './ExtractorMessage.js';
|
||||
|
||||
/**
|
||||
@@ -263,14 +263,14 @@ export class Extractor {
|
||||
DocCommentEnhancer.analyze(collector);
|
||||
ValidationEnhancer.analyze(collector);
|
||||
|
||||
const modelBuilder: ApiModelGenerator = new ApiModelGenerator(collector);
|
||||
const modelBuilder: ApiModelGenerator = new ApiModelGenerator(collector, extractorConfig);
|
||||
const apiPackage: ApiPackage = modelBuilder.buildApiPackage();
|
||||
|
||||
if (messageRouter.showDiagnostics) {
|
||||
messageRouter.logDiagnostic(''); // skip a line after any diagnostic messages
|
||||
}
|
||||
|
||||
if (extractorConfig.docModelEnabled) {
|
||||
if (modelBuilder.docModelEnabled) {
|
||||
messageRouter.logVerbose(ConsoleMessageId.WritingDocModelFile, 'Writing: ' + extractorConfig.apiJsonFilePath);
|
||||
apiPackage.saveToJsonFile(extractorConfig.apiJsonFilePath, {
|
||||
toolPackage: Extractor.packageName,
|
||||
@@ -282,93 +282,22 @@ export class Extractor {
|
||||
});
|
||||
}
|
||||
|
||||
let apiReportChanged = false;
|
||||
function writeApiReport(reportConfig: IExtractorConfigApiReport): boolean {
|
||||
return Extractor._writeApiReport(
|
||||
collector,
|
||||
extractorConfig,
|
||||
messageRouter,
|
||||
extractorConfig.reportTempFolder,
|
||||
extractorConfig.reportFolder,
|
||||
reportConfig,
|
||||
localBuild,
|
||||
);
|
||||
}
|
||||
|
||||
let anyReportChanged = false;
|
||||
if (extractorConfig.apiReportEnabled) {
|
||||
const actualApiReportPath: string = extractorConfig.reportTempFilePath;
|
||||
const actualApiReportShortPath: string = extractorConfig._getShortFilePath(extractorConfig.reportTempFilePath);
|
||||
|
||||
const expectedApiReportPath: string = extractorConfig.reportFilePath;
|
||||
const expectedApiReportShortPath: string = extractorConfig._getShortFilePath(extractorConfig.reportFilePath);
|
||||
|
||||
const actualApiReportContent: string = ApiReportGenerator.generateReviewFileContent(collector);
|
||||
|
||||
// Write the actual file
|
||||
FileSystem.writeFile(actualApiReportPath, actualApiReportContent, {
|
||||
ensureFolderExists: true,
|
||||
convertLineEndings: extractorConfig.newlineKind,
|
||||
});
|
||||
|
||||
// Compare it against the expected file
|
||||
if (FileSystem.exists(expectedApiReportPath)) {
|
||||
const expectedApiReportContent: string = FileSystem.readFile(expectedApiReportPath);
|
||||
|
||||
if (ApiReportGenerator.areEquivalentApiFileContents(actualApiReportContent, expectedApiReportContent)) {
|
||||
messageRouter.logVerbose(
|
||||
ConsoleMessageId.ApiReportUnchanged,
|
||||
`The API report is up to date: ${actualApiReportShortPath}`,
|
||||
);
|
||||
} else {
|
||||
apiReportChanged = true;
|
||||
|
||||
if (localBuild) {
|
||||
// For a local build, just copy the file automatically.
|
||||
messageRouter.logWarning(
|
||||
ConsoleMessageId.ApiReportCopied,
|
||||
`You have changed the public API signature for this project. Updating ${expectedApiReportShortPath}`,
|
||||
);
|
||||
|
||||
FileSystem.writeFile(expectedApiReportPath, actualApiReportContent, {
|
||||
ensureFolderExists: true,
|
||||
convertLineEndings: extractorConfig.newlineKind,
|
||||
});
|
||||
} else {
|
||||
// For a production build, issue a warning that will break the CI build.
|
||||
messageRouter.logWarning(
|
||||
ConsoleMessageId.ApiReportNotCopied,
|
||||
'You have changed the public API signature for this project.' +
|
||||
` Please copy the file "${actualApiReportShortPath}" to "${expectedApiReportShortPath}",` +
|
||||
` or perform a local build (which does this automatically).` +
|
||||
` See the Git repo documentation for more info.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// The target file does not exist, so we are setting up the API review file for the first time.
|
||||
//
|
||||
// NOTE: People sometimes make a mistake where they move a project and forget to update the "reportFolder"
|
||||
// setting, which causes a new file to silently get written to the wrong place. This can be confusing.
|
||||
// Thus we treat the initial creation of the file specially.
|
||||
apiReportChanged = true;
|
||||
|
||||
if (localBuild) {
|
||||
const expectedApiReportFolder: string = path.dirname(expectedApiReportPath);
|
||||
if (FileSystem.exists(expectedApiReportFolder)) {
|
||||
FileSystem.writeFile(expectedApiReportPath, actualApiReportContent, {
|
||||
convertLineEndings: extractorConfig.newlineKind,
|
||||
});
|
||||
messageRouter.logWarning(
|
||||
ConsoleMessageId.ApiReportCreated,
|
||||
'The API report file was missing, so a new file was created. Please add this file to Git:\n' +
|
||||
expectedApiReportPath,
|
||||
);
|
||||
} else {
|
||||
messageRouter.logError(
|
||||
ConsoleMessageId.ApiReportFolderMissing,
|
||||
'Unable to create the API report file. Please make sure the target folder exists:\n' +
|
||||
expectedApiReportFolder,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// For a production build, issue a warning that will break the CI build.
|
||||
messageRouter.logWarning(
|
||||
ConsoleMessageId.ApiReportNotCopied,
|
||||
'The API report file is missing.' +
|
||||
` Please copy the file "${actualApiReportShortPath}" to "${expectedApiReportShortPath}",` +
|
||||
` or perform a local build (which does this automatically).` +
|
||||
` See the Git repo documentation for more info.`,
|
||||
);
|
||||
}
|
||||
for (const reportConfig of extractorConfig.reportConfigs) {
|
||||
anyReportChanged = writeApiReport(reportConfig) || anyReportChanged;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -421,12 +350,151 @@ export class Extractor {
|
||||
compilerState,
|
||||
extractorConfig,
|
||||
succeeded,
|
||||
apiReportChanged,
|
||||
apiReportChanged: anyReportChanged,
|
||||
errorCount: messageRouter.errorCount,
|
||||
warningCount: messageRouter.warningCount,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the API report at the specified release level, writes it to the specified file path, and compares
|
||||
* the output to the existing report (if one exists).
|
||||
*
|
||||
* @param collector - The collector to get the entities from.
|
||||
* @param extractorConfig - The configuration for extracting.
|
||||
* @param messageRouter - The message router to use.
|
||||
* @param reportTempDirectoryPath - The path to the directory under which the temp report file will be written prior
|
||||
* to comparison with an existing report.
|
||||
* @param reportDirectoryPath - The path to the directory under which the existing report file is located, and to
|
||||
* which the new report will be written post-comparison.
|
||||
* @param reportConfig - API report configuration, including its file name and {@link ApiReportVariant}.
|
||||
* @param localBuild - Whether the report is made locally.
|
||||
* @returns Whether or not the newly generated report differs from the existing report (if one exists).
|
||||
*/
|
||||
private static _writeApiReport(
|
||||
collector: Collector,
|
||||
extractorConfig: ExtractorConfig,
|
||||
messageRouter: MessageRouter,
|
||||
reportTempDirectoryPath: string,
|
||||
reportDirectoryPath: string,
|
||||
reportConfig: IExtractorConfigApiReport,
|
||||
localBuild: boolean,
|
||||
): boolean {
|
||||
let apiReportChanged = false;
|
||||
|
||||
const actualApiReportPathWithoutExtension: string = path
|
||||
.resolve(reportTempDirectoryPath, reportConfig.fileName)
|
||||
.replace(/\.api\.md$/, '');
|
||||
|
||||
const expectedApiReportPathWithoutExtension: string = path
|
||||
.resolve(reportDirectoryPath, reportConfig.fileName)
|
||||
.replace(/\.api\.md$/, '');
|
||||
|
||||
const actualApiReportContentMap: Map<string, string> = ApiReportGenerator.generateReviewFileContent(
|
||||
collector,
|
||||
reportConfig.variant,
|
||||
);
|
||||
|
||||
for (const [modulePath, actualApiReportContent] of actualApiReportContentMap) {
|
||||
const actualEntryPointApiReportPath = `${actualApiReportPathWithoutExtension}${
|
||||
modulePath ? '.' : ''
|
||||
}${modulePath}.api.md`;
|
||||
const actualEntryPointApiReportShortPath: string =
|
||||
extractorConfig._getShortFilePath(actualEntryPointApiReportPath);
|
||||
const expectedEntryPointApiReportPath = `${expectedApiReportPathWithoutExtension}${
|
||||
modulePath ? '.' : ''
|
||||
}${modulePath}.api.md`;
|
||||
const expectedEntryPointApiReportShortPath: string = extractorConfig._getShortFilePath(
|
||||
expectedEntryPointApiReportPath,
|
||||
);
|
||||
|
||||
collector.messageRouter.logVerbose(
|
||||
ConsoleMessageId.WritingApiReport,
|
||||
`Generating ${reportConfig.variant} API report: ${expectedEntryPointApiReportPath}`,
|
||||
);
|
||||
|
||||
// Write the actual file
|
||||
FileSystem.writeFile(actualEntryPointApiReportPath, actualApiReportContent, {
|
||||
ensureFolderExists: true,
|
||||
convertLineEndings: extractorConfig.newlineKind,
|
||||
});
|
||||
|
||||
// Compare it against the expected file
|
||||
if (FileSystem.exists(expectedEntryPointApiReportPath)) {
|
||||
const expectedApiReportContent: string = FileSystem.readFile(expectedEntryPointApiReportPath);
|
||||
|
||||
if (ApiReportGenerator.areEquivalentApiFileContents(actualApiReportContent, expectedApiReportContent)) {
|
||||
messageRouter.logVerbose(
|
||||
ConsoleMessageId.ApiReportUnchanged,
|
||||
`The API report is up to date: ${actualEntryPointApiReportShortPath}`,
|
||||
);
|
||||
} else {
|
||||
apiReportChanged = true;
|
||||
|
||||
if (localBuild) {
|
||||
// For a local build, just copy the file automatically.
|
||||
messageRouter.logWarning(
|
||||
ConsoleMessageId.ApiReportCopied,
|
||||
`You have changed the API signature for this project. Updating ${actualEntryPointApiReportShortPath}`,
|
||||
);
|
||||
|
||||
FileSystem.writeFile(actualEntryPointApiReportPath, actualApiReportContent, {
|
||||
ensureFolderExists: true,
|
||||
convertLineEndings: extractorConfig.newlineKind,
|
||||
});
|
||||
} else {
|
||||
// For a production build, issue a warning that will break the CI build.
|
||||
messageRouter.logWarning(
|
||||
ConsoleMessageId.ApiReportNotCopied,
|
||||
'You have changed the API signature for this project.' +
|
||||
` Please copy the file "${actualEntryPointApiReportShortPath}" to "${expectedEntryPointApiReportShortPath}",` +
|
||||
` or perform a local build (which does this automatically).` +
|
||||
` See the Git repo documentation for more info.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// The target file does not exist, so we are setting up the API review file for the first time.
|
||||
//
|
||||
// NOTE: People sometimes make a mistake where they move a project and forget to update the "reportFolder"
|
||||
// setting, which causes a new file to silently get written to the wrong place. This can be confusing.
|
||||
// Thus we treat the initial creation of the file specially.
|
||||
apiReportChanged = true;
|
||||
|
||||
if (localBuild) {
|
||||
const expectedApiReportFolder: string = path.dirname(expectedEntryPointApiReportPath);
|
||||
if (FileSystem.exists(expectedApiReportFolder)) {
|
||||
FileSystem.writeFile(expectedEntryPointApiReportPath, actualApiReportContent, {
|
||||
convertLineEndings: extractorConfig.newlineKind,
|
||||
});
|
||||
messageRouter.logWarning(
|
||||
ConsoleMessageId.ApiReportCreated,
|
||||
'The API report file was missing, so a new file was created. Please add this file to Git:\n' +
|
||||
expectedEntryPointApiReportPath,
|
||||
);
|
||||
} else {
|
||||
messageRouter.logError(
|
||||
ConsoleMessageId.ApiReportFolderMissing,
|
||||
'Unable to create the API report file. Please make sure the target folder exists:\n' +
|
||||
expectedApiReportFolder,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// For a production build, issue a warning that will break the CI build.
|
||||
messageRouter.logWarning(
|
||||
ConsoleMessageId.ApiReportNotCopied,
|
||||
'The API report file is missing.' +
|
||||
` Please copy the file "${actualEntryPointApiReportShortPath}" to "${expectedEntryPointApiReportShortPath}",` +
|
||||
` or perform a local build (which does this automatically).` +
|
||||
` See the Git repo documentation for more info.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return apiReportChanged;
|
||||
}
|
||||
|
||||
private static _checkCompilerCompatibility(extractorConfig: ExtractorConfig, messageRouter: MessageRouter): void {
|
||||
messageRouter.logInfo(ConsoleMessageId.Preamble, `Analysis will use the bundled TypeScript version ${ts.version}`);
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
// See LICENSE in the project root for license information.
|
||||
|
||||
import * as path from 'node:path';
|
||||
import { EnumMemberOrder } from '@discordjs/api-extractor-model';
|
||||
import { TSDocConfiguration } from '@microsoft/tsdoc';
|
||||
import { EnumMemberOrder, ReleaseTag } from '@discordjs/api-extractor-model';
|
||||
import { TSDocConfiguration, TSDocTagDefinition } from '@microsoft/tsdoc';
|
||||
import { TSDocConfigFile } from '@microsoft/tsdoc-config';
|
||||
import {
|
||||
JsonFile,
|
||||
@@ -17,14 +17,21 @@ import {
|
||||
Path,
|
||||
NewlineKind,
|
||||
} from '@rushstack/node-core-library';
|
||||
import { type IRigConfig, RigConfig } from '@rushstack/rig-package';
|
||||
import type { MergeWithCustomizer } from 'lodash';
|
||||
import cloneDeep from 'lodash/cloneDeep.js';
|
||||
import merge from 'lodash/merge.js';
|
||||
import mergeWith from 'lodash/mergeWith.js';
|
||||
import * as resolve from 'resolve';
|
||||
import { PackageMetadataManager } from '../analyzer/PackageMetadataManager.js';
|
||||
import { MessageRouter } from '../collector/MessageRouter.js';
|
||||
import type { IApiModelGenerationOptions } from '../generators/ApiModelGenerator';
|
||||
import apiExtractorSchema from '../schemas/api-extractor.schema.json' assert { type: 'json' };
|
||||
import type { IConfigFile, IExtractorMessagesConfig } from './IConfigFile.js';
|
||||
import type {
|
||||
ApiReportVariant,
|
||||
IConfigApiReport,
|
||||
IConfigFile,
|
||||
IConfigEntryPoint,
|
||||
IExtractorMessagesConfig,
|
||||
} from './IConfigFile.js';
|
||||
|
||||
/**
|
||||
* Tokens used during variable expansion of path fields from api-extractor.json.
|
||||
@@ -66,8 +73,10 @@ export interface IExtractorConfigLoadForFolderOptions {
|
||||
|
||||
/**
|
||||
* An already constructed `RigConfig` object. If omitted, then a new `RigConfig` object will be constructed.
|
||||
*
|
||||
* @deprecated this is unsupported in the discord.js version
|
||||
*/
|
||||
rigConfig?: IRigConfig;
|
||||
rigConfig?: never;
|
||||
|
||||
/**
|
||||
* The folder path to start from when searching for api-extractor.json.
|
||||
@@ -147,14 +156,56 @@ export interface IExtractorConfigPrepareOptions {
|
||||
tsdocConfigFile?: TSDocConfigFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration for a single API report, including its {@link IExtractorConfigApiReport.variant}.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface IExtractorConfigApiReport {
|
||||
/**
|
||||
* Name of the output report file.
|
||||
*
|
||||
* @remarks Relative to the configured report directory path.
|
||||
*/
|
||||
fileName: string;
|
||||
|
||||
/**
|
||||
* Report variant.
|
||||
* Determines which API items will be included in the report output, based on their tagged release levels.
|
||||
*/
|
||||
variant: ApiReportVariant;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default {@link IConfigApiReport.reportVariants}
|
||||
*/
|
||||
const defaultApiReportVariants: readonly ApiReportVariant[] = ['complete'];
|
||||
|
||||
/**
|
||||
* Default {@link IConfigApiReport.tagsToReport}.
|
||||
*
|
||||
* @remarks
|
||||
* Note that this list is externally documented, and directly affects report output.
|
||||
* Also note that the order of tags in this list is significant, as it determines the order of tags in the report.
|
||||
* Any changes to this list should be considered breaking.
|
||||
*/
|
||||
const defaultTagsToReport: Readonly<Record<`@${string}`, boolean>> = {
|
||||
'@sealed': true,
|
||||
'@virtual': true,
|
||||
'@override': true,
|
||||
'@eventProperty': true,
|
||||
'@deprecated': true,
|
||||
};
|
||||
|
||||
interface IExtractorConfigParameters {
|
||||
additionalEntryPoints: IConfigEntryPoint[];
|
||||
alphaTrimmedFilePath: string;
|
||||
apiJsonFilePath: string;
|
||||
apiReportEnabled: boolean;
|
||||
apiReportIncludeForgottenExports: boolean;
|
||||
betaTrimmedFilePath: string;
|
||||
bundledPackages: string[];
|
||||
docModelEnabled: boolean;
|
||||
docModelGenerationOptions: IApiModelGenerationOptions | undefined;
|
||||
docModelIncludeForgottenExports: boolean;
|
||||
enumMemberOrder: EnumMemberOrder;
|
||||
mainEntryPointFilePath: string;
|
||||
@@ -167,10 +218,12 @@ interface IExtractorConfigParameters {
|
||||
projectFolder: string;
|
||||
projectFolderUrl: string | undefined;
|
||||
publicTrimmedFilePath: string;
|
||||
reportFilePath: string;
|
||||
reportTempFilePath: string;
|
||||
reportConfigs: readonly IExtractorConfigApiReport[];
|
||||
reportFolder: string;
|
||||
reportTempFolder: string;
|
||||
rollupEnabled: boolean;
|
||||
skipLibCheck: boolean;
|
||||
tagsToReport: Readonly<Record<`@${string}`, boolean>>;
|
||||
testMode: boolean;
|
||||
tsconfigFilePath: string;
|
||||
tsdocConfigFile: TSDocConfigFile;
|
||||
@@ -180,9 +233,22 @@ interface IExtractorConfigParameters {
|
||||
untrimmedFilePath: string;
|
||||
}
|
||||
|
||||
// Lodash merges array values by default, which is unintuitive for config files (and makes it impossible for derived configurations to overwrite arrays).
|
||||
// For example, given a base config containing an array property with value ["foo", "bar"] and a derived config that specifies ["baz"] for that property, lodash will produce ["baz", "bar"], which is unintuitive.
|
||||
// This customizer function ensures that arrays are always overwritten.
|
||||
const mergeCustomizer: MergeWithCustomizer = (_objValue, srcValue) => {
|
||||
if (Array.isArray(srcValue)) {
|
||||
return srcValue;
|
||||
}
|
||||
|
||||
// Fall back to default merge behavior.
|
||||
return undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* The `ExtractorConfig` class loads, validates, interprets, and represents the api-extractor.json config file.
|
||||
*
|
||||
* @sealed
|
||||
* @public
|
||||
*/
|
||||
export class ExtractorConfig {
|
||||
@@ -230,7 +296,12 @@ export class ExtractorConfig {
|
||||
/**
|
||||
* {@inheritDoc IConfigFile.mainEntryPointFilePath}
|
||||
*/
|
||||
public readonly mainEntryPointFilePath: string;
|
||||
public readonly mainEntryPointFilePath: IConfigEntryPoint;
|
||||
|
||||
/**
|
||||
* {@inheritDoc IConfigFile.additionalEntryPoints}
|
||||
*/
|
||||
public readonly additionalEntryPoints: IConfigEntryPoint[];
|
||||
|
||||
/**
|
||||
* {@inheritDoc IConfigFile.bundledPackages}
|
||||
@@ -258,14 +329,48 @@ export class ExtractorConfig {
|
||||
public readonly apiReportEnabled: boolean;
|
||||
|
||||
/**
|
||||
* The `reportFolder` path combined with the `reportFileName`.
|
||||
* List of configurations for report files to be generated.
|
||||
*
|
||||
* @remarks Derived from {@link IConfigApiReport.reportFileName} and {@link IConfigApiReport.reportVariants}.
|
||||
*/
|
||||
public readonly reportFilePath: string;
|
||||
public readonly reportConfigs: readonly IExtractorConfigApiReport[];
|
||||
|
||||
/**
|
||||
* The `reportTempFolder` path combined with the `reportFileName`.
|
||||
* {@inheritDoc IConfigApiReport.reportFolder}
|
||||
*/
|
||||
public readonly reportTempFilePath: string;
|
||||
public readonly reportFolder: string;
|
||||
|
||||
/**
|
||||
* {@inheritDoc IConfigApiReport.reportTempFolder}
|
||||
*/
|
||||
public readonly reportTempFolder: string;
|
||||
|
||||
/**
|
||||
* {@inheritDoc IConfigApiReport.tagsToReport}
|
||||
*/
|
||||
public readonly tagsToReport: Readonly<Record<`@${string}`, boolean>>;
|
||||
|
||||
/**
|
||||
* Gets the file path for the "complete" (default) report configuration, if one was specified.
|
||||
* Otherwise, returns an empty string.
|
||||
*
|
||||
* @deprecated Use {@link ExtractorConfig.reportConfigs} to access all report configurations.
|
||||
*/
|
||||
public get reportFilePath(): string {
|
||||
const completeConfig: IExtractorConfigApiReport | undefined = this._getCompleteReportConfig();
|
||||
return completeConfig === undefined ? '' : path.join(this.reportFolder, completeConfig.fileName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the temp file path for the "complete" (default) report configuration, if one was specified.
|
||||
* Otherwise, returns an empty string.
|
||||
*
|
||||
* @deprecated Use {@link ExtractorConfig.reportConfigs} to access all report configurations.
|
||||
*/
|
||||
public get reportTempFilePath(): string {
|
||||
const completeConfig: IExtractorConfigApiReport | undefined = this._getCompleteReportConfig();
|
||||
return completeConfig === undefined ? '' : path.join(this.reportTempFolder, completeConfig.fileName);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc IConfigApiReport.includeForgottenExports}
|
||||
@@ -273,9 +378,11 @@ export class ExtractorConfig {
|
||||
public readonly apiReportIncludeForgottenExports: boolean;
|
||||
|
||||
/**
|
||||
* {@inheritDoc IConfigDocModel.enabled}
|
||||
* If specified, the doc model is enabled and the specified options will be used.
|
||||
*
|
||||
* @beta
|
||||
*/
|
||||
public readonly docModelEnabled: boolean;
|
||||
public readonly docModelGenerationOptions: IApiModelGenerationOptions | undefined;
|
||||
|
||||
/**
|
||||
* {@inheritDoc IConfigDocModel.apiJsonFilePath}
|
||||
@@ -363,37 +470,77 @@ export class ExtractorConfig {
|
||||
*/
|
||||
public readonly enumMemberOrder: EnumMemberOrder;
|
||||
|
||||
private constructor(parameters: IExtractorConfigParameters) {
|
||||
this.projectFolder = parameters.projectFolder;
|
||||
this.packageJson = parameters.packageJson;
|
||||
this.packageFolder = parameters.packageFolder;
|
||||
this.mainEntryPointFilePath = parameters.mainEntryPointFilePath;
|
||||
this.bundledPackages = parameters.bundledPackages;
|
||||
this.tsconfigFilePath = parameters.tsconfigFilePath;
|
||||
this.overrideTsconfig = parameters.overrideTsconfig;
|
||||
this.skipLibCheck = parameters.skipLibCheck;
|
||||
this.apiReportEnabled = parameters.apiReportEnabled;
|
||||
this.reportFilePath = parameters.reportFilePath;
|
||||
this.reportTempFilePath = parameters.reportTempFilePath;
|
||||
this.apiReportIncludeForgottenExports = parameters.apiReportIncludeForgottenExports;
|
||||
this.docModelEnabled = parameters.docModelEnabled;
|
||||
this.apiJsonFilePath = parameters.apiJsonFilePath;
|
||||
this.docModelIncludeForgottenExports = parameters.docModelIncludeForgottenExports;
|
||||
this.projectFolderUrl = parameters.projectFolderUrl;
|
||||
this.rollupEnabled = parameters.rollupEnabled;
|
||||
this.untrimmedFilePath = parameters.untrimmedFilePath;
|
||||
this.alphaTrimmedFilePath = parameters.alphaTrimmedFilePath;
|
||||
this.betaTrimmedFilePath = parameters.betaTrimmedFilePath;
|
||||
this.publicTrimmedFilePath = parameters.publicTrimmedFilePath;
|
||||
this.omitTrimmingComments = parameters.omitTrimmingComments;
|
||||
this.tsdocMetadataEnabled = parameters.tsdocMetadataEnabled;
|
||||
this.tsdocMetadataFilePath = parameters.tsdocMetadataFilePath;
|
||||
this.tsdocConfigFile = parameters.tsdocConfigFile;
|
||||
this.tsdocConfiguration = parameters.tsdocConfiguration;
|
||||
this.newlineKind = parameters.newlineKind;
|
||||
this.messages = parameters.messages;
|
||||
this.testMode = parameters.testMode;
|
||||
this.enumMemberOrder = parameters.enumMemberOrder;
|
||||
private constructor({
|
||||
projectFolder,
|
||||
packageJson,
|
||||
packageFolder,
|
||||
mainEntryPointFilePath,
|
||||
additionalEntryPoints,
|
||||
bundledPackages,
|
||||
tsconfigFilePath,
|
||||
overrideTsconfig,
|
||||
skipLibCheck,
|
||||
apiReportEnabled,
|
||||
apiReportIncludeForgottenExports,
|
||||
reportConfigs,
|
||||
reportFolder,
|
||||
reportTempFolder,
|
||||
tagsToReport,
|
||||
docModelGenerationOptions,
|
||||
apiJsonFilePath,
|
||||
docModelIncludeForgottenExports,
|
||||
projectFolderUrl,
|
||||
rollupEnabled,
|
||||
untrimmedFilePath,
|
||||
alphaTrimmedFilePath,
|
||||
betaTrimmedFilePath,
|
||||
publicTrimmedFilePath,
|
||||
omitTrimmingComments,
|
||||
tsdocMetadataEnabled,
|
||||
tsdocMetadataFilePath,
|
||||
tsdocConfigFile,
|
||||
tsdocConfiguration,
|
||||
newlineKind,
|
||||
messages,
|
||||
testMode,
|
||||
enumMemberOrder,
|
||||
}: IExtractorConfigParameters) {
|
||||
this.projectFolder = projectFolder;
|
||||
this.packageJson = packageJson;
|
||||
this.packageFolder = packageFolder;
|
||||
this.mainEntryPointFilePath = {
|
||||
modulePath: '',
|
||||
filePath: mainEntryPointFilePath,
|
||||
};
|
||||
this.additionalEntryPoints = additionalEntryPoints;
|
||||
this.bundledPackages = bundledPackages;
|
||||
this.tsconfigFilePath = tsconfigFilePath;
|
||||
this.overrideTsconfig = overrideTsconfig;
|
||||
this.skipLibCheck = skipLibCheck;
|
||||
this.apiReportEnabled = apiReportEnabled;
|
||||
this.reportConfigs = reportConfigs;
|
||||
this.reportFolder = reportFolder;
|
||||
this.reportTempFolder = reportTempFolder;
|
||||
this.tagsToReport = tagsToReport;
|
||||
this.apiReportIncludeForgottenExports = apiReportIncludeForgottenExports;
|
||||
this.docModelGenerationOptions = docModelGenerationOptions;
|
||||
this.apiJsonFilePath = apiJsonFilePath;
|
||||
this.docModelIncludeForgottenExports = docModelIncludeForgottenExports;
|
||||
this.projectFolderUrl = projectFolderUrl;
|
||||
this.rollupEnabled = rollupEnabled;
|
||||
this.untrimmedFilePath = untrimmedFilePath;
|
||||
this.alphaTrimmedFilePath = alphaTrimmedFilePath;
|
||||
this.betaTrimmedFilePath = betaTrimmedFilePath;
|
||||
this.publicTrimmedFilePath = publicTrimmedFilePath;
|
||||
this.omitTrimmingComments = omitTrimmingComments;
|
||||
this.tsdocMetadataEnabled = tsdocMetadataEnabled;
|
||||
this.tsdocMetadataFilePath = tsdocMetadataFilePath;
|
||||
this.tsdocConfigFile = tsdocConfigFile;
|
||||
this.tsdocConfiguration = tsdocConfiguration;
|
||||
this.newlineKind = newlineKind;
|
||||
this.messages = messages;
|
||||
this.testMode = testMode;
|
||||
this.enumMemberOrder = enumMemberOrder;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -480,44 +627,8 @@ export class ExtractorConfig {
|
||||
configFilename = path.join(baseFolder, ExtractorConfig.FILENAME);
|
||||
|
||||
if (!FileSystem.exists(configFilename)) {
|
||||
// If We didn't find it in <packageFolder>/api-extractor.json or <packageFolder>/config/api-extractor.json
|
||||
// then check for a rig package
|
||||
if (packageFolder) {
|
||||
let rigConfig: IRigConfig;
|
||||
if (options.rigConfig) {
|
||||
// The caller provided an already solved RigConfig. Double-check that it is for the right project.
|
||||
if (!Path.isEqual(options.rigConfig.projectFolderPath, packageFolder)) {
|
||||
throw new Error(
|
||||
'The provided ILoadForFolderOptions.rigConfig is for the wrong project folder:\n' +
|
||||
'\nExpected path: ' +
|
||||
packageFolder +
|
||||
'\nProvided path: ' +
|
||||
options.rigConfig.projectFolderOriginalPath,
|
||||
);
|
||||
}
|
||||
|
||||
rigConfig = options.rigConfig;
|
||||
} else {
|
||||
rigConfig = RigConfig.loadForProjectFolder({
|
||||
projectFolderPath: packageFolder,
|
||||
});
|
||||
}
|
||||
|
||||
if (rigConfig.rigFound) {
|
||||
configFilename = path.join(rigConfig.getResolvedProfileFolder(), ExtractorConfig.FILENAME);
|
||||
|
||||
// If the "projectFolder" setting isn't specified in api-extractor.json, it defaults to the
|
||||
// "<lookup>" token which will probe for the tsconfig.json nearest to the api-extractor.json path.
|
||||
// But this won't work if api-extractor.json belongs to the rig. So instead "<lookup>" should be
|
||||
// the "<packageFolder>" that referenced the rig.
|
||||
projectFolderLookupToken = packageFolder;
|
||||
}
|
||||
}
|
||||
|
||||
if (!FileSystem.exists(configFilename)) {
|
||||
// API Extractor does not seem to be configured for this folder
|
||||
return undefined;
|
||||
}
|
||||
// API Extractor does not seem to be configured for this folder
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -618,7 +729,7 @@ export class ExtractorConfig {
|
||||
ExtractorConfig._resolveConfigFileRelativePaths(baseConfig, currentConfigFolderPath);
|
||||
|
||||
// Merge extractorConfig into baseConfig, mutating baseConfig
|
||||
merge(baseConfig, configObject);
|
||||
mergeWith(baseConfig, configObject, mergeCustomizer);
|
||||
configObject = baseConfig;
|
||||
|
||||
currentConfigFilePath = extendsField;
|
||||
@@ -628,7 +739,7 @@ export class ExtractorConfig {
|
||||
}
|
||||
|
||||
// Lastly, apply the defaults
|
||||
configObject = merge(cloneDeep(ExtractorConfig._defaultConfig), configObject);
|
||||
configObject = mergeWith(cloneDeep(ExtractorConfig._defaultConfig), configObject, mergeCustomizer);
|
||||
|
||||
ExtractorConfig.jsonSchema.validateObject(configObject, jsonFilePath);
|
||||
|
||||
@@ -653,6 +764,21 @@ export class ExtractorConfig {
|
||||
);
|
||||
}
|
||||
|
||||
if (configFile.additionalEntryPoints) {
|
||||
const entryPointWithAbsolutePath: IConfigEntryPoint[] = [];
|
||||
for (const entryPoint of configFile.additionalEntryPoints) {
|
||||
const absoluteFilePath: string = ExtractorConfig._resolveConfigFileRelativePath(
|
||||
'additionalEntryPoints',
|
||||
entryPoint.filePath,
|
||||
currentConfigFolderPath,
|
||||
);
|
||||
|
||||
entryPointWithAbsolutePath.push({ ...entryPoint, filePath: absoluteFilePath });
|
||||
}
|
||||
|
||||
configFile.additionalEntryPoints = entryPointWithAbsolutePath;
|
||||
}
|
||||
|
||||
if (configFile.compiler?.tsconfigFilePath) {
|
||||
configFile.compiler.tsconfigFilePath = ExtractorConfig._resolveConfigFileRelativePath(
|
||||
'tsconfigFilePath',
|
||||
@@ -885,6 +1011,25 @@ export class ExtractorConfig {
|
||||
throw new Error('The "mainEntryPointFilePath" path does not exist: ' + mainEntryPointFilePath);
|
||||
}
|
||||
|
||||
const additionalEntryPoints: IConfigEntryPoint[] = [];
|
||||
for (const entryPoint of configObject.additionalEntryPoints || []) {
|
||||
const absoluteEntryPointFilePath: string = ExtractorConfig._resolvePathWithTokens(
|
||||
'entryPointFilePath',
|
||||
entryPoint.filePath,
|
||||
tokenContext,
|
||||
);
|
||||
|
||||
if (!ExtractorConfig.hasDtsFileExtension(absoluteEntryPointFilePath)) {
|
||||
throw new Error('The "additionalEntryPoints" value is not a declaration file: ' + absoluteEntryPointFilePath);
|
||||
}
|
||||
|
||||
if (!FileSystem.exists(absoluteEntryPointFilePath)) {
|
||||
throw new Error('The "additionalEntryPoints" path does not exist: ' + absoluteEntryPointFilePath);
|
||||
}
|
||||
|
||||
additionalEntryPoints.push({ ...entryPoint, filePath: absoluteEntryPointFilePath });
|
||||
}
|
||||
|
||||
const bundledPackages: string[] = configObject.bundledPackages ?? [];
|
||||
for (const bundledPackage of bundledPackages) {
|
||||
if (!PackageName.isValidName(bundledPackage)) {
|
||||
@@ -908,51 +1053,92 @@ export class ExtractorConfig {
|
||||
}
|
||||
}
|
||||
|
||||
let apiReportEnabled = false;
|
||||
let reportFilePath = '';
|
||||
let reportTempFilePath = '';
|
||||
let apiReportIncludeForgottenExports = false;
|
||||
if (configObject.apiReport) {
|
||||
apiReportEnabled = Boolean(configObject.apiReport.enabled);
|
||||
|
||||
const reportFilename: string = ExtractorConfig._expandStringWithTokens(
|
||||
'reportFileName',
|
||||
configObject.apiReport.reportFileName ?? '',
|
||||
tokenContext,
|
||||
);
|
||||
|
||||
if (!reportFilename) {
|
||||
// A merged configuration should have this
|
||||
throw new Error('The "reportFilename" setting is missing');
|
||||
}
|
||||
|
||||
if (reportFilename.includes('/') || reportFilename.includes('\\')) {
|
||||
// A merged configuration should have this
|
||||
throw new Error(`The "reportFilename" setting contains invalid characters: "${reportFilename}"`);
|
||||
}
|
||||
|
||||
const reportFolder: string = ExtractorConfig._resolvePathWithTokens(
|
||||
'reportFolder',
|
||||
configObject.apiReport.reportFolder,
|
||||
tokenContext,
|
||||
);
|
||||
const reportTempFolder: string = ExtractorConfig._resolvePathWithTokens(
|
||||
'reportTempFolder',
|
||||
configObject.apiReport.reportTempFolder,
|
||||
tokenContext,
|
||||
);
|
||||
|
||||
reportFilePath = path.join(reportFolder, reportFilename);
|
||||
reportTempFilePath = path.join(reportTempFolder, reportFilename);
|
||||
apiReportIncludeForgottenExports = Boolean(configObject.apiReport.includeForgottenExports);
|
||||
if (configObject.apiReport?.tagsToReport) {
|
||||
_validateTagsToReport(configObject.apiReport.tagsToReport);
|
||||
}
|
||||
|
||||
let docModelEnabled = false;
|
||||
const apiReportEnabled: boolean = configObject.apiReport?.enabled ?? false;
|
||||
const apiReportIncludeForgottenExports: boolean = configObject.apiReport?.includeForgottenExports ?? false;
|
||||
let reportFolder: string = tokenContext.projectFolder;
|
||||
let reportTempFolder: string = tokenContext.projectFolder;
|
||||
const reportConfigs: IExtractorConfigApiReport[] = [];
|
||||
let tagsToReport: Record<`@${string}`, boolean> = {};
|
||||
if (apiReportEnabled) {
|
||||
// Undefined case checked above where we assign `apiReportEnabled`
|
||||
const apiReportConfig: IConfigApiReport = configObject.apiReport!;
|
||||
|
||||
const reportFileNameSuffix = '.api.md';
|
||||
let reportFileNameBase: string;
|
||||
if (apiReportConfig.reportFileName) {
|
||||
if (apiReportConfig.reportFileName.includes('/') || apiReportConfig.reportFileName.includes('\\')) {
|
||||
throw new Error(
|
||||
`The "reportFileName" setting contains invalid characters: "${apiReportConfig.reportFileName}"`,
|
||||
);
|
||||
}
|
||||
|
||||
if (apiReportConfig.reportFileName.endsWith(reportFileNameSuffix)) {
|
||||
// The system previously asked users to specify their filenames in a form containing the `.api.md` extension.
|
||||
// This guidance has changed, but to maintain backwards compatibility, we will temporarily support input
|
||||
// that ends with the `.api.md` extension specially, by stripping it out.
|
||||
// This should be removed in version 8, possibly replaced with an explicit error to help users
|
||||
// migrate their configs.
|
||||
reportFileNameBase = apiReportConfig.reportFileName.slice(0, -reportFileNameSuffix.length);
|
||||
} else {
|
||||
// `.api.md` extension was not specified. Use provided file name base as is.
|
||||
reportFileNameBase = apiReportConfig.reportFileName;
|
||||
}
|
||||
} else {
|
||||
// Default value
|
||||
reportFileNameBase = '<unscopedPackageName>';
|
||||
}
|
||||
|
||||
const reportVariantKinds: readonly ApiReportVariant[] =
|
||||
apiReportConfig.reportVariants ?? defaultApiReportVariants;
|
||||
|
||||
for (const reportVariantKind of reportVariantKinds) {
|
||||
// Omit the variant kind from the "complete" report file name for simplicity and for backwards compatibility.
|
||||
const fileNameWithTokens = `${reportFileNameBase}${
|
||||
reportVariantKind === 'complete' ? '' : `.${reportVariantKind}`
|
||||
}${reportFileNameSuffix}`;
|
||||
const normalizedFileName: string = ExtractorConfig._expandStringWithTokens(
|
||||
'reportFileName',
|
||||
fileNameWithTokens,
|
||||
tokenContext,
|
||||
);
|
||||
|
||||
reportConfigs.push({
|
||||
fileName: normalizedFileName,
|
||||
variant: reportVariantKind,
|
||||
});
|
||||
}
|
||||
|
||||
if (apiReportConfig.reportFolder) {
|
||||
reportFolder = ExtractorConfig._resolvePathWithTokens(
|
||||
'reportFolder',
|
||||
apiReportConfig.reportFolder,
|
||||
tokenContext,
|
||||
);
|
||||
}
|
||||
|
||||
if (apiReportConfig.reportTempFolder) {
|
||||
reportTempFolder = ExtractorConfig._resolvePathWithTokens(
|
||||
'reportTempFolder',
|
||||
apiReportConfig.reportTempFolder,
|
||||
tokenContext,
|
||||
);
|
||||
}
|
||||
|
||||
tagsToReport = {
|
||||
...defaultTagsToReport,
|
||||
...apiReportConfig.tagsToReport,
|
||||
};
|
||||
}
|
||||
|
||||
let docModelGenerationOptions: IApiModelGenerationOptions | undefined;
|
||||
let apiJsonFilePath = '';
|
||||
let docModelIncludeForgottenExports = false;
|
||||
let projectFolderUrl: string | undefined;
|
||||
if (configObject.docModel) {
|
||||
docModelEnabled = Boolean(configObject.docModel.enabled);
|
||||
if (configObject.docModel?.enabled) {
|
||||
apiJsonFilePath = ExtractorConfig._resolvePathWithTokens(
|
||||
'apiJsonFilePath',
|
||||
configObject.docModel.apiJsonFilePath,
|
||||
@@ -960,6 +1146,43 @@ export class ExtractorConfig {
|
||||
);
|
||||
docModelIncludeForgottenExports = Boolean(configObject.docModel.includeForgottenExports);
|
||||
projectFolderUrl = configObject.docModel.projectFolderUrl;
|
||||
|
||||
const releaseTagsToTrim: Set<ReleaseTag> = new Set<ReleaseTag>();
|
||||
const releaseTagsToTrimOption: string[] = configObject.docModel.releaseTagsToTrim || ['@internal'];
|
||||
for (const releaseTagToTrim of releaseTagsToTrimOption) {
|
||||
let releaseTag: ReleaseTag;
|
||||
switch (releaseTagToTrim) {
|
||||
case '@internal': {
|
||||
releaseTag = ReleaseTag.Internal;
|
||||
break;
|
||||
}
|
||||
|
||||
case '@alpha': {
|
||||
releaseTag = ReleaseTag.Alpha;
|
||||
break;
|
||||
}
|
||||
|
||||
case '@beta': {
|
||||
releaseTag = ReleaseTag.Beta;
|
||||
break;
|
||||
}
|
||||
|
||||
case '@public': {
|
||||
releaseTag = ReleaseTag.Public;
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
throw new Error(`The release tag "${releaseTagToTrim}" is not supported`);
|
||||
}
|
||||
}
|
||||
|
||||
releaseTagsToTrim.add(releaseTag);
|
||||
}
|
||||
|
||||
docModelGenerationOptions = {
|
||||
releaseTagsToTrim,
|
||||
};
|
||||
}
|
||||
|
||||
let tsdocMetadataEnabled = false;
|
||||
@@ -1014,6 +1237,15 @@ export class ExtractorConfig {
|
||||
|
||||
if (configObject.dtsRollup) {
|
||||
rollupEnabled = Boolean(configObject.dtsRollup.enabled);
|
||||
|
||||
// d.ts rollup is not supported when there are more than one entry points.
|
||||
if (rollupEnabled && additionalEntryPoints.length > 0) {
|
||||
throw new Error(
|
||||
`It seems that you have dtsRollup enabled while you also have defined additionalEntryPoints.` +
|
||||
`dtsRollup is not supported when there are multiple entry points in your package`,
|
||||
);
|
||||
}
|
||||
|
||||
untrimmedFilePath = ExtractorConfig._resolvePathWithTokens(
|
||||
'untrimmedFilePath',
|
||||
configObject.dtsRollup.untrimmedFilePath,
|
||||
@@ -1057,15 +1289,18 @@ export class ExtractorConfig {
|
||||
packageJson,
|
||||
packageFolder,
|
||||
mainEntryPointFilePath,
|
||||
additionalEntryPoints,
|
||||
bundledPackages,
|
||||
tsconfigFilePath,
|
||||
overrideTsconfig: configObject.compiler.overrideTsconfig,
|
||||
skipLibCheck: Boolean(configObject.compiler.skipLibCheck),
|
||||
apiReportEnabled,
|
||||
reportFilePath,
|
||||
reportTempFilePath,
|
||||
reportConfigs,
|
||||
reportFolder,
|
||||
reportTempFolder,
|
||||
apiReportIncludeForgottenExports,
|
||||
docModelEnabled,
|
||||
tagsToReport,
|
||||
docModelGenerationOptions,
|
||||
apiJsonFilePath,
|
||||
docModelIncludeForgottenExports,
|
||||
projectFolderUrl,
|
||||
@@ -1121,6 +1356,13 @@ export class ExtractorConfig {
|
||||
return new ExtractorConfig({ ...extractorConfigParameters, tsdocConfigFile, tsdocConfiguration });
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the report configuration for the "complete" (default) report configuration, if one was specified.
|
||||
*/
|
||||
private _getCompleteReportConfig(): IExtractorConfigApiReport | undefined {
|
||||
return this.reportConfigs.find((x) => x.variant === 'complete');
|
||||
}
|
||||
|
||||
private static _resolvePathWithTokens(
|
||||
fieldName: string,
|
||||
value: string | undefined,
|
||||
@@ -1194,3 +1436,47 @@ export class ExtractorConfig {
|
||||
throw new Error(`The "${fieldName}" value contains extra token characters ("<" or ">"): ${value}`);
|
||||
}
|
||||
}
|
||||
|
||||
const releaseTags: Set<string> = new Set(['@public', '@alpha', '@beta', '@internal']);
|
||||
|
||||
/**
|
||||
* Validate {@link ExtractorConfig.tagsToReport}.
|
||||
*/
|
||||
function _validateTagsToReport(
|
||||
tagsToReport: Record<string, boolean>,
|
||||
): asserts tagsToReport is Record<`@${string}`, boolean> {
|
||||
const includedReleaseTags: string[] = [];
|
||||
const invalidTags: [string, string][] = []; // tag name, error
|
||||
for (const tag of Object.keys(tagsToReport)) {
|
||||
if (releaseTags.has(tag)) {
|
||||
// If a release tags is specified, regardless of whether it is enabled, we will throw an error.
|
||||
// Release tags must not be specified.
|
||||
includedReleaseTags.push(tag);
|
||||
}
|
||||
|
||||
// If the tag is invalid, generate an error string from the inner error message.
|
||||
try {
|
||||
TSDocTagDefinition.validateTSDocTagName(tag);
|
||||
} catch (error) {
|
||||
invalidTags.push([tag, (error as Error).message]);
|
||||
}
|
||||
}
|
||||
|
||||
const errorMessages: string[] = [];
|
||||
for (const includedReleaseTag of includedReleaseTags) {
|
||||
errorMessages.push(
|
||||
`${includedReleaseTag}: Release tags are always included in API reports and must not be specified`,
|
||||
);
|
||||
}
|
||||
|
||||
for (const [invalidTag, innerError] of invalidTags) {
|
||||
errorMessages.push(`${invalidTag}: ${innerError}`);
|
||||
}
|
||||
|
||||
if (errorMessages.length > 0) {
|
||||
const errorMessage: string = [`"tagsToReport" contained one or more invalid tags:`, ...errorMessages].join(
|
||||
'\n\t- ',
|
||||
);
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,21 @@
|
||||
import type { EnumMemberOrder } from '@discordjs/api-extractor-model';
|
||||
import type { ExtractorLogLevel } from './ExtractorLogLevel.js';
|
||||
|
||||
/**
|
||||
* Represents an entry point in the package
|
||||
* Example:
|
||||
* ```
|
||||
* {
|
||||
* modulePath: 'Shape/Square',
|
||||
* filePath: './dist/Shape/Square/index.d.ts'
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export interface IConfigEntryPoint {
|
||||
filePath: string;
|
||||
modulePath: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines how the TypeScript compiler engine will be invoked by API Extractor.
|
||||
*
|
||||
@@ -47,6 +62,13 @@ export interface IConfigCompiler {
|
||||
tsconfigFilePath?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* The allowed variations of API reports.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type ApiReportVariant = 'alpha' | 'beta' | 'complete' | 'public';
|
||||
|
||||
/**
|
||||
* Configures how the API report files (*.api.md) will be generated.
|
||||
*
|
||||
@@ -71,11 +93,19 @@ export interface IConfigApiReport {
|
||||
includeForgottenExports?: boolean;
|
||||
|
||||
/**
|
||||
* The filename for the API report files. It will be combined with `reportFolder` or `reportTempFolder` to produce
|
||||
* a full output filename.
|
||||
* The base filename for the API report files, to be combined with {@link IConfigApiReport.reportFolder} or
|
||||
* {@link IConfigApiReport.reportTempFolder} to produce the full file path.
|
||||
*
|
||||
* @remarks
|
||||
* The file extension should be ".api.md", and the string should not contain a path separator such as `\` or `/`.
|
||||
* The `reportFileName` should not include any path separators such as `\` or `/`. The `reportFileName` should
|
||||
* not include a file extension, since API Extractor will automatically append an appropriate file extension such
|
||||
* as `.api.md`. If the {@link IConfigApiReport.reportVariants} setting is used, then the file extension includes
|
||||
* the variant name, for example `my-report.public.api.md` or `my-report.beta.api.md`. The `complete` variant always
|
||||
* uses the simple extension `my-report.api.md`.
|
||||
*
|
||||
* Previous versions of API Extractor required `reportFileName` to include the `.api.md` extension explicitly;
|
||||
* for backwards compatibility, that is still accepted but will be discarded before applying the above rules.
|
||||
* @defaultValue `<unscopedPackageName>`
|
||||
*/
|
||||
reportFileName?: string;
|
||||
|
||||
@@ -104,8 +134,62 @@ export interface IConfigApiReport {
|
||||
* prepend a folder token such as `<projectFolder>`.
|
||||
*/
|
||||
reportTempFolder?: string;
|
||||
|
||||
/**
|
||||
* The set of report variants to generate.
|
||||
*
|
||||
* @remarks
|
||||
* To support different approval requirements for different API levels, multiple "variants" of the API report can
|
||||
* be generated. The `reportVariants` setting specifies a list of variants to be generated. If omitted,
|
||||
* by default only the `complete` variant will be generated, which includes all `@internal`, `@alpha`, `@beta`,
|
||||
* and `@public` items. Other possible variants are `alpha` (`@alpha` + `@beta` + `@public`),
|
||||
* `beta` (`@beta` + `@public`), and `public` (`@public only`).
|
||||
*
|
||||
* The resulting API report file names will be derived from the {@link IConfigApiReport.reportFileName}.
|
||||
* @defaultValue `[ "complete" ]`
|
||||
*/
|
||||
reportVariants?: ApiReportVariant[];
|
||||
|
||||
/**
|
||||
* Specifies a list of {@link https://tsdoc.org/ | TSDoc} tags that should be reported in the API report file for
|
||||
* items whose documentation contains them.
|
||||
*
|
||||
* @remarks
|
||||
* Tag names must begin with `@`.
|
||||
*
|
||||
* This list may include standard TSDoc tags as well as custom ones.
|
||||
* For more information on defining custom TSDoc tags, see
|
||||
* {@link https://api-extractor.com/pages/configs/tsdoc_json/#defining-your-own-tsdoc-tags | here}.
|
||||
*
|
||||
* Note that an item's release tag will always reported; this behavior cannot be overridden.
|
||||
* @defaultValue `@sealed`, `@virtual`, `@override`, `@eventProperty`, and `@deprecated`
|
||||
* @example Omitting default tags
|
||||
* To omit the `@sealed` and `@virtual` tags from API reports, you would specify `tagsToReport` as follows:
|
||||
* ```json
|
||||
* "tagsToReport": {
|
||||
* "@sealed": false,
|
||||
* "@virtual": false
|
||||
* }
|
||||
* ```
|
||||
* @example Including additional tags
|
||||
* To include additional tags to the set included in API reports, you could specify `tagsToReport` like this:
|
||||
* ```json
|
||||
* "tagsToReport": {
|
||||
* "@customTag": true
|
||||
* }
|
||||
* ```
|
||||
* This will result in `@customTag` being included in addition to the default tags.
|
||||
*/
|
||||
tagsToReport?: Readonly<Record<`@${string}`, boolean>>;
|
||||
}
|
||||
|
||||
/**
|
||||
* The allowed release tags that can be used to mark API items.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type ReleaseTagForTrim = '@alpha' | '@beta' | '@internal' | '@public';
|
||||
|
||||
/**
|
||||
* Configures how the doc model file (*.api.json) will be generated.
|
||||
*
|
||||
@@ -151,6 +235,13 @@ export interface IConfigDocModel {
|
||||
* Can be omitted if you don't need source code links in your API documentation reference.
|
||||
*/
|
||||
projectFolderUrl?: string;
|
||||
|
||||
/**
|
||||
* Specifies a list of release tags that will be trimmed from the doc model.
|
||||
*
|
||||
* @defaultValue `["@internal"]`
|
||||
*/
|
||||
releaseTagsToTrim?: ReleaseTagForTrim[];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -322,6 +413,11 @@ export interface IExtractorMessagesConfig {
|
||||
* @public
|
||||
*/
|
||||
export interface IConfigFile {
|
||||
/**
|
||||
* support multiple entry points.
|
||||
*/
|
||||
additionalEntryPoints: IConfigEntryPoint[];
|
||||
|
||||
/**
|
||||
* {@inheritDoc IConfigApiReport}
|
||||
*/
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
|
||||
// See LICENSE in the project root for license information.
|
||||
|
||||
import * as path from 'node:path';
|
||||
import { StandardTags } from '@microsoft/tsdoc';
|
||||
import { ExtractorConfig } from '../ExtractorConfig.js';
|
||||
|
||||
const testDataFolder: string = path.join(__dirname, 'test-data');
|
||||
|
||||
describe('Extractor-custom-tags', () => {
|
||||
describe('should use a TSDocConfiguration', () => {
|
||||
it.only("with custom TSDoc tags defined in the package's tsdoc.json", () => {
|
||||
const extractorConfig: ExtractorConfig = ExtractorConfig.loadFileAndPrepare(
|
||||
path.join(testDataFolder, 'custom-tsdoc-tags/api-extractor.json'),
|
||||
);
|
||||
const { tsdocConfiguration } = extractorConfig;
|
||||
|
||||
expect(tsdocConfiguration.tryGetTagDefinition('@block')).not.toBe(undefined);
|
||||
expect(tsdocConfiguration.tryGetTagDefinition('@inline')).not.toBe(undefined);
|
||||
expect(tsdocConfiguration.tryGetTagDefinition('@modifier')).not.toBe(undefined);
|
||||
});
|
||||
it.only("with custom TSDoc tags enabled per the package's tsdoc.json", () => {
|
||||
const extractorConfig: ExtractorConfig = ExtractorConfig.loadFileAndPrepare(
|
||||
path.join(testDataFolder, 'custom-tsdoc-tags/api-extractor.json'),
|
||||
);
|
||||
const { tsdocConfiguration } = extractorConfig;
|
||||
const block = tsdocConfiguration.tryGetTagDefinition('@block')!;
|
||||
const inline = tsdocConfiguration.tryGetTagDefinition('@inline')!;
|
||||
const modifier = tsdocConfiguration.tryGetTagDefinition('@modifier')!;
|
||||
|
||||
expect(tsdocConfiguration.isTagSupported(block)).toBe(true);
|
||||
expect(tsdocConfiguration.isTagSupported(inline)).toBe(true);
|
||||
expect(tsdocConfiguration.isTagSupported(modifier)).toBe(false);
|
||||
});
|
||||
it.only("with standard tags and API Extractor custom tags defined and supported when the package's tsdoc.json extends API Extractor's tsdoc.json", () => {
|
||||
const extractorConfig: ExtractorConfig = ExtractorConfig.loadFileAndPrepare(
|
||||
path.join(testDataFolder, 'custom-tsdoc-tags/api-extractor.json'),
|
||||
);
|
||||
const { tsdocConfiguration } = extractorConfig;
|
||||
|
||||
expect(tsdocConfiguration.tryGetTagDefinition('@inline')).not.toBe(undefined);
|
||||
expect(tsdocConfiguration.tryGetTagDefinition('@block')).not.toBe(undefined);
|
||||
expect(tsdocConfiguration.tryGetTagDefinition('@modifier')).not.toBe(undefined);
|
||||
|
||||
for (const tag of StandardTags.allDefinitions.concat([
|
||||
tsdocConfiguration.tryGetTagDefinition('@betaDocumentation')!,
|
||||
tsdocConfiguration.tryGetTagDefinition('@internalRemarks')!,
|
||||
tsdocConfiguration.tryGetTagDefinition('@preapproved')!,
|
||||
])) {
|
||||
expect(tsdocConfiguration.tagDefinitions.includes(tag));
|
||||
expect(tsdocConfiguration.supportedTagDefinitions.includes(tag));
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,36 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
|
||||
// See LICENSE in the project root for license information.
|
||||
|
||||
import * as path from 'node:path';
|
||||
import { Path } from '@rushstack/node-core-library';
|
||||
import { ExtractorConfig } from '../ExtractorConfig.js';
|
||||
|
||||
const testDataFolder: string = path.join(__dirname, 'test-data');
|
||||
|
||||
function expectEqualPaths(path1: string, path2: string): void {
|
||||
if (!Path.isEqual(path1, path2)) {
|
||||
fail('Expected paths to be equal:\npath1: ' + path1 + '\npath2: ' + path2);
|
||||
}
|
||||
}
|
||||
|
||||
// Tests for expanding the "<lookup>" token for the "projectFolder" setting in api-extractor.json
|
||||
describe(`${ExtractorConfig.name}.${ExtractorConfig.loadFileAndPrepare.name}`, () => {
|
||||
it.only('config-lookup1: looks up ./api-extractor.json', () => {
|
||||
const extractorConfig: ExtractorConfig = ExtractorConfig.loadFileAndPrepare(
|
||||
path.join(testDataFolder, 'config-lookup1/api-extractor.json'),
|
||||
);
|
||||
expectEqualPaths(extractorConfig.projectFolder, path.join(testDataFolder, 'config-lookup1'));
|
||||
});
|
||||
it.only('config-lookup2: looks up ./config/api-extractor.json', () => {
|
||||
const extractorConfig: ExtractorConfig = ExtractorConfig.loadFileAndPrepare(
|
||||
path.join(testDataFolder, 'config-lookup2/config/api-extractor.json'),
|
||||
);
|
||||
expectEqualPaths(extractorConfig.projectFolder, path.join(testDataFolder, 'config-lookup2'));
|
||||
});
|
||||
it.only('config-lookup3a: looks up ./src/test/config/api-extractor.json', () => {
|
||||
const extractorConfig: ExtractorConfig = ExtractorConfig.loadFileAndPrepare(
|
||||
path.join(testDataFolder, 'config-lookup3/src/test/config/api-extractor.json'),
|
||||
);
|
||||
expectEqualPaths(extractorConfig.projectFolder, path.join(testDataFolder, 'config-lookup3/src/test/'));
|
||||
});
|
||||
});
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
|
||||
|
||||
"mainEntryPointFilePath": "<projectFolder>/index.d.ts",
|
||||
|
||||
"apiReport": {
|
||||
"enabled": true
|
||||
},
|
||||
|
||||
"docModel": {
|
||||
"enabled": true
|
||||
},
|
||||
|
||||
"dtsRollup": {
|
||||
"enabled": true
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
/* eslint-disable unicorn/no-empty-file */
|
||||
// empty file
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"name": "config-lookup1",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
{
|
||||
"$schema": "http://json.schemastore.org/tsconfig",
|
||||
|
||||
"compilerOptions": {
|
||||
"outDir": "lib",
|
||||
"rootDir": "src",
|
||||
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"jsx": "react",
|
||||
"declaration": true,
|
||||
"sourceMap": true,
|
||||
"declarationMap": true,
|
||||
"inlineSources": true,
|
||||
"experimentalDecorators": true,
|
||||
"strictNullChecks": true,
|
||||
"noUnusedLocals": true,
|
||||
"types": ["heft-jest", "node"],
|
||||
|
||||
"module": "commonjs",
|
||||
"target": "es2017",
|
||||
"lib": ["es2017"]
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx"],
|
||||
"exclude": ["node_modules", "lib"]
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
|
||||
|
||||
"mainEntryPointFilePath": "<projectFolder>/index.d.ts",
|
||||
|
||||
"apiReport": {
|
||||
"enabled": true
|
||||
},
|
||||
|
||||
"docModel": {
|
||||
"enabled": true
|
||||
},
|
||||
|
||||
"dtsRollup": {
|
||||
"enabled": true
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
/* eslint-disable unicorn/no-empty-file */
|
||||
// empty file
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"name": "config-lookup2",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
{
|
||||
"$schema": "http://json.schemastore.org/tsconfig",
|
||||
|
||||
"compilerOptions": {
|
||||
"outDir": "lib",
|
||||
"rootDir": "src",
|
||||
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"jsx": "react",
|
||||
"declaration": true,
|
||||
"sourceMap": true,
|
||||
"declarationMap": true,
|
||||
"inlineSources": true,
|
||||
"experimentalDecorators": true,
|
||||
"strictNullChecks": true,
|
||||
"noUnusedLocals": true,
|
||||
"types": ["heft-jest", "node"],
|
||||
|
||||
"module": "commonjs",
|
||||
"target": "es2017",
|
||||
"lib": ["es2017"]
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx"],
|
||||
"exclude": ["node_modules", "lib"]
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
|
||||
|
||||
"mainEntryPointFilePath": "<projectFolder>/index.d.ts",
|
||||
|
||||
"apiReport": {
|
||||
"enabled": true
|
||||
},
|
||||
|
||||
"docModel": {
|
||||
"enabled": true
|
||||
},
|
||||
|
||||
"dtsRollup": {
|
||||
"enabled": true
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
/* eslint-disable unicorn/no-empty-file */
|
||||
// empty file
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"name": "config-lookup3",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
|
||||
|
||||
"mainEntryPointFilePath": "<projectFolder>/index.d.ts",
|
||||
|
||||
"apiReport": {
|
||||
"enabled": true
|
||||
},
|
||||
|
||||
"docModel": {
|
||||
"enabled": true
|
||||
},
|
||||
|
||||
"dtsRollup": {
|
||||
"enabled": true
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
/* eslint-disable unicorn/no-empty-file */
|
||||
// empty file
|
||||
@@ -1,25 +0,0 @@
|
||||
{
|
||||
"$schema": "http://json.schemastore.org/tsconfig",
|
||||
|
||||
"compilerOptions": {
|
||||
"outDir": "lib",
|
||||
"rootDir": "src",
|
||||
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"jsx": "react",
|
||||
"declaration": true,
|
||||
"sourceMap": true,
|
||||
"declarationMap": true,
|
||||
"inlineSources": true,
|
||||
"experimentalDecorators": true,
|
||||
"strictNullChecks": true,
|
||||
"noUnusedLocals": true,
|
||||
"types": ["heft-jest", "node"],
|
||||
|
||||
"module": "commonjs",
|
||||
"target": "es2017",
|
||||
"lib": ["es2017"]
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx"],
|
||||
"exclude": ["node_modules", "lib"]
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
{
|
||||
"$schema": "http://json.schemastore.org/tsconfig",
|
||||
|
||||
"compilerOptions": {
|
||||
"outDir": "lib",
|
||||
"rootDir": "src",
|
||||
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"jsx": "react",
|
||||
"declaration": true,
|
||||
"sourceMap": true,
|
||||
"declarationMap": true,
|
||||
"inlineSources": true,
|
||||
"experimentalDecorators": true,
|
||||
"strictNullChecks": true,
|
||||
"noUnusedLocals": true,
|
||||
"types": ["heft-jest", "node"],
|
||||
|
||||
"module": "commonjs",
|
||||
"target": "es2017",
|
||||
"lib": ["es2017"]
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx"],
|
||||
"exclude": ["node_modules", "lib"]
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
|
||||
|
||||
"mainEntryPointFilePath": "<projectFolder>/index.d.ts",
|
||||
|
||||
"apiReport": {
|
||||
"enabled": true
|
||||
},
|
||||
|
||||
"docModel": {
|
||||
"enabled": true
|
||||
},
|
||||
|
||||
"dtsRollup": {
|
||||
"enabled": true
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
/* eslint-disable tsdoc/syntax */
|
||||
/**
|
||||
* @block
|
||||
* @inline test
|
||||
* @modifier
|
||||
*/
|
||||
interface CustomTagsTestInterface {}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"name": "config-lookup1",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
{
|
||||
"$schema": "http://json.schemastore.org/tsconfig",
|
||||
|
||||
"compilerOptions": {
|
||||
"outDir": "lib",
|
||||
"rootDir": "src",
|
||||
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"jsx": "react",
|
||||
"declaration": true,
|
||||
"sourceMap": true,
|
||||
"declarationMap": true,
|
||||
"inlineSources": true,
|
||||
"experimentalDecorators": true,
|
||||
"strictNullChecks": true,
|
||||
"noUnusedLocals": true,
|
||||
"types": ["heft-jest", "node"],
|
||||
|
||||
"module": "commonjs",
|
||||
"target": "es2017",
|
||||
"lib": ["es2017"]
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx"],
|
||||
"exclude": ["node_modules", "lib"]
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/tsdoc/v0/tsdoc.schema.json",
|
||||
"extends": ["../../../../../extends/tsdoc-base.json"],
|
||||
"tagDefinitions": [
|
||||
{
|
||||
"tagName": "@block",
|
||||
"syntaxKind": "block"
|
||||
},
|
||||
{
|
||||
"tagName": "@inline",
|
||||
"syntaxKind": "inline",
|
||||
"allowMultiple": true
|
||||
},
|
||||
{
|
||||
"tagName": "@modifier",
|
||||
"syntaxKind": "modifier"
|
||||
}
|
||||
],
|
||||
"supportForTags": {
|
||||
"@block": true,
|
||||
"@inline": true,
|
||||
"@modifier": false
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user