in apps/api-extractor/src/api/ExtractorConfig.ts [662:1011]
public static prepare(options: IExtractorConfigPrepareOptions): ExtractorConfig {
const filenameForErrors: string = options.configObjectFullPath || 'the configuration object';
const configObject: Partial<IConfigFile> = options.configObject;
if (configObject.extends) {
throw new Error(
'The IConfigFile.extends field must be expanded before calling ExtractorConfig.prepare()'
);
}
if (options.configObjectFullPath) {
if (!path.isAbsolute(options.configObjectFullPath)) {
throw new Error('The "configObjectFullPath" setting must be an absolute path');
}
}
ExtractorConfig.jsonSchema.validateObject(configObject, filenameForErrors);
const packageJsonFullPath: string | undefined = options.packageJsonFullPath;
let packageFolder: string | undefined = undefined;
let packageJson: INodePackageJson | undefined = undefined;
if (packageJsonFullPath) {
if (!/.json$/i.test(packageJsonFullPath)) {
// Catch common mistakes e.g. where someone passes a folder path instead of a file path
throw new Error('The "packageJsonFullPath" setting does not have a .json file extension');
}
if (!path.isAbsolute(packageJsonFullPath)) {
throw new Error('The "packageJsonFullPath" setting must be an absolute path');
}
if (options.packageJson) {
packageJson = options.packageJson;
} else {
const packageJsonLookup: PackageJsonLookup = new PackageJsonLookup();
packageJson = packageJsonLookup.loadNodePackageJson(packageJsonFullPath);
}
packageFolder = path.dirname(packageJsonFullPath);
}
// "tsdocConfigFile" and "tsdocConfiguration" are prepared outside the try-catch block,
// so that if exceptions are thrown, it will not get the "Error parsing api-extractor.json:" header
let extractorConfigParameters: Omit<IExtractorConfigParameters, 'tsdocConfigFile' | 'tsdocConfiguration'>;
try {
if (!configObject.compiler) {
// A merged configuration should have this
throw new Error('The "compiler" section is missing');
}
if (!configObject.projectFolder) {
// A merged configuration should have this
throw new Error('The "projectFolder" setting is missing');
}
let projectFolder: string;
if (configObject.projectFolder.trim() === '<lookup>') {
if (options.projectFolderLookupToken) {
// Use the manually specified "<lookup>" value
projectFolder = options.projectFolderLookupToken;
if (!FileSystem.exists(options.projectFolderLookupToken)) {
throw new Error(
'The specified "projectFolderLookupToken" path does not exist: ' +
options.projectFolderLookupToken
);
}
} else {
if (!options.configObjectFullPath) {
throw new Error(
'The "projectFolder" setting uses the "<lookup>" token, but it cannot be expanded because' +
' the "configObjectFullPath" setting was not specified'
);
}
// "The default value for `projectFolder` is the token `<lookup>`, which means the folder is determined
// by traversing parent folders, starting from the folder containing api-extractor.json, and stopping
// at the first folder that contains a tsconfig.json file. If a tsconfig.json file cannot be found in
// this way, then an error will be reported."
let currentFolder: string = path.dirname(options.configObjectFullPath);
for (;;) {
const tsconfigPath: string = path.join(currentFolder, 'tsconfig.json');
if (FileSystem.exists(tsconfigPath)) {
projectFolder = currentFolder;
break;
}
const parentFolder: string = path.dirname(currentFolder);
if (parentFolder === '' || parentFolder === currentFolder) {
throw new Error(
'The "projectFolder" setting uses the "<lookup>" token, but a tsconfig.json file cannot be' +
' found in this folder or any parent folder.'
);
}
currentFolder = parentFolder;
}
}
} else {
ExtractorConfig._rejectAnyTokensInPath(configObject.projectFolder, 'projectFolder');
if (!FileSystem.exists(configObject.projectFolder)) {
throw new Error('The specified "projectFolder" path does not exist: ' + configObject.projectFolder);
}
projectFolder = configObject.projectFolder;
}
const tokenContext: IExtractorConfigTokenContext = {
unscopedPackageName: 'unknown-package',
packageName: 'unknown-package',
projectFolder: projectFolder
};
if (packageJson) {
tokenContext.packageName = packageJson.name;
tokenContext.unscopedPackageName = PackageName.getUnscopedName(packageJson.name);
}
if (!configObject.mainEntryPointFilePath) {
// A merged configuration should have this
throw new Error('The "mainEntryPointFilePath" setting is missing');
}
const mainEntryPointFilePath: string = ExtractorConfig._resolvePathWithTokens(
'mainEntryPointFilePath',
configObject.mainEntryPointFilePath,
tokenContext
);
if (!ExtractorConfig.hasDtsFileExtension(mainEntryPointFilePath)) {
throw new Error(
'The "mainEntryPointFilePath" value is not a declaration file: ' + mainEntryPointFilePath
);
}
if (!FileSystem.exists(mainEntryPointFilePath)) {
throw new Error('The "mainEntryPointFilePath" path does not exist: ' + mainEntryPointFilePath);
}
const bundledPackages: string[] = configObject.bundledPackages || [];
for (const bundledPackage of bundledPackages) {
if (!PackageName.isValidName(bundledPackage)) {
throw new Error(`The "bundledPackages" list contains an invalid package name: "${bundledPackage}"`);
}
}
const tsconfigFilePath: string = ExtractorConfig._resolvePathWithTokens(
'tsconfigFilePath',
configObject.compiler.tsconfigFilePath,
tokenContext
);
if (configObject.compiler.overrideTsconfig === undefined) {
if (!tsconfigFilePath) {
throw new Error('Either the "tsconfigFilePath" or "overrideTsconfig" setting must be specified');
}
if (!FileSystem.exists(tsconfigFilePath)) {
throw new Error('The file referenced by "tsconfigFilePath" does not exist: ' + tsconfigFilePath);
}
}
let apiReportEnabled: boolean = false;
let reportFilePath: string = '';
let reportTempFilePath: string = '';
if (configObject.apiReport) {
apiReportEnabled = !!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.indexOf('/') >= 0 || reportFilename.indexOf('\\') >= 0) {
// 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);
}
let docModelEnabled: boolean = false;
let apiJsonFilePath: string = '';
if (configObject.docModel) {
docModelEnabled = !!configObject.docModel.enabled;
apiJsonFilePath = ExtractorConfig._resolvePathWithTokens(
'apiJsonFilePath',
configObject.docModel.apiJsonFilePath,
tokenContext
);
}
let tsdocMetadataEnabled: boolean = false;
let tsdocMetadataFilePath: string = '';
if (configObject.tsdocMetadata) {
tsdocMetadataEnabled = !!configObject.tsdocMetadata.enabled;
if (tsdocMetadataEnabled) {
tsdocMetadataFilePath = configObject.tsdocMetadata.tsdocMetadataFilePath || '';
if (tsdocMetadataFilePath.trim() === '<lookup>') {
if (!packageJson) {
throw new Error(
'The "<lookup>" token cannot be used with the "tsdocMetadataFilePath" setting because' +
' the "packageJson" option was not provided'
);
}
if (!packageJsonFullPath) {
throw new Error(
'The "<lookup>" token cannot be used with "tsdocMetadataFilePath" because' +
'the "packageJsonFullPath" option was not provided'
);
}
tsdocMetadataFilePath = PackageMetadataManager.resolveTsdocMetadataPath(
path.dirname(packageJsonFullPath),
packageJson
);
} else {
tsdocMetadataFilePath = ExtractorConfig._resolvePathWithTokens(
'tsdocMetadataFilePath',
configObject.tsdocMetadata.tsdocMetadataFilePath,
tokenContext
);
}
if (!tsdocMetadataFilePath) {
throw new Error(
'The "tsdocMetadata.enabled" setting is enabled,' +
' but "tsdocMetadataFilePath" is not specified'
);
}
}
}
let rollupEnabled: boolean = false;
let untrimmedFilePath: string = '';
let betaTrimmedFilePath: string = '';
let publicTrimmedFilePath: string = '';
let omitTrimmingComments: boolean = false;
if (configObject.dtsRollup) {
rollupEnabled = !!configObject.dtsRollup.enabled;
untrimmedFilePath = ExtractorConfig._resolvePathWithTokens(
'untrimmedFilePath',
configObject.dtsRollup.untrimmedFilePath,
tokenContext
);
betaTrimmedFilePath = ExtractorConfig._resolvePathWithTokens(
'betaTrimmedFilePath',
configObject.dtsRollup.betaTrimmedFilePath,
tokenContext
);
publicTrimmedFilePath = ExtractorConfig._resolvePathWithTokens(
'publicTrimmedFilePath',
configObject.dtsRollup.publicTrimmedFilePath,
tokenContext
);
omitTrimmingComments = !!configObject.dtsRollup.omitTrimmingComments;
}
let newlineKind: NewlineKind;
switch (configObject.newlineKind) {
case 'lf':
newlineKind = NewlineKind.Lf;
break;
case 'os':
newlineKind = NewlineKind.OsDefault;
break;
default:
newlineKind = NewlineKind.CrLf;
break;
}
extractorConfigParameters = {
projectFolder: projectFolder,
packageJson,
packageFolder,
mainEntryPointFilePath,
bundledPackages,
tsconfigFilePath,
overrideTsconfig: configObject.compiler.overrideTsconfig,
skipLibCheck: !!configObject.compiler.skipLibCheck,
apiReportEnabled,
reportFilePath,
reportTempFilePath,
docModelEnabled,
apiJsonFilePath,
rollupEnabled,
untrimmedFilePath,
betaTrimmedFilePath,
publicTrimmedFilePath,
omitTrimmingComments,
tsdocMetadataEnabled,
tsdocMetadataFilePath,
newlineKind,
messages: configObject.messages || {},
testMode: !!configObject.testMode
};
} catch (e) {
throw new Error(`Error parsing ${filenameForErrors}:\n` + (e as Error).message);
}
let tsdocConfigFile: TSDocConfigFile | undefined = options.tsdocConfigFile;
if (!tsdocConfigFile) {
// Example: "my-project/tsdoc.json"
let packageTSDocConfigPath: string = TSDocConfigFile.findConfigPathForFolder(
extractorConfigParameters.projectFolder
);
if (!packageTSDocConfigPath || !FileSystem.exists(packageTSDocConfigPath)) {
// If the project does not have a tsdoc.json config file, then use API Extractor's base file.
packageTSDocConfigPath = ExtractorConfig._tsdocBaseFilePath;
if (!FileSystem.exists(packageTSDocConfigPath)) {
throw new InternalError('Unable to load the built-in TSDoc config file: ' + packageTSDocConfigPath);
}
}
tsdocConfigFile = TSDocConfigFile.loadFile(packageTSDocConfigPath);
}
// IMPORTANT: After calling TSDocConfigFile.loadFile(), we need to check for errors.
if (tsdocConfigFile.hasErrors) {
throw new Error(tsdocConfigFile.getErrorSummary());
}
const tsdocConfiguration: TSDocConfiguration = new TSDocConfiguration();
tsdocConfigFile.configureParser(tsdocConfiguration);
// IMPORTANT: After calling TSDocConfigFile.configureParser(), we need to check for errors a second time.
if (tsdocConfigFile.hasErrors) {
throw new Error(tsdocConfigFile.getErrorSummary());
}
return new ExtractorConfig({ ...extractorConfigParameters, tsdocConfigFile, tsdocConfiguration });
}