Skip to content

Commit

Permalink
feat(nx): add friendlier output when running outside a workspace
Browse files Browse the repository at this point in the history
  • Loading branch information
catfireparty authored and vsavkin committed Nov 1, 2019
1 parent 99bc6df commit 44c8d17
Show file tree
Hide file tree
Showing 7 changed files with 319 additions and 69 deletions.
73 changes: 5 additions & 68 deletions packages/cli/bin/nx.ts
Original file line number Diff line number Diff line change
@@ -1,76 +1,13 @@
#!/usr/bin/env node
import { statSync } from 'fs';
import * as path from 'path';

function findWorkspaceRoot(dir: string) {
if (path.dirname(dir) === dir) return null;
if (exists(path.join(dir, 'angular.json'))) {
return { type: 'angular', dir };
} else if (exists(path.join(dir, 'workspace.json'))) {
return { type: 'nx', dir };
} else {
return findWorkspaceRoot(path.dirname(dir));
}
}

function exists(filePath: string): boolean {
try {
return statSync(filePath).isFile() || statSync(filePath).isDirectory();
} catch (err) {
return false;
}
}
import { findWorkspaceRoot } from '../lib/find-workspace-root';
import { initGlobal } from '../lib/init-global';
import { initLocal } from '../lib/init-local';

const workspace = findWorkspaceRoot(__dirname);

// we are running a local nx
if (workspace) {
// required to make sure nrwl/workspace import works
if (workspace.type === 'nx') {
require(path.join(
workspace.dir,
'node_modules',
'@nrwl',
'tao',
'src',
'compat',
'compat.js'
));
}

// The commandsObject is a Yargs object declared in `nx-commands.ts`,
// It is exposed and bootstrapped here to provide CLI features.
const w = require('@nrwl/workspace');
if (w.supportedNxCommands.includes(process.argv[2])) {
w.commandsObject.argv;
} else if (workspace.type === 'nx') {
require(path.join(
workspace.dir,
'node_modules',
'@nrwl',
'tao',
'index.js'
));
} else if (workspace.type === 'angular') {
w.output.note({
title: `Nx didn't recognize the command, forwarding on to the Angular CLI.`
});
require(path.join(
workspace.dir,
'node_modules',
'@angular',
'cli',
'lib',
'init.js'
));
}
initLocal(workspace);
} else {
// we are running global nx
const w = findWorkspaceRoot(process.cwd());
if (w) {
require(path.join(w.dir, 'node_modules', '@nrwl', 'cli', 'bin', 'nx.js'));
} else {
console.log(`The current directory isn't part of an Nx workspace.`);
process.exit(0);
}
initGlobal();
}
25 changes: 25 additions & 0 deletions packages/cli/lib/find-workspace-root.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { existsSync } from 'fs';
import * as path from 'path';
import { Workspace } from './workspace';

/**
* Recursive function that walks back up the directory
* tree to try and find a workspace file.
*
* @param dir Directory to start searching with
*/
export function findWorkspaceRoot(dir: string): Workspace {
if (path.dirname(dir) === dir) {
return null;
}

if (existsSync(path.join(dir, 'angular.json'))) {
return { type: 'angular', dir };
}

if (existsSync(path.join(dir, 'workspace.json'))) {
return { type: 'nx', dir };
}

return findWorkspaceRoot(path.dirname(dir));
}
36 changes: 36 additions & 0 deletions packages/cli/lib/init-global.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import chalk from 'chalk';
import * as path from 'path';
import { findWorkspaceRoot } from './find-workspace-root';
import { output } from './output';

/**
* Nx is being run from outside a workspace
*/
export function initGlobal() {
const workspace = findWorkspaceRoot(process.cwd());

if (workspace) {
// Found a workspace root - hand off to the local copy of Nx
require(path.join(
workspace.dir,
'node_modules',
'@nrwl',
'cli',
'bin',
'nx.js'
));
} else {
output.log({
title: `The current directory isn't part of an Nx workspace.`,
bodyLines: [
`To create a workspace run:`,
chalk.bold.white(`npx create-nx-workspace@latest <workspace name>`)
]
});

output.note({
title: `For more information please visit https://nx.dev/`
});
process.exit(0);
}
}
49 changes: 49 additions & 0 deletions packages/cli/lib/init-local.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import * as path from 'path';
import { Workspace } from './workspace';

/**
* Nx is being run inside a workspace.
*
* @param workspace Relevant local workspace properties
*/
export function initLocal(workspace: Workspace) {
// required to make sure nrwl/workspace import works
if (workspace.type === 'nx') {
require(path.join(
workspace.dir,
'node_modules',
'@nrwl',
'tao',
'src',
'compat',
'compat.js'
));
}

// The commandsObject is a Yargs object declared in `nx-commands.ts`,
// It is exposed and bootstrapped here to provide CLI features.
const w = require('@nrwl/workspace');
if (w.supportedNxCommands.includes(process.argv[2])) {
w.commandsObject.argv;
} else if (workspace.type === 'nx') {
require(path.join(
workspace.dir,
'node_modules',
'@nrwl',
'tao',
'index.js'
));
} else if (workspace.type === 'angular') {
w.output.note({
title: `Nx didn't recognize the command, forwarding on to the Angular CLI.`
});
require(path.join(
workspace.dir,
'node_modules',
'@angular',
'cli',
'lib',
'init.js'
));
}
}
198 changes: 198 additions & 0 deletions packages/cli/lib/output.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
/**
* This file has been copied from workspace/src/command-line/output
* Consider that file to be the golden source. Changes (which should be few)
* should be copied here if necessary.
*/
import chalk from 'chalk';

