From 2e14a3714d11fc5be21a25fef24015ef4dd31a3b Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Sun, 29 Oct 2017 11:45:34 -0700 Subject: [PATCH] feat(@angular-devkit/core): add support for color and no-color And namespace the terminal. --- .../angular_devkit/core/src/terminal/caps.ts | 189 ++++++++++++++++++ .../core/src/terminal/{ansi.ts => colors.ts} | 0 .../angular_devkit/core/src/terminal/index.ts | 5 +- .../angular_devkit/core/src/terminal/text.ts | 39 ++++ scripts/benchmark.ts | 6 +- 5 files changed, 235 insertions(+), 4 deletions(-) create mode 100644 packages/angular_devkit/core/src/terminal/caps.ts rename packages/angular_devkit/core/src/terminal/{ansi.ts => colors.ts} (100%) create mode 100644 packages/angular_devkit/core/src/terminal/text.ts diff --git a/packages/angular_devkit/core/src/terminal/caps.ts b/packages/angular_devkit/core/src/terminal/caps.ts new file mode 100644 index 0000000000..b93095d7a3 --- /dev/null +++ b/packages/angular_devkit/core/src/terminal/caps.ts @@ -0,0 +1,189 @@ +/** + * @license + * Copyright Google Inc. 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.io/license + */ +import ReadableStream = NodeJS.ReadableStream; +import WriteStream = NodeJS.WriteStream; +import Socket = NodeJS.Socket; + + +/** + * Node specific stuff. + */ +declare const process: { + env: { [name: string]: string }; + platform: string; + versions: { + node: string; + }; + + stdin: ReadableStream; + stdout: WriteStream; + stderr: WriteStream; +}; +declare const os: { + release: () => string; +}; + + +const _env = (typeof process == 'object' && process.env) || {}; +const _platform = (typeof process == 'object' && process.platform) || ''; +const _versions = (typeof process == 'object' && process.versions) || { node: '' }; +const _os = (typeof os == 'object' && os) || { release: () => '' }; + +const streamMap = new WeakMap<{}, StreamCapabilities>(); + + +export interface StreamCapabilities { + readable: boolean; + writable: boolean; + + /** + * Supports text. This should be true for any streams. + */ + text: boolean; + + /** + * Supports colors (16 colors). + */ + colors: boolean; + + /** + * Supports 256 colors. + */ + color256: boolean; + + /** + * Supports 16 millions (3x8-bit channels) colors. + */ + color16m: boolean; + + /** + * Height of the terminal. If the stream is not tied to a terminal, will be null. + */ + rows: number | null; + + /** + * Width of the terminal. If the stream is not tied to a terminal, will be null. + */ + columns: number | null; +} + + +const ciVars = ['TRAVIS', 'CIRCLECI', 'APPVEYOR', 'GITLAB_CI']; + + +function _getColorLevel(stream: Socket): number { + if (stream && !stream.isTTY) { + return 0; + } + + if (_platform.startsWith('win32')) { + // Node.js 7.5.0 is the first version of Node.js to include a patch to + // libuv that enables 256 color output on Windows. Anything earlier and it + // won't work. However, here we target Node.js 8 at minimum as it is an LTS + // release, and Node.js 7 is not. Windows 10 build 10586 is the first Windows + // release that supports 256 colors. + const osRelease = _os.release().split('.'); + if (Number(_versions.node.split('.')[0]) >= 8 + && Number(osRelease[0]) >= 10 + && Number(osRelease[2]) >= 10586) { + return 2; + } + + return 1; + } + + if ('CI' in _env) { + if (ciVars.some(sign => sign in _env) || _env.CI_NAME === 'codeship') { + return 1; + } + + return 0; + } + + if ('TEAMCITY_VERSION' in _env) { + return /^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(_env.TEAMCITY_VERSION) ? 1 : 0; + } + + if ('TERM_PROGRAM' in _env) { + const version = parseInt((_env.TERM_PROGRAM_VERSION || '').split('.')[0], 10); + + switch (_env.TERM_PROGRAM) { + case 'iTerm.app': + return version >= 3 ? 3 : 2; + case 'Hyper': + return 3; + case 'Apple_Terminal': + return 2; + + // No default + } + } + + if (/-256(color)?$/i.test(_env.TERM)) { + return 2; + } + + if (/^screen|^xterm|^vt100|^rxvt|color|ansi|cygwin|linux/i.test(_env.TERM)) { + return 1; + } + + if ('COLORTERM' in _env) { + return 1; + } + + if (_env.TERM === 'dumb') { + return 0; + } + + return 0; +} + + +function _getRows() { + return process.stdout.rows || null; +} +function _getColumns() { + return process.stdout.columns || null; +} + + +function _createCapabilities(stream: Socket, isTerminalStream: boolean): StreamCapabilities { + const level = _getColorLevel(stream); + + return { + readable: stream.readable, + writable: stream.writable, + text: true, + + colors: level > 0, + color256: level > 1, + color16m: level > 2, + + rows: isTerminalStream ? _getRows() : null, + columns: isTerminalStream ? _getColumns() : null, + }; +} + + +export function getCapabilities( + stream: Socket, + isTerminalStream = !!stream.isTTY, +): StreamCapabilities { + let maybeCaps = streamMap.get(stream); + if (!maybeCaps) { + maybeCaps = _createCapabilities(stream, isTerminalStream); + streamMap.set(stream, maybeCaps); + } + + return maybeCaps; +} + + +export const stdin = getCapabilities(process.stdin as Socket); +export const stdout = getCapabilities(process.stdout); +export const stderr = getCapabilities(process.stderr); diff --git a/packages/angular_devkit/core/src/terminal/ansi.ts b/packages/angular_devkit/core/src/terminal/colors.ts similarity index 100% rename from packages/angular_devkit/core/src/terminal/ansi.ts rename to packages/angular_devkit/core/src/terminal/colors.ts diff --git a/packages/angular_devkit/core/src/terminal/index.ts b/packages/angular_devkit/core/src/terminal/index.ts index 02e84c1016..e4bcae6655 100644 --- a/packages/angular_devkit/core/src/terminal/index.ts +++ b/packages/angular_devkit/core/src/terminal/index.ts @@ -5,4 +5,7 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -export * from './ansi'; +export * from './text'; + +import * as colors from './colors'; +export { colors }; diff --git a/packages/angular_devkit/core/src/terminal/text.ts b/packages/angular_devkit/core/src/terminal/text.ts new file mode 100644 index 0000000000..bf4f9e7eba --- /dev/null +++ b/packages/angular_devkit/core/src/terminal/text.ts @@ -0,0 +1,39 @@ +/** + * @license + * Copyright Google Inc. 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.io/license + */ +import * as caps from './caps'; +import * as ansi from './colors'; + + +export const reset = caps.stdout.colors ? ansi.reset : (x: string) => x; +export const bold = caps.stdout.colors ? ansi.bold : (x: string) => x; +export const dim = caps.stdout.colors ? ansi.dim : (x: string) => x; +export const italic = caps.stdout.colors ? ansi.italic : (x: string) => x; +export const underline = caps.stdout.colors ? ansi.underline : (x: string) => x; +export const inverse = caps.stdout.colors ? ansi.inverse : (x: string) => x; +export const hidden = caps.stdout.colors ? ansi.hidden : (x: string) => x; +export const strikethrough = caps.stdout.colors ? ansi.strikethrough : (x: string) => x; + +export const black = caps.stdout.colors ? ansi.black : (x: string) => x; +export const red = caps.stdout.colors ? ansi.red : (x: string) => x; +export const green = caps.stdout.colors ? ansi.green : (x: string) => x; +export const yellow = caps.stdout.colors ? ansi.yellow : (x: string) => x; +export const blue = caps.stdout.colors ? ansi.blue : (x: string) => x; +export const magenta = caps.stdout.colors ? ansi.magenta : (x: string) => x; +export const cyan = caps.stdout.colors ? ansi.cyan : (x: string) => x; +export const white = caps.stdout.colors ? ansi.white : (x: string) => x; +export const grey = caps.stdout.colors ? ansi.gray : (x: string) => x; +export const gray = caps.stdout.colors ? ansi.gray : (x: string) => x; + +export const bgBlack = caps.stdout.colors ? ansi.bgBlack : (x: string) => x; +export const bgRed = caps.stdout.colors ? ansi.bgRed : (x: string) => x; +export const bgGreen = caps.stdout.colors ? ansi.bgGreen : (x: string) => x; +export const bgYellow = caps.stdout.colors ? ansi.bgYellow : (x: string) => x; +export const bgBlue = caps.stdout.colors ? ansi.bgBlue : (x: string) => x; +export const bgMagenta = caps.stdout.colors ? ansi.bgMagenta : (x: string) => x; +export const bgCyan = caps.stdout.colors ? ansi.bgCyan : (x: string) => x; +export const bgWhite = caps.stdout.colors ? ansi.bgWhite : (x: string) => x; diff --git a/scripts/benchmark.ts b/scripts/benchmark.ts index 819bb055a7..9bf91eb164 100644 --- a/scripts/benchmark.ts +++ b/scripts/benchmark.ts @@ -8,7 +8,7 @@ import { tags, terminal } from '@angular-devkit/core'; import * as glob from 'glob'; import 'jasmine'; -import {SpecReporter as JasmineSpecReporter } from 'jasmine-spec-reporter'; +import { SpecReporter as JasmineSpecReporter } from 'jasmine-spec-reporter'; import { join, relative } from 'path'; @@ -84,7 +84,7 @@ class BenchmarkReporter extends JasmineSpecReporter implements jasmine.CustomRep const baseAverage = pad(Math.floor(stat.base.average)); const baseAverageMult = pad(precision(stat.average / stat.base.average), multPad); - console.log(terminal.yellow(tags.indentBy(6)` + console.log(terminal.colors.yellow(tags.indentBy(6)` fastest: ${fastest} (base) ${baseFastest} slowest: ${slowest} @@ -93,7 +93,7 @@ class BenchmarkReporter extends JasmineSpecReporter implements jasmine.CustomRep average: ${average} (${baseAverage}) (${baseAverageMult}x) `)); } else { - console.log(terminal.yellow(tags.indentBy(6)` + console.log(terminal.colors.yellow(tags.indentBy(6)` fastest: ${fastest} slowest: ${slowest} mean: ${mean}