fix: Support Prettier v3 in repo-tools (#27559)

Signed-off-by: Gabriel Dugny <gabriel.dugny@believe.com>
This commit is contained in:
Gabriel Dugny
2026-01-25 11:33:41 +01:00
parent 4ad63b8d9f
commit 65230402e2
8 changed files with 140 additions and 26 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/repo-tools': patch
---
Support Prettier v3 for api-reports
-2
View File
@@ -14,10 +14,8 @@ type-docs
# old reports
api-report.md
api-report-*.md
# new reports
report.api.md
report-*.api.md
knip-report.md
cli-report.md
plugins/scaffolder-backend/sample-templates
+2 -1
View File
@@ -54,6 +54,7 @@
"@microsoft/api-documenter": "^7.28.1",
"@microsoft/api-extractor": "^7.55.1",
"@openapitools/openapi-generator-cli": "^2.7.0",
"@prettier/sync": "^0.6.1",
"@stoplight/spectral-core": "^1.18.0",
"@stoplight/spectral-formatters": "^1.1.0",
"@stoplight/spectral-functions": "^1.7.2",
@@ -99,7 +100,7 @@
"@microsoft/tsdoc": "*",
"@microsoft/tsdoc-config": "*",
"@useoptic/optic": "^1.0.0",
"prettier": "^2.8.1",
"prettier": "^2.8.1 || ^3.8.1",
"typedoc": "^0.28.0",
"typescript": "> 3.0.0"
},
@@ -17,7 +17,8 @@
import { ExtractorMessage } from '@microsoft/api-extractor';
import { AstDeclaration } from '@microsoft/api-extractor/lib/analyzer/AstDeclaration';
import { Program } from 'typescript';
import { tryRunPrettier } from '../common';
import { tryRunPrettier } from '../common/tryRunPrettier';
import { paths as cliPaths } from '../../../lib/paths';
let applied = false;
@@ -152,12 +153,17 @@ export function patchApiReportGeneration() {
* the middle of the process as API Extractor does a comparison of the contents of the old
* and new files during generation. This inserts the formatting just before that comparison.
*/
const content = originalGenerateReviewFileContent.call(
this,
collector,
...moreArgs,
);
return tryRunPrettier(content);
return tryRunPrettier(content, {
parser: 'markdown',
// We need a real-looking filepath for proper config resolution, not just a directory
// Ideally, the real filepath would be better, but it would require too much patching, for very little gain.
filepath: `${cliPaths.targetRoot}/report.api.md`,
});
};
}
@@ -14,5 +14,5 @@
* limitations under the License.
*/
export { tryRunPrettier } from './tryRunPrettier';
export { tryRunPrettier, tryRunPrettierAsync } from './tryRunPrettier';
export { logApiReportInstructions } from './logApiReportInstructions';
@@ -17,19 +17,104 @@
import { paths as cliPaths } from '../../../lib/paths';
import type { Config } from 'prettier';
/**
* Tries to run prettier asynchronously, with Prettier v2 or v3.
* @param content - The content to format.
* @param extraConfig - Additional prettier config options (defaults to markdown parser). Providing a filepath is recommended for proper config resolution.
* @returns A promise that resolves to the formatted content or the original content if the formatting fails.
* @internal
*/
export async function tryRunPrettierAsync(
content: string,
extraConfig: Config = { parser: 'markdown' },
): Promise<string> {
try {
const prettier = require('prettier') as typeof import('prettier');
// Filepath for proper config resolution
const filepath =
extraConfig.filepath ??
`${cliPaths.targetRoot}/should-not-be-ignored.any`;
const config =
(await prettier.resolveConfig(filepath, { editorconfig: true })) ?? {};
const formattedContent = prettier.format(content, {
...config,
...extraConfig,
});
// XXX: Remove once only v3 is supported
if (typeof formattedContent === 'string') {
return Promise.resolve(formattedContent);
}
return formattedContent;
} catch (e) {
return Promise.resolve(content);
}
}
/**
* Creates a synchronous prettier formatter function for v2 or v3 via `@prettier/sync`.
*
* @param prettierSync - The prettier instance, either v2 or v3 via `@prettier/sync`.
* @param extraConfig - Additional prettier config options (defaults to markdown parser)
* @returns A function that formats content using the prettier instance and config
*/
export function createPrettierSyncFormatter(
prettierSync:
| typeof import('prettier')
| typeof import('@prettier/sync').default,
extraConfig: Config = {},
): (content: string) => string {
return function tryRunPrettierInner(content: string): string {
try {
// We need a filepath for proper config resolution, not just a directory
const filepath =
extraConfig.filepath ??
`${cliPaths.targetRoot}/should-not-be-ignored.any`;
const resolveConfig =
// @ts-expect-error: v2 requires .sync, @prettier/sync v3 does not
prettierSync.resolveConfig?.sync ?? prettierSync.resolveConfig;
const config =
resolveConfig(filepath, {
editorconfig: true,
}) ?? {};
return prettierSync.format(content, {
...config,
...extraConfig,
});
} catch (e) {
return content;
}
};
}
/**
* Loads the prettier instance, either v2 or v3 via @prettier/sync.
* XXX: Remove once only v3 is supported
* @returns The prettier instance, either v2 or v3 via @prettier/sync.
*/
function loadPrettierSync():
| typeof import('prettier')
| typeof import('@prettier/sync').default {
const prettier = require('prettier') as typeof import('prettier');
if (prettier.version.startsWith('2')) {
return prettier;
}
return require('@prettier/sync').default;
}
/**
* Tries to run prettier synchronously, with Prettier v2 or v3.
* @param content - The content to format.
* @param extraConfig - Additional prettier config options (defaults to markdown parser)
* @returns The formatted content.
* @internal
*/
export function tryRunPrettier(
content: string,
extraConfig: Config = { parser: 'markdown' },
): string {
try {
const prettier = require('prettier') as typeof import('prettier');
const config = prettier.resolveConfig.sync(cliPaths.targetRoot) ?? {};
return prettier.format(content, {
...config,
...extraConfig,
});
} catch (e) {
return content;
}
const formatter = createPrettierSyncFormatter(
loadPrettierSync(),
extraConfig,
);
return formatter(content);
}
@@ -22,7 +22,7 @@ import { SchemaInfo } from './types';
import { getPgSchemaInfo } from './getPgSchemaInfo';
import { generateSqlReport } from './generateSqlReport';
import type { Knex } from 'knex';
import { logApiReportInstructions, tryRunPrettier } from '../common';
import { logApiReportInstructions, tryRunPrettierAsync } from '../common';
interface SqlExtractionOptions {
packageDirs: string[];
@@ -152,19 +152,19 @@ async function runSingleSqlExtraction(
break;
}
}
const report = tryRunPrettier(
const reportPath = cliPaths.resolveTargetRoot(
targetDir,
`report${migrationTarget === '.' ? '' : `-${migrationTarget}`}.sql.md`,
);
const report = await tryRunPrettierAsync(
generateSqlReport({
reportName,
failedDownMigration,
schemaInfo,
}),
{ filepath: reportPath },
);
const reportPath = cliPaths.resolveTargetRoot(
targetDir,
`report${migrationTarget === '.' ? '' : `-${migrationTarget}`}.sql.md`,
);
const existingReport = await fs.readFile(reportPath, 'utf8').catch(error => {
if (error.code === 'ENOENT') {
return undefined;
+20 -1
View File
@@ -7868,6 +7868,7 @@ __metadata:
"@microsoft/api-documenter": "npm:^7.28.1"
"@microsoft/api-extractor": "npm:^7.55.1"
"@openapitools/openapi-generator-cli": "npm:^2.7.0"
"@prettier/sync": "npm:^0.6.1"
"@stoplight/spectral-core": "npm:^1.18.0"
"@stoplight/spectral-formatters": "npm:^1.1.0"
"@stoplight/spectral-functions": "npm:^1.7.2"
@@ -7907,7 +7908,7 @@ __metadata:
"@microsoft/tsdoc": "*"
"@microsoft/tsdoc-config": "*"
"@useoptic/optic": ^1.0.0
prettier: ^2.8.1
prettier: ^2.8.1 || ^3.8.1
typedoc: ^0.28.0
typescript: "> 3.0.0"
peerDependenciesMeta:
@@ -14355,6 +14356,17 @@ __metadata:
languageName: node
linkType: hard
"@prettier/sync@npm:^0.6.1":
version: 0.6.1
resolution: "@prettier/sync@npm:0.6.1"
dependencies:
make-synchronized: "npm:^0.8.0"
peerDependencies:
prettier: "*"
checksum: 10/2c53cd4ee718e2ebd2fb31aa5ec4773f743b9c29fcc6db6794dc3553bc87aa8fe7db47b51add6809cab655520b7550329d1cce2ca837f6f4643991eff44abad1
languageName: node
linkType: hard
"@protobufjs/aspromise@npm:^1.1.1, @protobufjs/aspromise@npm:^1.1.2":
version: 1.1.2
resolution: "@protobufjs/aspromise@npm:1.1.2"
@@ -38013,6 +38025,13 @@ __metadata:
languageName: node
linkType: hard
"make-synchronized@npm:^0.8.0":
version: 0.8.0
resolution: "make-synchronized@npm:0.8.0"
checksum: 10/e744bafcd61ee1ecabe6fb2c295ecb4b06a7bfe4e844222b80b7a5ae80a4d27ba657abc4892d1c702fa2f6ae568d8505e801c1498fe1379dd824ded5483d978c
languageName: node
linkType: hard
"makeerror@npm:1.0.12":
version: 1.0.12
resolution: "makeerror@npm:1.0.12"