export interface CLIErrorMessageConfig {
title: string;
bodyLines?: string[];
slug?: string;
}

export interface CLIWarnMessageConfig {
title: string;
bodyLines?: string[];
slug?: string;
}

export interface CLILogMessageConfig {
title: string;
bodyLines?: string[];
}

export interface CLINoteMessageConfig {
title: string;
bodyLines?: string[];
}

export interface CLISuccessMessageConfig {
title: string;
}

/**
* Automatically disable styling applied by chalk if CI=true
*/
if (process.env.CI === 'true') {
chalk.level = 0;
}

class CLIOutput {
private readonly NX_PREFIX = `${chalk.cyan(
'>'
)} ${chalk.reset.inverse.bold.cyan(' NX ')}`;
/**
* Longer dash character which forms more of a continuous line when place side to side
* with itself, unlike the standard dash character
*/
private readonly VERTICAL_SEPARATOR =
'———————————————————————————————————————————————';

/**
* Expose some color and other utility functions so that other parts of the codebase that need
* more fine-grained control of message bodies are still using a centralized
* implementation.
*/
colors = {
gray: chalk.gray
};
bold = chalk.bold;
underline = chalk.underline;

private writeToStdOut(str: string) {
process.stdout.write(str);
}

private writeOutputTitle({
label,
title
}: {
label?: string;
title: string;
}): void {
let outputTitle: string;
if (label) {
outputTitle = `${this.NX_PREFIX} ${label} ${title}\n`;
} else {
outputTitle = `${this.NX_PREFIX} ${title}\n`;
}
this.writeToStdOut(outputTitle);
}

private writeOptionalOutputBody(bodyLines?: string[]): void {
if (!bodyLines) {
return;
}
this.addNewline();
bodyLines.forEach(bodyLine => this.writeToStdOut(' ' + bodyLine + '\n'));
}

addNewline() {
this.writeToStdOut('\n');
}

addVerticalSeparator() {
this.writeToStdOut(`\n${chalk.gray(this.VERTICAL_SEPARATOR)}\n\n`);
}

error({ title, slug, bodyLines }: CLIErrorMessageConfig) {
this.addNewline();

this.writeOutputTitle({
label: chalk.reset.inverse.bold.red(' ERROR '),
title: chalk.bold.red(title)
});

this.writeOptionalOutputBody(bodyLines);

/**
* Optional slug to be used in an Nx error message redirect URL
*/
if (slug && typeof slug === 'string') {
this.addNewline();
this.writeToStdOut(
chalk.grey(' ' + 'Learn more about this error: ') +
'https://errors.nx.dev/' +
slug +
'\n'
);
}

this.addNewline();
}

warn({ title, slug, bodyLines }: CLIWarnMessageConfig) {
this.addNewline();

this.writeOutputTitle({
label: chalk.reset.inverse.bold.yellow(' WARNING '),
title: chalk.bold.yellow(title)
});

this.writeOptionalOutputBody(bodyLines);

/**
* Optional slug to be used in an Nx warning message redirect URL
*/
if (slug && typeof slug === 'string') {
this.addNewline();
this.writeToStdOut(
chalk.grey(' ' + 'Learn more about this warning: ') +
'https://errors.nx.dev/' +
slug +
'\n'
);
}

this.addNewline();
}

note({ title, bodyLines }: CLINoteMessageConfig) {
this.addNewline();

this.writeOutputTitle({
label: chalk.reset.inverse.bold.keyword('orange')(' NOTE '),
title: chalk.bold.keyword('orange')(title)
});

this.writeOptionalOutputBody(bodyLines);

this.addNewline();
}

success({ title }: CLISuccessMessageConfig) {
this.addNewline();

this.writeOutputTitle({
label: chalk.reset.inverse.bold.green(' SUCCESS '),
title: chalk.bold.green(title)
});

this.addNewline();
}

logSingleLine(message: string) {
this.addNewline();

this.writeOutputTitle({
title: message
});

this.addNewline();
}

log({ title, bodyLines }: CLIWarnMessageConfig) {
this.addNewline();

this.writeOutputTitle({
title: chalk.white(title)
});

this.writeOptionalOutputBody(bodyLines);

this.addNewline();
}
}

export const output = new CLIOutput();
4 changes: 4 additions & 0 deletions packages/cli/lib/workspace.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface Workspace {
type: 'nx' | 'angular';
dir: string;
}
Loading

0 comments on commit 44c8d17

Please sign in to comment.