public static prepare()

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 });
  }