From ef260bba18734d1f217219010c225eb96d3f4224 Mon Sep 17 00:00:00 2001 From: Ian Clanton-Thuon Date: Sun, 2 Nov 2025 18:28:42 -0800 Subject: [PATCH 01/17] Fix an issue where verbose API extractor messages may not be passed to the Heft plugin. --- .../src/ApiExtractorRunner.ts | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/heft-plugins/heft-api-extractor-plugin/src/ApiExtractorRunner.ts b/heft-plugins/heft-api-extractor-plugin/src/ApiExtractorRunner.ts index f0e6856ec1c..a70854a851d 100644 --- a/heft-plugins/heft-api-extractor-plugin/src/ApiExtractorRunner.ts +++ b/heft-plugins/heft-api-extractor-plugin/src/ApiExtractorRunner.ts @@ -77,40 +77,43 @@ export class ApiExtractorRunner { const extractorOptions: TApiExtractor.IExtractorInvokeOptions = { localBuild: !this._configuration.production, typescriptCompilerFolder: this._configuration.typescriptPackagePath, + // Always show verbose messages - we'll decide what to do with them in the callback + showVerboseMessages: true, messageCallback: (message: TApiExtractor.ExtractorMessage) => { - switch (message.logLevel) { + const { logLevel, sourceFilePath, messageId, text, sourceFileLine, sourceFileColumn } = message; + switch (logLevel) { case this._apiExtractor.ExtractorLogLevel.Error: case this._apiExtractor.ExtractorLogLevel.Warning: { let errorToEmit: Error | undefined; - if (message.sourceFilePath) { - errorToEmit = new FileError(`(${message.messageId}) ${message.text}`, { - absolutePath: message.sourceFilePath, + if (sourceFilePath) { + errorToEmit = new FileError(`(${messageId}) ${text}`, { + absolutePath: sourceFilePath, projectFolder: this._configuration.buildFolder, - line: message.sourceFileLine, - column: message.sourceFileColumn + line: sourceFileLine, + column: sourceFileColumn }); } else { - errorToEmit = new Error(message.text); + errorToEmit = new Error(text); } - if (message.logLevel === this._apiExtractor.ExtractorLogLevel.Error) { + if (logLevel === this._apiExtractor.ExtractorLogLevel.Error) { this._scopedLogger.emitError(errorToEmit); - } else if (message.logLevel === this._apiExtractor.ExtractorLogLevel.Warning) { + } else if (logLevel === this._apiExtractor.ExtractorLogLevel.Warning) { this._scopedLogger.emitWarning(errorToEmit); } else { // Should never happen, but just in case - throw new InternalError(`Unexpected log level: ${message.logLevel}`); + throw new InternalError(`Unexpected log level: ${logLevel}`); } break; } case this._apiExtractor.ExtractorLogLevel.Verbose: { - this._terminal.writeVerboseLine(message.text); + this._terminal.writeVerboseLine(text); break; } case this._apiExtractor.ExtractorLogLevel.Info: { - this._terminal.writeLine(message.text); + this._terminal.writeLine(text); break; } @@ -120,9 +123,7 @@ export class ApiExtractorRunner { } default: - this._scopedLogger.emitError( - new Error(`Unexpected API Extractor log level: ${message.logLevel}`) - ); + this._scopedLogger.emitError(new Error(`Unexpected API Extractor log level: ${logLevel}`)); } message.handled = true; From 12ed799cff8d402fd37747726fe196d398f3af12 Mon Sep 17 00:00:00 2001 From: Ian Clanton-Thuon Date: Sun, 2 Nov 2025 18:37:03 -0800 Subject: [PATCH 02/17] Add diff printing to API Extractor's report issue logging. --- apps/api-extractor/package.json | 1 + .../api-extractor/src/api/ConsoleMessageId.ts | 8 +++ apps/api-extractor/src/api/Extractor.ts | 68 +++++++++++++------ .../src/collector/MessageRouter.ts | 2 +- ...xtractor-report-diff_2025-11-03-02-36.json | 10 +++ .../config/subspaces/default/pnpm-lock.yaml | 20 +++--- .../config/subspaces/default/repo-state.json | 2 +- common/reviews/api/api-extractor.api.md | 2 + repo-scripts/repo-toolbox/package.json | 3 +- .../src/cli/actions/ReadmeAction.ts | 39 ++++------- 10 files changed, 97 insertions(+), 58 deletions(-) create mode 100644 common/changes/@microsoft/api-extractor/api-extractor-report-diff_2025-11-03-02-36.json diff --git a/apps/api-extractor/package.json b/apps/api-extractor/package.json index d3f0ebc7934..a382cfff1bb 100644 --- a/apps/api-extractor/package.json +++ b/apps/api-extractor/package.json @@ -44,6 +44,7 @@ "@rushstack/rig-package": "workspace:*", "@rushstack/terminal": "workspace:*", "@rushstack/ts-command-line": "workspace:*", + "diff": "~8.0.2", "lodash": "~4.17.15", "minimatch": "10.0.3", "resolve": "~1.22.1", diff --git a/apps/api-extractor/src/api/ConsoleMessageId.ts b/apps/api-extractor/src/api/ConsoleMessageId.ts index 8fa2d53decc..83f6de15173 100644 --- a/apps/api-extractor/src/api/ConsoleMessageId.ts +++ b/apps/api-extractor/src/api/ConsoleMessageId.ts @@ -61,6 +61,14 @@ export enum ConsoleMessageId { */ ApiReportNotCopied = 'console-api-report-not-copied', + /** + * Changes to the API report: + * ____ + * ____ + * ____ + */ + ApiReportDiff = 'console-api-report-diff', + /** * "You have changed the public API signature for this project. Updating ___" */ diff --git a/apps/api-extractor/src/api/Extractor.ts b/apps/api-extractor/src/api/Extractor.ts index 9b204b02054..30470761a6f 100644 --- a/apps/api-extractor/src/api/Extractor.ts +++ b/apps/api-extractor/src/api/Extractor.ts @@ -11,7 +11,7 @@ import type { ApiPackage } from '@microsoft/api-extractor-model'; import { TSDocConfigFile } from '@microsoft/tsdoc-config'; import { FileSystem, - type NewlineKind, + NewlineKind, PackageJsonLookup, type IPackageJson, type INodePackageJson, @@ -91,6 +91,15 @@ export interface IExtractorInvokeOptions { * the STDERR/STDOUT console. */ messageCallback?: (message: ExtractorMessage) => void; + + /** + * If true, then when running a non-local build, the differences between the + * actual and expected API reports will be displayed in the console. + * + * @remarks + * Note that the diff is always shown in verbose mode and is not shown if the destination report file is missing. + */ + alwaysShowChangedApiReportDiffOnNonLocalBuild?: boolean; } /** @@ -192,27 +201,23 @@ export class Extractor { * Invoke API Extractor using an already prepared `ExtractorConfig` object. */ public static invoke(extractorConfig: ExtractorConfig, options?: IExtractorInvokeOptions): ExtractorResult { - if (!options) { - options = {}; - } - - const localBuild: boolean = options.localBuild || false; - - let compilerState: CompilerState | undefined; - if (options.compilerState) { - compilerState = options.compilerState; - } else { - compilerState = CompilerState.create(extractorConfig, options); - } + const { + localBuild = false, + compilerState = CompilerState.create(extractorConfig, options), + messageCallback, + showVerboseMessages = false, + showDiagnostics = false, + alwaysShowChangedApiReportDiffOnNonLocalBuild = false + } = options ?? {}; const sourceMapper: SourceMapper = new SourceMapper(); const messageRouter: MessageRouter = new MessageRouter({ workingPackageFolder: extractorConfig.packageFolder, - messageCallback: options.messageCallback, + messageCallback, messagesConfig: extractorConfig.messages || {}, - showVerboseMessages: !!options.showVerboseMessages, - showDiagnostics: !!options.showDiagnostics, + showVerboseMessages, + showDiagnostics, tsdocConfiguration: extractorConfig.tsdocConfiguration, sourceMapper }); @@ -295,7 +300,8 @@ export class Extractor { extractorConfig.reportTempFolder, extractorConfig.reportFolder, reportConfig, - localBuild + localBuild, + alwaysShowChangedApiReportDiffOnNonLocalBuild ); } @@ -373,6 +379,7 @@ export class Extractor { * @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 alwaysShowChangedApiReportDiffOnNonLocalBuild - {@link IExtractorInvokeOptions.alwaysShowChangedApiReportDiffOnNonLocalBuild} * * @returns Whether or not the newly generated report differs from the existing report (if one exists). */ @@ -383,7 +390,8 @@ export class Extractor { reportTempDirectoryPath: string, reportDirectoryPath: string, reportConfig: IExtractorConfigApiReport, - localBuild: boolean + localBuild: boolean, + alwaysShowChangedApiReportDiffOnNonLocalBuild: boolean ): boolean { let apiReportChanged: boolean = false; @@ -411,7 +419,9 @@ export class Extractor { // Compare it against the expected file if (FileSystem.exists(expectedApiReportPath)) { - const expectedApiReportContent: string = FileSystem.readFile(expectedApiReportPath); + const expectedApiReportContent: string = FileSystem.readFile(expectedApiReportPath, { + convertLineEndings: NewlineKind.Lf + }); if ( !ApiReportGenerator.areEquivalentApiFileContents(actualApiReportContent, expectedApiReportContent) @@ -427,6 +437,26 @@ export class Extractor { ` or perform a local build (which does this automatically).` + ` See the Git repo documentation for more info.` ); + + if (messageRouter.showVerboseMessages || alwaysShowChangedApiReportDiffOnNonLocalBuild) { + const Diff: typeof import('diff') = require('diff'); + const patch: import('diff').StructuredPatch = Diff.structuredPatch( + expectedApiReportShortPath, + actualApiReportShortPath, + expectedApiReportContent, + actualApiReportContent + ); + const logFunction: + | (typeof MessageRouter.prototype)['logWarning'] + | (typeof MessageRouter.prototype)['logVerbose'] = alwaysShowChangedApiReportDiffOnNonLocalBuild + ? messageRouter.logWarning.bind(messageRouter) + : messageRouter.logVerbose.bind(messageRouter); + + logFunction( + ConsoleMessageId.ApiReportDiff, + 'Changes to the API report:\n' + Diff.formatPatch(patch) + ); + } } else { // For a local build, just copy the file automatically. messageRouter.logWarning( diff --git a/apps/api-extractor/src/collector/MessageRouter.ts b/apps/api-extractor/src/collector/MessageRouter.ts index d97742b4b06..bc09397c084 100644 --- a/apps/api-extractor/src/collector/MessageRouter.ts +++ b/apps/api-extractor/src/collector/MessageRouter.ts @@ -422,7 +422,7 @@ export class MessageRouter { /** * This returns all remaining messages that were flagged with `addToApiReportFile`, but which were not - * retreieved using `fetchAssociatedMessagesForReviewFile()`. + * retrieved using `fetchAssociatedMessagesForReviewFile()`. */ public fetchUnassociatedMessagesForReviewFile(): ExtractorMessage[] { const messagesForApiReportFile: ExtractorMessage[] = []; diff --git a/common/changes/@microsoft/api-extractor/api-extractor-report-diff_2025-11-03-02-36.json b/common/changes/@microsoft/api-extractor/api-extractor-report-diff_2025-11-03-02-36.json new file mode 100644 index 00000000000..f7bb1c3b4d7 --- /dev/null +++ b/common/changes/@microsoft/api-extractor/api-extractor-report-diff_2025-11-03-02-36.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@microsoft/api-extractor", + "comment": "Print a diff of the API report file if it's changed in a non-local build in verbose mode or if the new `alwaysShowChangedApiReportDiffOnNonLocalBuild` extractor option is set to `true`.", + "type": "minor" + } + ], + "packageName": "@microsoft/api-extractor" +} \ No newline at end of file diff --git a/common/config/subspaces/default/pnpm-lock.yaml b/common/config/subspaces/default/pnpm-lock.yaml index 91fbb81faac..2bd970ff230 100644 --- a/common/config/subspaces/default/pnpm-lock.yaml +++ b/common/config/subspaces/default/pnpm-lock.yaml @@ -82,6 +82,9 @@ importers: '@rushstack/ts-command-line': specifier: workspace:* version: link:../../libraries/ts-command-line + diff: + specifier: ~8.0.2 + version: 8.0.2 lodash: specifier: ~4.17.15 version: 4.17.21 @@ -4213,15 +4216,12 @@ importers: specifier: workspace:* version: link:../../libraries/ts-command-line diff: - specifier: ~5.0.0 - version: 5.0.0 + specifier: ~8.0.2 + version: 8.0.2 devDependencies: '@rushstack/heft': specifier: workspace:* version: link:../../apps/heft - '@types/diff': - specifier: 5.0.1 - version: 5.0.1 eslint: specifier: ~9.37.0 version: 9.37.0(supports-color@8.1.1) @@ -14197,10 +14197,6 @@ packages: '@types/node': 17.0.41 dev: true - /@types/diff@5.0.1: - resolution: {integrity: sha512-XIpxU6Qdvp1ZE6Kr3yrkv1qgUab0fyf4mHYvW8N3Bx3PCsbN6or1q9/q72cv5jIFWolaGH08U9XyYoLLIykyKQ==} - dev: true - /@types/eslint-scope@3.7.7: resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==} dependencies: @@ -18759,6 +18755,12 @@ packages: /diff@5.0.0: resolution: {integrity: sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==} engines: {node: '>=0.3.1'} + dev: true + + /diff@8.0.2: + resolution: {integrity: sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==} + engines: {node: '>=0.3.1'} + dev: false /diffie-hellman@5.0.3: resolution: {integrity: sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==} diff --git a/common/config/subspaces/default/repo-state.json b/common/config/subspaces/default/repo-state.json index 454f0007190..a9382398400 100644 --- a/common/config/subspaces/default/repo-state.json +++ b/common/config/subspaces/default/repo-state.json @@ -1,5 +1,5 @@ // DO NOT MODIFY THIS FILE MANUALLY BUT DO COMMIT IT. It is generated and used by Rush. { - "pnpmShrinkwrapHash": "e6511377a1c42a5495a696657c94e7cc7b3604ae", + "pnpmShrinkwrapHash": "323a7bb026bc0647b8a2ddd118e5f0dfa6a9cd32", "preferredVersionsHash": "a9b67c38568259823f9cfb8270b31bf6d8470b27" } diff --git a/common/reviews/api/api-extractor.api.md b/common/reviews/api/api-extractor.api.md index 01e2ed90146..623d540ee1a 100644 --- a/common/reviews/api/api-extractor.api.md +++ b/common/reviews/api/api-extractor.api.md @@ -28,6 +28,7 @@ export class CompilerState { export enum ConsoleMessageId { ApiReportCopied = "console-api-report-copied", ApiReportCreated = "console-api-report-created", + ApiReportDiff = "console-api-report-diff", ApiReportFolderMissing = "console-api-report-folder-missing", ApiReportNotCopied = "console-api-report-not-copied", ApiReportUnchanged = "console-api-report-unchanged", @@ -285,6 +286,7 @@ export interface IExtractorConfigPrepareOptions { // @public export interface IExtractorInvokeOptions { + alwaysShowChangedApiReportDiffOnNonLocalBuild?: boolean; compilerState?: CompilerState; localBuild?: boolean; messageCallback?: (message: ExtractorMessage) => void; diff --git a/repo-scripts/repo-toolbox/package.json b/repo-scripts/repo-toolbox/package.json index b9a50d5eb5a..5a56b906323 100644 --- a/repo-scripts/repo-toolbox/package.json +++ b/repo-scripts/repo-toolbox/package.json @@ -14,11 +14,10 @@ "@rushstack/node-core-library": "workspace:*", "@rushstack/terminal": "workspace:*", "@rushstack/ts-command-line": "workspace:*", - "diff": "~5.0.0" + "diff": "~8.0.2" }, "devDependencies": { "@rushstack/heft": "workspace:*", - "@types/diff": "5.0.1", "eslint": "~9.37.0", "local-node-rig": "workspace:*" } diff --git a/repo-scripts/repo-toolbox/src/cli/actions/ReadmeAction.ts b/repo-scripts/repo-toolbox/src/cli/actions/ReadmeAction.ts index 01961880ea3..070717028ac 100644 --- a/repo-scripts/repo-toolbox/src/cli/actions/ReadmeAction.ts +++ b/repo-scripts/repo-toolbox/src/cli/actions/ReadmeAction.ts @@ -1,8 +1,6 @@ // 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 * as Diff from 'diff'; import { StringBuilder, Sort, FileSystem, Text, AlreadyReportedError } from '@rushstack/node-core-library'; @@ -13,6 +11,8 @@ import { CommandLineAction, type CommandLineFlagParameter } from '@rushstack/ts- const GENERATED_PROJECT_SUMMARY_START_COMMENT_TEXT: string = ''; const GENERATED_PROJECT_SUMMARY_END_COMMENT_TEXT: string = ''; +const README_FILENAME: string = 'README.md'; + export class ReadmeAction extends CommandLineAction { private readonly _verifyParameter: CommandLineFlagParameter; @@ -37,7 +37,7 @@ export class ReadmeAction extends CommandLineAction { protected override async onExecuteAsync(): Promise { const rushConfiguration: RushConfiguration = RushConfiguration.loadFromDefaultLocation(); - const repoReadmePath: string = path.resolve(rushConfiguration.rushJsonFolder, 'README.md'); + const repoReadmePath: string = `${rushConfiguration.rushJsonFolder}/${README_FILENAME}`; let existingReadme: string = await FileSystem.readFileAsync(repoReadmePath); existingReadme = Text.convertToLf(existingReadme); const generatedProjectSummaryStartIndex: number = existingReadme.indexOf( @@ -54,12 +54,12 @@ export class ReadmeAction extends CommandLineAction { ); } - const readmePrefix: string = existingReadme.substr( + const readmePrefix: string = existingReadme.substring( 0, generatedProjectSummaryStartIndex + GENERATED_PROJECT_SUMMARY_START_COMMENT_TEXT.length ); - const readmePostfix: string = existingReadme.substr(generatedProjectSummaryEndIndex); + const readmePostfix: string = existingReadme.substring(generatedProjectSummaryEndIndex); const builder: StringBuilder = new StringBuilder(); const orderedProjects: RushConfigurationProject[] = [...rushConfiguration.projects]; @@ -146,32 +146,19 @@ export class ReadmeAction extends CommandLineAction { builder.append(readmePostfix); const readmeString: string = builder.toString(); - const diff: Diff.Change[] = Diff.diffLines(existingReadme, readmeString); - const readmeIsUpToDate: boolean = diff.length === 1 && !diff[0].added && !diff[0].removed; + const diff: Diff.StructuredPatch = Diff.structuredPatch( + README_FILENAME, + README_FILENAME, + existingReadme, + readmeString + ); + const readmeIsUpToDate: boolean = diff.hunks.length === 0; const terminal: Terminal = new Terminal(new ConsoleTerminalProvider()); if (!readmeIsUpToDate) { if (this._verifyParameter.value) { - for (const change of diff) { - const lines: string[] = change.value.trimEnd().split('\n'); - let linePrefix: string; - let colorizer: (text: string) => string; - if (change.added) { - linePrefix = '+ '; - colorizer = Colorize.green; - } else if (change.removed) { - linePrefix = '- '; - colorizer = Colorize.red; - } else { - linePrefix = ' '; - colorizer = Colorize.gray; - } - - for (const line of lines) { - terminal.writeLine(colorizer(linePrefix + line)); - } - } + terminal.writeLine(Diff.formatPatch(diff)); terminal.writeLine(); terminal.writeLine(); From a873ca0c2d0bcb768f7f74ea479452d1373b7564 Mon Sep 17 00:00:00 2001 From: Ian Clanton-Thuon Date: Sun, 2 Nov 2025 18:54:53 -0800 Subject: [PATCH 03/17] Expose an alwaysShowChangedApiReportDiffOnNonLocalBuild option in the api-extractor-heft-plugin configuration file. --- ...xtractor-report-diff_2025-11-03-02-53.json | 10 ++ .../src/ApiExtractorPlugin.ts | 28 ++-- .../src/ApiExtractorRunner.ts | 145 +++++++++--------- .../schemas/api-extractor-task.schema.json | 5 + 4 files changed, 108 insertions(+), 80 deletions(-) create mode 100644 common/changes/@rushstack/heft-api-extractor-plugin/api-extractor-report-diff_2025-11-03-02-53.json diff --git a/common/changes/@rushstack/heft-api-extractor-plugin/api-extractor-report-diff_2025-11-03-02-53.json b/common/changes/@rushstack/heft-api-extractor-plugin/api-extractor-report-diff_2025-11-03-02-53.json new file mode 100644 index 00000000000..d35365a29a5 --- /dev/null +++ b/common/changes/@rushstack/heft-api-extractor-plugin/api-extractor-report-diff_2025-11-03-02-53.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@rushstack/heft-api-extractor-plugin", + "comment": "Expose a `alwaysShowChangedApiReportDiffOnNonLocalBuild` config option to print a diff if the API report file exists and is changed during a non-local build. This is useful for diagnosing issues that only show up in CI.", + "type": "minor" + } + ], + "packageName": "@rushstack/heft-api-extractor-plugin" +} \ No newline at end of file diff --git a/heft-plugins/heft-api-extractor-plugin/src/ApiExtractorPlugin.ts b/heft-plugins/heft-api-extractor-plugin/src/ApiExtractorPlugin.ts index a88b78ba76c..e6b1090aa25 100644 --- a/heft-plugins/heft-api-extractor-plugin/src/ApiExtractorPlugin.ts +++ b/heft-plugins/heft-api-extractor-plugin/src/ApiExtractorPlugin.ts @@ -11,7 +11,7 @@ import type { ConfigurationFile } from '@rushstack/heft'; -import { ApiExtractorRunner } from './ApiExtractorRunner'; +import { invokeApiExtractorAsync } from './ApiExtractorRunner'; import apiExtractorConfigSchema from './schemas/api-extractor-task.schema.json'; // eslint-disable-next-line @rushstack/no-new-null @@ -51,6 +51,13 @@ export interface IApiExtractorTaskConfiguration { * If set to true, do a full run of api-extractor on every build. */ runInWatchMode?: boolean; + + /** + * If set to true, API Extractor will print a diff of the API report file if it's changed in + * a non-local build, regardless of the verbosity level. This corresponds to API Extractor's + * `IExtractorInvokeOptions.alwaysShowChangedApiReportDiffOnNonLocalBuild` API option. This option defaults to false + */ + alwaysShowChangedApiReportDiffOnNonLocalBuild?: boolean; } export default class ApiExtractorPlugin implements IHeftTaskPlugin { @@ -161,14 +168,14 @@ export default class ApiExtractorPlugin implements IHeftTaskPlugin { apiExtractor: typeof TApiExtractor, apiExtractorConfiguration: TApiExtractor.ExtractorConfig ): Promise { - const apiExtractorTaskConfiguration: IApiExtractorTaskConfiguration | undefined = - await heftConfiguration.tryLoadProjectConfigurationFileAsync( + const { runInWatchMode, useProjectTypescriptVersion, alwaysShowChangedApiReportDiffOnNonLocalBuild } = + (await heftConfiguration.tryLoadProjectConfigurationFileAsync( API_EXTRACTOR_CONFIG_SPECIFICATION, taskSession.logger.terminal - ); + )) ?? {}; if (runOptions.requestRun) { - if (!apiExtractorTaskConfiguration?.runInWatchMode) { + if (!runInWatchMode) { if (!this._printedWatchWarning) { this._printedWatchWarning = true; taskSession.logger.terminal.writeWarningLine( @@ -180,23 +187,22 @@ export default class ApiExtractorPlugin implements IHeftTaskPlugin { } let typescriptPackagePath: string | undefined; - if (apiExtractorTaskConfiguration?.useProjectTypescriptVersion) { + if (useProjectTypescriptVersion) { typescriptPackagePath = await heftConfiguration.rigPackageResolver.resolvePackageAsync( 'typescript', taskSession.logger.terminal ); } - const apiExtractorRunner: ApiExtractorRunner = new ApiExtractorRunner({ + // Run API Extractor + await invokeApiExtractorAsync({ apiExtractor, apiExtractorConfiguration, typescriptPackagePath, buildFolder: heftConfiguration.buildFolderPath, production: taskSession.parameters.production, - scopedLogger: taskSession.logger + scopedLogger: taskSession.logger, + alwaysShowChangedApiReportDiffOnNonLocalBuild }); - - // Run API Extractor - await apiExtractorRunner.invokeAsync(); } } diff --git a/heft-plugins/heft-api-extractor-plugin/src/ApiExtractorRunner.ts b/heft-plugins/heft-api-extractor-plugin/src/ApiExtractorRunner.ts index a70854a851d..4d4651b1e7a 100644 --- a/heft-plugins/heft-api-extractor-plugin/src/ApiExtractorRunner.ts +++ b/heft-plugins/heft-api-extractor-plugin/src/ApiExtractorRunner.ts @@ -5,7 +5,6 @@ import * as semver from 'semver'; import type { IScopedLogger } from '@rushstack/heft'; import { FileError, InternalError } from '@rushstack/node-core-library'; -import type { ITerminal } from '@rushstack/terminal'; import type * as TApiExtractor from '@microsoft/api-extractor'; export interface IApiExtractorRunnerConfiguration { @@ -40,55 +39,62 @@ export interface IApiExtractorRunnerConfiguration { * The scoped logger to use for logging */ scopedLogger: IScopedLogger; + + /** + * {@inheritdoc IApiExtractorTaskConfiguration.alwaysShowChangedApiReportDiffOnNonLocalBuild} + */ + alwaysShowChangedApiReportDiffOnNonLocalBuild: boolean | undefined; } const MIN_SUPPORTED_MAJOR_VERSION: number = 7; const MIN_SUPPORTED_MINOR_VERSION: number = 10; -export class ApiExtractorRunner { - private readonly _configuration: IApiExtractorRunnerConfiguration; - private readonly _scopedLogger: IScopedLogger; - private readonly _terminal: ITerminal; - private readonly _apiExtractor: typeof TApiExtractor; - - public constructor(configuration: IApiExtractorRunnerConfiguration) { - this._configuration = configuration; - this._apiExtractor = configuration.apiExtractor; - this._scopedLogger = configuration.scopedLogger; - this._terminal = configuration.scopedLogger.terminal; +export async function invokeApiExtractorAsync( + configuration: IApiExtractorRunnerConfiguration +): Promise { + const { + scopedLogger, + apiExtractor, + buildFolder, + production, + typescriptPackagePath, + apiExtractorConfiguration, + alwaysShowChangedApiReportDiffOnNonLocalBuild + } = configuration; + const { terminal } = scopedLogger; + + terminal.writeLine(`Using API Extractor version ${apiExtractor.Extractor.version}`); + + const apiExtractorVersion: semver.SemVer | null = semver.parse(apiExtractor.Extractor.version); + if ( + !apiExtractorVersion || + apiExtractorVersion.major < MIN_SUPPORTED_MAJOR_VERSION || + (apiExtractorVersion.major === MIN_SUPPORTED_MAJOR_VERSION && + apiExtractorVersion.minor < MIN_SUPPORTED_MINOR_VERSION) + ) { + scopedLogger.emitWarning(new Error(`Heft requires API Extractor version 7.10.0 or newer`)); } - public async invokeAsync(): Promise { - this._scopedLogger.terminal.writeLine( - `Using API Extractor version ${this._apiExtractor.Extractor.version}` - ); - - const apiExtractorVersion: semver.SemVer | null = semver.parse(this._apiExtractor.Extractor.version); - if ( - !apiExtractorVersion || - apiExtractorVersion.major < MIN_SUPPORTED_MAJOR_VERSION || - (apiExtractorVersion.major === MIN_SUPPORTED_MAJOR_VERSION && - apiExtractorVersion.minor < MIN_SUPPORTED_MINOR_VERSION) - ) { - this._scopedLogger.emitWarning(new Error(`Heft requires API Extractor version 7.10.0 or newer`)); - } - - const extractorConfig: TApiExtractor.ExtractorConfig = this._configuration.apiExtractorConfiguration; - const extractorOptions: TApiExtractor.IExtractorInvokeOptions = { - localBuild: !this._configuration.production, - typescriptCompilerFolder: this._configuration.typescriptPackagePath, - // Always show verbose messages - we'll decide what to do with them in the callback - showVerboseMessages: true, - messageCallback: (message: TApiExtractor.ExtractorMessage) => { - const { logLevel, sourceFilePath, messageId, text, sourceFileLine, sourceFileColumn } = message; - switch (logLevel) { - case this._apiExtractor.ExtractorLogLevel.Error: - case this._apiExtractor.ExtractorLogLevel.Warning: { + const extractorOptions: TApiExtractor.IExtractorInvokeOptions = { + localBuild: !production, + typescriptCompilerFolder: typescriptPackagePath, + // Always show verbose messages - we'll decide what to do with them in the callback + showVerboseMessages: true, + alwaysShowChangedApiReportDiffOnNonLocalBuild, + messageCallback: (message: TApiExtractor.ExtractorMessage) => { + const { logLevel, sourceFilePath, messageId, text, sourceFileLine, sourceFileColumn } = message; + switch (logLevel) { + case apiExtractor.ExtractorLogLevel.Error: + case apiExtractor.ExtractorLogLevel.Warning: { + if (messageId === apiExtractor.ConsoleMessageId.ApiReportDiff) { + // Re-route this to the normal terminal output so it doesn't show up in the list of warnings/errors + terminal.writeLine(text); + } else { let errorToEmit: Error | undefined; if (sourceFilePath) { errorToEmit = new FileError(`(${messageId}) ${text}`, { absolutePath: sourceFilePath, - projectFolder: this._configuration.buildFolder, + projectFolder: buildFolder, line: sourceFileLine, column: sourceFileColumn }); @@ -96,49 +102,50 @@ export class ApiExtractorRunner { errorToEmit = new Error(text); } - if (logLevel === this._apiExtractor.ExtractorLogLevel.Error) { - this._scopedLogger.emitError(errorToEmit); - } else if (logLevel === this._apiExtractor.ExtractorLogLevel.Warning) { - this._scopedLogger.emitWarning(errorToEmit); + if (logLevel === apiExtractor.ExtractorLogLevel.Error) { + scopedLogger.emitError(errorToEmit); + } else if (logLevel === apiExtractor.ExtractorLogLevel.Warning) { + scopedLogger.emitWarning(errorToEmit); } else { // Should never happen, but just in case throw new InternalError(`Unexpected log level: ${logLevel}`); } - break; } - case this._apiExtractor.ExtractorLogLevel.Verbose: { - this._terminal.writeVerboseLine(text); - break; - } + break; + } - case this._apiExtractor.ExtractorLogLevel.Info: { - this._terminal.writeLine(text); - break; - } + case apiExtractor.ExtractorLogLevel.Verbose: { + terminal.writeVerboseLine(text); + break; + } - case this._apiExtractor.ExtractorLogLevel.None: { - // Ignore messages with ExtractorLogLevel.None - break; - } + case apiExtractor.ExtractorLogLevel.Info: { + terminal.writeLine(text); + break; + } - default: - this._scopedLogger.emitError(new Error(`Unexpected API Extractor log level: ${logLevel}`)); + case apiExtractor.ExtractorLogLevel.None: { + // Ignore messages with ExtractorLogLevel.None + break; } - message.handled = true; + default: + scopedLogger.emitError(new Error(`Unexpected API Extractor log level: ${logLevel}`)); } - }; - const apiExtractorResult: TApiExtractor.ExtractorResult = this._apiExtractor.Extractor.invoke( - extractorConfig, - extractorOptions - ); - - if (!apiExtractorResult.succeeded) { - this._scopedLogger.emitError(new Error('API Extractor failed.')); - } else if (apiExtractorResult.apiReportChanged && this._configuration.production) { - this._scopedLogger.emitError(new Error('API Report changed while in production mode.')); + message.handled = true; } + }; + + const apiExtractorResult: TApiExtractor.ExtractorResult = apiExtractor.Extractor.invoke( + apiExtractorConfiguration, + extractorOptions + ); + + if (!apiExtractorResult.succeeded) { + scopedLogger.emitError(new Error('API Extractor failed.')); + } else if (apiExtractorResult.apiReportChanged && production) { + scopedLogger.emitError(new Error('API Report changed while in production mode.')); } } diff --git a/heft-plugins/heft-api-extractor-plugin/src/schemas/api-extractor-task.schema.json b/heft-plugins/heft-api-extractor-plugin/src/schemas/api-extractor-task.schema.json index 9710f292181..ae3fd7b525c 100644 --- a/heft-plugins/heft-api-extractor-plugin/src/schemas/api-extractor-task.schema.json +++ b/heft-plugins/heft-api-extractor-plugin/src/schemas/api-extractor-task.schema.json @@ -25,6 +25,11 @@ "runInWatchMode": { "type": "boolean", "description": "If set to true, api-extractor will be run even in watch mode. This option defaults to false." + }, + + "alwaysShowChangedApiReportDiffOnNonLocalBuild": { + "type": "boolean", + "description": "If set to true, API Extractor will print a diff of the API report file if it's changed in a non-local build, regardless of the verbosity level. This corresponds to API Extractor's `IExtractorInvokeOptions.alwaysShowChangedApiReportDiffOnNonLocalBuild` API option. This option defaults to false." } } } From aaa72dbe0567dd27ffd16b09a3370593c1f3e765 Mon Sep 17 00:00:00 2001 From: Ian Clanton-Thuon Date: Sun, 2 Nov 2025 18:55:12 -0800 Subject: [PATCH 04/17] Use the alwaysShowChangedApiReportDiffOnNonLocalBuild in the local rig --- .../profiles/default/config/api-extractor-task.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rigs/local-node-rig/profiles/default/config/api-extractor-task.json b/rigs/local-node-rig/profiles/default/config/api-extractor-task.json index 17416c0226f..58788c74142 100644 --- a/rigs/local-node-rig/profiles/default/config/api-extractor-task.json +++ b/rigs/local-node-rig/profiles/default/config/api-extractor-task.json @@ -1,5 +1,7 @@ { "$schema": "https://developer.microsoft.com/json-schemas/heft/v0/api-extractor-task.schema.json", - "extends": "@rushstack/heft-node-rig/profiles/default/config/api-extractor-task.json" + "extends": "@rushstack/heft-node-rig/profiles/default/config/api-extractor-task.json", + + "alwaysShowChangedApiReportDiffOnNonLocalBuild": true } From 11839b18153b944cdecbdeadb1581ee7e45e42e9 Mon Sep 17 00:00:00 2001 From: Ian Clanton-Thuon Date: Sun, 2 Nov 2025 19:01:16 -0800 Subject: [PATCH 05/17] fixup! Add diff printing to API Extractor's report issue logging. --- common/config/subspaces/build-tests-subspace/pnpm-lock.yaml | 6 ++++++ .../config/subspaces/build-tests-subspace/repo-state.json | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/common/config/subspaces/build-tests-subspace/pnpm-lock.yaml b/common/config/subspaces/build-tests-subspace/pnpm-lock.yaml index 29f0cad0bae..93b877e4229 100644 --- a/common/config/subspaces/build-tests-subspace/pnpm-lock.yaml +++ b/common/config/subspaces/build-tests-subspace/pnpm-lock.yaml @@ -2875,6 +2875,11 @@ packages: engines: {node: '>=0.3.1'} dev: true + /diff@8.0.2: + resolution: {integrity: sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==} + engines: {node: '>=0.3.1'} + dev: true + /dir-glob@3.0.1: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} @@ -7624,6 +7629,7 @@ packages: '@rushstack/rig-package': file:../../../libraries/rig-package '@rushstack/terminal': file:../../../libraries/terminal(@types/node@20.17.19) '@rushstack/ts-command-line': file:../../../libraries/ts-command-line(@types/node@20.17.19) + diff: 8.0.2 lodash: 4.17.21 minimatch: 10.0.3 resolve: 1.22.8 diff --git a/common/config/subspaces/build-tests-subspace/repo-state.json b/common/config/subspaces/build-tests-subspace/repo-state.json index 788045eed0f..928066cc31c 100644 --- a/common/config/subspaces/build-tests-subspace/repo-state.json +++ b/common/config/subspaces/build-tests-subspace/repo-state.json @@ -1,6 +1,6 @@ // DO NOT MODIFY THIS FILE MANUALLY BUT DO COMMIT IT. It is generated and used by Rush. { - "pnpmShrinkwrapHash": "b81cfe28585e2b8050e7810267a7cecaf868a506", + "pnpmShrinkwrapHash": "60c88d283572a8ffcf3b404f8052319c897496bc", "preferredVersionsHash": "550b4cee0bef4e97db6c6aad726df5149d20e7d9", - "packageJsonInjectedDependenciesHash": "a77c40152142c33eb9b6aba1057bfdfffa33b918" + "packageJsonInjectedDependenciesHash": "7559b58118afee665d0749269159d573701a804b" } From 5faa72b55174905b5c328fe5c12abe8b348c4f07 Mon Sep 17 00:00:00 2001 From: Ian Clanton-Thuon Date: Mon, 3 Nov 2025 12:14:51 -0800 Subject: [PATCH 06/17] fixup! Add diff printing to API Extractor's report issue logging. Co-authored-by: Pete Gonzalez <4673363+octogonz@users.noreply.github.com> --- apps/api-extractor/src/api/ConsoleMessageId.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/api-extractor/src/api/ConsoleMessageId.ts b/apps/api-extractor/src/api/ConsoleMessageId.ts index 83f6de15173..5d1345a2093 100644 --- a/apps/api-extractor/src/api/ConsoleMessageId.ts +++ b/apps/api-extractor/src/api/ConsoleMessageId.ts @@ -63,9 +63,7 @@ export enum ConsoleMessageId { /** * Changes to the API report: - * ____ - * ____ - * ____ + * ___ */ ApiReportDiff = 'console-api-report-diff', From ad8e9bd31f075c326a0cafd5fd5acccf759c0b1f Mon Sep 17 00:00:00 2001 From: Ian Clanton-Thuon Date: Mon, 3 Nov 2025 13:48:46 -0800 Subject: [PATCH 07/17] Rename alwaysShowChangedApiReportDiffOnNonLocalBuild to enableApiReportConsoleDiff. --- apps/api-extractor/src/api/Extractor.ts | 20 +++++++++---------- ...xtractor-report-diff_2025-11-03-02-36.json | 2 +- ...xtractor-report-diff_2025-11-03-02-53.json | 2 +- common/reviews/api/api-extractor.api.md | 2 +- .../src/ApiExtractorPlugin.ts | 8 ++++---- .../src/ApiExtractorRunner.ts | 8 ++++---- .../schemas/api-extractor-task.schema.json | 4 ++-- .../default/config/api-extractor-task.json | 2 +- 8 files changed, 24 insertions(+), 24 deletions(-) diff --git a/apps/api-extractor/src/api/Extractor.ts b/apps/api-extractor/src/api/Extractor.ts index 30470761a6f..e08c6e92a88 100644 --- a/apps/api-extractor/src/api/Extractor.ts +++ b/apps/api-extractor/src/api/Extractor.ts @@ -93,13 +93,13 @@ export interface IExtractorInvokeOptions { messageCallback?: (message: ExtractorMessage) => void; /** - * If true, then when running a non-local build, the differences between the - * actual and expected API reports will be displayed in the console. + * If true, then any differences between the actual and expected API reports will be + * printed on the console. * * @remarks - * Note that the diff is always shown in verbose mode and is not shown if the destination report file is missing. + * The diff is not printed if the expected API report file has not been created yet. */ - alwaysShowChangedApiReportDiffOnNonLocalBuild?: boolean; + enableApiReportConsoleDiff?: boolean; } /** @@ -207,7 +207,7 @@ export class Extractor { messageCallback, showVerboseMessages = false, showDiagnostics = false, - alwaysShowChangedApiReportDiffOnNonLocalBuild = false + enableApiReportConsoleDiff = false } = options ?? {}; const sourceMapper: SourceMapper = new SourceMapper(); @@ -301,7 +301,7 @@ export class Extractor { extractorConfig.reportFolder, reportConfig, localBuild, - alwaysShowChangedApiReportDiffOnNonLocalBuild + enableApiReportConsoleDiff ); } @@ -379,7 +379,7 @@ export class Extractor { * @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 alwaysShowChangedApiReportDiffOnNonLocalBuild - {@link IExtractorInvokeOptions.alwaysShowChangedApiReportDiffOnNonLocalBuild} + * @param enableApiReportConsoleDiff - {@link IExtractorInvokeOptions.enableApiReportConsoleDiff} * * @returns Whether or not the newly generated report differs from the existing report (if one exists). */ @@ -391,7 +391,7 @@ export class Extractor { reportDirectoryPath: string, reportConfig: IExtractorConfigApiReport, localBuild: boolean, - alwaysShowChangedApiReportDiffOnNonLocalBuild: boolean + enableApiReportConsoleDiff: boolean ): boolean { let apiReportChanged: boolean = false; @@ -438,7 +438,7 @@ export class Extractor { ` See the Git repo documentation for more info.` ); - if (messageRouter.showVerboseMessages || alwaysShowChangedApiReportDiffOnNonLocalBuild) { + if (messageRouter.showVerboseMessages || enableApiReportConsoleDiff) { const Diff: typeof import('diff') = require('diff'); const patch: import('diff').StructuredPatch = Diff.structuredPatch( expectedApiReportShortPath, @@ -448,7 +448,7 @@ export class Extractor { ); const logFunction: | (typeof MessageRouter.prototype)['logWarning'] - | (typeof MessageRouter.prototype)['logVerbose'] = alwaysShowChangedApiReportDiffOnNonLocalBuild + | (typeof MessageRouter.prototype)['logVerbose'] = enableApiReportConsoleDiff ? messageRouter.logWarning.bind(messageRouter) : messageRouter.logVerbose.bind(messageRouter); diff --git a/common/changes/@microsoft/api-extractor/api-extractor-report-diff_2025-11-03-02-36.json b/common/changes/@microsoft/api-extractor/api-extractor-report-diff_2025-11-03-02-36.json index f7bb1c3b4d7..6c798d49e9c 100644 --- a/common/changes/@microsoft/api-extractor/api-extractor-report-diff_2025-11-03-02-36.json +++ b/common/changes/@microsoft/api-extractor/api-extractor-report-diff_2025-11-03-02-36.json @@ -2,7 +2,7 @@ "changes": [ { "packageName": "@microsoft/api-extractor", - "comment": "Print a diff of the API report file if it's changed in a non-local build in verbose mode or if the new `alwaysShowChangedApiReportDiffOnNonLocalBuild` extractor option is set to `true`.", + "comment": "Print a diff of the API report file if it's changed in a non-local build in verbose mode or if the new `enableApiReportConsoleDiff` extractor option is set to `true`.", "type": "minor" } ], diff --git a/common/changes/@rushstack/heft-api-extractor-plugin/api-extractor-report-diff_2025-11-03-02-53.json b/common/changes/@rushstack/heft-api-extractor-plugin/api-extractor-report-diff_2025-11-03-02-53.json index d35365a29a5..a6514c569fb 100644 --- a/common/changes/@rushstack/heft-api-extractor-plugin/api-extractor-report-diff_2025-11-03-02-53.json +++ b/common/changes/@rushstack/heft-api-extractor-plugin/api-extractor-report-diff_2025-11-03-02-53.json @@ -2,7 +2,7 @@ "changes": [ { "packageName": "@rushstack/heft-api-extractor-plugin", - "comment": "Expose a `alwaysShowChangedApiReportDiffOnNonLocalBuild` config option to print a diff if the API report file exists and is changed during a non-local build. This is useful for diagnosing issues that only show up in CI.", + "comment": "Expose a `IExtractorInvokeOptions.enableApiReportConsoleDiff` config option to print a diff if the API report file exists and is changed during a non-local build. This is useful for diagnosing issues that only show up in CI.", "type": "minor" } ], diff --git a/common/reviews/api/api-extractor.api.md b/common/reviews/api/api-extractor.api.md index 623d540ee1a..b6c7bcdfdc5 100644 --- a/common/reviews/api/api-extractor.api.md +++ b/common/reviews/api/api-extractor.api.md @@ -286,8 +286,8 @@ export interface IExtractorConfigPrepareOptions { // @public export interface IExtractorInvokeOptions { - alwaysShowChangedApiReportDiffOnNonLocalBuild?: boolean; compilerState?: CompilerState; + enableApiReportConsoleDiff?: boolean; localBuild?: boolean; messageCallback?: (message: ExtractorMessage) => void; showDiagnostics?: boolean; diff --git a/heft-plugins/heft-api-extractor-plugin/src/ApiExtractorPlugin.ts b/heft-plugins/heft-api-extractor-plugin/src/ApiExtractorPlugin.ts index e6b1090aa25..8ab3ebdc5cd 100644 --- a/heft-plugins/heft-api-extractor-plugin/src/ApiExtractorPlugin.ts +++ b/heft-plugins/heft-api-extractor-plugin/src/ApiExtractorPlugin.ts @@ -55,9 +55,9 @@ export interface IApiExtractorTaskConfiguration { /** * If set to true, API Extractor will print a diff of the API report file if it's changed in * a non-local build, regardless of the verbosity level. This corresponds to API Extractor's - * `IExtractorInvokeOptions.alwaysShowChangedApiReportDiffOnNonLocalBuild` API option. This option defaults to false + * `IExtractorInvokeOptions.enableApiReportConsoleDiff` API option. This option defaults to false */ - alwaysShowChangedApiReportDiffOnNonLocalBuild?: boolean; + enableApiReportConsoleDiff?: boolean; } export default class ApiExtractorPlugin implements IHeftTaskPlugin { @@ -168,7 +168,7 @@ export default class ApiExtractorPlugin implements IHeftTaskPlugin { apiExtractor: typeof TApiExtractor, apiExtractorConfiguration: TApiExtractor.ExtractorConfig ): Promise { - const { runInWatchMode, useProjectTypescriptVersion, alwaysShowChangedApiReportDiffOnNonLocalBuild } = + const { runInWatchMode, useProjectTypescriptVersion, enableApiReportConsoleDiff } = (await heftConfiguration.tryLoadProjectConfigurationFileAsync( API_EXTRACTOR_CONFIG_SPECIFICATION, taskSession.logger.terminal @@ -202,7 +202,7 @@ export default class ApiExtractorPlugin implements IHeftTaskPlugin { buildFolder: heftConfiguration.buildFolderPath, production: taskSession.parameters.production, scopedLogger: taskSession.logger, - alwaysShowChangedApiReportDiffOnNonLocalBuild + enableApiReportConsoleDiff }); } } diff --git a/heft-plugins/heft-api-extractor-plugin/src/ApiExtractorRunner.ts b/heft-plugins/heft-api-extractor-plugin/src/ApiExtractorRunner.ts index 4d4651b1e7a..420189605e6 100644 --- a/heft-plugins/heft-api-extractor-plugin/src/ApiExtractorRunner.ts +++ b/heft-plugins/heft-api-extractor-plugin/src/ApiExtractorRunner.ts @@ -41,9 +41,9 @@ export interface IApiExtractorRunnerConfiguration { scopedLogger: IScopedLogger; /** - * {@inheritdoc IApiExtractorTaskConfiguration.alwaysShowChangedApiReportDiffOnNonLocalBuild} + * {@inheritdoc IApiExtractorTaskConfiguration.enableApiReportConsoleDiff} */ - alwaysShowChangedApiReportDiffOnNonLocalBuild: boolean | undefined; + enableApiReportConsoleDiff: boolean | undefined; } const MIN_SUPPORTED_MAJOR_VERSION: number = 7; @@ -59,7 +59,7 @@ export async function invokeApiExtractorAsync( production, typescriptPackagePath, apiExtractorConfiguration, - alwaysShowChangedApiReportDiffOnNonLocalBuild + enableApiReportConsoleDiff } = configuration; const { terminal } = scopedLogger; @@ -80,7 +80,7 @@ export async function invokeApiExtractorAsync( typescriptCompilerFolder: typescriptPackagePath, // Always show verbose messages - we'll decide what to do with them in the callback showVerboseMessages: true, - alwaysShowChangedApiReportDiffOnNonLocalBuild, + enableApiReportConsoleDiff, messageCallback: (message: TApiExtractor.ExtractorMessage) => { const { logLevel, sourceFilePath, messageId, text, sourceFileLine, sourceFileColumn } = message; switch (logLevel) { diff --git a/heft-plugins/heft-api-extractor-plugin/src/schemas/api-extractor-task.schema.json b/heft-plugins/heft-api-extractor-plugin/src/schemas/api-extractor-task.schema.json index ae3fd7b525c..5b32ae5ea19 100644 --- a/heft-plugins/heft-api-extractor-plugin/src/schemas/api-extractor-task.schema.json +++ b/heft-plugins/heft-api-extractor-plugin/src/schemas/api-extractor-task.schema.json @@ -27,9 +27,9 @@ "description": "If set to true, api-extractor will be run even in watch mode. This option defaults to false." }, - "alwaysShowChangedApiReportDiffOnNonLocalBuild": { + "enableApiReportConsoleDiff": { "type": "boolean", - "description": "If set to true, API Extractor will print a diff of the API report file if it's changed in a non-local build, regardless of the verbosity level. This corresponds to API Extractor's `IExtractorInvokeOptions.alwaysShowChangedApiReportDiffOnNonLocalBuild` API option. This option defaults to false." + "description": "If set to true, API Extractor will print a diff of the API report file if it's changed in a non-local build, regardless of the verbosity level. This corresponds to API Extractor's `IExtractorInvokeOptions.enableApiReportConsoleDiff` API option. This option defaults to false." } } } diff --git a/rigs/local-node-rig/profiles/default/config/api-extractor-task.json b/rigs/local-node-rig/profiles/default/config/api-extractor-task.json index 58788c74142..b8f662c27aa 100644 --- a/rigs/local-node-rig/profiles/default/config/api-extractor-task.json +++ b/rigs/local-node-rig/profiles/default/config/api-extractor-task.json @@ -3,5 +3,5 @@ "extends": "@rushstack/heft-node-rig/profiles/default/config/api-extractor-task.json", - "alwaysShowChangedApiReportDiffOnNonLocalBuild": true + "enableApiReportConsoleDiff": true } From 68bbf4a35b1ef933f67ff78594cd5b757a7e2d84 Mon Sep 17 00:00:00 2001 From: Ian Clanton-Thuon Date: Mon, 3 Nov 2025 14:10:55 -0800 Subject: [PATCH 08/17] Use destructuring on extractorConfig. --- apps/api-extractor/src/api/Extractor.ts | 80 ++++++++++++++----------- 1 file changed, 46 insertions(+), 34 deletions(-) diff --git a/apps/api-extractor/src/api/Extractor.ts b/apps/api-extractor/src/api/Extractor.ts index e08c6e92a88..244c4d6cc27 100644 --- a/apps/api-extractor/src/api/Extractor.ts +++ b/apps/api-extractor/src/api/Extractor.ts @@ -201,6 +201,26 @@ export class Extractor { * Invoke API Extractor using an already prepared `ExtractorConfig` object. */ public static invoke(extractorConfig: ExtractorConfig, options?: IExtractorInvokeOptions): ExtractorResult { + const { + packageFolder, + messages, + tsdocConfiguration, + tsdocConfigFile: { filePath: tsdocConfigFilePath, fileNotFound: tsdocConfigFileNotFound }, + apiJsonFilePath, + newlineKind, + reportTempFolder, + reportFolder, + apiReportEnabled, + reportConfigs, + testMode, + rollupEnabled, + publicTrimmedFilePath, + alphaTrimmedFilePath, + betaTrimmedFilePath, + untrimmedFilePath, + tsdocMetadataEnabled, + tsdocMetadataFilePath + } = extractorConfig; const { localBuild = false, compilerState = CompilerState.create(extractorConfig, options), @@ -213,20 +233,20 @@ export class Extractor { const sourceMapper: SourceMapper = new SourceMapper(); const messageRouter: MessageRouter = new MessageRouter({ - workingPackageFolder: extractorConfig.packageFolder, + workingPackageFolder: packageFolder, messageCallback, - messagesConfig: extractorConfig.messages || {}, + messagesConfig: messages || {}, showVerboseMessages, showDiagnostics, - tsdocConfiguration: extractorConfig.tsdocConfiguration, + tsdocConfiguration, sourceMapper }); - if (extractorConfig.tsdocConfigFile.filePath && !extractorConfig.tsdocConfigFile.fileNotFound) { - if (!Path.isEqual(extractorConfig.tsdocConfigFile.filePath, ExtractorConfig._tsdocBaseFilePath)) { + if (tsdocConfigFilePath && !tsdocConfigFileNotFound) { + if (!Path.isEqual(tsdocConfigFilePath, ExtractorConfig._tsdocBaseFilePath)) { messageRouter.logVerbose( ConsoleMessageId.UsingCustomTSDocConfig, - 'Using custom TSDoc config from ' + extractorConfig.tsdocConfigFile.filePath + `Using custom TSDoc config from ${tsdocConfigFilePath}` ); } } @@ -248,9 +268,7 @@ export class Extractor { messageRouter.logDiagnosticHeader('TSDoc configuration'); // Convert the TSDocConfiguration into a tsdoc.json representation - const combinedConfigFile: TSDocConfigFile = TSDocConfigFile.loadFromParser( - extractorConfig.tsdocConfiguration - ); + const combinedConfigFile: TSDocConfigFile = TSDocConfigFile.loadFromParser(tsdocConfiguration); const serializedTSDocConfig: object = MessageRouter.buildJsonDumpObject( combinedConfigFile.saveToObject() ); @@ -278,17 +296,14 @@ export class Extractor { } if (modelBuilder.docModelEnabled) { - messageRouter.logVerbose( - ConsoleMessageId.WritingDocModelFile, - 'Writing: ' + extractorConfig.apiJsonFilePath - ); - apiPackage.saveToJsonFile(extractorConfig.apiJsonFilePath, { + messageRouter.logVerbose(ConsoleMessageId.WritingDocModelFile, `Writing: ${apiJsonFilePath}`); + apiPackage.saveToJsonFile(apiJsonFilePath, { toolPackage: Extractor.packageName, toolVersion: Extractor.version, - newlineConversion: extractorConfig.newlineKind, + newlineConversion: newlineKind, ensureFolderExists: true, - testMode: extractorConfig.testMode + testMode }); } @@ -297,8 +312,8 @@ export class Extractor { collector, extractorConfig, messageRouter, - extractorConfig.reportTempFolder, - extractorConfig.reportFolder, + reportTempFolder, + reportFolder, reportConfig, localBuild, enableApiReportConsoleDiff @@ -306,45 +321,42 @@ export class Extractor { } let anyReportChanged: boolean = false; - if (extractorConfig.apiReportEnabled) { - for (const reportConfig of extractorConfig.reportConfigs) { + if (apiReportEnabled) { + for (const reportConfig of reportConfigs) { anyReportChanged = writeApiReport(reportConfig) || anyReportChanged; } } - if (extractorConfig.rollupEnabled) { + if (rollupEnabled) { Extractor._generateRollupDtsFile( collector, - extractorConfig.publicTrimmedFilePath, + publicTrimmedFilePath, DtsRollupKind.PublicRelease, - extractorConfig.newlineKind + newlineKind ); Extractor._generateRollupDtsFile( collector, - extractorConfig.alphaTrimmedFilePath, + alphaTrimmedFilePath, DtsRollupKind.AlphaRelease, - extractorConfig.newlineKind + newlineKind ); Extractor._generateRollupDtsFile( collector, - extractorConfig.betaTrimmedFilePath, + betaTrimmedFilePath, DtsRollupKind.BetaRelease, - extractorConfig.newlineKind + newlineKind ); Extractor._generateRollupDtsFile( collector, - extractorConfig.untrimmedFilePath, + untrimmedFilePath, DtsRollupKind.InternalRelease, - extractorConfig.newlineKind + newlineKind ); } - if (extractorConfig.tsdocMetadataEnabled) { + if (tsdocMetadataEnabled) { // Write the tsdoc-metadata.json file for this project - PackageMetadataManager.writeTsdocMetadataFile( - extractorConfig.tsdocMetadataFilePath, - extractorConfig.newlineKind - ); + PackageMetadataManager.writeTsdocMetadataFile(tsdocMetadataFilePath, newlineKind); } // Show all the messages that we collected during analysis From 0079911f283c8d7f1c5068edd208b1d0fb208aef Mon Sep 17 00:00:00 2001 From: Ian Clanton-Thuon Date: Mon, 3 Nov 2025 15:32:52 -0800 Subject: [PATCH 09/17] fixup! Rename alwaysShowChangedApiReportDiffOnNonLocalBuild to enableApiReportConsoleDiff. --- .../api-extractor-report-diff_2025-11-03-02-36.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/changes/@microsoft/api-extractor/api-extractor-report-diff_2025-11-03-02-36.json b/common/changes/@microsoft/api-extractor/api-extractor-report-diff_2025-11-03-02-36.json index 6c798d49e9c..6731f27d780 100644 --- a/common/changes/@microsoft/api-extractor/api-extractor-report-diff_2025-11-03-02-36.json +++ b/common/changes/@microsoft/api-extractor/api-extractor-report-diff_2025-11-03-02-36.json @@ -2,7 +2,7 @@ "changes": [ { "packageName": "@microsoft/api-extractor", - "comment": "Print a diff of the API report file if it's changed in a non-local build in verbose mode or if the new `enableApiReportConsoleDiff` extractor option is set to `true`.", + "comment": "Add a new setting `IExtractorInvokeOptions.enableApiReportConsoleDiff` that makes build logs easier to diagnose by printing a diff of any changes to API report files (*.api.md).", "type": "minor" } ], From 4ab41bfeed4b716e490bb71077326396b5251984 Mon Sep 17 00:00:00 2001 From: Ian Clanton-Thuon Date: Mon, 3 Nov 2025 15:34:57 -0800 Subject: [PATCH 10/17] Add a --api-report-diff flag. --- apps/api-extractor/src/cli/RunAction.ts | 36 ++++++++++++------- ...xtractor-report-diff_2025-11-03-23-34.json | 10 ++++++ 2 files changed, 33 insertions(+), 13 deletions(-) create mode 100644 common/changes/@microsoft/api-extractor/api-extractor-report-diff_2025-11-03-23-34.json diff --git a/apps/api-extractor/src/cli/RunAction.ts b/apps/api-extractor/src/cli/RunAction.ts index 6eeb0cb17ee..4cf958e177c 100644 --- a/apps/api-extractor/src/cli/RunAction.ts +++ b/apps/api-extractor/src/cli/RunAction.ts @@ -24,10 +24,11 @@ import { ExtractorConfig, type IExtractorConfigPrepareOptions } from '../api/Ext export class RunAction extends CommandLineAction { private readonly _configFileParameter: CommandLineStringParameter; - private readonly _localParameter: CommandLineFlagParameter; - private readonly _verboseParameter: CommandLineFlagParameter; + private readonly _localFlag: CommandLineFlagParameter; + private readonly _verboseFlag: CommandLineFlagParameter; private readonly _diagnosticsParameter: CommandLineFlagParameter; - private readonly _typescriptCompilerFolder: CommandLineStringParameter; + private readonly _typescriptCompilerFolderParameter: CommandLineStringParameter; + private readonly _apiReportConsoleDiffFlag: CommandLineFlagParameter; public constructor(parser: ApiExtractorCommandLine) { super({ @@ -43,7 +44,7 @@ export class RunAction extends CommandLineAction { description: `Use the specified ${ExtractorConfig.FILENAME} file path, rather than guessing its location` }); - this._localParameter = this.defineFlagParameter({ + this._localFlag = this.defineFlagParameter({ parameterLongName: '--local', parameterShortName: '-l', description: @@ -53,7 +54,7 @@ export class RunAction extends CommandLineAction { ' report file is automatically copied in a local build.' }); - this._verboseParameter = this.defineFlagParameter({ + this._verboseFlag = this.defineFlagParameter({ parameterLongName: '--verbose', parameterShortName: '-v', description: 'Show additional informational messages in the output.' @@ -66,7 +67,7 @@ export class RunAction extends CommandLineAction { ' This flag also enables the "--verbose" flag.' }); - this._typescriptCompilerFolder = this.defineStringParameter({ + this._typescriptCompilerFolderParameter = this.defineStringParameter({ parameterLongName: '--typescript-compiler-folder', argumentName: 'PATH', description: @@ -76,13 +77,21 @@ export class RunAction extends CommandLineAction { ' "--typescriptCompilerFolder" option to specify the folder path where you installed the TypeScript package,' + " and API Extractor's compiler will use those system typings instead." }); + + this._apiReportConsoleDiffFlag = this.defineFlagParameter({ + parameterLongName: '--api-report-diff', + description: + 'If provided, then any differences between the actual and expected API reports will be ' + + 'printed on the console. Note that the diff is not printed if the expected API report file has not been ' + + 'created yet.' + }); } protected override async onExecuteAsync(): Promise { const lookup: PackageJsonLookup = new PackageJsonLookup(); let configFilename: string; - let typescriptCompilerFolder: string | undefined = this._typescriptCompilerFolder.value; + let typescriptCompilerFolder: string | undefined = this._typescriptCompilerFolderParameter.value; if (typescriptCompilerFolder) { typescriptCompilerFolder = path.normalize(typescriptCompilerFolder); @@ -93,17 +102,17 @@ export class RunAction extends CommandLineAction { : undefined; if (!typescriptCompilerPackageJson) { throw new Error( - `The path specified in the ${this._typescriptCompilerFolder.longName} parameter is not a package.` + `The path specified in the ${this._typescriptCompilerFolderParameter.longName} parameter is not a package.` ); } else if (typescriptCompilerPackageJson.name !== 'typescript') { throw new Error( - `The path specified in the ${this._typescriptCompilerFolder.longName} parameter is not a TypeScript` + + `The path specified in the ${this._typescriptCompilerFolderParameter.longName} parameter is not a TypeScript` + ' compiler package.' ); } } else { throw new Error( - `The path specified in the ${this._typescriptCompilerFolder.longName} parameter does not exist.` + `The path specified in the ${this._typescriptCompilerFolderParameter.longName} parameter does not exist.` ); } } @@ -136,10 +145,11 @@ export class RunAction extends CommandLineAction { } const extractorResult: ExtractorResult = Extractor.invoke(extractorConfig, { - localBuild: this._localParameter.value, - showVerboseMessages: this._verboseParameter.value, + localBuild: this._localFlag.value, + showVerboseMessages: this._verboseFlag.value, showDiagnostics: this._diagnosticsParameter.value, - typescriptCompilerFolder: typescriptCompilerFolder + typescriptCompilerFolder: typescriptCompilerFolder, + enableApiReportConsoleDiff: this._apiReportConsoleDiffFlag.value }); if (extractorResult.succeeded) { diff --git a/common/changes/@microsoft/api-extractor/api-extractor-report-diff_2025-11-03-23-34.json b/common/changes/@microsoft/api-extractor/api-extractor-report-diff_2025-11-03-23-34.json new file mode 100644 index 00000000000..22f7f92a210 --- /dev/null +++ b/common/changes/@microsoft/api-extractor/api-extractor-report-diff_2025-11-03-23-34.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@microsoft/api-extractor", + "comment": "Add a `--api-report-diff` CLI flag that causes a diff of any changes to API report files (*.api.md) to be printed.", + "type": "minor" + } + ], + "packageName": "@microsoft/api-extractor" +} \ No newline at end of file From 6e2a5b74d1116842a41bb87a1c1b84c489d0087b Mon Sep 17 00:00:00 2001 From: Ian Clanton-Thuon Date: Mon, 3 Nov 2025 15:43:13 -0800 Subject: [PATCH 11/17] Rename the api-extractor-task.json enableApiReportConsoleDiff option to enableApiReportDiff. --- ...pi-extractor-report-diff_2025-11-03-02-53.json | 2 +- .../src/ApiExtractorPlugin.ts | 15 +++++++++------ .../src/ApiExtractorRunner.ts | 4 ++-- .../src/schemas/api-extractor-task.schema.json | 2 +- .../default/config/api-extractor-task.json | 2 +- 5 files changed, 14 insertions(+), 11 deletions(-) diff --git a/common/changes/@rushstack/heft-api-extractor-plugin/api-extractor-report-diff_2025-11-03-02-53.json b/common/changes/@rushstack/heft-api-extractor-plugin/api-extractor-report-diff_2025-11-03-02-53.json index a6514c569fb..76debfc8888 100644 --- a/common/changes/@rushstack/heft-api-extractor-plugin/api-extractor-report-diff_2025-11-03-02-53.json +++ b/common/changes/@rushstack/heft-api-extractor-plugin/api-extractor-report-diff_2025-11-03-02-53.json @@ -2,7 +2,7 @@ "changes": [ { "packageName": "@rushstack/heft-api-extractor-plugin", - "comment": "Expose a `IExtractorInvokeOptions.enableApiReportConsoleDiff` config option to print a diff if the API report file exists and is changed during a non-local build. This is useful for diagnosing issues that only show up in CI.", + "comment": "Include a `enableApiReportDiff` option in the `config/api-extractor-task.json` config file that, when set to `true`, causes a diff of the API report (*.api.md) file is changed during a non-local build. This is useful for diagnosing issues that only show up in CI.", "type": "minor" } ], diff --git a/heft-plugins/heft-api-extractor-plugin/src/ApiExtractorPlugin.ts b/heft-plugins/heft-api-extractor-plugin/src/ApiExtractorPlugin.ts index 8ab3ebdc5cd..585621350ae 100644 --- a/heft-plugins/heft-api-extractor-plugin/src/ApiExtractorPlugin.ts +++ b/heft-plugins/heft-api-extractor-plugin/src/ApiExtractorPlugin.ts @@ -168,11 +168,14 @@ export default class ApiExtractorPlugin implements IHeftTaskPlugin { apiExtractor: typeof TApiExtractor, apiExtractorConfiguration: TApiExtractor.ExtractorConfig ): Promise { - const { runInWatchMode, useProjectTypescriptVersion, enableApiReportConsoleDiff } = - (await heftConfiguration.tryLoadProjectConfigurationFileAsync( - API_EXTRACTOR_CONFIG_SPECIFICATION, - taskSession.logger.terminal - )) ?? {}; + const { + runInWatchMode, + useProjectTypescriptVersion, + enableApiReportConsoleDiff: enableApiReportDiff + } = (await heftConfiguration.tryLoadProjectConfigurationFileAsync( + API_EXTRACTOR_CONFIG_SPECIFICATION, + taskSession.logger.terminal + )) ?? {}; if (runOptions.requestRun) { if (!runInWatchMode) { @@ -202,7 +205,7 @@ export default class ApiExtractorPlugin implements IHeftTaskPlugin { buildFolder: heftConfiguration.buildFolderPath, production: taskSession.parameters.production, scopedLogger: taskSession.logger, - enableApiReportConsoleDiff + enableApiReportDiff }); } } diff --git a/heft-plugins/heft-api-extractor-plugin/src/ApiExtractorRunner.ts b/heft-plugins/heft-api-extractor-plugin/src/ApiExtractorRunner.ts index 420189605e6..378cc5d73f8 100644 --- a/heft-plugins/heft-api-extractor-plugin/src/ApiExtractorRunner.ts +++ b/heft-plugins/heft-api-extractor-plugin/src/ApiExtractorRunner.ts @@ -43,7 +43,7 @@ export interface IApiExtractorRunnerConfiguration { /** * {@inheritdoc IApiExtractorTaskConfiguration.enableApiReportConsoleDiff} */ - enableApiReportConsoleDiff: boolean | undefined; + enableApiReportDiff: boolean | undefined; } const MIN_SUPPORTED_MAJOR_VERSION: number = 7; @@ -59,7 +59,7 @@ export async function invokeApiExtractorAsync( production, typescriptPackagePath, apiExtractorConfiguration, - enableApiReportConsoleDiff + enableApiReportDiff: enableApiReportConsoleDiff } = configuration; const { terminal } = scopedLogger; diff --git a/heft-plugins/heft-api-extractor-plugin/src/schemas/api-extractor-task.schema.json b/heft-plugins/heft-api-extractor-plugin/src/schemas/api-extractor-task.schema.json index 5b32ae5ea19..6e3e760ba28 100644 --- a/heft-plugins/heft-api-extractor-plugin/src/schemas/api-extractor-task.schema.json +++ b/heft-plugins/heft-api-extractor-plugin/src/schemas/api-extractor-task.schema.json @@ -27,7 +27,7 @@ "description": "If set to true, api-extractor will be run even in watch mode. This option defaults to false." }, - "enableApiReportConsoleDiff": { + "enableApiReportDiff": { "type": "boolean", "description": "If set to true, API Extractor will print a diff of the API report file if it's changed in a non-local build, regardless of the verbosity level. This corresponds to API Extractor's `IExtractorInvokeOptions.enableApiReportConsoleDiff` API option. This option defaults to false." } diff --git a/rigs/local-node-rig/profiles/default/config/api-extractor-task.json b/rigs/local-node-rig/profiles/default/config/api-extractor-task.json index b8f662c27aa..e4e2ebf9b1f 100644 --- a/rigs/local-node-rig/profiles/default/config/api-extractor-task.json +++ b/rigs/local-node-rig/profiles/default/config/api-extractor-task.json @@ -3,5 +3,5 @@ "extends": "@rushstack/heft-node-rig/profiles/default/config/api-extractor-task.json", - "enableApiReportConsoleDiff": true + "enableApiReportDiff": true } From bf25113d58420f33317f01caba00cf1ebbf99d04 Mon Sep 17 00:00:00 2001 From: Ian Clanton-Thuon Date: Mon, 3 Nov 2025 15:45:03 -0800 Subject: [PATCH 12/17] fixup! Add diff printing to API Extractor's report issue logging. --- apps/api-extractor/src/api/Extractor.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/api-extractor/src/api/Extractor.ts b/apps/api-extractor/src/api/Extractor.ts index 244c4d6cc27..68045174a9e 100644 --- a/apps/api-extractor/src/api/Extractor.ts +++ b/apps/api-extractor/src/api/Extractor.ts @@ -466,7 +466,7 @@ export class Extractor { logFunction( ConsoleMessageId.ApiReportDiff, - 'Changes to the API report:\n' + Diff.formatPatch(patch) + 'Changes to the API report:\n\n' + Diff.formatPatch(patch) ); } } else { From 1da84d2afcce378d9c55a218d12a1a9d1f669bb7 Mon Sep 17 00:00:00 2001 From: Ian Clanton-Thuon Date: Mon, 3 Nov 2025 15:46:13 -0800 Subject: [PATCH 13/17] fixup! Rename the api-extractor-task.json enableApiReportConsoleDiff option to enableApiReportDiff. --- .../src/ApiExtractorPlugin.ts | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/heft-plugins/heft-api-extractor-plugin/src/ApiExtractorPlugin.ts b/heft-plugins/heft-api-extractor-plugin/src/ApiExtractorPlugin.ts index 585621350ae..be9720b43c2 100644 --- a/heft-plugins/heft-api-extractor-plugin/src/ApiExtractorPlugin.ts +++ b/heft-plugins/heft-api-extractor-plugin/src/ApiExtractorPlugin.ts @@ -57,7 +57,7 @@ export interface IApiExtractorTaskConfiguration { * a non-local build, regardless of the verbosity level. This corresponds to API Extractor's * `IExtractorInvokeOptions.enableApiReportConsoleDiff` API option. This option defaults to false */ - enableApiReportConsoleDiff?: boolean; + enableApiReportDiff?: boolean; } export default class ApiExtractorPlugin implements IHeftTaskPlugin { @@ -168,14 +168,11 @@ export default class ApiExtractorPlugin implements IHeftTaskPlugin { apiExtractor: typeof TApiExtractor, apiExtractorConfiguration: TApiExtractor.ExtractorConfig ): Promise { - const { - runInWatchMode, - useProjectTypescriptVersion, - enableApiReportConsoleDiff: enableApiReportDiff - } = (await heftConfiguration.tryLoadProjectConfigurationFileAsync( - API_EXTRACTOR_CONFIG_SPECIFICATION, - taskSession.logger.terminal - )) ?? {}; + const { runInWatchMode, useProjectTypescriptVersion, enableApiReportDiff } = + (await heftConfiguration.tryLoadProjectConfigurationFileAsync( + API_EXTRACTOR_CONFIG_SPECIFICATION, + taskSession.logger.terminal + )) ?? {}; if (runOptions.requestRun) { if (!runInWatchMode) { From cbfd99d1839db1087b30d561fb74ac0cadee4e54 Mon Sep 17 00:00:00 2001 From: Ian Clanton-Thuon Date: Mon, 3 Nov 2025 17:11:09 -0800 Subject: [PATCH 14/17] Rationalize names. --- apps/api-extractor/src/api/Extractor.ts | 14 +++++++------- apps/api-extractor/src/cli/RunAction.ts | 4 ++-- ...api-extractor-report-diff_2025-11-03-02-36.json | 2 +- ...api-extractor-report-diff_2025-11-03-23-34.json | 2 +- ...api-extractor-report-diff_2025-11-03-02-53.json | 2 +- common/reviews/api/api-extractor.api.md | 2 +- .../src/ApiExtractorPlugin.ts | 8 ++++---- .../src/ApiExtractorRunner.ts | 8 ++++---- .../src/schemas/api-extractor-task.schema.json | 4 ++-- .../default/config/api-extractor-task.json | 2 +- 10 files changed, 24 insertions(+), 24 deletions(-) diff --git a/apps/api-extractor/src/api/Extractor.ts b/apps/api-extractor/src/api/Extractor.ts index 68045174a9e..21a9631ad4c 100644 --- a/apps/api-extractor/src/api/Extractor.ts +++ b/apps/api-extractor/src/api/Extractor.ts @@ -99,7 +99,7 @@ export interface IExtractorInvokeOptions { * @remarks * The diff is not printed if the expected API report file has not been created yet. */ - enableApiReportConsoleDiff?: boolean; + printApiReportDiff?: boolean; } /** @@ -227,7 +227,7 @@ export class Extractor { messageCallback, showVerboseMessages = false, showDiagnostics = false, - enableApiReportConsoleDiff = false + printApiReportDiff = false } = options ?? {}; const sourceMapper: SourceMapper = new SourceMapper(); @@ -316,7 +316,7 @@ export class Extractor { reportFolder, reportConfig, localBuild, - enableApiReportConsoleDiff + printApiReportDiff ); } @@ -391,7 +391,7 @@ export class Extractor { * @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 enableApiReportConsoleDiff - {@link IExtractorInvokeOptions.enableApiReportConsoleDiff} + * @param printApiReportDiff - {@link IExtractorInvokeOptions.printApiReportDiff} * * @returns Whether or not the newly generated report differs from the existing report (if one exists). */ @@ -403,7 +403,7 @@ export class Extractor { reportDirectoryPath: string, reportConfig: IExtractorConfigApiReport, localBuild: boolean, - enableApiReportConsoleDiff: boolean + printApiReportDiff: boolean ): boolean { let apiReportChanged: boolean = false; @@ -450,7 +450,7 @@ export class Extractor { ` See the Git repo documentation for more info.` ); - if (messageRouter.showVerboseMessages || enableApiReportConsoleDiff) { + if (messageRouter.showVerboseMessages || printApiReportDiff) { const Diff: typeof import('diff') = require('diff'); const patch: import('diff').StructuredPatch = Diff.structuredPatch( expectedApiReportShortPath, @@ -460,7 +460,7 @@ export class Extractor { ); const logFunction: | (typeof MessageRouter.prototype)['logWarning'] - | (typeof MessageRouter.prototype)['logVerbose'] = enableApiReportConsoleDiff + | (typeof MessageRouter.prototype)['logVerbose'] = printApiReportDiff ? messageRouter.logWarning.bind(messageRouter) : messageRouter.logVerbose.bind(messageRouter); diff --git a/apps/api-extractor/src/cli/RunAction.ts b/apps/api-extractor/src/cli/RunAction.ts index 4cf958e177c..7ed7071638b 100644 --- a/apps/api-extractor/src/cli/RunAction.ts +++ b/apps/api-extractor/src/cli/RunAction.ts @@ -79,7 +79,7 @@ export class RunAction extends CommandLineAction { }); this._apiReportConsoleDiffFlag = this.defineFlagParameter({ - parameterLongName: '--api-report-diff', + parameterLongName: '--print-api-report-diff', description: 'If provided, then any differences between the actual and expected API reports will be ' + 'printed on the console. Note that the diff is not printed if the expected API report file has not been ' + @@ -149,7 +149,7 @@ export class RunAction extends CommandLineAction { showVerboseMessages: this._verboseFlag.value, showDiagnostics: this._diagnosticsParameter.value, typescriptCompilerFolder: typescriptCompilerFolder, - enableApiReportConsoleDiff: this._apiReportConsoleDiffFlag.value + printApiReportDiff: this._apiReportConsoleDiffFlag.value }); if (extractorResult.succeeded) { diff --git a/common/changes/@microsoft/api-extractor/api-extractor-report-diff_2025-11-03-02-36.json b/common/changes/@microsoft/api-extractor/api-extractor-report-diff_2025-11-03-02-36.json index 6731f27d780..bf81b22bb9f 100644 --- a/common/changes/@microsoft/api-extractor/api-extractor-report-diff_2025-11-03-02-36.json +++ b/common/changes/@microsoft/api-extractor/api-extractor-report-diff_2025-11-03-02-36.json @@ -2,7 +2,7 @@ "changes": [ { "packageName": "@microsoft/api-extractor", - "comment": "Add a new setting `IExtractorInvokeOptions.enableApiReportConsoleDiff` that makes build logs easier to diagnose by printing a diff of any changes to API report files (*.api.md).", + "comment": "Add a new setting `IExtractorInvokeOptions.printApiReportDiff` that makes build logs easier to diagnose by printing a diff of any changes to API report files (*.api.md).", "type": "minor" } ], diff --git a/common/changes/@microsoft/api-extractor/api-extractor-report-diff_2025-11-03-23-34.json b/common/changes/@microsoft/api-extractor/api-extractor-report-diff_2025-11-03-23-34.json index 22f7f92a210..9af79bd525e 100644 --- a/common/changes/@microsoft/api-extractor/api-extractor-report-diff_2025-11-03-23-34.json +++ b/common/changes/@microsoft/api-extractor/api-extractor-report-diff_2025-11-03-23-34.json @@ -2,7 +2,7 @@ "changes": [ { "packageName": "@microsoft/api-extractor", - "comment": "Add a `--api-report-diff` CLI flag that causes a diff of any changes to API report files (*.api.md) to be printed.", + "comment": "Add a `--print-api-report-diff` CLI flag that causes a diff of any changes to API report files (*.api.md) to be printed.", "type": "minor" } ], diff --git a/common/changes/@rushstack/heft-api-extractor-plugin/api-extractor-report-diff_2025-11-03-02-53.json b/common/changes/@rushstack/heft-api-extractor-plugin/api-extractor-report-diff_2025-11-03-02-53.json index 76debfc8888..935a23d6e1a 100644 --- a/common/changes/@rushstack/heft-api-extractor-plugin/api-extractor-report-diff_2025-11-03-02-53.json +++ b/common/changes/@rushstack/heft-api-extractor-plugin/api-extractor-report-diff_2025-11-03-02-53.json @@ -2,7 +2,7 @@ "changes": [ { "packageName": "@rushstack/heft-api-extractor-plugin", - "comment": "Include a `enableApiReportDiff` option in the `config/api-extractor-task.json` config file that, when set to `true`, causes a diff of the API report (*.api.md) file is changed during a non-local build. This is useful for diagnosing issues that only show up in CI.", + "comment": "Include a `printApiReportDiff` option in the `config/api-extractor-task.json` config file that, when set to `true`, causes a diff of the API report (*.api.md) file is changed during a non-local build. This is useful for diagnosing issues that only show up in CI.", "type": "minor" } ], diff --git a/common/reviews/api/api-extractor.api.md b/common/reviews/api/api-extractor.api.md index b6c7bcdfdc5..379d33cce8a 100644 --- a/common/reviews/api/api-extractor.api.md +++ b/common/reviews/api/api-extractor.api.md @@ -287,7 +287,7 @@ export interface IExtractorConfigPrepareOptions { // @public export interface IExtractorInvokeOptions { compilerState?: CompilerState; - enableApiReportConsoleDiff?: boolean; + printApiReportDiff?: boolean; localBuild?: boolean; messageCallback?: (message: ExtractorMessage) => void; showDiagnostics?: boolean; diff --git a/heft-plugins/heft-api-extractor-plugin/src/ApiExtractorPlugin.ts b/heft-plugins/heft-api-extractor-plugin/src/ApiExtractorPlugin.ts index be9720b43c2..ee7b203bca3 100644 --- a/heft-plugins/heft-api-extractor-plugin/src/ApiExtractorPlugin.ts +++ b/heft-plugins/heft-api-extractor-plugin/src/ApiExtractorPlugin.ts @@ -55,9 +55,9 @@ export interface IApiExtractorTaskConfiguration { /** * If set to true, API Extractor will print a diff of the API report file if it's changed in * a non-local build, regardless of the verbosity level. This corresponds to API Extractor's - * `IExtractorInvokeOptions.enableApiReportConsoleDiff` API option. This option defaults to false + * `IExtractorInvokeOptions.printApiReportDiff` API option. This option defaults to false */ - enableApiReportDiff?: boolean; + printApiReportDiff?: boolean; } export default class ApiExtractorPlugin implements IHeftTaskPlugin { @@ -168,7 +168,7 @@ export default class ApiExtractorPlugin implements IHeftTaskPlugin { apiExtractor: typeof TApiExtractor, apiExtractorConfiguration: TApiExtractor.ExtractorConfig ): Promise { - const { runInWatchMode, useProjectTypescriptVersion, enableApiReportDiff } = + const { runInWatchMode, useProjectTypescriptVersion, printApiReportDiff } = (await heftConfiguration.tryLoadProjectConfigurationFileAsync( API_EXTRACTOR_CONFIG_SPECIFICATION, taskSession.logger.terminal @@ -202,7 +202,7 @@ export default class ApiExtractorPlugin implements IHeftTaskPlugin { buildFolder: heftConfiguration.buildFolderPath, production: taskSession.parameters.production, scopedLogger: taskSession.logger, - enableApiReportDiff + printApiReportDiff }); } } diff --git a/heft-plugins/heft-api-extractor-plugin/src/ApiExtractorRunner.ts b/heft-plugins/heft-api-extractor-plugin/src/ApiExtractorRunner.ts index 378cc5d73f8..091e2530ccd 100644 --- a/heft-plugins/heft-api-extractor-plugin/src/ApiExtractorRunner.ts +++ b/heft-plugins/heft-api-extractor-plugin/src/ApiExtractorRunner.ts @@ -41,9 +41,9 @@ export interface IApiExtractorRunnerConfiguration { scopedLogger: IScopedLogger; /** - * {@inheritdoc IApiExtractorTaskConfiguration.enableApiReportConsoleDiff} + * {@inheritdoc IApiExtractorTaskConfiguration.printApiReportDiff} */ - enableApiReportDiff: boolean | undefined; + printApiReportDiff: boolean | undefined; } const MIN_SUPPORTED_MAJOR_VERSION: number = 7; @@ -59,7 +59,7 @@ export async function invokeApiExtractorAsync( production, typescriptPackagePath, apiExtractorConfiguration, - enableApiReportDiff: enableApiReportConsoleDiff + printApiReportDiff: printApiReportDiff } = configuration; const { terminal } = scopedLogger; @@ -80,7 +80,7 @@ export async function invokeApiExtractorAsync( typescriptCompilerFolder: typescriptPackagePath, // Always show verbose messages - we'll decide what to do with them in the callback showVerboseMessages: true, - enableApiReportConsoleDiff, + printApiReportDiff, messageCallback: (message: TApiExtractor.ExtractorMessage) => { const { logLevel, sourceFilePath, messageId, text, sourceFileLine, sourceFileColumn } = message; switch (logLevel) { diff --git a/heft-plugins/heft-api-extractor-plugin/src/schemas/api-extractor-task.schema.json b/heft-plugins/heft-api-extractor-plugin/src/schemas/api-extractor-task.schema.json index 6e3e760ba28..ca30899d051 100644 --- a/heft-plugins/heft-api-extractor-plugin/src/schemas/api-extractor-task.schema.json +++ b/heft-plugins/heft-api-extractor-plugin/src/schemas/api-extractor-task.schema.json @@ -27,9 +27,9 @@ "description": "If set to true, api-extractor will be run even in watch mode. This option defaults to false." }, - "enableApiReportDiff": { + "printApiReportDiff": { "type": "boolean", - "description": "If set to true, API Extractor will print a diff of the API report file if it's changed in a non-local build, regardless of the verbosity level. This corresponds to API Extractor's `IExtractorInvokeOptions.enableApiReportConsoleDiff` API option. This option defaults to false." + "description": "If set to true, API Extractor will print a diff of the API report file if it's changed in a non-local build, regardless of the verbosity level. This corresponds to API Extractor's `IExtractorInvokeOptions.printApiReportDiff` API option. This option defaults to false." } } } diff --git a/rigs/local-node-rig/profiles/default/config/api-extractor-task.json b/rigs/local-node-rig/profiles/default/config/api-extractor-task.json index e4e2ebf9b1f..68c0e0f1185 100644 --- a/rigs/local-node-rig/profiles/default/config/api-extractor-task.json +++ b/rigs/local-node-rig/profiles/default/config/api-extractor-task.json @@ -3,5 +3,5 @@ "extends": "@rushstack/heft-node-rig/profiles/default/config/api-extractor-task.json", - "enableApiReportDiff": true + "printApiReportDiff": true } From d648c9b0e70080941c89b543ead06d9b63df5792 Mon Sep 17 00:00:00 2001 From: Ian Clanton-Thuon Date: Mon, 3 Nov 2025 17:14:02 -0800 Subject: [PATCH 15/17] fixup! Rationalize names. --- apps/api-extractor/src/cli/RunAction.ts | 6 +++--- common/reviews/api/api-extractor.api.md | 2 +- .../heft-api-extractor-plugin/src/ApiExtractorRunner.ts | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/api-extractor/src/cli/RunAction.ts b/apps/api-extractor/src/cli/RunAction.ts index 7ed7071638b..d0e60bb9326 100644 --- a/apps/api-extractor/src/cli/RunAction.ts +++ b/apps/api-extractor/src/cli/RunAction.ts @@ -28,7 +28,7 @@ export class RunAction extends CommandLineAction { private readonly _verboseFlag: CommandLineFlagParameter; private readonly _diagnosticsParameter: CommandLineFlagParameter; private readonly _typescriptCompilerFolderParameter: CommandLineStringParameter; - private readonly _apiReportConsoleDiffFlag: CommandLineFlagParameter; + private readonly _printApiReportDiffFlag: CommandLineFlagParameter; public constructor(parser: ApiExtractorCommandLine) { super({ @@ -78,7 +78,7 @@ export class RunAction extends CommandLineAction { " and API Extractor's compiler will use those system typings instead." }); - this._apiReportConsoleDiffFlag = this.defineFlagParameter({ + this._printApiReportDiffFlag = this.defineFlagParameter({ parameterLongName: '--print-api-report-diff', description: 'If provided, then any differences between the actual and expected API reports will be ' + @@ -149,7 +149,7 @@ export class RunAction extends CommandLineAction { showVerboseMessages: this._verboseFlag.value, showDiagnostics: this._diagnosticsParameter.value, typescriptCompilerFolder: typescriptCompilerFolder, - printApiReportDiff: this._apiReportConsoleDiffFlag.value + printApiReportDiff: this._printApiReportDiffFlag.value }); if (extractorResult.succeeded) { diff --git a/common/reviews/api/api-extractor.api.md b/common/reviews/api/api-extractor.api.md index 379d33cce8a..f44ecd8e92b 100644 --- a/common/reviews/api/api-extractor.api.md +++ b/common/reviews/api/api-extractor.api.md @@ -287,9 +287,9 @@ export interface IExtractorConfigPrepareOptions { // @public export interface IExtractorInvokeOptions { compilerState?: CompilerState; - printApiReportDiff?: boolean; localBuild?: boolean; messageCallback?: (message: ExtractorMessage) => void; + printApiReportDiff?: boolean; showDiagnostics?: boolean; showVerboseMessages?: boolean; typescriptCompilerFolder?: string; diff --git a/heft-plugins/heft-api-extractor-plugin/src/ApiExtractorRunner.ts b/heft-plugins/heft-api-extractor-plugin/src/ApiExtractorRunner.ts index 091e2530ccd..ae5fa860b2d 100644 --- a/heft-plugins/heft-api-extractor-plugin/src/ApiExtractorRunner.ts +++ b/heft-plugins/heft-api-extractor-plugin/src/ApiExtractorRunner.ts @@ -59,7 +59,7 @@ export async function invokeApiExtractorAsync( production, typescriptPackagePath, apiExtractorConfiguration, - printApiReportDiff: printApiReportDiff + printApiReportDiff } = configuration; const { terminal } = scopedLogger; From daa24df9004d63231c871170f1b66f61701c9230 Mon Sep 17 00:00:00 2001 From: Ian Clanton-Thuon Date: Mon, 3 Nov 2025 17:30:32 -0800 Subject: [PATCH 16/17] fixup! Rationalize names. --- apps/api-extractor/src/api/Extractor.ts | 40 ++++++++++++------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/apps/api-extractor/src/api/Extractor.ts b/apps/api-extractor/src/api/Extractor.ts index 21a9631ad4c..b48a323b094 100644 --- a/apps/api-extractor/src/api/Extractor.ts +++ b/apps/api-extractor/src/api/Extractor.ts @@ -449,26 +449,6 @@ export class Extractor { ` or perform a local build (which does this automatically).` + ` See the Git repo documentation for more info.` ); - - if (messageRouter.showVerboseMessages || printApiReportDiff) { - const Diff: typeof import('diff') = require('diff'); - const patch: import('diff').StructuredPatch = Diff.structuredPatch( - expectedApiReportShortPath, - actualApiReportShortPath, - expectedApiReportContent, - actualApiReportContent - ); - const logFunction: - | (typeof MessageRouter.prototype)['logWarning'] - | (typeof MessageRouter.prototype)['logVerbose'] = printApiReportDiff - ? messageRouter.logWarning.bind(messageRouter) - : messageRouter.logVerbose.bind(messageRouter); - - logFunction( - ConsoleMessageId.ApiReportDiff, - 'Changes to the API report:\n\n' + Diff.formatPatch(patch) - ); - } } else { // For a local build, just copy the file automatically. messageRouter.logWarning( @@ -481,6 +461,26 @@ export class Extractor { convertLineEndings: extractorConfig.newlineKind }); } + + if (messageRouter.showVerboseMessages || printApiReportDiff) { + const Diff: typeof import('diff') = require('diff'); + const patch: import('diff').StructuredPatch = Diff.structuredPatch( + expectedApiReportShortPath, + actualApiReportShortPath, + expectedApiReportContent, + actualApiReportContent + ); + const logFunction: + | (typeof MessageRouter.prototype)['logWarning'] + | (typeof MessageRouter.prototype)['logVerbose'] = printApiReportDiff + ? messageRouter.logWarning.bind(messageRouter) + : messageRouter.logVerbose.bind(messageRouter); + + logFunction( + ConsoleMessageId.ApiReportDiff, + 'Changes to the API report:\n\n' + Diff.formatPatch(patch) + ); + } } else { messageRouter.logVerbose( ConsoleMessageId.ApiReportUnchanged, From 48b843647862307f8ef62fbbf9a74f524daca987 Mon Sep 17 00:00:00 2001 From: Ian Clanton-Thuon Date: Mon, 3 Nov 2025 17:31:04 -0800 Subject: [PATCH 17/17] Expand the heft plugin to use 'always', 'production', and 'never'. --- ...xtractor-report-diff_2025-11-03-02-53.json | 2 +- .../src/ApiExtractorPlugin.ts | 29 ++++++++++++------- .../schemas/api-extractor-task.schema.json | 5 ++-- .../default/config/api-extractor-task.json | 2 +- 4 files changed, 24 insertions(+), 14 deletions(-) diff --git a/common/changes/@rushstack/heft-api-extractor-plugin/api-extractor-report-diff_2025-11-03-02-53.json b/common/changes/@rushstack/heft-api-extractor-plugin/api-extractor-report-diff_2025-11-03-02-53.json index 935a23d6e1a..8ad9fac0149 100644 --- a/common/changes/@rushstack/heft-api-extractor-plugin/api-extractor-report-diff_2025-11-03-02-53.json +++ b/common/changes/@rushstack/heft-api-extractor-plugin/api-extractor-report-diff_2025-11-03-02-53.json @@ -2,7 +2,7 @@ "changes": [ { "packageName": "@rushstack/heft-api-extractor-plugin", - "comment": "Include a `printApiReportDiff` option in the `config/api-extractor-task.json` config file that, when set to `true`, causes a diff of the API report (*.api.md) file is changed during a non-local build. This is useful for diagnosing issues that only show up in CI.", + "comment": "Include a `printApiReportDiff` option in the `config/api-extractor-task.json` config file that, when set to `\"production\"` (and the `--production` flag is specified) or `\"always\"`, causes a diff of the API report (*.api.md) to be printed if the report is changed. This is useful for diagnosing issues that only show up in CI.", "type": "minor" } ], diff --git a/heft-plugins/heft-api-extractor-plugin/src/ApiExtractorPlugin.ts b/heft-plugins/heft-api-extractor-plugin/src/ApiExtractorPlugin.ts index ee7b203bca3..8ab77d46808 100644 --- a/heft-plugins/heft-api-extractor-plugin/src/ApiExtractorPlugin.ts +++ b/heft-plugins/heft-api-extractor-plugin/src/ApiExtractorPlugin.ts @@ -53,11 +53,13 @@ export interface IApiExtractorTaskConfiguration { runInWatchMode?: boolean; /** - * If set to true, API Extractor will print a diff of the API report file if it's changed in - * a non-local build, regardless of the verbosity level. This corresponds to API Extractor's - * `IExtractorInvokeOptions.printApiReportDiff` API option. This option defaults to false + * Controls whether API Extractor prints a diff of the API report file if it's changed. + * If set to `"production"`, this will only be printed if Heft is run in `--production` + * mode, and if set to `"always"`, this will always be printed if the API report is changed. + * This corresponds to API Extractor's `IExtractorInvokeOptions.printApiReportDiff` API option. + * This option defaults to `"never"`. */ - printApiReportDiff?: boolean; + printApiReportDiff?: 'production' | 'always' | 'never'; } export default class ApiExtractorPlugin implements IHeftTaskPlugin { @@ -168,11 +170,14 @@ export default class ApiExtractorPlugin implements IHeftTaskPlugin { apiExtractor: typeof TApiExtractor, apiExtractorConfiguration: TApiExtractor.ExtractorConfig ): Promise { - const { runInWatchMode, useProjectTypescriptVersion, printApiReportDiff } = - (await heftConfiguration.tryLoadProjectConfigurationFileAsync( - API_EXTRACTOR_CONFIG_SPECIFICATION, - taskSession.logger.terminal - )) ?? {}; + const { + runInWatchMode, + useProjectTypescriptVersion, + printApiReportDiff: printApiReportDiffOption + } = (await heftConfiguration.tryLoadProjectConfigurationFileAsync( + API_EXTRACTOR_CONFIG_SPECIFICATION, + taskSession.logger.terminal + )) ?? {}; if (runOptions.requestRun) { if (!runInWatchMode) { @@ -194,13 +199,17 @@ export default class ApiExtractorPlugin implements IHeftTaskPlugin { ); } + const production: boolean = taskSession.parameters.production; + const printApiReportDiff: boolean = + printApiReportDiffOption === 'always' || (printApiReportDiffOption === 'production' && production); + // Run API Extractor await invokeApiExtractorAsync({ apiExtractor, apiExtractorConfiguration, typescriptPackagePath, buildFolder: heftConfiguration.buildFolderPath, - production: taskSession.parameters.production, + production, scopedLogger: taskSession.logger, printApiReportDiff }); diff --git a/heft-plugins/heft-api-extractor-plugin/src/schemas/api-extractor-task.schema.json b/heft-plugins/heft-api-extractor-plugin/src/schemas/api-extractor-task.schema.json index ca30899d051..84e0fe3f689 100644 --- a/heft-plugins/heft-api-extractor-plugin/src/schemas/api-extractor-task.schema.json +++ b/heft-plugins/heft-api-extractor-plugin/src/schemas/api-extractor-task.schema.json @@ -28,8 +28,9 @@ }, "printApiReportDiff": { - "type": "boolean", - "description": "If set to true, API Extractor will print a diff of the API report file if it's changed in a non-local build, regardless of the verbosity level. This corresponds to API Extractor's `IExtractorInvokeOptions.printApiReportDiff` API option. This option defaults to false." + "type": "string", + "description": "Controls whether API Extractor prints a diff of the API report file if it's changed. If set to `\"production\"`, this will only be printed if Heft is run in `--production` mode, and if set to `\"always\"`, this will always be printed if the API report is changed. This corresponds to API Extractor's `IExtractorInvokeOptions.printApiReportDiff` API option. This option defaults to `\"never\"`.", + "enum": ["always", "production", "never"] } } } diff --git a/rigs/local-node-rig/profiles/default/config/api-extractor-task.json b/rigs/local-node-rig/profiles/default/config/api-extractor-task.json index 68c0e0f1185..c6cc966b418 100644 --- a/rigs/local-node-rig/profiles/default/config/api-extractor-task.json +++ b/rigs/local-node-rig/profiles/default/config/api-extractor-task.json @@ -3,5 +3,5 @@ "extends": "@rushstack/heft-node-rig/profiles/default/config/api-extractor-task.json", - "printApiReportDiff": true + "printApiReportDiff": "production" }