Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 28 additions & 5 deletions packages/angular_devkit/architect/bin/architect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
*/

import { JsonValue, json, logging, schema, strings, tags, workspaces } from '@angular-devkit/core';
import { NodeJsSyncHost, createConsoleLogger } from '@angular-devkit/core/node';
import { NodeJsSyncHost } from '@angular-devkit/core/node';
import { existsSync } from 'node:fs';
import * as path from 'node:path';
import { parseArgs, styleText } from 'node:util';
Expand Down Expand Up @@ -217,12 +217,35 @@ async function main(args: string[]): Promise<number> {
const { positionals, cliOptions, builderOptions } = parseOptions(args);

/** Create the DevKit Logger used through the CLI. */
const logger = createConsoleLogger(!!cliOptions['verbose'], process.stdout, process.stderr, {
const logger = new logging.IndentLogger('architect');
const colorLevels: Record<string, (message: string) => string> = {
info: (s) => s,
debug: (s) => s,
warn: (s) => styleText(['yellow', 'bold'], s),
error: (s) => styleText(['red', 'bold'], s),
fatal: (s) => styleText(['red', 'bold'], s),
warn: (s) => styleText(['yellow', 'bold'], s, { stream: process.stderr }),
error: (s) => styleText(['red', 'bold'], s, { stream: process.stderr }),
fatal: (s) => styleText(['red', 'bold'], s, { stream: process.stderr }),
};

logger.subscribe((entry) => {
if (entry.level === 'debug' && !cliOptions['verbose']) {
return;
}

const color = colorLevels[entry.level];
const message = color ? color(entry.message) : entry.message;

switch (entry.level) {
case 'warn':
case 'fatal':
case 'error':
// eslint-disable-next-line no-console
console.error(message);
break;
default:
// eslint-disable-next-line no-console
console.log(message);
break;
}
});

// Check the target.
Expand Down
45 changes: 34 additions & 11 deletions packages/angular_devkit/schematics_cli/bin/schematics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
*/

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

export interface MainOptions {
args: string[];
stdout?: ProcessOutput;
stderr?: ProcessOutput;
stdout?: NodeJS.WritableStream;
stderr?: NodeJS.WritableStream;
}

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

function createLogger(
verbose: boolean,
stdout: NodeJS.WritableStream,
stderr: NodeJS.WritableStream,
): logging.Logger {
const logger = new logging.IndentLogger('schematics');
const colorLevels: Record<string, (message: string, stream: NodeJS.WritableStream) => string> = {
info: (s) => s,
debug: (s) => s,
warn: (s, stream) => styleText(['bold', 'yellow'], s, { stream }),
error: (s, stream) => styleText(['bold', 'red'], s, { stream }),
fatal: (s, stream) => styleText(['bold', 'red'], s, { stream }),
};

logger.subscribe((entry) => {
if (entry.level === 'debug' && !verbose) {
return;
}

const output =
entry.level === 'warn' || entry.level === 'fatal' || entry.level === 'error'
? stderr
: stdout;
const color = colorLevels[entry.level];
const message = color ? color(entry.message, output) : entry.message;
output.write(message + '\n');
});

return logger;
}

export async function main({
args,
stdout = process.stdout,
stderr = process.stderr,
}: MainOptions): Promise<0 | 1> {
const { cliOptions, schematicOptions, _ } = parseOptions(args);

/** Create the DevKit Logger used through the CLI. */
const logger = createConsoleLogger(!!cliOptions.verbose, stdout, stderr, {
info: (s) => s,
debug: (s) => s,
warn: (s) => styleText(['bold', 'yellow'], s),
error: (s) => styleText(['bold', 'red'], s),
fatal: (s) => styleText(['bold', 'red'], s),
});
const logger = createLogger(!!cliOptions.verbose, stdout, stderr);

if (cliOptions.help) {
logger.info(getUsage());
Expand Down
51 changes: 23 additions & 28 deletions packages/angular_devkit/schematics_cli/test/schematics_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,32 +6,24 @@
* found in the LICENSE file at https://angular.dev/license
*/

import { PassThrough } from 'node:stream';
import { stripVTControlCharacters } from 'node:util';
import { main } from '../bin/schematics';

// We only care about the write method in these mocks of NodeJS.WriteStream.
class MockWriteStream {
lines: string[] = [];
write(str: string) {
// Strip color control characters.
this.lines.push(str.replace(/[^\x20-\x7F]\[\d+m/g, ''));

return true;
}
}

describe('schematics-cli binary', () => {
let stdout: MockWriteStream, stderr: MockWriteStream;
let stdout: PassThrough, stderr: PassThrough;

beforeEach(() => {
stdout = new MockWriteStream();
stderr = new MockWriteStream();
stdout = new PassThrough();
stderr = new PassThrough();
});

it('list-schematics works', async () => {
const args = ['--list-schematics'];
const res = await main({ args, stdout, stderr });
expect(stdout.lines).toMatch(/blank/);
expect(stdout.lines).toMatch(/schematic/);
const output = stripVTControlCharacters(stdout.read()?.toString() || '');
expect(output).toMatch(/blank/);
expect(output).toMatch(/schematic/);
expect(res).toEqual(0);
});

Expand All @@ -45,30 +37,33 @@ describe('schematics-cli binary', () => {
it('dry-run works', async () => {
const args = ['blank', 'foo', '--dry-run'];
const res = await main({ args, stdout, stderr });
expect(stdout.lines).toMatch(/CREATE foo\/README.md/);
expect(stdout.lines).toMatch(/CREATE foo\/.gitignore/);
expect(stdout.lines).toMatch(/CREATE foo\/src\/foo\/index.ts/);
expect(stdout.lines).toMatch(/CREATE foo\/src\/foo\/index_spec.ts/);
expect(stdout.lines).toMatch(/Dry run enabled./);
const output = stripVTControlCharacters(stdout.read()?.toString() || '');
expect(output).toMatch(/CREATE foo\/README.md/);
expect(output).toMatch(/CREATE foo\/.gitignore/);
expect(output).toMatch(/CREATE foo\/src\/foo\/index.ts/);
expect(output).toMatch(/CREATE foo\/src\/foo\/index_spec.ts/);
expect(output).toMatch(/Dry run enabled./);
expect(res).toEqual(0);
});

it('dry-run is default when debug mode', async () => {
const args = ['blank', 'foo', '--debug'];
const res = await main({ args, stdout, stderr });
expect(stdout.lines).toMatch(/Debug mode enabled./);
expect(stdout.lines).toMatch(/CREATE foo\/README.md/);
expect(stdout.lines).toMatch(/CREATE foo\/.gitignore/);
expect(stdout.lines).toMatch(/CREATE foo\/src\/foo\/index.ts/);
expect(stdout.lines).toMatch(/CREATE foo\/src\/foo\/index_spec.ts/);
expect(stdout.lines).toMatch(/Dry run enabled by default in debug mode./);
const output = stripVTControlCharacters(stdout.read()?.toString() || '');
expect(output).toMatch(/Debug mode enabled./);
expect(output).toMatch(/CREATE foo\/README.md/);
expect(output).toMatch(/CREATE foo\/.gitignore/);
expect(output).toMatch(/CREATE foo\/src\/foo\/index.ts/);
expect(output).toMatch(/CREATE foo\/src\/foo\/index_spec.ts/);
expect(output).toMatch(/Dry run enabled by default in debug mode./);
expect(res).toEqual(0);
});

it('error when no name is provided', async () => {
const args = ['blank'];
const res = await main({ args, stdout, stderr });
expect(stderr.lines).toMatch(/Error: name option is required/);
const output = stripVTControlCharacters(stderr.read()?.toString() || '');
expect(output).toMatch(/Error: name option is required/);
expect(res).toEqual(1);
});
});
Loading