Skip to content

Commit ae361e7

Browse files
committed
refactor(@angular-devkit/schematics-cli): use custom indent logger and improve tests
Replaces `createConsoleLogger` with a custom `logging.IndentLogger` implementation that respects custom output streams. Also refactors tests to use standard `PassThrough` streams and `stripVTControlCharacters` instead of custom mocks.
1 parent f29cb3b commit ae361e7

2 files changed

Lines changed: 57 additions & 39 deletions

File tree

packages/angular_devkit/schematics_cli/bin/schematics.ts

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
*/
99

1010
import { JsonValue, logging, schema } from '@angular-devkit/core';
11-
import { ProcessOutput, createConsoleLogger } from '@angular-devkit/core/node';
1211
import { UnsuccessfulWorkflowExecution, strings } from '@angular-devkit/schematics';
1312
import { NodeWorkflow } from '@angular-devkit/schematics/tools';
1413
import { existsSync } from 'node:fs';
@@ -50,8 +49,8 @@ function removeLeadingSlash(value: string): string {
5049

5150
export interface MainOptions {
5251
args: string[];
53-
stdout?: ProcessOutput;
54-
stderr?: ProcessOutput;
52+
stdout?: NodeJS.WritableStream;
53+
stderr?: NodeJS.WritableStream;
5554
}
5655

5756
function _listSchematics(workflow: NodeWorkflow, collectionName: string, logger: logging.Logger) {
@@ -217,21 +216,45 @@ function getPackageManagerName() {
217216
return 'npm';
218217
}
219218

219+
function createLogger(
220+
verbose: boolean,
221+
stdout: NodeJS.WritableStream,
222+
stderr: NodeJS.WritableStream,
223+
): logging.Logger {
224+
const logger = new logging.IndentLogger('schematics');
225+
const colorLevels: Record<string, (message: string, stream: NodeJS.WritableStream) => string> = {
226+
info: (s) => s,
227+
debug: (s) => s,
228+
warn: (s, stream) => styleText(['bold', 'yellow'], s, { stream }),
229+
error: (s, stream) => styleText(['bold', 'red'], s, { stream }),
230+
fatal: (s, stream) => styleText(['bold', 'red'], s, { stream }),
231+
};
232+
233+
logger.subscribe((entry) => {
234+
if (entry.level === 'debug' && !verbose) {
235+
return;
236+
}
237+
238+
const output =
239+
entry.level === 'warn' || entry.level === 'fatal' || entry.level === 'error'
240+
? stderr
241+
: stdout;
242+
const color = colorLevels[entry.level];
243+
const message = color ? color(entry.message, output) : entry.message;
244+
output.write(message + '\n');
245+
});
246+
247+
return logger;
248+
}
249+
220250
export async function main({
221251
args,
222252
stdout = process.stdout,
223253
stderr = process.stderr,
224254
}: MainOptions): Promise<0 | 1> {
225255
const { cliOptions, schematicOptions, _ } = parseOptions(args);
226256

227-
/** Create the DevKit Logger used through the CLI. */
228-
const logger = createConsoleLogger(!!cliOptions.verbose, stdout, stderr, {
229-
info: (s) => s,
230-
debug: (s) => s,
231-
warn: (s) => styleText(['bold', 'yellow'], s),
232-
error: (s) => styleText(['bold', 'red'], s),
233-
fatal: (s) => styleText(['bold', 'red'], s),
234-
});
257+
const logger = createLogger(!!cliOptions.verbose, stdout, stderr);
235258

236259
if (cliOptions.help) {
237260
logger.info(getUsage());

packages/angular_devkit/schematics_cli/test/schematics_spec.ts

Lines changed: 23 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -6,32 +6,24 @@
66
* found in the LICENSE file at https://angular.dev/license
77
*/
88

9+
import { PassThrough } from 'node:stream';
10+
import { stripVTControlCharacters } from 'node:util';
911
import { main } from '../bin/schematics';
1012

11-
// We only care about the write method in these mocks of NodeJS.WriteStream.
12-
class MockWriteStream {
13-
lines: string[] = [];
14-
write(str: string) {
15-
// Strip color control characters.
16-
this.lines.push(str.replace(/[^\x20-\x7F]\[\d+m/g, ''));
17-
18-
return true;
19-
}
20-
}
21-
2213
describe('schematics-cli binary', () => {
23-
let stdout: MockWriteStream, stderr: MockWriteStream;
14+
let stdout: PassThrough, stderr: PassThrough;
2415

2516
beforeEach(() => {
26-
stdout = new MockWriteStream();
27-
stderr = new MockWriteStream();
17+
stdout = new PassThrough();
18+
stderr = new PassThrough();
2819
});
2920

3021
it('list-schematics works', async () => {
3122
const args = ['--list-schematics'];
3223
const res = await main({ args, stdout, stderr });
33-
expect(stdout.lines).toMatch(/blank/);
34-
expect(stdout.lines).toMatch(/schematic/);
24+
const output = stripVTControlCharacters(stdout.read()?.toString() || '');
25+
expect(output).toMatch(/blank/);
26+
expect(output).toMatch(/schematic/);
3527
expect(res).toEqual(0);
3628
});
3729

@@ -45,30 +37,33 @@ describe('schematics-cli binary', () => {
4537
it('dry-run works', async () => {
4638
const args = ['blank', 'foo', '--dry-run'];
4739
const res = await main({ args, stdout, stderr });
48-
expect(stdout.lines).toMatch(/CREATE foo\/README.md/);
49-
expect(stdout.lines).toMatch(/CREATE foo\/.gitignore/);
50-
expect(stdout.lines).toMatch(/CREATE foo\/src\/foo\/index.ts/);
51-
expect(stdout.lines).toMatch(/CREATE foo\/src\/foo\/index_spec.ts/);
52-
expect(stdout.lines).toMatch(/Dry run enabled./);
40+
const output = stripVTControlCharacters(stdout.read()?.toString() || '');
41+
expect(output).toMatch(/CREATE foo\/README.md/);
42+
expect(output).toMatch(/CREATE foo\/.gitignore/);
43+
expect(output).toMatch(/CREATE foo\/src\/foo\/index.ts/);
44+
expect(output).toMatch(/CREATE foo\/src\/foo\/index_spec.ts/);
45+
expect(output).toMatch(/Dry run enabled./);
5346
expect(res).toEqual(0);
5447
});
5548

5649
it('dry-run is default when debug mode', async () => {
5750
const args = ['blank', 'foo', '--debug'];
5851
const res = await main({ args, stdout, stderr });
59-
expect(stdout.lines).toMatch(/Debug mode enabled./);
60-
expect(stdout.lines).toMatch(/CREATE foo\/README.md/);
61-
expect(stdout.lines).toMatch(/CREATE foo\/.gitignore/);
62-
expect(stdout.lines).toMatch(/CREATE foo\/src\/foo\/index.ts/);
63-
expect(stdout.lines).toMatch(/CREATE foo\/src\/foo\/index_spec.ts/);
64-
expect(stdout.lines).toMatch(/Dry run enabled by default in debug mode./);
52+
const output = stripVTControlCharacters(stdout.read()?.toString() || '');
53+
expect(output).toMatch(/Debug mode enabled./);
54+
expect(output).toMatch(/CREATE foo\/README.md/);
55+
expect(output).toMatch(/CREATE foo\/.gitignore/);
56+
expect(output).toMatch(/CREATE foo\/src\/foo\/index.ts/);
57+
expect(output).toMatch(/CREATE foo\/src\/foo\/index_spec.ts/);
58+
expect(output).toMatch(/Dry run enabled by default in debug mode./);
6559
expect(res).toEqual(0);
6660
});
6761

6862
it('error when no name is provided', async () => {
6963
const args = ['blank'];
7064
const res = await main({ args, stdout, stderr });
71-
expect(stderr.lines).toMatch(/Error: name option is required/);
65+
const output = stripVTControlCharacters(stderr.read()?.toString() || '');
66+
expect(output).toMatch(/Error: name option is required/);
7267
expect(res).toEqual(1);
7368
});
7469
});

0 commit comments

Comments
 (0)