From e104de6e7b1ae001e5adbddce78a3a48a59cc448 Mon Sep 17 00:00:00 2001 From: Alan Agius <17563226+alan-agius4@users.noreply.github.com> Date: Tue, 27 Jan 2026 08:42:04 +0000 Subject: [PATCH 1/5] feat(@angular/cli): automatic formatting files modified by schematics This change introduces automatic formatting of files generated or modified during a schematic execution. When a schematic is executed, the CLI will now attempt to find and use the project's local Prettier installation to format all created or updated files. This ensures that generated code adheres to the project's established coding style without requiring developers to manually run a formatting command. If Prettier is not installed in the user's workspace, the formatting step will be gracefully skipped. This avoids adding a direct dependency on Prettier to the CLI and respects the user's project setup. The implementation leverages the Prettier CLI directly for a more robust and reliable formatting process. Closes #10777 --- .../schematics-command-module.ts | 12 +++- .../src/command-builder/utilities/prettier.ts | 61 +++++++++++++++++++ 2 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 packages/angular/cli/src/command-builder/utilities/prettier.ts diff --git a/packages/angular/cli/src/command-builder/schematics-command-module.ts b/packages/angular/cli/src/command-builder/schematics-command-module.ts index ef317700d1a6..f0846c7be82f 100644 --- a/packages/angular/cli/src/command-builder/schematics-command-module.ts +++ b/packages/angular/cli/src/command-builder/schematics-command-module.ts @@ -29,6 +29,7 @@ import { OtherOptions, } from './command-module'; import { Option, parseJsonSchemaToOptions } from './utilities/json-schema'; +import { formatFiles } from './utilities/prettier'; import { SchematicEngineHost } from './utilities/schematic-engine-host'; import { subscribeToWorkflow } from './utilities/schematic-workflow'; @@ -361,7 +362,16 @@ export abstract class SchematicsCommandModule if (executionOptions.dryRun) { logger.warn(`\nNOTE: The "--dry-run" option means no changes were made.`); + + return 0; + } + + if (files.size) { + // Note: we could use a task executor to format the files but this is simpler. + await formatFiles(this.context.root, files); } + + return 0; } catch (err) { // In case the workflow was not successful, show an appropriate error message. if (err instanceof UnsuccessfulWorkflowExecution) { @@ -376,8 +386,6 @@ export abstract class SchematicsCommandModule } finally { unsubscribe(); } - - return 0; } private getProjectName(): string | undefined { diff --git a/packages/angular/cli/src/command-builder/utilities/prettier.ts b/packages/angular/cli/src/command-builder/utilities/prettier.ts new file mode 100644 index 000000000000..ad5a3047f823 --- /dev/null +++ b/packages/angular/cli/src/command-builder/utilities/prettier.ts @@ -0,0 +1,61 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import { execFile } from 'node:child_process'; +import { createRequire } from 'node:module'; +import { dirname, extname, join, relative } from 'node:path'; +import { promisify } from 'node:util'; + +const execFileAsync = promisify(execFile); +let prettierCliPath: string | null | undefined; + +/** + * File types that can be formatted using Prettier. + */ +const fileTypes: ReadonlySet = new Set([ + '.ts', + '.html', + '.js', + '.mjs', + '.cjs', + '.json', + '.css', + '.less', + '.scss', + '.sass', +]); + +/** + * Formats files using Prettier. + * @param cwd The current working directory. + * @param files The files to format. + */ +export async function formatFiles(cwd: string, files: Set): Promise { + if (!files.size) { + return; + } + + if (prettierCliPath === undefined) { + try { + const prettierPath = createRequire(cwd + '/').resolve('prettier/package.json'); + prettierCliPath = join(dirname(prettierPath), 'bin/prettier.cjs'); + } catch { + // Prettier is not installed. + prettierCliPath = null; + } + } + + if (!prettierCliPath) { + return; + } + + await execFileAsync(prettierCliPath, [ + '--write', + ...[...files].filter((f) => fileTypes.has(extname(f))).map((f) => relative(cwd, f)), + ]); +} From fe825b05dfbee274da555464c5f4de1a6ffc50ec Mon Sep 17 00:00:00 2001 From: Alan Agius <17563226+alan-agius4@users.noreply.github.com> Date: Tue, 27 Jan 2026 16:38:23 +0000 Subject: [PATCH 2/5] fixup! feat(@angular/cli): automatic formatting files modified by schematics --- .../src/command-builder/schematics-command-module.ts | 10 +++++++++- .../cli/src/command-builder/utilities/prettier.ts | 6 +++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/packages/angular/cli/src/command-builder/schematics-command-module.ts b/packages/angular/cli/src/command-builder/schematics-command-module.ts index f0846c7be82f..5a8a8e7b3f3b 100644 --- a/packages/angular/cli/src/command-builder/schematics-command-module.ts +++ b/packages/angular/cli/src/command-builder/schematics-command-module.ts @@ -368,7 +368,15 @@ export abstract class SchematicsCommandModule if (files.size) { // Note: we could use a task executor to format the files but this is simpler. - await formatFiles(this.context.root, files); + try { + await formatFiles(this.context.root, files); + } catch (error) { + assertIsError(error); + + logger.warn( + `WARNING: Formatting of files failed with the following error: ${error.message}`, + ); + } } return 0; diff --git a/packages/angular/cli/src/command-builder/utilities/prettier.ts b/packages/angular/cli/src/command-builder/utilities/prettier.ts index ad5a3047f823..79955cfa62f5 100644 --- a/packages/angular/cli/src/command-builder/utilities/prettier.ts +++ b/packages/angular/cli/src/command-builder/utilities/prettier.ts @@ -7,6 +7,7 @@ */ import { execFile } from 'node:child_process'; +import { readFile } from 'node:fs/promises'; import { createRequire } from 'node:module'; import { dirname, extname, join, relative } from 'node:path'; import { promisify } from 'node:util'; @@ -43,7 +44,10 @@ export async function formatFiles(cwd: string, files: Set): Promise Date: Tue, 27 Jan 2026 18:03:13 +0000 Subject: [PATCH 3/5] fixup! feat(@angular/cli): automatic formatting files modified by schematics --- .../angular/cli/src/command-builder/utilities/prettier.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/angular/cli/src/command-builder/utilities/prettier.ts b/packages/angular/cli/src/command-builder/utilities/prettier.ts index 79955cfa62f5..91a2190ec260 100644 --- a/packages/angular/cli/src/command-builder/utilities/prettier.ts +++ b/packages/angular/cli/src/command-builder/utilities/prettier.ts @@ -45,9 +45,9 @@ export async function formatFiles(cwd: string, files: Set): Promise Date: Tue, 27 Jan 2026 19:52:47 +0000 Subject: [PATCH 4/5] fixup! feat(@angular/cli): automatic formatting files modified by schematics --- .../src/command-builder/utilities/prettier.ts | 16 ++++++++++++---- tests/e2e/tests/commands/add/add-tailwindcss.ts | 2 +- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/packages/angular/cli/src/command-builder/utilities/prettier.ts b/packages/angular/cli/src/command-builder/utilities/prettier.ts index 91a2190ec260..5e7ea08eed6a 100644 --- a/packages/angular/cli/src/command-builder/utilities/prettier.ts +++ b/packages/angular/cli/src/command-builder/utilities/prettier.ts @@ -58,8 +58,16 @@ export async function formatFiles(cwd: string, files: Set): Promise fileTypes.has(extname(f))).map((f) => relative(cwd, f)), - ]); + const filesToFormat: string[] = []; + for (const file of files) { + if (fileTypes.has(extname(file))) { + filesToFormat.push(relative(cwd, file)); + } + } + + if (!filesToFormat.length) { + return; + } + + await execFileAsync(prettierCliPath, ['--write', ...filesToFormat], { cwd }); } diff --git a/tests/e2e/tests/commands/add/add-tailwindcss.ts b/tests/e2e/tests/commands/add/add-tailwindcss.ts index 1444bb6a9a07..54fd617a4e1e 100644 --- a/tests/e2e/tests/commands/add/add-tailwindcss.ts +++ b/tests/e2e/tests/commands/add/add-tailwindcss.ts @@ -13,7 +13,7 @@ export default async function () { try { await ng('add', 'tailwindcss', '--skip-confirmation'); await expectFileToExist('.postcssrc.json'); - await expectFileToMatch('src/styles.css', /@import "tailwindcss";/); + await expectFileToMatch('src/styles.css', /@import 'tailwindcss';/); await expectFileToMatch('package.json', /"tailwindcss":/); await expectFileToMatch('package.json', /"@tailwindcss\/postcss":/); await expectFileToMatch('package.json', /"postcss":/); From 438b2d67181fc24e43296a8627cdc385788d14a6 Mon Sep 17 00:00:00 2001 From: Alan Agius <17563226+alan-agius4@users.noreply.github.com> Date: Wed, 28 Jan 2026 07:04:20 +0000 Subject: [PATCH 5/5] fixup! feat(@angular/cli): automatic formatting files modified by schematics --- tests/e2e/tests/test/karma-junit-output.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/tests/test/karma-junit-output.ts b/tests/e2e/tests/test/karma-junit-output.ts index 056adea26ab3..dbc61c3fafc5 100644 --- a/tests/e2e/tests/test/karma-junit-output.ts +++ b/tests/e2e/tests/test/karma-junit-output.ts @@ -9,7 +9,7 @@ const E2E_CUSTOM_LAUNCHER = ` flags: ['--no-sandbox', '--headless', '--disable-gpu', '--disable-dev-shm-usage'], }, }, - restartOnFileChange: true, + restartOnFileChange: true `; export default async function () {