Skip to content

Commit fbae1b6

Browse files
authored
feat(@angular/cli): automatic formatting files modified by schematics
This change introduces automatic formatting of files generated or modified during a schematic execution.
1 parent 247855c commit fbae1b6

File tree

4 files changed

+93
-4
lines changed

4 files changed

+93
-4
lines changed

packages/angular/cli/src/command-builder/schematics-command-module.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import {
2929
OtherOptions,
3030
} from './command-module';
3131
import { Option, parseJsonSchemaToOptions } from './utilities/json-schema';
32+
import { formatFiles } from './utilities/prettier';
3233
import { SchematicEngineHost } from './utilities/schematic-engine-host';
3334
import { subscribeToWorkflow } from './utilities/schematic-workflow';
3435

@@ -361,7 +362,24 @@ export abstract class SchematicsCommandModule
361362

362363
if (executionOptions.dryRun) {
363364
logger.warn(`\nNOTE: The "--dry-run" option means no changes were made.`);
365+
366+
return 0;
364367
}
368+
369+
if (files.size) {
370+
// Note: we could use a task executor to format the files but this is simpler.
371+
try {
372+
await formatFiles(this.context.root, files);
373+
} catch (error) {
374+
assertIsError(error);
375+
376+
logger.warn(
377+
`WARNING: Formatting of files failed with the following error: ${error.message}`,
378+
);
379+
}
380+
}
381+
382+
return 0;
365383
} catch (err) {
366384
// In case the workflow was not successful, show an appropriate error message.
367385
if (err instanceof UnsuccessfulWorkflowExecution) {
@@ -376,8 +394,6 @@ export abstract class SchematicsCommandModule
376394
} finally {
377395
unsubscribe();
378396
}
379-
380-
return 0;
381397
}
382398

383399
private getProjectName(): string | undefined {
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.dev/license
7+
*/
8+
9+
import { execFile } from 'node:child_process';
10+
import { readFile } from 'node:fs/promises';
11+
import { createRequire } from 'node:module';
12+
import { dirname, extname, join, relative } from 'node:path';
13+
import { promisify } from 'node:util';
14+
15+
const execFileAsync = promisify(execFile);
16+
let prettierCliPath: string | null | undefined;
17+
18+
/**
19+
* File types that can be formatted using Prettier.
20+
*/
21+
const fileTypes: ReadonlySet<string> = new Set([
22+
'.ts',
23+
'.html',
24+
'.js',
25+
'.mjs',
26+
'.cjs',
27+
'.json',
28+
'.css',
29+
'.less',
30+
'.scss',
31+
'.sass',
32+
]);
33+
34+
/**
35+
* Formats files using Prettier.
36+
* @param cwd The current working directory.
37+
* @param files The files to format.
38+
*/
39+
export async function formatFiles(cwd: string, files: Set<string>): Promise<void> {
40+
if (!files.size) {
41+
return;
42+
}
43+
44+
if (prettierCliPath === undefined) {
45+
try {
46+
const prettierPath = createRequire(cwd + '/').resolve('prettier/package.json');
47+
const prettierPackageJson = JSON.parse(await readFile(prettierPath, 'utf-8')) as {
48+
bin: string;
49+
};
50+
prettierCliPath = join(dirname(prettierPath), prettierPackageJson.bin);
51+
} catch {
52+
// Prettier is not installed.
53+
prettierCliPath = null;
54+
}
55+
}
56+
57+
if (!prettierCliPath) {
58+
return;
59+
}
60+
61+
const filesToFormat: string[] = [];
62+
for (const file of files) {
63+
if (fileTypes.has(extname(file))) {
64+
filesToFormat.push(relative(cwd, file));
65+
}
66+
}
67+
68+
if (!filesToFormat.length) {
69+
return;
70+
}
71+
72+
await execFileAsync(prettierCliPath, ['--write', ...filesToFormat], { cwd });
73+
}

tests/e2e/tests/commands/add/add-tailwindcss.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export default async function () {
1313
try {
1414
await ng('add', 'tailwindcss', '--skip-confirmation');
1515
await expectFileToExist('.postcssrc.json');
16-
await expectFileToMatch('src/styles.css', /@import "tailwindcss";/);
16+
await expectFileToMatch('src/styles.css', /@import 'tailwindcss';/);
1717
await expectFileToMatch('package.json', /"tailwindcss":/);
1818
await expectFileToMatch('package.json', /"@tailwindcss\/postcss":/);
1919
await expectFileToMatch('package.json', /"postcss":/);

tests/e2e/tests/test/karma-junit-output.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const E2E_CUSTOM_LAUNCHER = `
99
flags: ['--no-sandbox', '--headless', '--disable-gpu', '--disable-dev-shm-usage'],
1010
},
1111
},
12-
restartOnFileChange: true,
12+
restartOnFileChange: true
1313
`;
1414

1515
export default async function () {

0 commit comments

Comments
 (0)