From 65230402e204c2306c766d84ecbe7afb01162adf Mon Sep 17 00:00:00 2001 From: Gabriel Dugny Date: Sun, 25 Jan 2026 11:33:41 +0100 Subject: [PATCH] fix: Support Prettier v3 in repo-tools (#27559) Signed-off-by: Gabriel Dugny --- .changeset/public-keys-sip.md | 5 + .prettierignore | 2 - packages/repo-tools/package.json | 3 +- .../api-reports/patchApiReportGeneration.ts | 12 +- .../src/commands/api-reports/common/index.ts | 2 +- .../api-reports/common/tryRunPrettier.ts | 107 ++++++++++++++++-- .../sql-reports/runSqlExtraction.ts | 14 +-- yarn.lock | 21 +++- 8 files changed, 140 insertions(+), 26 deletions(-) create mode 100644 .changeset/public-keys-sip.md diff --git a/.changeset/public-keys-sip.md b/.changeset/public-keys-sip.md new file mode 100644 index 0000000000..8254ac54d5 --- /dev/null +++ b/.changeset/public-keys-sip.md @@ -0,0 +1,5 @@ +--- +'@backstage/repo-tools': patch +--- + +Support Prettier v3 for api-reports diff --git a/.prettierignore b/.prettierignore index 7269620fe6..1ca4e07850 100644 --- a/.prettierignore +++ b/.prettierignore @@ -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 diff --git a/packages/repo-tools/package.json b/packages/repo-tools/package.json index e98735f62f..ea19cf66bd 100644 --- a/packages/repo-tools/package.json +++ b/packages/repo-tools/package.json @@ -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" }, diff --git a/packages/repo-tools/src/commands/api-reports/api-reports/patchApiReportGeneration.ts b/packages/repo-tools/src/commands/api-reports/api-reports/patchApiReportGeneration.ts index 982707e815..44441ac6a9 100644 --- a/packages/repo-tools/src/commands/api-reports/api-reports/patchApiReportGeneration.ts +++ b/packages/repo-tools/src/commands/api-reports/api-reports/patchApiReportGeneration.ts @@ -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`, + }); }; } diff --git a/packages/repo-tools/src/commands/api-reports/common/index.ts b/packages/repo-tools/src/commands/api-reports/common/index.ts index 34bd95bfbe..820f18fa54 100644 --- a/packages/repo-tools/src/commands/api-reports/common/index.ts +++ b/packages/repo-tools/src/commands/api-reports/common/index.ts @@ -14,5 +14,5 @@ * limitations under the License. */ -export { tryRunPrettier } from './tryRunPrettier'; +export { tryRunPrettier, tryRunPrettierAsync } from './tryRunPrettier'; export { logApiReportInstructions } from './logApiReportInstructions'; diff --git a/packages/repo-tools/src/commands/api-reports/common/tryRunPrettier.ts b/packages/repo-tools/src/commands/api-reports/common/tryRunPrettier.ts index 020853d726..174b68f499 100644 --- a/packages/repo-tools/src/commands/api-reports/common/tryRunPrettier.ts +++ b/packages/repo-tools/src/commands/api-reports/common/tryRunPrettier.ts @@ -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 { + 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); } diff --git a/packages/repo-tools/src/commands/api-reports/sql-reports/runSqlExtraction.ts b/packages/repo-tools/src/commands/api-reports/sql-reports/runSqlExtraction.ts index 9462412436..31a40b635c 100644 --- a/packages/repo-tools/src/commands/api-reports/sql-reports/runSqlExtraction.ts +++ b/packages/repo-tools/src/commands/api-reports/sql-reports/runSqlExtraction.ts @@ -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; diff --git a/yarn.lock b/yarn.lock index 97185a87a4..2f0dbafcba 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"