packages/cli: added a more reusable and less verbose logging util and use in watch-deps

This commit is contained in:
Patrik Oldsberg
2020-04-18 02:42:44 +02:00
parent 462f82e4e6
commit 921beb792d
7 changed files with 80 additions and 53 deletions
+2 -4
View File
@@ -17,7 +17,7 @@
import { spawn } from 'child_process';
import { waitForExit } from 'lib/run';
import { watchDeps } from 'commands/watch-deps';
import { createLogger } from 'commands/watch-deps/logger';
import { createLogFunc } from 'lib/logging';
export default async () => {
// Start dynamic watch and build of dependencies, then serve the app
@@ -35,8 +35,6 @@ export default async () => {
});
// We need to avoid clearing the terminal, or the build feedback of dependencies will be lost
const log = createLogger();
child.stdout.setEncoding('utf8');
child.stdout!.on('data', log.out);
child.stdout.on('data', createLogFunc(process.stdout));
await waitForExit(child);
};
@@ -15,8 +15,7 @@
*/
import { spawn } from 'child_process';
import { createLogger } from './logger';
import { createLogPipe } from 'lib/logging';
export function startChild(args: string[]) {
const [command, ...commandArgs] = args;
@@ -27,12 +26,8 @@ export function startChild(args: string[]) {
});
// We need to avoid clearing the terminal, or the build feedback of dependencies will be lost
const log = createLogger();
child.stdout!.on('data', (data: Buffer) => {
log.out(data.toString('utf8'));
});
child.stderr!.on('data', data => {
log.err(data.toString('utf8'));
});
const logPipe = createLogPipe();
child.stdout.on('data', logPipe(process.stdout));
child.stderr.on('data', logPipe(process.stderr));
return child;
}
@@ -15,11 +15,11 @@
*/
import { spawn } from 'child_process';
import { Logger } from './logger';
import { LogPipe } from 'lib/logging';
import chalk from 'chalk';
import { Package } from './packages';
export function startCompiler(pkg: Package, log: Logger) {
export function startCompiler(pkg: Package, logPipe: LogPipe) {
// First we figure out which yarn script is a available, falling back to "build --watch"
const scriptName = ['build:watch', 'watch'].find(
script => script in pkg.scripts,
@@ -35,12 +35,9 @@ export function startCompiler(pkg: Package, log: Logger) {
});
watch.stdin.end();
watch.stdout!.on('data', (data: Buffer) => {
log.out(data.toString('utf8'));
});
watch.stderr!.on('data', data => {
log.err(data.toString('utf8'));
});
watch.stdout.on('data', logPipe(process.stdout));
const logErr = logPipe(process.stderr);
watch.stderr.on('data', logErr);
const promise = new Promise<void>((resolve, reject) => {
watch.on('error', error => {
@@ -50,7 +47,7 @@ export function startCompiler(pkg: Package, log: Logger) {
watch.on('close', (code: number) => {
if (code !== 0) {
const msg = `Compiler exited with code ${code}`;
log.err(chalk.red(msg));
logErr(chalk.red(msg));
reject(new Error(msg));
} else {
resolve();
@@ -16,7 +16,7 @@
import chalk from 'chalk';
import fs from 'fs-extra';
import { createLoggerFactory } from './logger';
import { createLogPipeFactory } from './logger';
import { findAllDeps } from './packages';
import { startWatcher, startPackageWatcher } from './watcher';
import { startCompiler } from './compiler';
@@ -42,7 +42,7 @@ export async function watchDeps(options: Options = {}) {
const localPackagePath = paths.resolveTarget('package.json');
// Rotate through different prefix colors to make it easier to differenciate between different deps
const logFactory = createLoggerFactory([
const createLogPipe = createLogPipeFactory([
chalk.yellow,
chalk.blue,
chalk.magenta,
@@ -69,7 +69,7 @@ export async function watchDeps(options: Options = {}) {
// We lazily watch all our deps, as in we don't start the actual watch compiler until a change is detected
const watcher = await startWatcher(deps, WATCH_LOCATIONS, pkg => {
startCompiler(pkg, logFactory(pkg.name)).promise.catch(error => {
startCompiler(pkg, createLogPipe(pkg.name)).promise.catch(error => {
process.stderr.write(`${error}\n`);
});
});
+4 -25
View File
@@ -14,33 +14,12 @@
* limitations under the License.
*/
export type Logger = {
out(msg: string): void;
err(msg: string): void;
};
import { createLogPipe } from 'lib/logging';
export type ColorFunc = (msg: string) => string;
// Logger utility that prefixes logs and removes terminal clear commands
export function createLogger(prefix: string = ''): Logger {
const write = (stream: NodeJS.WriteStream, msg: string) => {
const noClearMsg = msg.startsWith('\x1b\x63') ? msg.slice(2) : msg;
const prefixedMsg = noClearMsg.trimRight().replace(/^/gm, prefix);
stream.write(`${prefixedMsg}\n`, 'utf8');
};
return {
out(msg: string) {
write(process.stdout, msg);
},
err(msg: string) {
write(process.stderr, msg);
},
};
}
// A factory for creating loggers that rotate between different coloring functions
export function createLoggerFactory(colorFuncs: ColorFunc[]) {
// A factory for creating log pipes that rotate between different coloring functions
export function createLogPipeFactory(colorFuncs: ColorFunc[]) {
let colorIndex = 0;
return (name: string) => {
@@ -49,6 +28,6 @@ export function createLoggerFactory(colorFuncs: ColorFunc[]) {
colorIndex = (colorIndex + 1) % colorFuncs.length;
const prefix = `${colorFunc(name)}: `;
return createLogger(prefix);
return createLogPipe({ prefix });
};
}
@@ -18,7 +18,7 @@ import { resolve as resolvePath } from 'path';
import chalk from 'chalk';
import chokidar from 'chokidar';
import { Package } from './packages';
import { createLogger } from './logger';
import { createLogFunc } from 'lib/logging';
export type Watcher = {
update(newPackages: Package[]): Promise<void>;
@@ -36,7 +36,7 @@ export async function startWatcher(
callback: (pkg: Package) => void,
): Promise<Watcher> {
const watchedPackageLocations = new Set<string>();
const logger = createLogger();
const log = createLogFunc(process.stdout);
const watchPackage = async (pkg: Package) => {
let signalled = false;
@@ -71,7 +71,7 @@ export async function startWatcher(
continue;
}
logger.out(chalk.green(`Starting watch of new dependency ${pkg.name}`));
log(chalk.green(`Starting watch of new dependency ${pkg.name}`));
promises.push(watchPackage(pkg));
}
+58
View File
@@ -0,0 +1,58 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export type LogFunc = (data: Buffer | string) => void;
export type LogPipe = (dst: NodeJS.WriteStream) => LogFunc;
export type LogOptions = {
// If set, prefix each log message with this string
prefix?: string;
// If true, clear terminal commands will be forwarded, otherwise they are removed
forwardClearTerm?: boolean;
};
// Creates a log pipe that binds to a destination stream and forwards logs with optional transforms.
// Use returned logPipe e.g. as follows: child.stdout.on('data', logPipe(process.stdout))
export function createLogPipe(options: LogOptions = {}): LogPipe {
const { prefix = '', forwardClearTerm = false } = options;
return (dst: NodeJS.WriteStream) => (data: Buffer | string) => {
let str = typeof data === 'string' ? data : data.toString('utf8');
if (!forwardClearTerm) {
str = trimClearTerm(str);
}
if (prefix) {
str = `${prefix}${str}`;
}
dst.write(Buffer.from(str, 'utf8'));
};
}
// Wrapper around createLogPipe to avoid awkward immediate call of returned function
export function createLogFunc(
dst: NodeJS.WriteStream,
options: LogOptions = {},
): LogFunc {
return createLogPipe(options)(dst);
}
// Returns the string without terminal clear command if it was prefixed with one.
export function trimClearTerm(msg: string): string {
return msg.startsWith('\x1b\x63') ? msg.slice(2) : msg;
}