cleanup(core): extract "core" folder from "command-line"

This commit is contained in:
Victor Savkin 2019-12-15 13:30:46 -05:00 committed by Victor Savkin
parent b794aa68a9
commit 8749c18f91
74 changed files with 768 additions and 941 deletions

View File

@ -4,8 +4,8 @@ import { callRule, runSchematic } from '../utils/testing';
import { stripIndents } from '@angular-devkit/core/src/utils/literals'; import { stripIndents } from '@angular-devkit/core/src/utils/literals';
import { updateJsonInTree } from '@nrwl/workspace/src/utils/ast-utils'; import { updateJsonInTree } from '@nrwl/workspace/src/utils/ast-utils';
import { updateWorkspace } from '@nrwl/workspace/src/utils/workspace'; import { updateWorkspace } from '@nrwl/workspace/src/utils/workspace';
import { NxJson } from '@nrwl/workspace/src/command-line/shared';
import { rulesNodeJSSha, rulesNodeJSVersion } from '../utils/versions'; import { rulesNodeJSSha, rulesNodeJSVersion } from '../utils/versions';
import { NxJson } from '@nrwl/workspace/src/core/shared-interfaces';
describe('@nrwl/bazel:sync', () => { describe('@nrwl/bazel:sync', () => {
let tree: Tree; let tree: Tree;

View File

@ -21,7 +21,7 @@ import { join, normalize } from '@angular-devkit/core';
import { import {
ProjectGraph, ProjectGraph,
ProjectGraphNode ProjectGraphNode
} from '@nrwl/workspace/src/command-line/project-graph'; } from '@nrwl/workspace/src/core/project-graph';
import { rulesNodeJSSha, rulesNodeJSVersion } from '../utils/versions'; import { rulesNodeJSSha, rulesNodeJSVersion } from '../utils/versions';
import { TargetDefinition } from '@angular-devkit/core/src/workspace'; import { TargetDefinition } from '@angular-devkit/core/src/workspace';

View File

@ -1,7 +1,7 @@
#!/usr/bin/env node #!/usr/bin/env node
// we can import from '@nrwl/workspace' because it will require typescript // we can import from '@nrwl/workspace' because it will require typescript
import { output } from '@nrwl/workspace/src/command-line/output'; import { output } from '@nrwl/workspace/src/utils/output';
import { execSync } from 'child_process'; import { execSync } from 'child_process';
import { writeFileSync } from 'fs'; import { writeFileSync } from 'fs';
import * as inquirer from 'inquirer'; import * as inquirer from 'inquirer';

View File

@ -1,8 +1,3 @@
import {
readWorkspaceJson,
readNxJson,
normalizedProjectRoot
} from '@nrwl/workspace/src/command-line/shared';
import { appRootPath } from '@nrwl/workspace/src/utils/app-root'; import { appRootPath } from '@nrwl/workspace/src/utils/app-root';
import { import {
DepConstraint, DepConstraint,
@ -24,7 +19,12 @@ import {
createProjectGraph, createProjectGraph,
ProjectGraph, ProjectGraph,
ProjectType ProjectType
} from '@nrwl/workspace/src/command-line/project-graph'; } from '@nrwl/workspace/src/core/project-graph';
import {
normalizedProjectRoot,
readNxJson,
readWorkspaceJson
} from '@nrwl/workspace/src/core/file-utils';
type Options = [ type Options = [
{ {

View File

@ -8,7 +8,7 @@ import {
DependencyType, DependencyType,
ProjectGraph, ProjectGraph,
ProjectType ProjectType
} from '@nrwl/workspace/src/command-line/project-graph'; } from '@nrwl/workspace/src/core/project-graph';
import { extname } from 'path'; import { extname } from 'path';
describe('Enforce Module Boundaries', () => { describe('Enforce Module Boundaries', () => {

View File

@ -10,8 +10,8 @@ import {
DefaultTasksRunnerOptions DefaultTasksRunnerOptions
} from '@nrwl/workspace/src/tasks-runner/default-tasks-runner'; } from '@nrwl/workspace/src/tasks-runner/default-tasks-runner';
import * as fs from 'fs'; import * as fs from 'fs';
import { TasksMap } from '@nrwl/workspace/src/command-line/run-tasks/run-command'; import { TasksMap } from '@nrwl/workspace/src/tasks-runner/run-command';
import { ProjectGraph } from '@nrwl/workspace/src/command-line/project-graph'; import { ProjectGraph } from '@nrwl/workspace/src/core/project-graph';
const axios = require('axios'); const axios = require('axios');
interface InsightsTaskRunnerOptions extends DefaultTasksRunnerOptions { interface InsightsTaskRunnerOptions extends DefaultTasksRunnerOptions {

View File

@ -19,16 +19,13 @@ export {
ExistingPrettierConfig, ExistingPrettierConfig,
resolveUserExistingPrettierConfig resolveUserExistingPrettierConfig
} from './src/utils/common'; } from './src/utils/common';
export { output } from './src/command-line/output'; export { output } from './src/utils/output';
export { export {
commandsObject, commandsObject,
supportedNxCommands supportedNxCommands
} from './src/command-line/nx-commands'; } from './src/command-line/nx-commands';
export { export { readWorkspaceJson, readNxJson } from './src/core/file-utils';
readWorkspaceJson, export { NxJson } from './src/core/shared-interfaces';
readNxJson,
NxJson
} from './src/command-line/shared';
export { export {
readJsonInTree, readJsonInTree,
updateJsonInTree, updateJsonInTree,

View File

@ -6,7 +6,7 @@ import {
import { JsonObject } from '@angular-devkit/core'; import { JsonObject } from '@angular-devkit/core';
import { exec } from 'child_process'; import { exec } from 'child_process';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { TEN_MEGABYTES } from '../../command-line/shared'; import { TEN_MEGABYTES } from '@nrwl/workspace/src/core/file-utils';
try { try {
require('dotenv').config(); require('dotenv').config();

View File

@ -0,0 +1,145 @@
import * as yargs from 'yargs';
import { generateGraph } from './dep-graph';
import { output } from '../utils/output';
import { parseFiles, printArgsWarning } from './shared';
import { runCommand } from '../tasks-runner/run-command';
import { NxArgs, splitArgsIntoNxArgsAndTargetArgs } from './utils';
import { filterAffected } from '../core/affected-project-graph';
import {
createProjectGraph,
ProjectGraphNode,
ProjectType,
withDeps
} from '../core/project-graph';
import { calculateFileChanges, readEnvironment } from '../core/file-utils';
import { printAffected } from './print-affected';
import { projectHasTargetAndConfiguration } from '../utils/project-has-target-and-configuration';
export function affected(command: string, parsedArgs: yargs.Arguments): void {
const { nxArgs, targetArgs } = splitArgsIntoNxArgsAndTargetArgs(parsedArgs);
const env = readEnvironment(nxArgs.target);
const projectGraph = createProjectGraph();
const fileChanges = readFileChanges(nxArgs);
let affectedGraph = filterAffected(projectGraph, fileChanges);
if (parsedArgs.withDeps) {
affectedGraph = withDeps(projectGraph, Object.values(affectedGraph.nodes));
}
const affectedProjects = Object.values(
parsedArgs.all ? projectGraph.nodes : affectedGraph.nodes
)
.filter(n => !parsedArgs.exclude.includes(n.name))
.filter(n => !parsedArgs.onlyFailed || !env.workspace.getResult(n.name));
try {
switch (command) {
case 'apps':
const apps = affectedProjects
.filter(p => p.type === ProjectType.app)
.map(p => p.name);
if (parsedArgs.plain) {
console.log(apps.join(' '));
} else {
printArgsWarning(parsedArgs);
if (apps.length) {
output.log({
title: 'Affected apps:',
bodyLines: apps.map(app => `${output.colors.gray('-')} ${app}`)
});
}
}
break;
case 'libs':
const libs = affectedProjects
.filter(p => p.type === ProjectType.lib)
.map(p => p.name);
if (parsedArgs.plain) {
console.log(libs.join(' '));
} else {
printArgsWarning(parsedArgs);
if (libs.length) {
output.log({
title: 'Affected libs:',
bodyLines: libs.map(lib => `${output.colors.gray('-')} ${lib}`)
});
}
}
break;
case 'dep-graph':
const projectNames = affectedProjects.map(p => p.name);
printArgsWarning(parsedArgs);
generateGraph(parsedArgs as any, projectNames);
break;
case 'print-affected':
if (nxArgs.target) {
const projectWithTargetAndConfig = allProjectsWithTargetAndConfiguration(
affectedProjects,
nxArgs
);
printAffected(
projectWithTargetAndConfig,
affectedProjects,
projectGraph,
nxArgs,
targetArgs
);
} else {
printAffected([], affectedProjects, projectGraph, nxArgs, targetArgs);
}
break;
case 'affected': {
const projectWithTargetAndConfig = allProjectsWithTargetAndConfiguration(
affectedProjects,
nxArgs
);
printArgsWarning(parsedArgs);
runCommand(
projectWithTargetAndConfig,
projectGraph,
env,
nxArgs,
targetArgs
);
break;
}
}
} catch (e) {
printError(e, parsedArgs.verbose);
process.exit(1);
}
}
function readFileChanges(nxArgs: NxArgs) {
// Do we still need this `--all` option?
if (nxArgs.all) {
return [];
}
const files = parseFiles(nxArgs).files;
return calculateFileChanges(files, nxArgs.base, nxArgs.head);
}
function allProjectsWithTargetAndConfiguration(
projects: ProjectGraphNode[],
nxArgs: NxArgs
) {
return projects.filter(p =>
projectHasTargetAndConfiguration(p, nxArgs.target, nxArgs.configuration)
);
}
function printError(e: any, verbose?: boolean) {
const bodyLines = [e.message];
if (verbose && e.stack) {
bodyLines.push('');
bodyLines.push(e.stack);
}
output.error({
title: 'There was a critical error when running your command',
bodyLines
});
}

View File

@ -5,8 +5,8 @@ import {
createProjectGraph, createProjectGraph,
ProjectGraph, ProjectGraph,
ProjectGraphNode ProjectGraphNode
} from './project-graph'; } from '../core/project-graph';
import { output } from './output'; import { output } from '../utils/output';
export function generateGraph( export function generateGraph(
args: { file?: string; filter?: string[]; exclude?: string[] }, args: { file?: string; filter?: string[]; exclude?: string[] },

View File

@ -2,16 +2,13 @@ import { execSync } from 'child_process';
import * as path from 'path'; import * as path from 'path';
import * as resolve from 'resolve'; import * as resolve from 'resolve';
import { getProjectRoots, parseFiles, printArgsWarning } from './shared'; import { getProjectRoots, parseFiles, printArgsWarning } from './shared';
import { YargsAffectedOptions } from './run-tasks/affected';
import { fileExists } from '../utils/fileutils'; import { fileExists } from '../utils/fileutils';
import { output } from './output'; import { output } from '../utils/output';
import { createProjectGraph } from './project-graph'; import { createProjectGraph } from '../core/project-graph';
import { filterAffected } from './affected-project-graph'; import { filterAffected } from '../core/affected-project-graph';
import { calculateFileChanges } from './file-utils'; import { calculateFileChanges } from '../core/file-utils';
import * as yargs from 'yargs';
export interface YargsFormatOptions extends YargsAffectedOptions { import { NxArgs } from './utils';
libsAndApps?: boolean;
}
const PRETTIER_EXTENSIONS = [ const PRETTIER_EXTENSIONS = [
'ts', 'ts',
@ -26,11 +23,11 @@ const PRETTIER_EXTENSIONS = [
'md' 'md'
]; ];
export function format(command: 'check' | 'write', args: YargsFormatOptions) { export function format(command: 'check' | 'write', args: yargs.Arguments) {
let patterns: string[]; let patterns: string[];
try { try {
patterns = getPatterns(args); patterns = getPatterns(args as any);
} catch (e) { } catch (e) {
output.error({ output.error({
title: e.message, title: e.message,
@ -60,7 +57,7 @@ export function format(command: 'check' | 'write', args: YargsFormatOptions) {
} }
} }
function getPatterns(args: YargsAffectedOptions) { function getPatterns(args: NxArgs & { libsAndApps: boolean; _: string[] }) {
const allFilesPattern = [`"**/*.{${PRETTIER_EXTENSIONS.join(',')}}"`]; const allFilesPattern = [`"**/*.{${PRETTIER_EXTENSIONS.join(',')}}"`];
try { try {

View File

@ -1,9 +1,9 @@
import { createProjectGraph } from './project-graph'; import { createProjectGraph } from '../core/project-graph';
import { WorkspaceIntegrityChecks } from './workspace-integrity-checks'; import { WorkspaceIntegrityChecks } from './workspace-integrity-checks';
import * as path from 'path'; import * as path from 'path';
import { appRootPath } from '../utils/app-root'; import { appRootPath } from '../utils/app-root';
import { allFilesInDir } from '../command-line/file-utils'; import { allFilesInDir } from '../core/file-utils';
import { output } from './output'; import { output } from '../utils/output';
export function workspaceLint() { export function workspaceLint() {
const graph = createProjectGraph(); const graph = createProjectGraph();

View File

@ -8,7 +8,7 @@ import {
readCapabilitiesFromNodeModules readCapabilitiesFromNodeModules
} from '../utils/plugin-utils'; } from '../utils/plugin-utils';
import { approvedPlugins } from '../utils/plugins'; import { approvedPlugins } from '../utils/plugins';
import { output } from './output'; import { output } from '../utils/output';
export interface YargsListArgs extends yargs.Arguments, ListArgs {} export interface YargsListArgs extends yargs.Arguments, ListArgs {}

View File

@ -3,13 +3,14 @@ import { execSync } from 'child_process';
import { platform } from 'os'; import { platform } from 'os';
import * as yargs from 'yargs'; import * as yargs from 'yargs';
import { nxVersion } from '../utils/versions'; import { nxVersion } from '../utils/versions';
import { affected, runMany } from './run-tasks';
import { generateGraph } from './dep-graph'; import { generateGraph } from './dep-graph';
import { format } from './format'; import { format } from './format';
import { workspaceLint } from './lint'; import { workspaceLint } from './lint';
import { list } from './list'; import { list } from './list';
import { report } from './report'; import { report } from './report';
import { workspaceSchematic } from './workspace-schematic'; import { workspaceSchematic } from './workspace-schematic';
import { affected } from './affected';
import { runMany } from '@nrwl/workspace/src/command-line/run-many';
const noop = (yargs: yargs.Argv): yargs.Argv => yargs; const noop = (yargs: yargs.Argv): yargs.Argv => yargs;
@ -133,7 +134,7 @@ export const commandsObject = yargs
.command( .command(
'print-affected', 'print-affected',
'Graph execution plan', 'Graph execution plan',
yargs => withAffectedOptions(yargs), yargs => withAffectedOptions(withPrintAffectedOptions(yargs)),
args => args =>
affected('print-affected', { affected('print-affected', {
...args ...args
@ -225,6 +226,10 @@ function withFormatOptions(yargs: yargs.Argv): yargs.Argv {
}); });
} }
function withPrintAffectedOptions(yargs: yargs.Argv): yargs.Argv {
return yargs.option('select', { type: 'string' });
}
function withAffectedOptions(yargs: yargs.Argv): yargs.Argv { function withAffectedOptions(yargs: yargs.Argv): yargs.Argv {
return yargs return yargs
.option('files', { .option('files', {

View File

@ -1,84 +0,0 @@
import * as fs from 'fs';
import * as path from 'path';
import { readWorkspaceConfigPath, updateJsonFile } from '../utils/fileutils';
type Migration = { description: string; run(): void };
type MigrationName = { name: string; migration: Migration };
const allMigrations = fs
.readdirSync(path.join(__dirname, '/../../migrations'))
.filter(f => f.endsWith('.js') && !f.endsWith('.d.js'))
.map(file => ({
migration: require(`../../migrations/${file}`).default,
name: path.parse(file).name
}));
const latestMigration = readLatestMigration();
const migrationsToRun = calculateMigrationsToRun(
allMigrations,
latestMigration
);
if (migrationsToRun.length === 0) {
console.log('No migrations to run');
process.exit(0);
}
printMigrationsNames(latestMigration, migrationsToRun);
runMigrations(migrationsToRun);
updateLatestMigration();
console.log('All migrations run successfully');
function readLatestMigration(): string {
const angularCli = readWorkspaceConfigPath();
return angularCli.project.latestMigration;
}
function calculateMigrationsToRun(
migrations: MigrationName[],
latestMigration: string
) {
const startingWith = latestMigration
? migrations.findIndex(item => item.name === latestMigration) + 1
: 0;
return migrations.slice(startingWith);
}
function printMigrationsNames(
latestMigration: string,
migrations: MigrationName[]
): void {
console.log(
`Nx will run the following migrations (after ${latestMigration}):`
);
migrations.forEach(m => {
console.log(`- ${m.name}`);
});
console.log('---------------------------------------------');
}
function runMigrations(migrations: MigrationName[]): void {
migrations.forEach(m => {
try {
console.log(`Running ${m.name}`);
console.log(m.migration.description);
m.migration.run();
console.log('---------------------------------------------');
} catch (e) {
console.error(`Migration ${m.name} failed`);
console.error(e);
console.error(`Please run 'git checkout .'`);
process.exit(1);
}
});
}
function updateLatestMigration(): void {
// we must reread .angular-cli.json because some of the migrations could have modified it
updateJsonFile('.angular-cli.json', angularCliJson => {
angularCliJson.project.latestMigration =
migrationsToRun[migrationsToRun.length - 1].name;
});
}

View File

@ -0,0 +1,77 @@
import { ProjectGraph, ProjectGraphNode } from '../core/project-graph';
import { Task } from '../tasks-runner/tasks-runner';
import { createTask } from '../tasks-runner/run-command';
import { basename } from 'path';
import { getCommand, getOutputs } from '../tasks-runner/utils';
import * as yargs from 'yargs';
import { NxArgs } from './utils';
import { cliCommand } from '@nrwl/workspace/src/core/file-utils';
export function printAffected(
affectedProjectsWithTargetAndConfig: ProjectGraphNode[],
affectedProjects: ProjectGraphNode[],
projectGraph: ProjectGraph,
nxArgs: NxArgs,
targetArgs: yargs.Arguments
) {
const projectNames = affectedProjects.map(p => p.name);
const tasksJson = createTasks(
affectedProjectsWithTargetAndConfig,
projectGraph,
nxArgs,
targetArgs
);
const result = {
tasks: tasksJson,
projects: projectNames,
projectGraph: serializeProjectGraph(projectGraph)
};
console.log(JSON.stringify(selectPrintAffected(result, null), null, 2));
}
function createTasks(
affectedProjectsWithTargetAndConfig: ProjectGraphNode[],
projectGraph: ProjectGraph,
nxArgs: NxArgs,
targetArgs: yargs.Arguments
) {
const tasks: Task[] = affectedProjectsWithTargetAndConfig.map(
affectedProject =>
createTask({
project: affectedProject,
target: nxArgs.target,
configuration: nxArgs.configuration,
overrides: targetArgs
})
);
const cli = cliCommand();
const isYarn = basename(process.env.npm_execpath || 'npm').startsWith('yarn');
return tasks.map(task => ({
id: task.id,
overrides: targetArgs,
target: task.target,
command: `${isYarn ? 'yarn' : 'npm run'} ${getCommand(cli, isYarn, task)}`,
outputs: getOutputs(projectGraph.nodes, task)
}));
}
function serializeProjectGraph(projectGraph: ProjectGraph) {
const nodes = Object.values(projectGraph.nodes).map(n => n.name);
return { nodes, dependencies: projectGraph.dependencies };
}
function selectPrintAffected(result: any, select: string) {
if (!select) return result;
const parts = select.indexOf('.') > -1 ? select.split('.') : [select];
return parts
.reduce((m, c) => {
if (m[c]) {
return m[c];
} else {
throw new Error(
`Cannot select '${select}' in the results of print-affected.`
);
}
}, result)
.join(', ');
}

View File

@ -2,7 +2,7 @@ import { terminal } from '@angular-devkit/core';
import { readFileSync } from 'fs'; import { readFileSync } from 'fs';
import * as path from 'path'; import * as path from 'path';
import { appRootPath } from '../utils/app-root'; import { appRootPath } from '../utils/app-root';
import { output } from './output'; import { output } from '../utils/output';
export const packagesWeCareAbout = [ export const packagesWeCareAbout = [
'@nrwl/angular', '@nrwl/angular',

View File

@ -0,0 +1,86 @@
import * as yargs from 'yargs';
import { runCommand } from '../tasks-runner/run-command';
import { splitArgsIntoNxArgsAndTargetArgs, NxArgs } from './utils';
import { output } from '../utils/output';
import {
createProjectGraph,
ProjectGraph,
ProjectGraphNode,
withDeps
} from '../core/project-graph';
import { readEnvironment } from '../core/file-utils';
import { projectHasTargetAndConfiguration } from '../utils/project-has-target-and-configuration';
export function runMany(parsedArgs: yargs.Arguments): void {
const { nxArgs, targetArgs } = splitArgsIntoNxArgsAndTargetArgs(parsedArgs);
const env = readEnvironment(nxArgs.target);
const projectGraph = createProjectGraph();
const projects = projectsToRun(nxArgs, projectGraph);
runCommand(projects, projectGraph, env, nxArgs, targetArgs);
}
function projectsToRun(nxArgs: NxArgs, projectGraph: ProjectGraph) {
const allProjects = Object.values(projectGraph.nodes);
if (nxArgs.all) {
return runnableForTargetAndConfiguration(
allProjects,
nxArgs.target,
nxArgs.configuration
);
} else {
checkForInvalidProjects(nxArgs, allProjects);
let selectedProjects = allProjects.filter(
p => nxArgs.projects.indexOf(p.name) > -1
);
if (nxArgs.withDeps) {
selectedProjects = Object.values(
withDeps(projectGraph, selectedProjects).nodes
);
}
return runnableForTargetAndConfiguration(
selectedProjects,
nxArgs.target,
nxArgs.configuration,
true
);
}
}
function checkForInvalidProjects(
nxArgs: NxArgs,
allProjects: ProjectGraphNode[]
) {
const invalid = nxArgs.projects.filter(
name => !allProjects.find(p => p.name === name)
);
if (invalid.length !== 0) {
throw new Error(`Invalid projects: ${invalid.join(', ')}`);
}
}
function runnableForTargetAndConfiguration(
projects: ProjectGraphNode[],
target: string,
configuration?: string,
strict = false
): ProjectGraphNode[] {
const notRunnable = [];
const runnable = [];
for (let project of projects) {
if (projectHasTargetAndConfiguration(project, target, configuration)) {
runnable.push(project);
} else {
notRunnable.push(project);
}
}
if (strict && notRunnable.length) {
output.warn({
title: `the following do not have configuration for "${target}"`,
bodyLines: notRunnable.map(p => '- ' + p)
});
}
return runnable;
}

View File

@ -1,275 +0,0 @@
import { basename } from 'path';
import * as yargs from 'yargs';
import { Task } from '../../tasks-runner/tasks-runner';
import { generateGraph } from '../dep-graph';
import { output } from '../output';
import { cliCommand, parseFiles, printArgsWarning } from '../shared';
import { getCommand, getOutputs } from '../../tasks-runner/utils';
import { createTask, runCommand } from './run-command';
import {
Arguments,
projectHasTargetAndConfiguration,
readEnvironment,
splitArgs
} from './utils';
import { filterAffected } from '../affected-project-graph';
import {
createProjectGraph,
ProjectGraph,
ProjectGraphNode,
ProjectType,
reverse,
withDeps
} from '../project-graph';
import { calculateFileChanges } from '../file-utils';
export interface YargsAffectedOptions
extends yargs.Arguments,
AffectedOptions {}
export interface AffectedOptions {
target?: string;
configuration?: string;
runner?: string;
parallel?: boolean;
maxParallel?: number;
untracked?: boolean;
uncommitted?: boolean;
all?: boolean;
base?: string;
head?: string;
exclude?: string[];
files?: string[];
onlyFailed?: boolean;
'only-failed'?: boolean;
'max-parallel'?: boolean;
verbose?: boolean;
help?: boolean;
version?: boolean;
quiet?: boolean;
plain?: boolean;
withDeps?: boolean;
}
export function affected(
command: string,
parsedArgs: YargsAffectedOptions
): void {
const env = readEnvironment(parsedArgs.target);
const projectGraph = createProjectGraph();
const fileChanges = readFileChanges(parsedArgs);
let affectedGraph = filterAffected(projectGraph, fileChanges);
if (parsedArgs.withDeps) {
affectedGraph = withDeps(projectGraph, Object.values(affectedGraph.nodes));
}
const projects = Object.values(
parsedArgs.all ? projectGraph.nodes : affectedGraph.nodes
)
.filter(n => !parsedArgs.exclude.includes(n.name))
.filter(n => !parsedArgs.onlyFailed || !env.workspace.getResult(n.name));
try {
switch (command) {
case 'apps':
const apps = projects
.filter(p => p.type === ProjectType.app)
.map(p => p.name);
if (parsedArgs.plain) {
console.log(apps.join(' '));
} else {
printArgsWarning(parsedArgs);
if (apps.length) {
output.log({
title: 'Affected apps:',
bodyLines: apps.map(app => `${output.colors.gray('-')} ${app}`)
});
}
}
break;
case 'libs':
const libs = projects
.filter(p => p.type === ProjectType.lib)
.map(p => p.name);
if (parsedArgs.plain) {
console.log(libs.join(' '));
} else {
printArgsWarning(parsedArgs);
if (libs.length) {
output.log({
title: 'Affected libs:',
bodyLines: libs.map(lib => `${output.colors.gray('-')} ${lib}`)
});
}
}
break;
case 'print-affected':
if (parsedArgs && parsedArgs.target) {
const {
args,
projectWithTargetAndConfig
} = allProjectsWithTargetAndConfiguration(projects, parsedArgs);
printAffectedWithTasks(
projectWithTargetAndConfig,
projects,
projectGraph,
args
);
} else {
printAffectedWithoutTasks(projects, projectGraph);
}
break;
case 'dep-graph': {
const projectNames = projects.map(p => p.name);
printArgsWarning(parsedArgs);
generateGraph(parsedArgs as any, projectNames);
break;
}
case 'affected': {
const {
args,
projectWithTargetAndConfig
} = allProjectsWithTargetAndConfiguration(projects, parsedArgs);
printArgsWarning(parsedArgs);
runCommand(projectWithTargetAndConfig, projectGraph, args, env);
break;
}
}
} catch (e) {
printError(e, parsedArgs.verbose);
process.exit(1);
}
}
// -----------------------------------------------------------------------------
function readFileChanges(parsedArgs: YargsAffectedOptions) {
// Do we still need this `--all` option?
if (parsedArgs.all) {
return [];
}
const files = parseFiles(parsedArgs).files;
return calculateFileChanges(files, parsedArgs.base, parsedArgs.head);
}
function allProjectsWithTargetAndConfiguration(
projects: ProjectGraphNode[],
parsedArgs: YargsAffectedOptions
) {
const args = splitArgs(parsedArgs, nxSpecificFlags);
const projectWithTargetAndConfig = projects.filter(p =>
projectHasTargetAndConfiguration(
p,
args.nxArgs.target,
args.nxArgs.configuration
)
);
return { args, projectWithTargetAndConfig };
}
function printError(e: any, verbose?: boolean) {
const bodyLines = [e.message];
if (verbose && e.stack) {
bodyLines.push('');
bodyLines.push(e.stack);
}
output.error({
title: 'There was a critical error when running your command',
bodyLines
});
}
function printAffectedWithTasks(
affectedProjectsWithTargetAndConfig: ProjectGraphNode[],
affectedProjects: ProjectGraphNode[],
projectGraph: ProjectGraph,
args: Arguments<YargsAffectedOptions>
) {
const tasks: Task[] = affectedProjectsWithTargetAndConfig.map(
affectedProject =>
createTask({
project: affectedProject,
target: args.nxArgs.target,
configuration: args.nxArgs.configuration,
overrides: args.overrides
})
);
const cli = cliCommand();
const isYarn = basename(process.env.npm_execpath || 'npm').startsWith('yarn');
const projectNames = affectedProjects.map(p => p.name);
const tasksJson = tasks.map(task => ({
id: task.id,
overrides: task.overrides,
target: task.target,
command: `${isYarn ? 'yarn' : 'npm run'} ${getCommand(cli, isYarn, task)}`,
outputs: getOutputs(projectGraph.nodes, task)
}));
console.log(
JSON.stringify(
{
tasks: tasksJson,
projects: projectNames,
projectGraph: serializeProjectGraph(projectGraph)
},
null,
2
)
);
}
function printAffectedWithoutTasks(
affectedProjects: ProjectGraphNode[],
projectGraph: ProjectGraph
) {
const projectNames = affectedProjects.map(p => p.name);
console.log(
JSON.stringify(
{
tasks: [],
projects: projectNames,
projectGraph: serializeProjectGraph(projectGraph)
},
null,
2
)
);
}
function serializeProjectGraph(projectGraph: ProjectGraph) {
const nodes = Object.values(projectGraph.nodes).map(n => n.name);
return { nodes, dependencies: projectGraph.dependencies };
}
/**
* These options are only for getting an array with properties of AffectedOptions.
*
* @remark They are not defaults or useful for anything else
*/
const dummyOptions: AffectedOptions = {
target: '',
configuration: '',
onlyFailed: false,
'only-failed': false,
untracked: false,
uncommitted: false,
runner: '',
help: false,
version: false,
quiet: false,
all: false,
base: 'base',
head: 'head',
exclude: ['exclude'],
files: [''],
verbose: false,
plain: false
};
const nxSpecificFlags = Object.keys(dummyOptions);

View File

@ -1,2 +0,0 @@
export * from './run-many';
export * from './affected';

View File

@ -1,128 +0,0 @@
import * as yargs from 'yargs';
import { runCommand } from './run-command';
import {
readEnvironment,
splitArgs,
projectHasTargetAndConfiguration
} from './utils';
import { output } from '../output';
import {
createProjectGraph,
ProjectGraph,
ProjectGraphNode,
withDeps
} from '../project-graph';
export type YargsRunManyOptions = yargs.Arguments & RunManyOptions;
export interface RunManyOptions {
target: string;
projects: string[];
all: boolean;
configuration?: string;
runner?: string;
parallel?: boolean;
maxParallel?: number;
onlyFailed?: boolean;
'only-failed'?: boolean;
'max-parallel'?: boolean;
verbose?: boolean;
help?: boolean;
version?: boolean;
quiet?: boolean;
withDeps?: boolean;
'with-deps'?: boolean;
}
export function runMany(parsedArgs: yargs.Arguments): void {
const args = splitArgs(normalize(parsedArgs) as YargsRunManyOptions, flags);
const env = readEnvironment(args.nxArgs.target);
const projectGraph = createProjectGraph();
const projects = projectsToRun(args.nxArgs, projectGraph);
runCommand(projects, projectGraph, args, env);
}
function projectsToRun(nxArgs: RunManyOptions, projectGraph: ProjectGraph) {
const allProjects = Object.values(projectGraph.nodes);
if (nxArgs.all) {
return runnableForTargetAndConfiguration(
allProjects,
nxArgs.target,
nxArgs.configuration
);
} else {
let selectedProjects = allProjects.filter(
p => nxArgs.projects.indexOf(p.name) > -1
);
if (nxArgs.withDeps) {
selectedProjects = Object.values(
withDeps(projectGraph, selectedProjects).nodes
);
}
return runnableForTargetAndConfiguration(
selectedProjects,
nxArgs.target,
nxArgs.configuration,
true
);
}
}
function normalize(args: yargs.Arguments): yargs.Arguments {
if (!args.all) {
args.all = false;
}
if (!args.projects) {
args.projects = [];
} else {
args.projects = args.projects.split(',').map((p: string) => p.trim());
}
return args;
}
function runnableForTargetAndConfiguration(
projects: ProjectGraphNode[],
target: string,
configuration?: string,
strict = false
): ProjectGraphNode[] {
const notRunnable = [];
const runnable = [];
for (let project of projects) {
if (projectHasTargetAndConfiguration(project, target, configuration)) {
runnable.push(project);
} else {
notRunnable.push(project);
}
}
if (strict && notRunnable.length) {
output.warn({
title: `the following do not have configuration for "${target}"`,
bodyLines: notRunnable.map(p => '- ' + p)
});
}
return runnable;
}
const dummyOptions: RunManyOptions = {
target: '',
projects: [],
all: false,
configuration: '',
onlyFailed: false,
'only-failed': false,
runner: '',
help: false,
version: false,
quiet: false,
verbose: false,
withDeps: false,
'with-deps': false
};
const flags = Object.keys(dummyOptions);

View File

@ -1,51 +0,0 @@
import { splitArgs } from './utils';
describe('splitArgs', () => {
it('should split nx specific arguments into nxArgs', () => {
expect(
splitArgs(
{
files: [''],
notNxArg: true,
_: ['--override'],
$0: ''
},
['files']
).nxArgs
).toEqual({
files: ['']
});
});
it('should split non nx specific arguments into target args', () => {
expect(
splitArgs(
{
files: [''],
notNxArg: true,
_: ['--override'],
$0: ''
},
['files']
).targetArgs
).toEqual({
notNxArg: true
});
});
it('should split delimited args into task overrides', () => {
expect(
splitArgs(
{
files: [''],
notNxArg: true,
_: ['', '--override'],
$0: ''
},
['files']
).overrides
).toEqual({
override: true
});
});
});

View File

@ -1,75 +0,0 @@
import * as yargsParser from 'yargs-parser';
import * as yargs from 'yargs';
import { NxJson, readNxJson, readWorkspaceJson } from '../shared';
import { WorkspaceResults } from '../workspace-results';
import { ProjectGraphNode } from '../project-graph';
export interface Arguments<T extends yargs.Arguments> {
nxArgs: T;
targetArgs: T;
overrides: any;
}
const ignoreArgs = ['$0', '_'];
export function splitArgs<T extends yargs.Arguments>(
args: T,
nxSpecific: (keyof T)[]
): Arguments<T> {
const nx: any = {};
const targetArgs: any = {};
const overrides = yargsParser(args._.slice(1));
delete overrides._;
Object.entries(args).forEach(([key, value]) => {
if (nxSpecific.includes(key as any)) {
nx[key] = value;
} else if (!ignoreArgs.includes(key)) {
targetArgs[key] = value;
}
});
return { nxArgs: nx, targetArgs, overrides };
}
export interface TaskArgs {
projects: string[];
target: string;
all?: boolean;
}
export function projectHasTargetAndConfiguration(
project: ProjectGraphNode,
target: string,
configuration?: string
) {
if (
!project.data ||
!project.data.architect ||
!project.data.architect[target]
) {
return false;
}
if (!configuration) {
return !!project.data.architect[target];
} else {
return (
project.data.architect[target].configurations &&
project.data.architect[target].configurations[configuration]
);
}
}
export interface Environment {
nxJson: NxJson;
workspaceJson: any;
workspace: any;
}
export function readEnvironment(target: string): Environment {
const nxJson = readNxJson();
const workspaceJson = readWorkspaceJson();
const workspace = new WorkspaceResults(target);
return { nxJson, workspaceJson, workspace };
}

View File

@ -1,44 +1,12 @@
import { execSync } from 'child_process'; import { execSync } from 'child_process';
import * as fs from 'fs'; import * as fs from 'fs';
import { appRootPath } from '../utils/app-root'; import { output } from '../utils/output';
import { readJsonFile } from '../utils/fileutils'; import { createProjectGraph, ProjectGraphNode } from '../core/project-graph';
import { YargsAffectedOptions } from './run-tasks/affected'; import { NxArgs } from './utils';
import { output } from './output'; import { NxJson } from '../core/shared-interfaces';
import { allFilesInDir, FileData } from './file-utils'; import { readWorkspaceJson, TEN_MEGABYTES } from '../core/file-utils';
import {
createProjectGraph,
ProjectGraphNode,
ProjectType
} from './project-graph';
export const TEN_MEGABYTES = 1024 * 10000; export function printArgsWarning(options: NxArgs) {
export type ImplicitDependencyEntry = { [key: string]: '*' | string[] };
export type NormalizedImplicitDependencyEntry = { [key: string]: string[] };
export type ImplicitDependencies = {
files: NormalizedImplicitDependencyEntry;
projects: NormalizedImplicitDependencyEntry;
};
export interface NxJson {
implicitDependencies?: ImplicitDependencyEntry;
npmScope: string;
projects: {
[projectName: string]: NxJsonProjectConfig;
};
tasksRunnerOptions?: {
[tasksRunnerName: string]: {
runner: string;
options?: unknown;
};
};
}
export interface NxJsonProjectConfig {
implicitDependencies?: string[];
tags?: string[];
}
export function printArgsWarning(options: YargsAffectedOptions) {
const { files, uncommitted, untracked, base, head, all } = options; const { files, uncommitted, untracked, base, head, all } = options;
if ( if (
@ -72,7 +40,7 @@ export function printArgsWarning(options: YargsAffectedOptions) {
} }
} }
export function parseFiles(options: YargsAffectedOptions): { files: string[] } { export function parseFiles(options: NxArgs): { files: string[] } {
const { files, uncommitted, untracked, base, head } = options; const { files, uncommitted, untracked, base, head } = options;
if (files) { if (files) {
@ -147,146 +115,7 @@ function parseGitOutput(command: string): string[] {
.filter(a => a.length > 0); .filter(a => a.length > 0);
} }
function detectAndSetInvalidProjectValues( // TODO: remove it in Nx 10
map: Map<string, string[]>,
sourceName: string,
desiredProjectNames: string[],
validProjects: any
) {
const invalidProjects = desiredProjectNames.filter(
projectName => !validProjects[projectName]
);
if (invalidProjects.length > 0) {
map.set(sourceName, invalidProjects);
}
}
export function assertWorkspaceValidity(workspaceJson, nxJson) {
const workspaceJsonProjects = Object.keys(workspaceJson.projects);
const nxJsonProjects = Object.keys(nxJson.projects);
if (minus(workspaceJsonProjects, nxJsonProjects).length > 0) {
throw new Error(
`${workspaceFileName()} and nx.json are out of sync. The following projects are missing in nx.json: ${minus(
workspaceJsonProjects,
nxJsonProjects
).join(', ')}`
);
}
if (minus(nxJsonProjects, workspaceJsonProjects).length > 0) {
throw new Error(
`${workspaceFileName()} and nx.json are out of sync. The following projects are missing in ${workspaceFileName()}: ${minus(
nxJsonProjects,
workspaceJsonProjects
).join(', ')}`
);
}
const projects = {
...workspaceJson.projects,
...nxJson.projects
};
const invalidImplicitDependencies = new Map<string, string[]>();
Object.entries<'*' | string[]>(nxJson.implicitDependencies || {})
.filter(([_, val]) => val !== '*') // These are valid since it is calculated
.reduce((map, [filename, projectNames]: [string, string[]]) => {
detectAndSetInvalidProjectValues(map, filename, projectNames, projects);
return map;
}, invalidImplicitDependencies);
nxJsonProjects
.filter(nxJsonProjectName => {
const project = nxJson.projects[nxJsonProjectName];
return !!project.implicitDependencies;
})
.reduce((map, nxJsonProjectName) => {
const project = nxJson.projects[nxJsonProjectName];
detectAndSetInvalidProjectValues(
map,
nxJsonProjectName,
project.implicitDependencies,
projects
);
return map;
}, invalidImplicitDependencies);
if (invalidImplicitDependencies.size === 0) {
return;
}
let message = `The following implicitDependencies specified in nx.json are invalid:
`;
invalidImplicitDependencies.forEach((projectNames, key) => {
const str = ` ${key}
${projectNames.map(projectName => ` ${projectName}`).join('\n')}`;
message += str;
});
throw new Error(message);
}
function minus(a: string[], b: string[]): string[] {
const res = [];
a.forEach(aa => {
if (!b.find(bb => bb === aa)) {
res.push(aa);
}
});
return res;
}
export function cliCommand() {
return workspaceFileName() === 'angular.json' ? 'ng' : 'nx';
}
export function readWorkspaceJson(): any {
return readJsonFile(`${appRootPath}/${workspaceFileName()}`);
}
export function workspaceFileName() {
const packageJson = readPackageJson();
if (
packageJson.devDependencies['@angular/cli'] ||
packageJson.dependencies['@angular/cli']
) {
return 'angular.json';
} else {
return 'workspace.json';
}
}
export function readPackageJson(): any {
return readJsonFile(`${appRootPath}/package.json`);
}
export function readNxJson(): NxJson {
const config = readJsonFile<NxJson>(`${appRootPath}/nx.json`);
if (!config.npmScope) {
throw new Error(`nx.json must define the npmScope property.`);
}
return config;
}
export function readWorkspaceFiles(): FileData[] {
const workspaceJson = readWorkspaceJson();
const files = [];
// Add known workspace files and directories
files.push(...allFilesInDir(appRootPath, false));
files.push(...allFilesInDir(`${appRootPath}/tools`));
// Add files for workspace projects
Object.keys(workspaceJson.projects).forEach(projectName => {
const project = workspaceJson.projects[projectName];
files.push(...allFilesInDir(`${appRootPath}/${project.root}`));
});
return files;
}
export function getProjectNodes( export function getProjectNodes(
workspaceJson: any, workspaceJson: any,
nxJson: NxJson nxJson: NxJson
@ -295,30 +124,7 @@ export function getProjectNodes(
return Object.values(graph.nodes); return Object.values(graph.nodes);
} }
export function normalizedProjectRoot(p: ProjectGraphNode): string {
if (p.data && p.data.root) {
return p.data.root
.split('/')
.filter(v => !!v)
.slice(1)
.join('/');
} else {
return '';
}
}
export function getProjectRoots(projectNames: string[]): string[] { export function getProjectRoots(projectNames: string[]): string[] {
const { projects } = readWorkspaceJson(); const { projects } = readWorkspaceJson();
return projectNames.map(name => projects[name].root); return projectNames.map(name => projects[name].root);
} }
/**
* Returns the time when file was last modified
* Returns -Infinity for a non-existent file
*/
export function mtime(filePath: string): number {
if (!fs.existsSync(filePath)) {
return -Infinity;
}
return fs.statSync(filePath).mtimeMs;
}

View File

@ -0,0 +1,31 @@
import { splitArgsIntoNxArgsAndTargetArgs } from './utils';
describe('splitArgs', () => {
it('should split nx specific arguments into nxArgs', () => {
expect(
splitArgsIntoNxArgsAndTargetArgs({
files: [''],
notNxArg: true,
_: ['--override'],
$0: ''
}).nxArgs
).toEqual({
projects: [],
files: ['']
});
});
it('should split non nx specific arguments into target args', () => {
expect(
splitArgsIntoNxArgsAndTargetArgs({
files: [''],
notNxArg: true,
_: ['--override'],
$0: ''
}).targetArgs
).toEqual({
notNxArg: true,
override: true
});
});
});

View File

@ -0,0 +1,92 @@
import * as yargsParser from 'yargs-parser';
import * as yargs from 'yargs';
import { WorkspaceResults } from './workspace-results';
import { ProjectGraphNode } from '../core/project-graph';
import { readWorkspaceJson, readNxJson } from '../core/file-utils';
import { NxJson } from '../core/shared-interfaces';
/**
* These options are only for getting an array with properties of AffectedOptions.
*
* @remark They are not defaults or useful for anything else
*/
const dummyOptions: NxArgs = {
target: '',
configuration: '',
runner: '',
parallel: false,
maxParallel: 0,
'max-parallel': 0,
untracked: false,
uncommitted: false,
all: false,
base: 'base',
head: 'head',
exclude: ['exclude'],
files: [''],
onlyFailed: false,
'only-failed': false,
verbose: false,
help: false,
version: false,
quiet: false,
plain: false,
withDeps: false,
'with-deps': false,
projects: []
} as any;
const nxSpecific = Object.keys(dummyOptions);
export interface NxArgs {
target?: string;
configuration?: string;
runner?: string;
parallel?: boolean;
maxParallel?: number;
'max-parallel'?: number;
untracked?: boolean;
uncommitted?: boolean;
all?: boolean;
base?: string;
head?: string;
exclude?: string[];
files?: string[];
onlyFailed?: boolean;
'only-failed'?: boolean;
verbose?: boolean;
help?: boolean;
version?: boolean;
quiet?: boolean;
plain?: boolean;
withDeps?: boolean;
'with-deps'?: boolean;
projects?: string[];
_: string[];
}
const ignoreArgs = ['$0', '_'];
export function splitArgsIntoNxArgsAndTargetArgs(
args: yargs.Arguments
): { nxArgs: NxArgs; targetArgs: yargs.Arguments } {
const nxArgs: any = {};
const targetArgs = yargsParser(args._);
delete targetArgs._;
Object.entries(args).forEach(([key, value]) => {
if (nxSpecific.includes(key as any)) {
nxArgs[key] = value;
} else if (!ignoreArgs.includes(key)) {
targetArgs[key] = value;
}
});
if (!nxArgs.projects) {
nxArgs.projects = [];
} else {
nxArgs.projects = args.projects.split(',').map((p: string) => p.trim());
}
return { nxArgs, targetArgs };
}

View File

@ -1,6 +1,6 @@
import { WorkspaceIntegrityChecks } from './workspace-integrity-checks'; import { WorkspaceIntegrityChecks } from './workspace-integrity-checks';
import chalk from 'chalk'; import chalk from 'chalk';
import { ProjectType, ProjectGraph } from './project-graph'; import { ProjectType, ProjectGraph } from '../core/project-graph';
import { extname } from 'path'; import { extname } from 'path';
describe('WorkspaceIntegrityChecks', () => { describe('WorkspaceIntegrityChecks', () => {

View File

@ -1,6 +1,6 @@
import { output, CLIErrorMessageConfig } from './output'; import { output, CLIErrorMessageConfig } from '../utils/output';
import { workspaceFileName } from './shared'; import { ProjectGraph } from '../core/project-graph';
import { ProjectGraph } from './project-graph'; import { workspaceFileName } from '@nrwl/workspace/src/core/file-utils';
export class WorkspaceIntegrityChecks { export class WorkspaceIntegrityChecks {
constructor(private projectGraph: ProjectGraph, private files: string[]) {} constructor(private projectGraph: ProjectGraph, private files: string[]) {}

View File

@ -3,7 +3,7 @@ import * as fs from 'fs';
import { WorkspaceResults } from './workspace-results'; import { WorkspaceResults } from './workspace-results';
import { serializeJson } from '../utils/fileutils'; import { serializeJson } from '../utils/fileutils';
import { stripIndents } from '@angular-devkit/core/src/utils/literals'; import { stripIndents } from '@angular-devkit/core/src/utils/literals';
import { output } from './output'; import { output } from '../utils/output';
describe('WorkspacesResults', () => { describe('WorkspacesResults', () => {
let results: WorkspaceResults; let results: WorkspaceResults;

View File

@ -29,7 +29,7 @@ import * as yargsParser from 'yargs-parser';
import { appRootPath } from '../utils/app-root'; import { appRootPath } from '../utils/app-root';
import { detectPackageManager } from '../utils/detect-package-manager'; import { detectPackageManager } from '../utils/detect-package-manager';
import { fileExists, readJsonFile } from '../utils/fileutils'; import { fileExists, readJsonFile } from '../utils/fileutils';
import { output } from './output'; import { output } from '../utils/output';
const rootDirectory = appRootPath; const rootDirectory = appRootPath;

View File

@ -1,4 +1,4 @@
import { NxJson } from '../shared'; import { NxJson } from '../shared-interfaces';
export interface AffectedProjectGraphContext { export interface AffectedProjectGraphContext {
workspaceJson: any; workspaceJson: any;

View File

@ -4,7 +4,7 @@ import { stripIndents } from '@angular-devkit/core/src/utils/literals';
import { createProjectGraph } from '../project-graph'; import { createProjectGraph } from '../project-graph';
import { filterAffected } from './affected-project-graph'; import { filterAffected } from './affected-project-graph';
import { FileData } from '../file-utils'; import { FileData } from '../file-utils';
import { NxJson } from '../shared'; import { NxJson } from '../shared-interfaces';
jest.mock('fs', () => require('memfs').fs); jest.mock('fs', () => require('memfs').fs);
jest.mock('../../utils/app-root', () => ({ appRootPath: '/root' })); jest.mock('../../utils/app-root', () => ({ appRootPath: '/root' }));

View File

@ -1,7 +1,7 @@
import { ProjectGraph } from '../project-graph'; import { ProjectGraph } from '../project-graph';
import { NxJson, readNxJson, readWorkspaceJson } from '../shared'; import { FileChange, readNxJson, readWorkspaceJson } from '../file-utils';
import { FileChange } from '../file-utils';
import { filterAffectedProjects } from './filter-affected-projects'; import { filterAffectedProjects } from './filter-affected-projects';
import { NxJson } from '../shared-interfaces';
export function filterAffected( export function filterAffected(
graph: ProjectGraph, graph: ProjectGraph,

View File

@ -1,4 +1,4 @@
import { assertWorkspaceValidity } from './shared'; import { assertWorkspaceValidity } from './assert-workspace-validity';
describe('assertWorkspaceValidity', () => { describe('assertWorkspaceValidity', () => {
let mockNxJson: any; let mockNxJson: any;

View File

@ -0,0 +1,92 @@
import { workspaceFileName } from './file-utils';
export function assertWorkspaceValidity(workspaceJson, nxJson) {
const workspaceJsonProjects = Object.keys(workspaceJson.projects);
const nxJsonProjects = Object.keys(nxJson.projects);
if (minus(workspaceJsonProjects, nxJsonProjects).length > 0) {
throw new Error(
`${workspaceFileName()} and nx.json are out of sync. The following projects are missing in nx.json: ${minus(
workspaceJsonProjects,
nxJsonProjects
).join(', ')}`
);
}
if (minus(nxJsonProjects, workspaceJsonProjects).length > 0) {
throw new Error(
`${workspaceFileName()} and nx.json are out of sync. The following projects are missing in ${workspaceFileName()}: ${minus(
nxJsonProjects,
workspaceJsonProjects
).join(', ')}`
);
}
const projects = {
...workspaceJson.projects,
...nxJson.projects
};
const invalidImplicitDependencies = new Map<string, string[]>();
Object.entries<'*' | string[]>(nxJson.implicitDependencies || {})
.filter(([_, val]) => val !== '*') // These are valid since it is calculated
.reduce((map, [filename, projectNames]: [string, string[]]) => {
detectAndSetInvalidProjectValues(map, filename, projectNames, projects);
return map;
}, invalidImplicitDependencies);
nxJsonProjects
.filter(nxJsonProjectName => {
const project = nxJson.projects[nxJsonProjectName];
return !!project.implicitDependencies;
})
.reduce((map, nxJsonProjectName) => {
const project = nxJson.projects[nxJsonProjectName];
detectAndSetInvalidProjectValues(
map,
nxJsonProjectName,
project.implicitDependencies,
projects
);
return map;
}, invalidImplicitDependencies);
if (invalidImplicitDependencies.size === 0) {
return;
}
let message = `The following implicitDependencies specified in nx.json are invalid:
`;
invalidImplicitDependencies.forEach((projectNames, key) => {
const str = ` ${key}
${projectNames.map(projectName => ` ${projectName}`).join('\n')}`;
message += str;
});
throw new Error(message);
}
function detectAndSetInvalidProjectValues(
map: Map<string, string[]>,
sourceName: string,
desiredProjectNames: string[],
validProjects: any
) {
const invalidProjects = desiredProjectNames.filter(
projectName => !validProjects[projectName]
);
if (invalidProjects.length > 0) {
map.set(sourceName, invalidProjects);
}
}
function minus(a: string[], b: string[]): string[] {
const res = [];
a.forEach(aa => {
if (!b.find(bb => bb === aa)) {
res.push(aa);
}
});
return res;
}

View File

@ -2,10 +2,14 @@ import * as path from 'path';
import * as fs from 'fs'; import * as fs from 'fs';
import { appRootPath } from '../utils/app-root'; import { appRootPath } from '../utils/app-root';
import { extname } from 'path'; import { extname } from 'path';
import { mtime } from './shared';
import { jsonDiff } from '../utils/json-diff'; import { jsonDiff } from '../utils/json-diff';
import { readFileSync } from 'fs'; import { readFileSync } from 'fs';
import { execSync } from 'child_process'; import { execSync } from 'child_process';
import { readJsonFile } from '../utils/fileutils';
import { Environment, NxJson } from './shared-interfaces';
import { ProjectGraphNode } from './project-graph';
import { WorkspaceResults } from '../command-line/workspace-results';
const ignore = require('ignore'); const ignore = require('ignore');
export interface FileData { export interface FileData {
@ -59,7 +63,7 @@ export function calculateFileChanges(
}); });
} }
const TEN_MEGABYTES = 1024 * 10000; export const TEN_MEGABYTES = 1024 * 10000;
function defaultReadFileAtRevision( function defaultReadFileAtRevision(
file: string, file: string,
@ -126,3 +130,83 @@ function getIgnoredGlobs() {
function readFileIfExisting(path: string) { function readFileIfExisting(path: string) {
return fs.existsSync(path) ? fs.readFileSync(path, 'UTF-8').toString() : ''; return fs.existsSync(path) ? fs.readFileSync(path, 'UTF-8').toString() : '';
} }
export function readWorkspaceJson(): any {
return readJsonFile(`${appRootPath}/${workspaceFileName()}`);
}
export function cliCommand() {
return workspaceFileName() === 'angular.json' ? 'ng' : 'nx';
}
export function workspaceFileName() {
const packageJson = readPackageJson();
if (
packageJson.devDependencies['@angular/cli'] ||
packageJson.dependencies['@angular/cli']
) {
return 'angular.json';
} else {
return 'workspace.json';
}
}
export function readPackageJson(): any {
return readJsonFile(`${appRootPath}/package.json`);
}
export function readNxJson(): NxJson {
const config = readJsonFile<NxJson>(`${appRootPath}/nx.json`);
if (!config.npmScope) {
throw new Error(`nx.json must define the npmScope property.`);
}
return config;
}
export function readWorkspaceFiles(): FileData[] {
const workspaceJson = readWorkspaceJson();
const files = [];
// Add known workspace files and directories
files.push(...allFilesInDir(appRootPath, false));
files.push(...allFilesInDir(`${appRootPath}/tools`));
// Add files for workspace projects
Object.keys(workspaceJson.projects).forEach(projectName => {
const project = workspaceJson.projects[projectName];
files.push(...allFilesInDir(`${appRootPath}/${project.root}`));
});
return files;
}
export function readEnvironment(target: string): Environment {
const nxJson = readNxJson();
const workspaceJson = readWorkspaceJson();
const workspace = new WorkspaceResults(target);
return { nxJson, workspaceJson, workspace };
}
/**
* Returns the time when file was last modified
* Returns -Infinity for a non-existent file
*/
export function mtime(filePath: string): number {
if (!fs.existsSync(filePath)) {
return -Infinity;
}
return fs.statSync(filePath).mtimeMs;
}
export function normalizedProjectRoot(p: ProjectGraphNode): string {
if (p.data && p.data.root) {
return p.data.root
.split('/')
.filter(v => !!v)
.slice(1)
.join('/');
} else {
return '';
}
}

View File

@ -5,7 +5,7 @@ import {
ProjectGraphNodeRecords ProjectGraphNodeRecords
} from '../project-graph-models'; } from '../project-graph-models';
import { TypeScriptImportLocator } from './typescript-import-locator'; import { TypeScriptImportLocator } from './typescript-import-locator';
import { normalizedProjectRoot } from '../../shared'; import { normalizedProjectRoot } from '../../file-utils';
export function buildExplicitTypeScriptDependencies( export function buildExplicitTypeScriptDependencies(
ctx: ProjectGraphContext, ctx: ProjectGraphContext,

View File

@ -1,6 +1,6 @@
import { NxJson } from '../shared';
import { FileMap } from '../file-graph'; import { FileMap } from '../file-graph';
import { FileData } from '../file-utils'; import { FileData } from '../file-utils';
import { NxJson } from '../shared-interfaces';
export interface ProjectGraph { export interface ProjectGraph {
nodes: Record<string, ProjectGraphNode>; nodes: Record<string, ProjectGraphNode>;

View File

@ -4,7 +4,7 @@ import { stripIndents } from '@angular-devkit/core/src/utils/literals';
import { createProjectGraph } from './project-graph'; import { createProjectGraph } from './project-graph';
import { DependencyType } from './project-graph-models'; import { DependencyType } from './project-graph-models';
import { FileData } from '../file-utils'; import { FileData } from '../file-utils';
import { NxJson } from '../shared'; import { NxJson } from '../shared-interfaces';
jest.mock('fs', () => require('memfs').fs); jest.mock('fs', () => require('memfs').fs);
jest.mock('../../utils/app-root', () => ({ appRootPath: '/root' })); jest.mock('../../utils/app-root', () => ({ appRootPath: '/root' }));

View File

@ -1,13 +1,6 @@
import { mkdirSync, readFileSync } from 'fs'; import { mkdirSync, readFileSync } from 'fs';
import { ProjectGraph, ProjectGraphContext } from './project-graph-models'; import { ProjectGraph, ProjectGraphContext } from './project-graph-models';
import { ProjectGraphBuilder } from './project-graph-builder'; import { ProjectGraphBuilder } from './project-graph-builder';
import {
assertWorkspaceValidity,
mtime,
readNxJson,
readWorkspaceFiles,
readWorkspaceJson
} from '../shared';
import { appRootPath } from '../../utils/app-root'; import { appRootPath } from '../../utils/app-root';
import { import {
directoryExists, directoryExists,
@ -15,7 +8,13 @@ import {
readJsonFile, readJsonFile,
writeJsonFile writeJsonFile
} from '../../utils/fileutils'; } from '../../utils/fileutils';
import { FileData } from '../file-utils'; import {
FileData,
mtime,
readNxJson,
readWorkspaceFiles,
readWorkspaceJson
} from '../file-utils';
import { createFileMap, FileMap } from '../file-graph'; import { createFileMap, FileMap } from '../file-graph';
import { BuildNodes, buildWorkspaceProjectNodes } from './build-nodes'; import { BuildNodes, buildWorkspaceProjectNodes } from './build-nodes';
import { import {
@ -23,6 +22,7 @@ import {
buildExplicitTypeScriptDependencies, buildExplicitTypeScriptDependencies,
buildImplicitProjectDependencies buildImplicitProjectDependencies
} from './build-dependencies'; } from './build-dependencies';
import { assertWorkspaceValidity } from '../assert-workspace-validity';
export function createProjectGraph( export function createProjectGraph(
workspaceJson = readWorkspaceJson(), workspaceJson = readWorkspaceJson(),

View File

@ -0,0 +1,26 @@
export type ImplicitDependencyEntry = { [key: string]: '*' | string[] };
export interface NxJson {
implicitDependencies?: ImplicitDependencyEntry;
npmScope: string;
projects: {
[projectName: string]: NxJsonProjectConfig;
};
tasksRunnerOptions?: {
[tasksRunnerName: string]: {
runner: string;
options?: unknown;
};
};
}
export interface NxJsonProjectConfig {
implicitDependencies?: string[];
tags?: string[];
}
export interface Environment {
nxJson: NxJson;
workspaceJson: any;
workspace: any;
}

View File

@ -1,4 +1,4 @@
import { output } from '../output'; import { output } from '../utils/output';
export interface ReporterArgs { export interface ReporterArgs {
target?: string; target?: string;

View File

@ -2,7 +2,7 @@ import defaultTaskRunner from './default-tasks-runner';
import { AffectedEventType, Task } from './tasks-runner'; import { AffectedEventType, Task } from './tasks-runner';
jest.mock('npm-run-all', () => jest.fn()); jest.mock('npm-run-all', () => jest.fn());
import * as runAll from 'npm-run-all'; import * as runAll from 'npm-run-all';
jest.mock('../command-line/shared', () => ({ jest.mock('../core/file-utils', () => ({
cliCommand: () => 'nx' cliCommand: () => 'nx'
})); }));
jest.mock('../utils/fileutils', () => ({ jest.mock('../utils/fileutils', () => ({

View File

@ -7,10 +7,10 @@ import {
TaskCompleteEvent, TaskCompleteEvent,
TasksRunner TasksRunner
} from './tasks-runner'; } from './tasks-runner';
import { cliCommand } from '../command-line/shared'; import { output } from '../utils/output';
import { output } from '../command-line/output';
import { readJsonFile } from '../utils/fileutils'; import { readJsonFile } from '../utils/fileutils';
import { getCommand } from './utils'; import { getCommand } from './utils';
import { cliCommand } from '../core/file-utils';
export interface DefaultTasksRunnerOptions { export interface DefaultTasksRunnerOptions {
parallel?: boolean; parallel?: boolean;

View File

@ -1,7 +1,7 @@
import { NxJson } from '../shared'; import { TasksRunner } from './tasks-runner';
import { TasksRunner } from '../../tasks-runner/tasks-runner'; import defaultTasksRunner from './default-tasks-runner';
import defaultTasksRunner from '../../tasks-runner/default-tasks-runner';
import { getRunner } from './run-command'; import { getRunner } from './run-command';
import { NxJson } from '../core/shared-interfaces';
describe('getRunner', () => { describe('getRunner', () => {
let nxJson: NxJson; let nxJson: NxJson;

View File

@ -1,22 +1,18 @@
import { NxJson } from '../shared';
import { import {
AffectedEventType, AffectedEventType,
Task, Task,
TaskCompleteEvent, TaskCompleteEvent,
TasksRunner TasksRunner
} from '../../tasks-runner/tasks-runner'; } from './tasks-runner';
import { defaultTasksRunner } from '../../tasks-runner/default-tasks-runner'; import { defaultTasksRunner } from './default-tasks-runner';
import { isRelativePath } from '../../utils/fileutils'; import { isRelativePath } from '../utils/fileutils';
import { join } from 'path'; import { join } from 'path';
import { appRootPath } from '../../utils/app-root'; import { appRootPath } from '../utils/app-root';
import { DefaultReporter, ReporterArgs } from './default-reporter'; import { DefaultReporter, ReporterArgs } from './default-reporter';
import {
Arguments,
Environment,
projectHasTargetAndConfiguration
} from './utils';
import * as yargs from 'yargs'; import * as yargs from 'yargs';
import { ProjectGraph, ProjectGraphNode } from '../project-graph'; import { ProjectGraph, ProjectGraphNode } from '../core/project-graph';
import { Environment, NxJson } from '../core/shared-interfaces';
import { projectHasTargetAndConfiguration } from '../utils/project-has-target-and-configuration';
export interface TasksMap { export interface TasksMap {
[projectName: string]: { [targetName: string]: Task }; [projectName: string]: { [targetName: string]: Task };
@ -27,17 +23,18 @@ type RunArgs = yargs.Arguments & ReporterArgs;
export function runCommand<T extends RunArgs>( export function runCommand<T extends RunArgs>(
projectsToRun: ProjectGraphNode[], projectsToRun: ProjectGraphNode[],
projectGraph: ProjectGraph, projectGraph: ProjectGraph,
{ nxArgs, overrides, targetArgs }: Arguments<T>, { nxJson, workspace }: Environment,
{ nxJson, workspace }: Environment nxArgs: any,
targetArgs: any
) { ) {
const reporter = new DefaultReporter(); const reporter = new DefaultReporter();
reporter.beforeRun(projectsToRun.map(p => p.name), nxArgs, overrides); reporter.beforeRun(projectsToRun.map(p => p.name), nxArgs, targetArgs);
const tasks: Task[] = projectsToRun.map(project => const tasks: Task[] = projectsToRun.map(project =>
createTask({ createTask({
project, project,
target: nxArgs.target, target: nxArgs.target,
configuration: nxArgs.configuration, configuration: nxArgs.configuration,
overrides: overrides overrides: targetArgs
}) })
); );
@ -54,7 +51,7 @@ export function runCommand<T extends RunArgs>(
project: project, project: project,
target: nxArgs.target, target: nxArgs.target,
configuration: nxArgs.configuration, configuration: nxArgs.configuration,
overrides: overrides overrides: targetArgs
}) })
}; };
} }

View File

@ -1,7 +1,7 @@
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { Target } from '@angular-devkit/architect'; import { Target } from '@angular-devkit/architect';
import { ProjectGraph } from '../command-line/project-graph'; import { ProjectGraph } from '../core/project-graph';
export interface Task { export interface Task {
id: string; id: string;

View File

@ -1,5 +1,5 @@
import { Task } from './tasks-runner'; import { Task } from './tasks-runner';
import { ProjectGraphNode } from '../command-line/project-graph'; import { ProjectGraphNode } from '../core/project-graph';
const commonCommands = ['build', 'test', 'lint', 'e2e', 'deploy']; const commonCommands = ['build', 'test', 'lint', 'e2e', 'deploy'];

View File

@ -3,8 +3,8 @@ import * as ts from 'typescript';
import * as fs from 'fs'; import * as fs from 'fs';
import { Rule } from './nxEnforceModuleBoundariesRule'; import { Rule } from './nxEnforceModuleBoundariesRule';
import { DependencyType, ProjectGraph } from '../command-line/project-graph'; import { DependencyType, ProjectGraph } from '../core/project-graph';
import { ProjectType } from '../command-line/project-graph'; import { ProjectType } from '../core/project-graph';
import { extname } from 'path'; import { extname } from 'path';
describe('Enforce Module Boundaries', () => { describe('Enforce Module Boundaries', () => {

View File

@ -5,12 +5,7 @@ import {
createProjectGraph, createProjectGraph,
ProjectGraph, ProjectGraph,
ProjectGraphNode ProjectGraphNode
} from '../command-line/project-graph'; } from '../core/project-graph';
import {
normalizedProjectRoot,
readNxJson,
readWorkspaceJson
} from '../command-line/shared';
import { appRootPath } from '../utils/app-root'; import { appRootPath } from '../utils/app-root';
import { import {
DepConstraint, DepConstraint,
@ -27,7 +22,12 @@ import {
onlyLoadChildren onlyLoadChildren
} from '../utils/runtime-lint-utils'; } from '../utils/runtime-lint-utils';
import { normalize } from '@angular-devkit/core'; import { normalize } from '@angular-devkit/core';
import { ProjectType } from '../command-line/project-graph'; import { ProjectType } from '../core/project-graph';
import {
normalizedProjectRoot,
readNxJson,
readWorkspaceJson
} from '@nrwl/workspace/src/core/file-utils';
export class Rule extends Lint.Rules.AbstractRule { export class Rule extends Lint.Rules.AbstractRule {
constructor( constructor(

View File

@ -11,13 +11,10 @@ import * as stripJsonComments from 'strip-json-comments';
import { serializeJson } from './fileutils'; import { serializeJson } from './fileutils';
import { NodePackageInstallTask } from '@angular-devkit/schematics/tasks'; import { NodePackageInstallTask } from '@angular-devkit/schematics/tasks';
import { getWorkspacePath } from './cli-config-utils'; import { getWorkspacePath } from './cli-config-utils';
import { import { createProjectGraph, ProjectGraph } from '../core/project-graph';
createProjectGraph, import { FileData } from '../core/file-utils';
ProjectGraph
} from '../command-line/project-graph';
import { NxJson } from '../command-line/shared';
import { FileData } from '../command-line/file-utils';
import { extname, join, normalize, Path } from '@angular-devkit/core'; import { extname, join, normalize, Path } from '@angular-devkit/core';
import { NxJson } from '@nrwl/workspace/src/core/shared-interfaces';
function nodesByPosition(first: ts.Node, second: ts.Node): number { function nodesByPosition(first: ts.Node, second: ts.Node): number {
return first.getStart() - second.getStart(); return first.getStart() - second.getStart();
@ -42,7 +39,7 @@ function insertAfterLastOccurrence(
} }
if (!lastItem && fallbackPos == undefined) { if (!lastItem && fallbackPos == undefined) {
throw new Error( throw new Error(
`tried to insert ${toInsert} as first occurence with no fallback position` `tried to insert ${toInsert} as first occurrence with no fallback position`
); );
} }
const lastItemPosition: number = lastItem ? lastItem.getEnd() : fallbackPos; const lastItemPosition: number = lastItem ? lastItem.getEnd() : fallbackPos;

View File

@ -1,6 +1,6 @@
import { Tree } from '@angular-devkit/schematics'; import { Tree } from '@angular-devkit/schematics';
import { readJsonInTree } from './ast-utils'; import { readJsonInTree } from './ast-utils';
import { NxJson } from '../command-line/shared'; import { NxJson } from '@nrwl/workspace/src/core/shared-interfaces';
export function getWorkspacePath(host: Tree) { export function getWorkspacePath(host: Tree) {
const possibleFiles = ['/workspace.json', '/angular.json', '/.angular.json']; const possibleFiles = ['/workspace.json', '/angular.json', '/.angular.json'];

View File

@ -12,11 +12,6 @@ export interface CLIWarnMessageConfig {
slug?: string; slug?: string;
} }
export interface CLILogMessageConfig {
title: string;
bodyLines?: string[];
}
export interface CLINoteMessageConfig { export interface CLINoteMessageConfig {
title: string; title: string;
bodyLines?: string[]; bodyLines?: string[];

View File

@ -0,0 +1,24 @@
import { ProjectGraphNode } from '@nrwl/workspace/src/core/project-graph';
export function projectHasTargetAndConfiguration(
project: ProjectGraphNode,
target: string,
configuration?: string
) {
if (
!project.data ||
!project.data.architect ||
!project.data.architect[target]
) {
return false;
}
if (!configuration) {
return !!project.data.architect[target];
} else {
return (
project.data.architect[target].configurations &&
project.data.architect[target].configurations[configuration]
);
}
}

View File

@ -1,13 +1,12 @@
import * as path from 'path'; import * as path from 'path';
import { normalizedProjectRoot } from '../command-line/shared';
import { import {
DependencyType, DependencyType,
ProjectGraphNode, ProjectGraphNode,
ProjectGraphDependency, ProjectGraphDependency,
ProjectGraph ProjectGraph
} from '../command-line/project-graph'; } from '../core/project-graph';
import { normalize } from '@angular-devkit/core'; import { normalize } from '@angular-devkit/core';
import { FileData } from '../command-line/file-utils'; import { FileData, normalizedProjectRoot } from '../core/file-utils';
export type Deps = { [projectName: string]: ProjectGraphDependency[] }; export type Deps = { [projectName: string]: ProjectGraphDependency[] };
export type DepConstraint = { export type DepConstraint = {

View File

@ -1,17 +1,9 @@
import { import { Tree } from '@angular-devkit/schematics';
Tree,
MergeStrategy,
DirEntry,
FileEntry,
FilePredicate,
UpdateRecorder,
Action
} from '@angular-devkit/schematics';
import { NxJson } from '../command-line/shared';
import { import {
_test_addWorkspaceFile, _test_addWorkspaceFile,
WorkspaceFormat WorkspaceFormat
} from '@angular-devkit/core/src/workspace/core'; } from '@angular-devkit/core/src/workspace/core';
import { NxJson } from '@nrwl/workspace/src/core/shared-interfaces';
export function getFileContent(tree: Tree, path: string): string { export function getFileContent(tree: Tree, path: string): string {
const fileEntry = tree.get(path); const fileEntry = tree.get(path);