"use strict"; // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information. var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Extractor = exports.ExtractorResult = void 0; const path = __importStar(require("path")); const semver = __importStar(require("semver")); const ts = __importStar(require("typescript")); const resolve = __importStar(require("resolve")); const node_core_library_1 = require("@rushstack/node-core-library"); const ExtractorConfig_1 = require("./ExtractorConfig"); const Collector_1 = require("../collector/Collector"); const DtsRollupGenerator_1 = require("../generators/DtsRollupGenerator"); const ApiModelGenerator_1 = require("../generators/ApiModelGenerator"); const ApiReportGenerator_1 = require("../generators/ApiReportGenerator"); const PackageMetadataManager_1 = require("../analyzer/PackageMetadataManager"); const ValidationEnhancer_1 = require("../enhancers/ValidationEnhancer"); const DocCommentEnhancer_1 = require("../enhancers/DocCommentEnhancer"); const CompilerState_1 = require("./CompilerState"); const MessageRouter_1 = require("../collector/MessageRouter"); const tsdoc_config_1 = require("@microsoft/tsdoc-config"); const SourceMapper_1 = require("../collector/SourceMapper"); /** * This object represents the outcome of an invocation of API Extractor. * * @public */ class ExtractorResult { /** @internal */ constructor(properties) { this.compilerState = properties.compilerState; this.extractorConfig = properties.extractorConfig; this.succeeded = properties.succeeded; this.apiReportChanged = properties.apiReportChanged; this.errorCount = properties.errorCount; this.warningCount = properties.warningCount; } } exports.ExtractorResult = ExtractorResult; /** * The starting point for invoking the API Extractor tool. * @public */ class Extractor { /** * Returns the version number of the API Extractor NPM package. */ static get version() { return Extractor._getPackageJson().version; } /** * Returns the package name of the API Extractor NPM package. */ static get packageName() { return Extractor._getPackageJson().name; } static _getPackageJson() { return node_core_library_1.PackageJsonLookup.loadOwnPackageJson(__dirname); } /** * Load the api-extractor.json config file from the specified path, and then invoke API Extractor. */ static loadConfigAndInvoke(configFilePath, options) { const extractorConfig = ExtractorConfig_1.ExtractorConfig.loadFileAndPrepare(configFilePath); return Extractor.invoke(extractorConfig, options); } /** * Invoke API Extractor using an already prepared `ExtractorConfig` object. */ static invoke(extractorConfig, options) { if (!options) { options = {}; } const localBuild = options.localBuild || false; let compilerState; if (options.compilerState) { compilerState = options.compilerState; } else { compilerState = CompilerState_1.CompilerState.create(extractorConfig, options); } const sourceMapper = new SourceMapper_1.SourceMapper(); const messageRouter = new MessageRouter_1.MessageRouter({ workingPackageFolder: extractorConfig.packageFolder, messageCallback: options.messageCallback, messagesConfig: extractorConfig.messages || {}, showVerboseMessages: !!options.showVerboseMessages, showDiagnostics: !!options.showDiagnostics, tsdocConfiguration: extractorConfig.tsdocConfiguration, sourceMapper }); if (extractorConfig.tsdocConfigFile.filePath && !extractorConfig.tsdocConfigFile.fileNotFound) { if (!node_core_library_1.Path.isEqual(extractorConfig.tsdocConfigFile.filePath, ExtractorConfig_1.ExtractorConfig._tsdocBaseFilePath)) { messageRouter.logVerbose("console-using-custom-tsdoc-config" /* ConsoleMessageId.UsingCustomTSDocConfig */, 'Using custom TSDoc config from ' + extractorConfig.tsdocConfigFile.filePath); } } this._checkCompilerCompatibility(extractorConfig, messageRouter); if (messageRouter.showDiagnostics) { messageRouter.logDiagnostic(''); messageRouter.logDiagnosticHeader('Final prepared ExtractorConfig'); messageRouter.logDiagnostic(extractorConfig.getDiagnosticDump()); messageRouter.logDiagnosticFooter(); messageRouter.logDiagnosticHeader('Compiler options'); const serializedCompilerOptions = MessageRouter_1.MessageRouter.buildJsonDumpObject(compilerState.program.getCompilerOptions()); messageRouter.logDiagnostic(JSON.stringify(serializedCompilerOptions, undefined, 2)); messageRouter.logDiagnosticFooter(); messageRouter.logDiagnosticHeader('TSDoc configuration'); // Convert the TSDocConfiguration into a tsdoc.json representation const combinedConfigFile = tsdoc_config_1.TSDocConfigFile.loadFromParser(extractorConfig.tsdocConfiguration); const serializedTSDocConfig = MessageRouter_1.MessageRouter.buildJsonDumpObject(combinedConfigFile.saveToObject()); messageRouter.logDiagnostic(JSON.stringify(serializedTSDocConfig, undefined, 2)); messageRouter.logDiagnosticFooter(); } const collector = new Collector_1.Collector({ program: compilerState.program, messageRouter, extractorConfig: extractorConfig, sourceMapper }); collector.analyze(); DocCommentEnhancer_1.DocCommentEnhancer.analyze(collector); ValidationEnhancer_1.ValidationEnhancer.analyze(collector); const modelBuilder = new ApiModelGenerator_1.ApiModelGenerator(collector); const apiPackage = modelBuilder.buildApiPackage(); if (messageRouter.showDiagnostics) { messageRouter.logDiagnostic(''); // skip a line after any diagnostic messages } if (extractorConfig.docModelEnabled) { messageRouter.logVerbose("console-writing-doc-model-file" /* ConsoleMessageId.WritingDocModelFile */, 'Writing: ' + extractorConfig.apiJsonFilePath); apiPackage.saveToJsonFile(extractorConfig.apiJsonFilePath, { toolPackage: Extractor.packageName, toolVersion: Extractor.version, newlineConversion: extractorConfig.newlineKind, ensureFolderExists: true, testMode: extractorConfig.testMode }); } let apiReportChanged = false; if (extractorConfig.apiReportEnabled) { const actualApiReportPath = extractorConfig.reportTempFilePath; const actualApiReportShortPath = extractorConfig._getShortFilePath(extractorConfig.reportTempFilePath); const expectedApiReportPath = extractorConfig.reportFilePath; const expectedApiReportShortPath = extractorConfig._getShortFilePath(extractorConfig.reportFilePath); const actualApiReportContent = ApiReportGenerator_1.ApiReportGenerator.generateReviewFileContent(collector); // Write the actual file node_core_library_1.FileSystem.writeFile(actualApiReportPath, actualApiReportContent, { ensureFolderExists: true, convertLineEndings: extractorConfig.newlineKind }); // Compare it against the expected file if (node_core_library_1.FileSystem.exists(expectedApiReportPath)) { const expectedApiReportContent = node_core_library_1.FileSystem.readFile(expectedApiReportPath); if (!ApiReportGenerator_1.ApiReportGenerator.areEquivalentApiFileContents(actualApiReportContent, expectedApiReportContent)) { apiReportChanged = true; if (!localBuild) { // For a production build, issue a warning that will break the CI build. messageRouter.logWarning("console-api-report-not-copied" /* 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 { // For a local build, just copy the file automatically. messageRouter.logWarning("console-api-report-copied" /* ConsoleMessageId.ApiReportCopied */, 'You have changed the public API signature for this project.' + ` Updating ${expectedApiReportShortPath}`); node_core_library_1.FileSystem.writeFile(expectedApiReportPath, actualApiReportContent, { ensureFolderExists: true, convertLineEndings: extractorConfig.newlineKind }); } } else { messageRouter.logVerbose("console-api-report-unchanged" /* ConsoleMessageId.ApiReportUnchanged */, `The API report is up to date: ${actualApiReportShortPath}`); } } 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) { // For a production build, issue a warning that will break the CI build. messageRouter.logWarning("console-api-report-not-copied" /* 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.`); } else { const expectedApiReportFolder = path.dirname(expectedApiReportPath); if (!node_core_library_1.FileSystem.exists(expectedApiReportFolder)) { messageRouter.logError("console-api-report-folder-missing" /* ConsoleMessageId.ApiReportFolderMissing */, 'Unable to create the API report file. Please make sure the target folder exists:\n' + expectedApiReportFolder); } else { node_core_library_1.FileSystem.writeFile(expectedApiReportPath, actualApiReportContent, { convertLineEndings: extractorConfig.newlineKind }); messageRouter.logWarning("console-api-report-created" /* ConsoleMessageId.ApiReportCreated */, 'The API report file was missing, so a new file was created. Please add this file to Git:\n' + expectedApiReportPath); } } } } if (extractorConfig.rollupEnabled) { Extractor._generateRollupDtsFile(collector, extractorConfig.publicTrimmedFilePath, DtsRollupGenerator_1.DtsRollupKind.PublicRelease, extractorConfig.newlineKind); Extractor._generateRollupDtsFile(collector, extractorConfig.alphaTrimmedFilePath, DtsRollupGenerator_1.DtsRollupKind.AlphaRelease, extractorConfig.newlineKind); Extractor._generateRollupDtsFile(collector, extractorConfig.betaTrimmedFilePath, DtsRollupGenerator_1.DtsRollupKind.BetaRelease, extractorConfig.newlineKind); Extractor._generateRollupDtsFile(collector, extractorConfig.untrimmedFilePath, DtsRollupGenerator_1.DtsRollupKind.InternalRelease, extractorConfig.newlineKind); } if (extractorConfig.tsdocMetadataEnabled) { // Write the tsdoc-metadata.json file for this project PackageMetadataManager_1.PackageMetadataManager.writeTsdocMetadataFile(extractorConfig.tsdocMetadataFilePath, extractorConfig.newlineKind); } // Show all the messages that we collected during analysis messageRouter.handleRemainingNonConsoleMessages(); // Determine success let succeeded; if (localBuild) { // For a local build, fail if there were errors (but ignore warnings) succeeded = messageRouter.errorCount === 0; } else { // For a production build, fail if there were any errors or warnings succeeded = messageRouter.errorCount + messageRouter.warningCount === 0; } return new ExtractorResult({ compilerState, extractorConfig, succeeded, apiReportChanged, errorCount: messageRouter.errorCount, warningCount: messageRouter.warningCount }); } static _checkCompilerCompatibility(extractorConfig, messageRouter) { messageRouter.logInfo("console-preamble" /* ConsoleMessageId.Preamble */, `Analysis will use the bundled TypeScript version ${ts.version}`); try { const typescriptPath = resolve.sync('typescript', { basedir: extractorConfig.projectFolder, preserveSymlinks: false }); const packageJsonLookup = new node_core_library_1.PackageJsonLookup(); const packageJson = packageJsonLookup.tryLoadNodePackageJsonFor(typescriptPath); if (packageJson && packageJson.version && semver.valid(packageJson.version)) { // Consider a newer MINOR release to be incompatible const ourMajor = semver.major(ts.version); const ourMinor = semver.minor(ts.version); const theirMajor = semver.major(packageJson.version); const theirMinor = semver.minor(packageJson.version); if (theirMajor > ourMajor || (theirMajor === ourMajor && theirMinor > ourMinor)) { messageRouter.logInfo("console-compiler-version-notice" /* ConsoleMessageId.CompilerVersionNotice */, `*** The target project appears to use TypeScript ${packageJson.version} which is newer than the` + ` bundled compiler engine; consider upgrading API Extractor.`); } } } catch (e) { // The compiler detection heuristic is not expected to work in many configurations } } static _generateRollupDtsFile(collector, outputPath, dtsKind, newlineKind) { if (outputPath !== '') { collector.messageRouter.logVerbose("console-writing-dts-rollup" /* ConsoleMessageId.WritingDtsRollup */, `Writing package typings: ${outputPath}`); DtsRollupGenerator_1.DtsRollupGenerator.writeTypingsFile(collector, outputPath, dtsKind, newlineKind); } } } exports.Extractor = Extractor; //# sourceMappingURL=Extractor.js.map