[tslint] Fix violations in kbn-pm (#19335)

* [tslint/kbn-pm] apply autofixes

* [tslint/kbn-pm] whitelist all console.log calls for now

* [tslint/kbn-pm] sort object keys alphabetically

* [tslint/kbn-pm] use comments to "fill" empty blocks

* [tslint/kbn-pm] use I-prefix for interface names

* [tslint/kbn-pm] use arrow-functions where possible

* [tslint/kbn-pm] use lowerCamelCase for variable names

* [tslint/kbn-pm] prevent shadowed variable names

* [tslint/kbn-pm] avoid variable name and type clashes

* [tslint/kbn-pm] replace console.log statements with bare-bones logger
This commit is contained in:
Spencer 2018-05-23 14:50:02 -07:00 committed by GitHub
parent 3d08c5cb31
commit ce20463f59
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 248 additions and 228 deletions

View file

@ -1,17 +1,18 @@
import getopts from 'getopts';
import dedent from 'dedent';
import chalk from 'chalk';
import dedent from 'dedent';
import getopts from 'getopts';
import { resolve } from 'path';
import { commands } from './commands';
import { runCommand } from './run';
import { log } from './utils/log';
function help() {
const availableCommands = Object.keys(commands)
.map(commandName => commands[commandName])
.map(command => `${command.name} - ${command.description}`);
console.log(dedent`
log.write(dedent`
usage: kbn <command> [<args>]
By default commands are run for Kibana itself, all packages in the 'packages/'
@ -35,7 +36,7 @@ export async function run(argv: string[]) {
// starts forwarding the `--` directly to this script, see
// https://github.com/yarnpkg/yarn/blob/b2d3e1a8fe45ef376b716d597cc79b38702a9320/src/cli/index.js#L174-L182
if (argv.includes('--')) {
console.log(
log.write(
chalk.red(
`Using "--" is not allowed, as it doesn't work with 'yarn kbn'.`
)
@ -45,9 +46,9 @@ export async function run(argv: string[]) {
const options = getopts(argv, {
alias: {
e: 'exclude',
h: 'help',
i: 'include',
e: 'exclude',
},
});
@ -69,7 +70,7 @@ export async function run(argv: string[]) {
const command = commands[commandName];
if (command === undefined) {
console.log(
log.write(
chalk.red(`[${commandName}] is not a valid command, see 'kbn --help'`)
);
process.exit(1);

View file

@ -7,18 +7,18 @@ import {
absolutePathSnapshotSerializer,
stripAnsiSnapshotSerializer,
} from '../test_helpers';
import { BootstrapCommand } from './bootstrap';
import { PackageJson } from '../utils/package_json';
import { linkProjectExecutables } from '../utils/link_project_executables';
import { IPackageJson } from '../utils/package_json';
import { Project } from '../utils/project';
import { buildProjectGraph } from '../utils/projects';
import { installInDir, runScriptInPackageStreaming } from '../utils/scripts';
import { linkProjectExecutables } from '../utils/link_project_executables';
import { BootstrapCommand } from './bootstrap';
const mockInstallInDir = installInDir as jest.Mock;
const mockRunScriptInPackageStreaming = runScriptInPackageStreaming as jest.Mock;
const mockLinkProjectExecutables = linkProjectExecutables as jest.Mock;
const createProject = (packageJson: PackageJson, path = '.') =>
const createProject = (packageJson: IPackageJson, path = '.') =>
new Project(
{
name: 'kibana',
@ -31,7 +31,9 @@ const createProject = (packageJson: PackageJson, path = '.') =>
expect.addSnapshotSerializer(absolutePathSnapshotSerializer);
expect.addSnapshotSerializer(stripAnsiSnapshotSerializer);
const noop = () => {};
const noop = () => {
// noop
};
afterEach(() => {
jest.resetAllMocks();
@ -45,19 +47,19 @@ test('handles dependencies of dependencies', async () => {
});
const foo = createProject(
{
name: 'foo',
dependencies: {
bar: 'link:../bar',
},
name: 'foo',
},
'packages/foo'
);
const bar = createProject(
{
name: 'bar',
dependencies: {
baz: 'link:../baz',
},
name: 'bar',
},
'packages/bar'
);

View file

@ -1,13 +1,14 @@
import chalk from 'chalk';
import { topologicallyBatchProjects } from '../utils/projects';
import { linkProjectExecutables } from '../utils/link_project_executables';
import { log } from '../utils/log';
import { parallelizeBatches } from '../utils/parallelize';
import { Command } from './';
import { topologicallyBatchProjects } from '../utils/projects';
import { ICommand } from './';
export const BootstrapCommand: Command = {
name: 'bootstrap',
export const BootstrapCommand: ICommand = {
description: 'Install dependencies and crosslink projects',
name: 'bootstrap',
async run(projects, projectGraph, { options }) {
const batchedProjects = topologicallyBatchProjects(projects, projectGraph);
@ -15,7 +16,7 @@ export const BootstrapCommand: Command = {
const frozenLockfile = options['frozen-lockfile'] === true;
const extraArgs = frozenLockfile ? ['--frozen-lockfile'] : [];
console.log(chalk.bold('\nRunning installs in topological order:'));
log.write(chalk.bold('\nRunning installs in topological order:'));
for (const batch of batchedProjects) {
for (const project of batch) {
@ -25,7 +26,7 @@ export const BootstrapCommand: Command = {
}
}
console.log(
log.write(
chalk.bold('\nInstalls completed, linking package executables:\n')
);
await linkProjectExecutables(projects, projectGraph);
@ -36,7 +37,7 @@ export const BootstrapCommand: Command = {
* transpiled before they can be used. Ideally we shouldn't do this unless we
* have to, as it will slow down the bootstrapping process.
*/
console.log(
log.write(
chalk.bold(
'\nLinking executables completed, running `kbn:bootstrap` scripts\n'
)
@ -47,6 +48,6 @@ export const BootstrapCommand: Command = {
}
});
console.log(chalk.green.bold('\nBootstrapping completed!\n'));
log.write(chalk.green.bold('\nBootstrapping completed!\n'));
},
};

View file

@ -1,15 +1,16 @@
import del from 'del';
import chalk from 'chalk';
import { relative } from 'path';
import del from 'del';
import ora from 'ora';
import { relative } from 'path';
import { isDirectory } from '../utils/fs';
import { Command } from './';
import { log } from '../utils/log';
import { ICommand } from './';
export const CleanCommand: Command = {
name: 'clean',
export const CleanCommand: ICommand = {
description:
'Remove the node_modules and target directories from all projects.',
name: 'clean',
async run(projects, projectGraph, { rootPath }) {
const directoriesToDelete = [];
@ -24,9 +25,9 @@ export const CleanCommand: Command = {
}
if (directoriesToDelete.length === 0) {
console.log(chalk.bold.green('\n\nNo directories to delete'));
log.write(chalk.bold.green('\n\nNo directories to delete'));
} else {
console.log(chalk.bold.red('\n\nDeleting directories:\n'));
log.write(chalk.bold.red('\n\nDeleting directories:\n'));
for (const dir of directoriesToDelete) {
const deleting = del(dir, { force: true });

View file

@ -1,19 +1,19 @@
import { ProjectGraph, ProjectMap } from '../utils/projects';
export interface CommandConfig {
export interface ICommandConfig {
extraArgs: string[];
options: { [key: string]: any };
rootPath: string;
}
export interface Command {
export interface ICommand {
name: string;
description: string;
run: (
projects: ProjectMap,
projectGraph: ProjectGraph,
config: CommandConfig
config: ICommandConfig
) => Promise<void>;
}
@ -22,7 +22,7 @@ import { CleanCommand } from './clean';
import { RunCommand } from './run';
import { WatchCommand } from './watch';
export const commands: { [key: string]: Command } = {
export const commands: { [key: string]: ICommand } = {
bootstrap: BootstrapCommand,
clean: CleanCommand,
run: RunCommand,

View file

@ -1,26 +1,27 @@
import chalk from 'chalk';
import { topologicallyBatchProjects } from '../utils/projects';
import { log } from '../utils/log';
import { parallelizeBatches } from '../utils/parallelize';
import { Command } from './';
import { topologicallyBatchProjects } from '../utils/projects';
import { ICommand } from './';
export const RunCommand: Command = {
name: 'run',
export const RunCommand: ICommand = {
description:
'Run script defined in package.json in each package that contains that script.',
name: 'run',
async run(projects, projectGraph, { extraArgs }) {
const batchedProjects = topologicallyBatchProjects(projects, projectGraph);
if (extraArgs.length === 0) {
console.log(chalk.red.bold('\nNo script specified'));
log.write(chalk.red.bold('\nNo script specified'));
process.exit(1);
}
const scriptName = extraArgs[0];
const scriptArgs = extraArgs.slice(1);
console.log(
log.write(
chalk.bold(
`\nRunning script [${chalk.green(
scriptName

View file

@ -1,8 +1,9 @@
import chalk from 'chalk';
import { topologicallyBatchProjects, ProjectMap } from '../utils/projects';
import { log } from '../utils/log';
import { parallelizeBatches } from '../utils/parallelize';
import { ProjectMap, topologicallyBatchProjects } from '../utils/projects';
import { waitUntilWatchIsReady } from '../utils/watch';
import { Command } from './';
import { ICommand } from './';
/**
* Name of the script in the package/project package.json file to run during `kbn watch`.
@ -24,9 +25,9 @@ const kibanaProjectName = 'kibana';
* the `kbn:watch` script and eventually for the entire batch. Currently we support completion "markers" for
* `webpack` and `tsc` only, for the rest we rely on predefined timeouts.
*/
export const WatchCommand: Command = {
name: 'watch',
export const WatchCommand: ICommand = {
description: 'Runs `kbn:watch` script for every project.',
name: 'watch',
async run(projects, projectGraph) {
const projectsToWatch: ProjectMap = new Map();
@ -38,7 +39,7 @@ export const WatchCommand: Command = {
}
if (projectsToWatch.size === 0) {
console.log(
log.write(
chalk.red(
`\nThere are no projects to watch found. Make sure that projects define 'kbn:watch' script in 'package.json'.\n`
)
@ -47,7 +48,7 @@ export const WatchCommand: Command = {
}
const projectNames = Array.from(projectsToWatch.keys());
console.log(
log.write(
chalk.bold(
chalk.green(
`Running ${watchScriptName} scripts for [${projectNames.join(', ')}].`
@ -73,7 +74,7 @@ export const WatchCommand: Command = {
pkg.runScriptStreaming(watchScriptName).stdout
);
console.log(
log.write(
chalk.bold(
`[${chalk.green(
pkg.name

View file

@ -1,14 +1,17 @@
import { resolve } from 'path';
export type ProjectPathOptions = {
export interface IProjectPathOptions {
'skip-kibana-extra'?: boolean;
'oss'?: boolean;
};
oss?: boolean;
}
/**
* Returns all the paths where plugins are located
*/
export function getProjectPaths(rootPath: string, options: ProjectPathOptions) {
export function getProjectPaths(
rootPath: string,
options: IProjectPathOptions
) {
const skipKibanaExtra = Boolean(options['skip-kibana-extra']);
const ossOnly = Boolean(options.oss);

View file

@ -1,22 +1,22 @@
import del from 'del';
import { relative, resolve, join } from 'path';
import copy from 'cpy';
import del from 'del';
import { join, relative, resolve } from 'path';
import { getProjectPaths } from '../config';
import {
getProjects,
buildProjectGraph,
topologicallyBatchProjects,
ProjectMap,
includeTransitiveProjects,
} from '../utils/projects';
import { isDirectory, isFile } from '../utils/fs';
import { log } from '../utils/log';
import {
createProductionPackageJson,
writePackageJson,
readPackageJson,
writePackageJson,
} from '../utils/package_json';
import { isDirectory, isFile } from '../utils/fs';
import { Project } from '../utils/project';
import {
buildProjectGraph,
getProjects,
includeTransitiveProjects,
topologicallyBatchProjects,
} from '../utils/projects';
export async function buildProductionProjects({
kibanaRoot,
@ -30,7 +30,7 @@ export async function buildProductionProjects({
const batchedProjects = topologicallyBatchProjects(projects, projectGraph);
const projectNames = [...projects.values()].map(project => project.name);
console.log(`Preparing production build for [${projectNames.join(', ')}]`);
log.write(`Preparing production build for [${projectNames.join(', ')}]`);
for (const batch of batchedProjects) {
for (const project of batch) {
@ -99,9 +99,9 @@ async function copyToBuild(
await copy(['**/*', '!node_modules/**'], buildProjectPath, {
cwd: project.getIntermediateBuildDirectory(),
parents: true,
nodir: true,
dot: true,
nodir: true,
parents: true,
});
// If a project is using an intermediate build directory, we special-case our

View file

@ -1,16 +1,16 @@
import tempy from 'tempy';
import copy from 'cpy';
import { resolve, relative, join } from 'path';
import globby from 'globby';
import { join, resolve } from 'path';
import tempy from 'tempy';
import { buildProductionProjects } from '../build_production_projects';
import { getProjects } from '../../utils/projects';
import { readPackageJson } from '../../utils/package_json';
import { getProjects } from '../../utils/projects';
import { buildProductionProjects } from '../build_production_projects';
describe('kbn-pm production', function() {
describe('kbn-pm production', () => {
test(
'builds and copies projects for production',
async function() {
async () => {
const tmpDir = tempy.directory();
const buildRoot = tempy.directory();
const fixturesPath = resolve(__dirname, '__fixtures__');
@ -18,9 +18,9 @@ describe('kbn-pm production', function() {
// Copy all the test fixtures into a tmp dir, as we will be mutating them
await copy(['**/*'], tmpDir, {
cwd: fixturesPath,
parents: true,
nodir: true,
dot: true,
nodir: true,
parents: true,
});
const projects = await getProjects(tmpDir, ['.', './packages/*']);

View file

@ -1,4 +1,4 @@
import { resolve, join } from 'path';
import { join, resolve } from 'path';
import { prepareExternalProjectDependencies } from './prepare_project_dependencies';

View file

@ -1,5 +1,5 @@
import { Project } from '../utils/project';
import { isLinkDependency } from '../utils/package_json';
import { Project } from '../utils/project';
/**
* All external projects are located within `../kibana-extra/{plugin}` relative

View file

@ -1,7 +1,7 @@
import { resolve } from 'path';
import { ICommand, ICommandConfig } from './commands';
import { runCommand } from './run';
import { Project } from './utils/project';
import { Command, CommandConfig } from './commands';
const rootPath = resolve(`${__dirname}/utils/__fixtures__/kibana`);
@ -25,12 +25,12 @@ function getExpectedProjectsAndGraph(runMock: any) {
return { projects, graph };
}
let command: Command;
let config: CommandConfig;
let command: ICommand;
let config: ICommandConfig;
beforeEach(() => {
command = {
name: 'test name',
description: 'test description',
name: 'test name',
run: jest.fn(),
};
@ -41,7 +41,9 @@ beforeEach(() => {
};
// Reduce the noise that comes from the run command.
jest.spyOn(console, 'log').mockImplementation(() => {});
jest.spyOn(console, 'log').mockImplementation(() => {
// noop
});
});
test('passes all found projects to the command if no filter is specified', async () => {
@ -102,9 +104,9 @@ test('respects both `include` and `exclude` filters if specified at the same tim
});
test('does not run command if all projects are filtered out', async () => {
let mockProcessExit = jest
.spyOn(process, 'exit')
.mockImplementation(() => {});
const mockProcessExit = jest.spyOn(process, 'exit').mockImplementation(() => {
// noop
});
await runCommand(command, {
...config,

View file

@ -1,16 +1,17 @@
import chalk from 'chalk';
import wrapAnsi from 'wrap-ansi';
import indentString from 'indent-string';
import wrapAnsi from 'wrap-ansi';
import { ICommand, ICommandConfig } from './commands';
import { getProjectPaths, IProjectPathOptions } from './config';
import { CliError } from './utils/errors';
import { getProjects, buildProjectGraph } from './utils/projects';
import { log } from './utils/log';
import { buildProjectGraph, getProjects } from './utils/projects';
import { renderProjectsTree } from './utils/projects_tree';
import { getProjectPaths, ProjectPathOptions } from './config';
import { Command, CommandConfig } from './commands';
export async function runCommand(command: Command, config: CommandConfig) {
export async function runCommand(command: ICommand, config: ICommandConfig) {
try {
console.log(
log.write(
chalk.bold(
`Running [${chalk.green(command.name)}] command from [${chalk.yellow(
config.rootPath
@ -20,7 +21,7 @@ export async function runCommand(command: Command, config: CommandConfig) {
const projectPaths = getProjectPaths(
config.rootPath,
config.options as ProjectPathOptions
config.options as IProjectPathOptions
);
const projects = await getProjects(config.rootPath, projectPaths, {
@ -29,7 +30,7 @@ export async function runCommand(command: Command, config: CommandConfig) {
});
if (projects.size === 0) {
console.log(
log.write(
chalk.red(
`There are no projects found. Double check project name(s) in '-i/--include' and '-e/--exclude' filters.\n`
)
@ -39,18 +40,18 @@ export async function runCommand(command: Command, config: CommandConfig) {
const projectGraph = buildProjectGraph(projects);
console.log(
log.write(
chalk.bold(`Found [${chalk.green(projects.size.toString())}] projects:\n`)
);
console.log(renderProjectsTree(config.rootPath, projects));
log.write(renderProjectsTree(config.rootPath, projects));
await command.run(projects, projectGraph, config);
} catch (e) {
console.log(chalk.bold.red(`\n[${command.name}] failed:\n`));
log.write(chalk.bold.red(`\n[${command.name}] failed:\n`));
if (e instanceof CliError) {
const msg = chalk.red(`CliError: ${e.message}\n`);
console.log(wrapAnsi(msg, 80));
log.write(wrapAnsi(msg, 80));
const keys = Object.keys(e.meta);
if (keys.length > 0) {
@ -59,11 +60,11 @@ export async function runCommand(command: Command, config: CommandConfig) {
return `${key}: ${value}`;
});
console.log('Additional debugging info:\n');
console.log(indentString(metaOutput.join('\n'), 3));
log.write('Additional debugging info:\n');
log.write(indentString(metaOutput.join('\n'), 3));
}
} else {
console.log(e.stack);
log.write(e.stack);
}
process.exit(1);

View file

@ -1,5 +1,5 @@
import { resolve, sep as pathSep } from 'path';
import cloneDeepWith from 'lodash.clonedeepwith';
import { resolve, sep as pathSep } from 'path';
const repoRoot = resolve(__dirname, '../../../../');
@ -16,8 +16,8 @@ const normalizePaths = (value: any) => {
});
return {
didReplacement,
clone,
didReplacement,
};
};

View file

@ -1,7 +1,7 @@
import execa from 'execa';
import chalk from 'chalk';
import logTransformer from 'strong-log-transformer';
import execa from 'execa';
import logSymbols from 'log-symbols';
import logTransformer from 'strong-log-transformer';
function generateColors() {
const colorWheel = [
@ -42,8 +42,8 @@ export function spawnStreaming(
const color = nextColor();
const prefixedStdout = logTransformer({ tag: `${color.bold(prefix)}:` });
const prefixedStderr = logTransformer({
tag: `${logSymbols.error} ${color.bold(prefix)}:`,
mergeMultiline: true,
tag: `${logSymbols.error} ${color.bold(prefix)}:`,
});
spawned.stdout.pipe(prefixedStdout).pipe(process.stdout);

View file

@ -1,8 +1,8 @@
import fs from 'fs';
import { relative, dirname } from 'path';
import { promisify } from 'util';
import cmdShimCb from 'cmd-shim';
import fs from 'fs';
import mkdirpCb from 'mkdirp';
import { dirname, relative } from 'path';
import { promisify } from 'util';
const stat = promisify(fs.stat);
const readFile = promisify(fs.readFile);

View file

@ -15,10 +15,10 @@ const projectsByName = new Map([
'foo',
new Project(
{
name: 'foo',
dependencies: {
bar: 'link:../bar',
},
name: 'foo',
},
resolve(__dirname, 'foo')
),
@ -27,8 +27,8 @@ const projectsByName = new Map([
'bar',
new Project(
{
name: 'bar',
bin: 'bin/bar.js',
name: 'bar',
},
resolve(__dirname, 'bar')
),
@ -37,10 +37,10 @@ const projectsByName = new Map([
'baz',
new Project(
{
name: 'baz',
devDependencies: {
bar: 'link:../bar',
},
name: 'baz',
},
resolve(__dirname, 'baz')
),
@ -82,7 +82,9 @@ describe('bin script points to a file', () => {
const fs = require('./fs');
fs.isFile.mockReturnValue(true);
const logMock = jest.spyOn(console, 'log').mockImplementation(() => {});
const logMock = jest.spyOn(console, 'log').mockImplementation(() => {
// noop
});
await linkProjectExecutables(projectsByName, projectGraph);
logMock.mockRestore();

View file

@ -1,8 +1,9 @@
import { resolve, relative, dirname, sep } from 'path';
import { dirname, relative, resolve, sep } from 'path';
import chalk from 'chalk';
import { createSymlink, isFile, chmod, mkdirp } from './fs';
import { chmod, createSymlink, isFile, mkdirp } from './fs';
import { log } from './log';
import { ProjectGraph, ProjectMap } from './projects';
/**
@ -39,7 +40,7 @@ export async function linkProjectExecutables(
.split(sep)
.join('/');
console.log(
log.write(
chalk`{dim [${project.name}]} ${name} -> {dim ${projectRelativePath}}`
);

View file

@ -0,0 +1,11 @@
export const log = {
/**
* Log something to the console. Ideally we would use a real logger in
* kbn-pm, but that's a pretty big change for now.
* @param ...args
*/
write(...args: any[]) {
// tslint:disable no-console
console.log(...args);
},
};

View file

@ -1,20 +1,25 @@
import readPkg from 'read-pkg';
import writePkg from 'write-pkg';
import path from 'path';
export type PackageJson = { [key: string]: any };
export type PackageDependencies = { [key: string]: string };
export type PackageScripts = { [key: string]: string };
export interface IPackageJson {
[key: string]: any;
}
export interface IPackageDependencies {
[key: string]: string;
}
export interface IPackageScripts {
[key: string]: string;
}
export function readPackageJson(dir: string) {
return readPkg(dir, { normalize: false });
}
export function writePackageJson(path: string, json: PackageJson) {
export function writePackageJson(path: string, json: IPackageJson) {
return writePkg(path, json);
}
export const createProductionPackageJson = (pkgJson: PackageJson) => ({
export const createProductionPackageJson = (pkgJson: IPackageJson) => ({
...pkgJson,
dependencies: transformDependencies(pkgJson.dependencies),
});
@ -31,8 +36,8 @@ export const isLinkDependency = (depVersion: string) =>
* will then _copy_ the `file:` dependencies into `node_modules` instead of
* symlinking like we do in development.
*/
export function transformDependencies(dependencies: PackageDependencies = {}) {
const newDeps: PackageDependencies = {};
export function transformDependencies(dependencies: IPackageDependencies = {}) {
const newDeps: IPackageDependencies = {};
for (const name of Object.keys(dependencies)) {
const depVersion = dependencies[name];
if (isLinkDependency(depVersion)) {

View file

@ -106,9 +106,9 @@ test('rejects if any promise rejects', async () => {
function createPromiseWithResolve() {
let resolve: (val?: any) => void;
let reject: (err?: any) => void;
const promise = new Promise((_resolve, _reject) => {
resolve = _resolve;
reject = _reject;
const promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
return { promise, resolve: resolve!, reject: reject!, called: false };
}

View file

@ -1,11 +1,11 @@
import { resolve, join } from 'path';
import { join, resolve } from 'path';
import { PackageJson } from './package_json';
import { IPackageJson } from './package_json';
import { Project } from './project';
const rootPath = resolve(`${__dirname}/__fixtures__/kibana`);
const createProjectWith = (packageJson: PackageJson, path = '') =>
const createProjectWith = (packageJson: IPackageJson, path = '') =>
new Project(
{
name: 'kibana',
@ -25,12 +25,12 @@ describe('fromPath', () => {
test('fields', async () => {
const kibana = createProjectWith({
scripts: {
test: 'jest',
},
dependencies: {
foo: '1.2.3',
},
scripts: {
test: 'jest',
},
});
expect(kibana.name).toBe('kibana');

View file

@ -1,49 +1,50 @@
import path from 'path';
import { inspect } from 'util';
import chalk from 'chalk';
import { relative, resolve as resolvePath } from 'path';
import { inspect } from 'util';
import { CliError } from './errors';
import { log } from './log';
import {
IPackageDependencies,
IPackageJson,
IPackageScripts,
isLinkDependency,
readPackageJson,
} from './package_json';
import {
installInDir,
runScriptInPackage,
runScriptInPackageStreaming,
} from './scripts';
import {
PackageJson,
PackageDependencies,
PackageScripts,
isLinkDependency,
readPackageJson,
} from './package_json';
import { CliError } from './errors';
interface BuildConfig {
interface IBuildConfig {
skip?: boolean;
intermediateBuildDirectory?: string;
}
export class Project {
static async fromPath(path: string) {
public static async fromPath(path: string) {
const pkgJson = await readPackageJson(path);
return new Project(pkgJson, path);
}
public readonly json: PackageJson;
public readonly json: IPackageJson;
public readonly packageJsonLocation: string;
public readonly nodeModulesLocation: string;
public readonly targetLocation: string;
public readonly path: string;
public readonly allDependencies: PackageDependencies;
public readonly productionDependencies: PackageDependencies;
public readonly devDependencies: PackageDependencies;
public readonly scripts: PackageScripts;
public readonly allDependencies: IPackageDependencies;
public readonly productionDependencies: IPackageDependencies;
public readonly devDependencies: IPackageDependencies;
public readonly scripts: IPackageScripts;
constructor(packageJson: PackageJson, projectPath: string) {
constructor(packageJson: IPackageJson, projectPath: string) {
this.json = Object.freeze(packageJson);
this.path = projectPath;
this.packageJsonLocation = path.resolve(this.path, 'package.json');
this.nodeModulesLocation = path.resolve(this.path, 'node_modules');
this.targetLocation = path.resolve(this.path, 'target');
this.packageJsonLocation = resolvePath(this.path, 'package.json');
this.nodeModulesLocation = resolvePath(this.path, 'node_modules');
this.targetLocation = resolvePath(this.path, 'target');
this.productionDependencies = this.json.dependencies || {};
this.devDependencies = this.json.devDependencies || {};
@ -59,9 +60,9 @@ export class Project {
return this.json.name;
}
ensureValidProjectDependency(project: Project) {
public ensureValidProjectDependency(project: Project) {
const relativePathToProject = normalizePath(
path.relative(this.path, project.path)
relative(this.path, project.path)
);
const versionInPackageJson = this.allDependencies[project.name];
@ -73,9 +74,9 @@ export class Project {
const updateMsg = 'Update its package.json to the expected value below.';
const meta = {
package: `${this.name} (${this.packageJsonLocation})`,
expected: `"${project.name}": "${expectedVersionInPackageJson}"`,
actual: `"${project.name}": "${versionInPackageJson}"`,
expected: `"${project.name}": "${expectedVersionInPackageJson}"`,
package: `${this.name} (${this.packageJsonLocation})`,
};
if (isLinkDependency(versionInPackageJson)) {
@ -95,7 +96,7 @@ export class Project {
);
}
getBuildConfig(): BuildConfig {
public getBuildConfig(): IBuildConfig {
return (this.json.kibana && this.json.kibana.build) || {};
}
@ -104,18 +105,18 @@ export class Project {
* This config can be specified to only include the project's build artifacts
* instead of everything located in the project directory.
*/
getIntermediateBuildDirectory() {
return path.resolve(
public getIntermediateBuildDirectory() {
return resolvePath(
this.path,
this.getBuildConfig().intermediateBuildDirectory || '.'
);
}
hasScript(name: string) {
public hasScript(name: string) {
return name in this.scripts;
}
getExecutables(): { [key: string]: string } {
public getExecutables(): { [key: string]: string } {
const raw = this.json.bin;
if (!raw) {
@ -124,14 +125,14 @@ export class Project {
if (typeof raw === 'string') {
return {
[this.name]: path.resolve(this.path, raw),
[this.name]: resolvePath(this.path, raw),
};
}
if (typeof raw === 'object') {
const binsConfig: { [k: string]: string } = {};
for (const binName of Object.keys(raw)) {
binsConfig[binName] = path.resolve(this.path, raw[binName]);
binsConfig[binName] = resolvePath(this.path, raw[binName]);
}
return binsConfig;
}
@ -140,14 +141,14 @@ export class Project {
`[${this.name}] has an invalid "bin" field in its package.json, ` +
`expected an object or a string`,
{
package: `${this.name} (${this.packageJsonLocation})`,
binConfig: inspect(raw),
package: `${this.name} (${this.packageJsonLocation})`,
}
);
}
async runScript(scriptName: string, args: string[] = []) {
console.log(
public async runScript(scriptName: string, args: string[] = []) {
log.write(
chalk.bold(
`\n\nRunning script [${chalk.green(scriptName)}] in [${chalk.green(
this.name
@ -157,16 +158,16 @@ export class Project {
return runScriptInPackage(scriptName, args, this);
}
runScriptStreaming(scriptName: string, args: string[] = []) {
public runScriptStreaming(scriptName: string, args: string[] = []) {
return runScriptInPackageStreaming(scriptName, args, this);
}
hasDependencies() {
public hasDependencies() {
return Object.keys(this.allDependencies).length > 0;
}
async installDependencies({ extraArgs }: { extraArgs: string[] }) {
console.log(
public async installDependencies({ extraArgs }: { extraArgs: string[] }) {
log.write(
chalk.bold(
`\n\nInstalling dependencies in [${chalk.green(this.name)}]:\n`
)

View file

@ -1,15 +1,15 @@
import { resolve } from 'path';
import {
getProjects,
buildProjectGraph,
topologicallyBatchProjects,
includeTransitiveProjects,
ProjectMap,
ProjectGraph,
} from './projects';
import { Project } from './project';
import { getProjectPaths } from '../config';
import { Project } from './project';
import {
buildProjectGraph,
getProjects,
includeTransitiveProjects,
ProjectGraph,
ProjectMap,
topologicallyBatchProjects,
} from './projects';
const rootPath = resolve(`${__dirname}/__fixtures__/kibana`);
@ -164,12 +164,12 @@ describe('#getProjects', () => {
describe('#buildProjectGraph', () => {
test('builds full project graph', async () => {
const projects = await getProjects(rootPath, [
const allProjects = await getProjects(rootPath, [
'.',
'packages/*',
'../plugins/*',
]);
const graph = buildProjectGraph(projects);
const graph = buildProjectGraph(allProjects);
const expected: { [k: string]: string[] } = {};
for (const [projectName, projects] of graph.entries()) {

View file

@ -9,12 +9,15 @@ const glob = promisify(globSync);
export type ProjectMap = Map<string, Project>;
export type ProjectGraph = Map<string, Project[]>;
export type ProjectsOptions = { include?: string[]; exclude?: string[] };
export interface IProjectsOptions {
include?: string[];
exclude?: string[];
}
export async function getProjects(
rootPath: string,
projectsPathsPatterns: string[],
{ include = [], exclude = [] }: ProjectsOptions = {}
{ include = [], exclude = [] }: IProjectsOptions = {}
) {
const projects: ProjectMap = new Map();

View file

@ -1,9 +1,9 @@
import { resolve } from 'path';
import { stripAnsiSnapshotSerializer } from '../test_helpers';
import { renderProjectsTree } from './projects_tree';
import { getProjects } from './projects';
import { getProjectPaths } from '../config';
import { stripAnsiSnapshotSerializer } from '../test_helpers';
import { getProjects } from './projects';
import { renderProjectsTree } from './projects_tree';
const rootPath = resolve(`${__dirname}/__fixtures__/kibana`);

View file

@ -1,5 +1,5 @@
import path from 'path';
import chalk from 'chalk';
import path from 'path';
import { Project } from './project';
@ -13,41 +13,44 @@ export function renderProjectsTree(
return treeToString(createTreeStructure(projectsTree));
}
type Tree = {
interface ITree {
name?: string;
children?: TreeChildren;
};
interface TreeChildren extends Array<Tree> {}
children?: ITreeChildren;
}
interface ITreeChildren extends Array<ITree> {}
type DirOrProjectName = string | typeof projectKey;
type ProjectsTree = Map<DirOrProjectName, ProjectsTreeValue | string>;
interface ProjectsTreeValue extends ProjectsTree {}
function treeToString(tree: Tree) {
return [tree.name].concat(childrenToString(tree.children, '')).join('\n');
interface IProjectsTree extends Map<DirOrProjectName, string | IProjectsTree> {}
function treeToString(tree: ITree) {
return [tree.name].concat(childrenToStrings(tree.children, '')).join('\n');
}
function childrenToString(tree: TreeChildren | undefined, treePrefix: string) {
function childrenToStrings(
tree: ITreeChildren | undefined,
treePrefix: string
) {
if (tree === undefined) {
return [];
}
let string: string[] = [];
let strings: string[] = [];
tree.forEach((node, index) => {
const isLastNode = tree.length - 1 === index;
const nodePrefix = isLastNode ? '└── ' : '├── ';
const childPrefix = isLastNode ? ' ' : '│ ';
const childrenPrefix = treePrefix + childPrefix;
string.push(`${treePrefix}${nodePrefix}${node.name}`);
string = string.concat(childrenToString(node.children, childrenPrefix));
strings.push(`${treePrefix}${nodePrefix}${node.name}`);
strings = strings.concat(childrenToStrings(node.children, childrenPrefix));
});
return string;
return strings;
}
function createTreeStructure(tree: ProjectsTree): Tree {
function createTreeStructure(tree: IProjectsTree): ITree {
let name: string | undefined;
const children: TreeChildren = [];
const children: ITreeChildren = [];
for (const [dir, project] of tree.entries()) {
// This is a leaf node (aka a project)
@ -63,8 +66,8 @@ function createTreeStructure(tree: ProjectsTree): Tree {
if (project.size === 1 && project.has(projectKey)) {
const projectName = project.get(projectKey)! as string;
children.push({
name: dirOrProjectName(dir, projectName),
children: [],
name: dirOrProjectName(dir, projectName),
});
continue;
}
@ -77,8 +80,8 @@ function createTreeStructure(tree: ProjectsTree): Tree {
const projectName = subtree.name;
children.push({
name: dirOrProjectName(dir, projectName),
children: subtree.children,
name: dirOrProjectName(dir, projectName),
});
continue;
}
@ -91,15 +94,15 @@ function createTreeStructure(tree: ProjectsTree): Tree {
const newName = chalk.dim(path.join(dir.toString(), child.name!));
children.push({
name: newName,
children: child.children,
name: newName,
});
continue;
}
children.push({
name: chalk.dim(dir.toString()),
children: subtree.children,
name: chalk.dim(dir.toString()),
});
}
@ -113,7 +116,7 @@ function dirOrProjectName(dir: DirOrProjectName, projectName: string) {
}
function buildProjectsTree(rootPath: string, projects: Map<string, Project>) {
const tree: ProjectsTree = new Map();
const tree: IProjectsTree = new Map();
for (const project of projects.values()) {
if (rootPath === project.path) {
@ -128,7 +131,7 @@ function buildProjectsTree(rootPath: string, projects: Map<string, Project>) {
}
function addProjectToTree(
tree: ProjectsTree,
tree: IProjectsTree,
pathParts: string[],
project: Project
) {
@ -141,7 +144,7 @@ function addProjectToTree(
tree.set(currentDir, new Map());
}
const subtree = tree.get(currentDir) as ProjectsTree;
const subtree = tree.get(currentDir) as IProjectsTree;
addProjectToTree(subtree, rest, project);
}
}

View file

@ -14,7 +14,7 @@ const defaultHandlerReadinessTimeout = 2000;
/**
* Describes configurable watch options.
*/
interface WatchOptions {
interface IWatchOptions {
/**
* Number of milliseconds to wait before we fall back to default watch handler.
*/
@ -33,7 +33,7 @@ function getWatchHandlers(
{
handlerDelay = defaultHandlerDelay,
handlerReadinessTimeout = defaultHandlerReadinessTimeout,
}: WatchOptions
}: IWatchOptions
) {
const typescriptHandler = buildOutput$
.first(data => data.includes('$ tsc'))
@ -60,7 +60,7 @@ function getWatchHandlers(
export function waitUntilWatchIsReady(
stream: NodeJS.EventEmitter,
opts: WatchOptions = {}
opts: IWatchOptions = {}
) {
const buildOutput$ = new Subject<string>();
const onDataListener = (data: Buffer) =>

View file

@ -1,19 +0,0 @@
extends: ../../tslint.yaml
rules:
max-classes-per-file: false
interface-name: false
variable-name: false
no-empty: false
object-literal-sort-keys: false
member-ordering: false
no-console: false
only-arrow-functions: false
no-shadowed-variable: false
no-empty-interface: false
ordered-imports: false
interface-over-type-literal: false
prettier: false
prefer-const: false
member-access: false
no-unused-variable: false