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..5a8a8e7b3f3b 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,24 @@ 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. + 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; } catch (err) { // In case the workflow was not successful, show an appropriate error message. if (err instanceof UnsuccessfulWorkflowExecution) { @@ -376,8 +394,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..5e7ea08eed6a --- /dev/null +++ b/packages/angular/cli/src/command-builder/utilities/prettier.ts @@ -0,0 +1,73 @@ +/** + * @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 { readFile } from 'node:fs/promises'; +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'); + const prettierPackageJson = JSON.parse(await readFile(prettierPath, 'utf-8')) as { + bin: string; + }; + prettierCliPath = join(dirname(prettierPath), prettierPackageJson.bin); + } catch { + // Prettier is not installed. + prettierCliPath = null; + } + } + + if (!prettierCliPath) { + return; + } + + 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":/); 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 () {