feat(nx): support to generate visualization graph

`npm run dep-graph` outputs a visual dependency graph
This commit is contained in:
Nitin Vericherla 2018-04-04 20:27:00 -04:00 committed by Victor Savkin
parent eca52599d9
commit 49525efe3e
18 changed files with 665 additions and 60 deletions

View File

@ -286,4 +286,136 @@ describe('Command line', () => {
},
1000000
);
describe('dep-graph', () => {
beforeEach(() => {
newProject();
newApp('myapp');
newApp('myapp2');
newApp('myapp3');
newLib('mylib');
newLib('mylib2');
updateFile(
'apps/myapp/src/main.ts',
`
import '@proj/mylib';
const s = {loadChildren: '@proj/mylib2'};
`
);
updateFile(
'apps/myapp2/src/app/app.component.spec.ts',
`import '@proj/mylib';`
);
updateFile(
'libs/mylib/src/mylib.module.spec.ts',
`import '@proj/mylib2';`
);
});
it(
'dep-graph should output json (without critical path) to file',
() => {
const file = 'dep-graph.json';
runCommand(`npm run dep-graph -- --file="${file}"`);
expect(() => checkFilesExist(file)).not.toThrow();
const jsonFileContents = readJson(file);
expect(jsonFileContents).toEqual({
deps: {
mylib2: [],
myapp3: [],
myapp2: [
{
projectName: 'mylib',
type: 'es6Import'
}
],
mylib: [
{
projectName: 'mylib2',
type: 'es6Import'
}
],
myapp: [
{
projectName: 'mylib',
type: 'es6Import'
},
{
projectName: 'mylib2',
type: 'loadChildren'
}
]
},
criticalPath: []
});
},
1000000
);
it(
'dep-graph should output json with critical path to file',
() => {
const file = 'dep-graph.json';
runCommand(
`npm run affected:dep-graph -- --files="libs/mylib/index.ts" --file="${file}"`
);
expect(() => checkFilesExist(file)).not.toThrow();
const jsonFileContents = readJson(file);
expect(jsonFileContents.criticalPath).toContain('myapp');
expect(jsonFileContents.criticalPath).toContain('myapp2');
expect(jsonFileContents.criticalPath).toContain('mylib');
expect(jsonFileContents.criticalPath).not.toContain('mylib2');
},
1000000
);
it(
'dep-graph should output dot to file',
() => {
const file = 'dep-graph.dot';
runCommand(
`npm run dep-graph -- --files="libs/mylib/index.ts" --file="${file}"`
);
expect(() => checkFilesExist(file)).not.toThrow();
const fileContents = readFile(file);
expect(fileContents).toContain('"myapp" -> "mylib"');
expect(fileContents).toContain('"myapp2" -> "mylib"');
expect(fileContents).toContain('"mylib" -> "mylib2"');
},
1000000
);
it(
'dep-graph should output html to file',
() => {
const file = 'dep-graph.html';
runCommand(
`npm run dep-graph -- --files="libs/mylib/index.ts" --file="${file}"`
);
expect(() => checkFilesExist(file)).not.toThrow();
const fileContents = readFile(file);
expect(fileContents).toContain('<html>');
expect(fileContents).toContain('<title>myapp&#45;&gt;mylib</title>');
expect(fileContents).toContain('<title>myapp&#45;&gt;mylib2</title>');
expect(fileContents).toContain('<title>mylib&#45;&gt;mylib2</title>');
},
1000000
);
});
});

View File

@ -14,8 +14,7 @@
"copy": "./scripts/copy.sh",
"test:schematics": "yarn linknpm fast && ./scripts/test_schematics.sh",
"test:nx": "yarn linknpm fast && ./scripts/test_nx.sh",
"test":
"yarn linknpm fast && ./scripts/test_nx.sh && ./scripts/test_schematics.sh",
"test": "yarn linknpm fast && ./scripts/test_nx.sh && ./scripts/test_schematics.sh",
"checkformat": "prettier \"{packages,e2e}/**/*.ts\" --list-different",
"publish_npm": "./scripts/publish.sh"
},
@ -44,6 +43,7 @@
"angular": "1.6.6",
"app-root-path": "^2.0.1",
"cosmiconfig": "^4.0.0",
"graphviz": "^0.0.8",
"husky": "^0.14.3",
"jasmine-core": "~2.8.0",
"jasmine-spec-reporter": "~4.2.1",
@ -52,8 +52,9 @@
"karma-chrome-launcher": "~2.2.0",
"karma-jasmine": "~1.1.0",
"karma-webpack": "2.0.4",
"npm-run-all": "4.1.2",
"ng-packagr": "2.2.0",
"npm-run-all": "4.1.2",
"opn": "^5.3.0",
"precise-commits": "1.0.0",
"prettier": "1.10.2",
"rxjs": "^5.5.6",
@ -62,6 +63,7 @@
"tmp": "0.0.33",
"tslint": "5.9.1",
"typescript": "2.6.2",
"viz.js": "^1.8.1",
"yargs-parser": "9.0.2",
"zone.js": "^0.8.19",
"fs-extra": "5.0.0"

View File

@ -12,12 +12,14 @@
"affected:apps": "./node_modules/.bin/nx affected apps",
"affected:build": "./node_modules/.bin/nx affected build",
"affected:e2e": "./node_modules/.bin/nx affected e2e",
"affected:dep-graph": "./node_modules/.bin/nx affected dep-graph",
"format": "./node_modules/.bin/nx format write",
"format:write": "./node_modules/.bin/nx format write",
"format:check": "./node_modules/.bin/nx format check",
"update": "./node_modules/.bin/nx update",
"update:check": "./node_modules/.bin/nx update check",
"update:skip": "./node_modules/.bin/nx update skip",
"dep-graph": "./node_modules/.bin/nx dep-graph",
"postinstall": "ngc -p ngc.tsconfig.json"
},
"private": true,

View File

@ -1,6 +1,10 @@
import * as fs from 'fs';
import * as path from 'path';
export function writeToFile(path: string, str: string) {
fs.writeFileSync(path, str);
}
/**
* This method is specifically for updating a JSON file using the filesystem
*
@ -12,7 +16,7 @@ import * as path from 'path';
export function updateJsonFile(path: string, callback: (a: any) => any) {
const json = readJsonFile(path);
callback(json);
fs.writeFileSync(path, JSON.stringify(json, null, 2));
writeToFile(path, JSON.stringify(json, null, 2));
}
export function addApp(apps: any[] | undefined, newApp: any): any[] {
@ -59,3 +63,17 @@ export function copyFile(file: string, target: string) {
source.pipe(dest);
source.on('error', e => console.error(e));
}
function directoryExists(name) {
try {
return fs.statSync(name).isDirectory();
} catch (e) {
return false;
}
}
export function createDirectory(name: string) {
if (!directoryExists(name)) {
fs.mkdirSync(name);
}
}

View File

@ -0,0 +1,14 @@
import { updateJsonFile } from '../src/utils/fileutils';
export default {
description: 'Update npm scripts to use the nx command',
run: () => {
updateJsonFile('package.json', json => {
json.scripts = {
...json.scripts,
'dep-graph': './node_modules/.bin/nx dep-graph',
'affected:dep-graph': './node_modules/.bin/nx affected dep-graph'
};
});
}
};

View File

@ -20,7 +20,7 @@
},
"main": "index.js",
"types": "index.d.js",
"peerDependencies": { },
"peerDependencies": {},
"author": "Victor Savkin",
"license": "MIT",
"bugs": {
@ -32,9 +32,12 @@
"@ngrx/schematics": "5.2.0",
"@schematics/angular": "0.4.6",
"app-root-path": "^2.0.1",
"graphviz": "0.0.8",
"npm-run-all": "4.1.2",
"opn": "^5.3.0",
"semver": "5.4.1",
"tmp": "0.0.33",
"viz.js": "^1.8.1",
"yargs-parser": "9.0.2"
},
"devDependencies": {

View File

@ -9,20 +9,18 @@
"test": "ng test",
"lint": "./node_modules/.bin/nx lint && ng lint",
"e2e": "ng e2e",
"affected:apps": "./node_modules/.bin/nx affected apps",
"affected:build": "./node_modules/.bin/nx affected build",
"affected:e2e": "./node_modules/.bin/nx affected e2e",
"affected:dep-graph": "./node_modules/.bin/nx affected dep-graph",
"format": "./node_modules/.bin/nx format write",
"format:write": "./node_modules/.bin/nx format write",
"format:check": "./node_modules/.bin/nx format check",
"update": "./node_modules/.bin/nx update",
"update:check": "./node_modules/.bin/nx update check",
"update:skip": "./node_modules/.bin/nx update skip",
"workspace-schematic": "./node_modules/.bin/nx workspace-schematic",
"dep-graph": "./node_modules/.bin/nx dep-graph",
"postinstall": "./node_modules/.bin/nx postinstall"
},
"private": true,
@ -43,7 +41,8 @@
"@ngrx/router-store": "<%= routerStoreVersion %>",
"@ngrx/store": "<%= ngrxVersion %>",
"@ngrx/store-devtools": "<%= ngrxVersion %>",
"ngrx-store-freeze": "<%= ngrxStoreFreezeVersion %>"
"ngrx-store-freeze": "<%= ngrxStoreFreezeVersion %>",
"viz": "^0.0.1"
},
"devDependencies": {
"@angular/cli": "<%= angularCliVersion %>",
@ -52,19 +51,20 @@
"@nrwl/schematics": "<%= schematicsVersion %>",
"@angular/language-service": "<%= angularVersion %>",<% if (!minimal) { %>
"@types/jasmine": "~2.8.3",
"@types/jasminewd2": "~2.0.2",
"@types/node": "~6.0.60",
"codelyzer": "^4.0.1",
"jasmine-core": "~2.8.0",
"jasmine-spec-reporter": "~4.2.1",
"karma": "~2.0.0",
"karma-chrome-launcher": "~2.2.0",
"karma-coverage-istanbul-reporter": "^1.2.1",
"karma-jasmine": "~1.1.0",
"karma-jasmine-html-reporter": "^0.2.2",
"protractor": "~5.1.2",
"ts-node": "~4.1.0",
"tslint": "~5.9.1",<% } %>
"@types/jasminewd2": "~2.0.2",
"@types/node": "~6.0.60",
"codelyzer": "^4.0.1",
"jasmine-core": "~2.8.0",
"jasmine-spec-reporter": "~4.2.1",
"karma": "~2.0.0",
"karma-chrome-launcher": "~2.2.0",
"karma-coverage-istanbul-reporter": "^1.2.1",
"karma-jasmine": "~1.1.0",
"karma-jasmine-html-reporter": "^0.2.2",
"protractor": "~5.1.2",
"ts-node": "~4.1.0",
"tslint": "~5.9.1",<%
} %>
"typescript": "<%= typescriptVersion %>",
"prettier": "<%= prettierVersion %>"
}

View File

@ -85,6 +85,9 @@ function updatePackageJson() {
'./node_modules/.bin/nx affected build';
packageJson.scripts['affected:e2e'] = './node_modules/.bin/nx affected e2e';
packageJson.scripts['affected:dep-graph'] =
'./node_modules/.bin/nx affected dep-graph';
packageJson.scripts['format'] = './node_modules/.bin/nx format write';
packageJson.scripts['format:write'] = './node_modules/.bin/nx format write';
packageJson.scripts['format:check'] = './node_modules/.bin/nx format check';
@ -94,6 +97,9 @@ function updatePackageJson() {
packageJson.scripts['update:skip'] = './node_modules/.bin/nx update skip';
packageJson.scripts['lint'] = './node_modules/.bin/nx lint && ng lint';
packageJson.scripts['dep-graph'] = './node_modules/.bin/nx dep-graph';
packageJson.scripts['postinstall'] = './node_modules/.bin/nx postinstall';
packageJson.scripts['workspace-schematic'] =
'./node_modules/.bin/nx workspace-schematic';

View File

@ -1,5 +1,5 @@
import {
affectedApps,
affectedAppNames,
dependencies,
DependencyType,
ProjectType,
@ -254,9 +254,9 @@ describe('Calculates Dependencies Between Apps and Libs', () => {
});
});
describe('affectedApps', () => {
describe('affectedAppNames', () => {
it('should return the list of affected files', () => {
const affected = affectedApps(
const affected = affectedAppNames(
'nrwl',
[
{
@ -309,7 +309,7 @@ describe('Calculates Dependencies Between Apps and Libs', () => {
});
it('should return app app names if a touched file is not part of a project', () => {
const affected = affectedApps(
const affected = affectedAppNames(
'nrwl',
[
{
@ -353,7 +353,7 @@ describe('Calculates Dependencies Between Apps and Libs', () => {
});
it('should handle slashes', () => {
const affected = affectedApps(
const affected = affectedAppNames(
'nrwl',
[
{
@ -379,7 +379,7 @@ describe('Calculates Dependencies Between Apps and Libs', () => {
});
it('should handle circular dependencies', () => {
const affected = affectedApps(
const affected = affectedAppNames(
'nrwl',
[
{

View File

@ -8,7 +8,8 @@ export enum ProjectType {
export enum DependencyType {
es6Import = 'es6Import',
loadChildren = 'loadChildren'
loadChildren = 'loadChildren',
implicit = 'implicit'
}
export type ProjectNode = {
@ -32,23 +33,51 @@ export function touchedProjects(
});
}
export function affectedApps(
function affectedProjects(
npmScope: string,
projects: ProjectNode[],
fileRead: (s: string) => string,
touchedFiles: string[]
): ProjectNode[] {
projects = normalizeProjects(projects);
const deps = dependencies(npmScope, projects, fileRead);
const tp = touchedProjects(projects, touchedFiles);
if (tp.indexOf(null) > -1) {
return projects;
} else {
return projects.filter(proj =>
hasDependencyOnTouchedProjects(proj.name, tp, deps, [])
);
}
}
export type AffectedFetcher = (
npmScope: string,
projects: ProjectNode[],
fileRead: (s: string) => string,
touchedFiles: string[]
) => string[];
export function affectedAppNames(
npmScope: string,
projects: ProjectNode[],
fileRead: (s: string) => string,
touchedFiles: string[]
): string[] {
projects = normalizeProjects(projects);
const deps = dependencies(npmScope, projects, fileRead);
const tp = touchedProjects(projects, touchedFiles);
if (tp.indexOf(null) > -1) {
return projects.filter(p => p.type === ProjectType.app).map(p => p.name);
} else {
return projects
.filter(p => p.type === ProjectType.app)
.map(p => p.name)
.filter(name => hasDependencyOnTouchedProjects(name, tp, deps, []));
}
return affectedProjects(npmScope, projects, fileRead, touchedFiles)
.filter(p => p.type === ProjectType.app)
.map(p => p.name);
}
export function affectedProjectNames(
npmScope: string,
projects: ProjectNode[],
fileRead: (s: string) => string,
touchedFiles: string[]
): string[] {
return affectedProjects(npmScope, projects, fileRead, touchedFiles).map(
p => p.name
);
}
function hasDependencyOnTouchedProjects(
@ -86,16 +115,18 @@ function normalizeFiles(files: string[]): string[] {
return files.map(f => f.replace(/[\\\/]+/g, '/'));
}
export type Deps = { [projectName: string]: Dependency[] };
export function dependencies(
npmScope: string,
projects: ProjectNode[],
fileRead: (s: string) => string
): { [projectName: string]: Dependency[] } {
return new Deps(npmScope, projects, fileRead).calculateDeps();
): Deps {
return new DepsCalculator(npmScope, projects, fileRead).calculateDeps();
}
class Deps {
private deps: { [projectName: string]: Dependency[] };
class DepsCalculator {
private deps: Deps;
constructor(
private npmScope: string,

View File

@ -1,18 +1,21 @@
import { execSync } from 'child_process';
import { getAffectedApps, parseFiles } from './shared';
import { getAffectedApps, getAffectedProjects, parseFiles } from './shared';
import * as path from 'path';
import * as resolve from 'resolve';
import * as runAll from 'npm-run-all';
import * as yargsParser from 'yargs-parser';
import { generateGraph } from './dep-graph';
export function affected(args: string[]): void {
const command = args[0];
let apps: string[];
let projects: string[];
let rest: string[];
try {
const p = parseFiles(args.slice(1));
rest = p.rest;
apps = getAffectedApps(p.files);
projects = getAffectedProjects(p.files);
} catch (e) {
printError(command, e);
process.exit(1);
@ -28,6 +31,9 @@ export function affected(args: string[]): void {
case 'e2e':
e2e(apps, rest);
break;
case 'dep-graph':
generateGraph(yargsParser(rest), projects);
break;
}
}
@ -39,7 +45,7 @@ function printError(command: string, e: any) {
`Or pass the list of files, as follows: npm run affected:${command} -- --files="libs/mylib/index.ts,libs/mylib2/index.ts".`
);
console.error(
`Or to get the list of files from local changes: npm run affected:${command} -- --uncommitted | --untracked".`
`Or to get the list of files from local changes: npm run affected:${command} -- --uncommitted | --untracked.`
);
console.error(e.message);
}

View File

@ -0,0 +1,5 @@
describe('dep-graph', () => {
describe('getNodeProps', () => {
it('should get highlighted props for critical path', () => {});
});
});

View File

@ -0,0 +1,346 @@
import { writeToFile } from '../utils/fileutils';
import * as graphviz from 'graphviz';
import * as appRoot from 'app-root-path';
import * as opn from 'opn';
import { readFileSync } from 'fs';
import {
ProjectNode,
ProjectType,
dependencies,
Deps,
Dependency,
DependencyType
} from './affected-apps';
import { readCliConfig, getProjectNodes } from './shared';
import * as path from 'path';
import { tmpNameSync } from 'tmp';
const viz = require('viz.js'); // typings are incorrect in viz.js library - need to use `require`
export enum NodeEdgeVariant {
default = 'default',
highlighted = 'highlighted'
}
export type GraphvizOptions = {
fontname?: string;
fontsize?: number;
shape?: string;
color?: string;
style?: string;
fillcolor?: string;
};
export type AttrValue = {
attr: string;
value: boolean | number | string;
};
export type GraphvizOptionNodeEdge = {
[key: string]: {
[variant: string]: GraphvizOptions;
};
};
export type GraphvizConfig = {
graph: AttrValue[];
nodes: GraphvizOptionNodeEdge;
edges: GraphvizOptionNodeEdge;
};
export type ProjectMap = {
[name: string]: ProjectNode;
};
export type CriticalPathMap = {
[name: string]: boolean;
};
export enum OutputType {
'json' = 'json',
'html' = 'html',
'dot' = 'dot'
}
export type UserOptions = {
file?: string;
output?: string;
files?: string;
};
type ParsedUserOptions = {
isFilePresent?: boolean;
filename?: string;
type?: string;
output?: string;
shouldOpen: boolean;
};
type OutputOptions = {
data: string;
shouldOpen: boolean;
shouldWriteToFile: boolean;
filename?: string;
};
type JSONOutput = {
deps: Deps;
criticalPath: string[];
};
const defaultConfig = {
isFilePresent: false,
filename: undefined,
type: OutputType.html,
shouldOpen: true
};
export const graphvizConfig: GraphvizConfig = {
graph: [
{
attr: 'overlap',
value: false
},
{
attr: 'pad',
value: 0.111
}
],
nodes: {
[ProjectType.app]: {
[NodeEdgeVariant.default]: {
fontname: 'Arial',
fontsize: 14,
shape: 'box'
},
[NodeEdgeVariant.highlighted]: {
fontname: 'Arial',
fontsize: 14,
shape: 'box',
color: '#FF0033'
}
},
[ProjectType.lib]: {
[NodeEdgeVariant.default]: {
fontname: 'Arial',
fontsize: 14,
style: 'filled',
fillcolor: '#EFEFEF'
},
[NodeEdgeVariant.highlighted]: {
fontname: 'Arial',
fontsize: 14,
style: 'filled',
fillcolor: '#EFEFEF',
color: '#FF0033'
}
}
},
edges: {
[DependencyType.es6Import]: {
[NodeEdgeVariant.default]: {
color: '#757575'
},
[NodeEdgeVariant.highlighted]: {
color: '#FF0033'
}
},
[DependencyType.loadChildren]: {
[NodeEdgeVariant.default]: {
color: '#757575',
style: 'dotted'
},
[NodeEdgeVariant.highlighted]: {
color: '#FF0033',
style: 'dotted'
}
},
[DependencyType.implicit]: {
[NodeEdgeVariant.default]: {
color: '#000000',
style: 'bold'
},
[NodeEdgeVariant.highlighted]: {
color: '#FF0033',
style: 'bold'
}
}
}
};
function mapProjectNodes(projects: ProjectNode[]) {
return projects.reduce((m, proj) => ({ ...m, [proj.name]: proj }), {});
}
function getVariant(map: CriticalPathMap, key: string) {
return map[key] ? NodeEdgeVariant.highlighted : NodeEdgeVariant.default;
}
function getNodeProps(
config: GraphvizOptionNodeEdge,
projectNode: ProjectNode,
criticalPath: CriticalPathMap
) {
const nodeProps = config[projectNode.type];
return nodeProps[getVariant(criticalPath, projectNode.name)];
}
function getEdgeProps(
config: GraphvizOptionNodeEdge,
depType: DependencyType,
child: string,
criticalPath: CriticalPathMap
) {
const edgeProps = config[depType];
return edgeProps[getVariant(criticalPath, child)];
}
export function createGraphviz(
config: GraphvizConfig,
deps: Deps,
projects: ProjectNode[],
criticalPath: CriticalPathMap
) {
const projectMap: ProjectMap = mapProjectNodes(projects);
const g = graphviz.digraph('G');
config.graph.forEach(({ attr, value }) => g.set(attr, value));
Object.keys(deps)
.sort() // sorting helps with testing
.forEach(key => {
const projectNode = projectMap[key];
const dependencies = deps[key];
g.addNode(key, getNodeProps(config.nodes, projectNode, criticalPath));
if (dependencies.length > 0) {
dependencies.forEach((dep: Dependency, i: number) => {
g.addNode(
dep.projectName,
getNodeProps(config.nodes, projectNode, criticalPath)
); // child node
g.addEdge(
key,
dep.projectName,
getEdgeProps(config.edges, dep.type, dep.projectName, criticalPath)
);
});
}
});
return g.to_dot();
}
function handleOutput({
data,
shouldOpen,
shouldWriteToFile,
filename
}: OutputOptions) {
if (shouldOpen) {
const tmpFilename = `${tmpNameSync()}.html`;
writeToFile(tmpFilename, data);
opn(tmpFilename, {
wait: false
});
} else if (!shouldWriteToFile) {
return console.log(data);
} else {
writeToFile(filename, data);
}
}
function applyHTMLTemplate(svg: string) {
return `<!DOCTYPE html>
<html>
<head><title></title></head>
<body>${svg}</body>
</html>
`;
}
function generateGraphJson(criticalPath?: string[]): JSONOutput {
const config = readCliConfig();
const npmScope = config.project.npmScope;
const projects: ProjectNode[] = getProjectNodes(config);
// fetch all apps and libs
const deps = dependencies(npmScope, projects, f =>
readFileSync(`${appRoot.path}/${f}`, 'utf-8')
);
return {
deps,
criticalPath
};
}
function getDot(json: JSONOutput) {
const config = readCliConfig();
const projects: ProjectNode[] = getProjectNodes(config);
return createGraphviz(
graphvizConfig,
json.deps,
projects,
json.criticalPath.reduce((m, proj) => ({ ...m, [proj]: true }), {})
);
}
function getConfigFromUserInput(cmdOpts: UserOptions): ParsedUserOptions {
const filename = cmdOpts.file;
const output = cmdOpts.output;
if (filename && output) {
throw new Error(
'Received both filename as well as output type. Please only specify one of the options.'
);
}
const extension = !!filename
? path.extname(filename).substring(1)
: output || OutputType.html;
return {
isFilePresent: !output,
type: extension,
output: output,
shouldOpen: !output && !filename,
filename
};
}
function extractDataFromJson(json, type) {
switch (type) {
case OutputType.json:
return JSON.stringify(json, null, 2);
case OutputType.dot:
return getDot(json);
case OutputType.html:
return applyHTMLTemplate(viz(getDot(json)));
default:
throw new Error(
'Unrecognized file extension. Supported extensions are "json", "html", and "dot"'
);
}
}
export function generateGraph(
args: UserOptions,
criticalPath?: string[]
): void {
const json = generateGraphJson(criticalPath || []);
const config = {
...defaultConfig,
...getConfigFromUserInput(args)
};
handleOutput({
data: extractDataFromJson(json, config.type),
filename: config.filename,
shouldWriteToFile: config.isFilePresent,
shouldOpen: config.shouldOpen
});
}

View File

@ -7,6 +7,7 @@ import { update } from './update';
import { patchNg } from './patch-ng';
import { lint } from './lint';
import { workspaceSchematic } from './workspace-schematic';
import { generateGraph } from './dep-graph';
const processedArgs = yargsParser(process.argv, {
alias: {
@ -21,6 +22,9 @@ switch (command) {
case 'affected':
affected(args);
break;
case 'dep-graph':
generateGraph(yargsParser(args));
break;
case 'format':
format(args);
break;

View File

@ -1,16 +1,16 @@
import { execSync } from 'child_process';
import * as path from 'path';
import {
affectedApps,
AffectedFetcher,
affectedAppNames,
ProjectNode,
ProjectType,
touchedProjects
touchedProjects,
dependencies,
Dependency,
affectedProjectNames
} from './affected-apps';
import * as fs from 'fs';
import {
dependencies,
Dependency
} from '@nrwl/schematics/src/command-line/affected-apps';
import { statSync } from 'fs';
import * as appRoot from 'app-root-path';
import { readJsonFile } from '../utils/fileutils';
@ -110,17 +110,22 @@ export function readCliConfig(): any {
return config;
}
export function getAffectedApps(touchedFiles: string[]): string[] {
export const getAffected = (affectedNamesFetcher: AffectedFetcher) => (
touchedFiles: string[]
): string[] => {
const config = readCliConfig();
const projects = getProjectNodes(config);
return affectedApps(
return affectedNamesFetcher(
config.project.npmScope,
projects,
f => fs.readFileSync(`${appRoot.path}/${f}`, 'utf-8'),
touchedFiles
);
}
};
export const getAffectedApps = getAffected(affectedAppNames);
export const getAffectedProjects = getAffected(affectedProjectNames);
export function getTouchedProjects(touchedFiles: string[]): string[] {
return touchedProjects(getProjectNodes(readCliConfig()), touchedFiles).filter(

View File

@ -1,6 +1,10 @@
import * as fs from 'fs';
import * as path from 'path';
export function writeToFile(path: string, str: string) {
fs.writeFileSync(path, str);
}
/**
* This method is specifically for updating a JSON file using the filesystem
*
@ -12,7 +16,7 @@ import * as path from 'path';
export function updateJsonFile(path: string, callback: (a: any) => any) {
const json = readJsonFile(path);
callback(json);
fs.writeFileSync(path, serializeJson(json));
writeToFile(path, serializeJson(json));
}
export function addApp(apps: any[] | undefined, newApp: any): any[] {
@ -58,3 +62,17 @@ export function copyFile(file: string, target: string) {
source.pipe(dest);
source.on('error', e => console.error(e));
}
function directoryExists(name) {
try {
return fs.statSync(name).isDirectory();
} catch (e) {
return false;
}
}
export function createDirectory(name: string) {
if (!directoryExists(name)) {
fs.mkdirSync(name);
}
}

View File

@ -1,6 +1,5 @@
#!/usr/bin/env bash
npm run build
./scripts/link.sh
rm -rf tmp
mkdir tmp

View File

@ -3017,6 +3017,12 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.3, graceful-fs@^4.1.6:
version "4.1.11"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658"
graphviz@^0.0.8:
version "0.0.8"
resolved "https://registry.yarnpkg.com/graphviz/-/graphviz-0.0.8.tgz#e599e40733ef80e1653bfe89a5f031ecf2aa4aaa"
dependencies:
temp "~0.4.0"
growly@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081"
@ -5263,7 +5269,7 @@ onetime@^2.0.0:
dependencies:
mimic-fn "^1.0.0"
opn@^5.1.0:
opn@^5.1.0, opn@^5.3.0:
version "5.3.0"
resolved "https://registry.yarnpkg.com/opn/-/opn-5.3.0.tgz#64871565c863875f052cfdf53d3e3cb5adb53b1c"
dependencies:
@ -7153,6 +7159,10 @@ tar@^2.0.0, tar@^2.2.1:
fstream "^1.0.2"
inherits "2"
temp@~0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/temp/-/temp-0.4.0.tgz#671ad63d57be0fe9d7294664b3fc400636678a60"
term-size@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/term-size/-/term-size-1.2.0.tgz#458b83887f288fc56d6fffbfad262e26638efa69"
@ -7615,6 +7625,10 @@ verror@1.10.0:
core-util-is "1.0.2"
extsprintf "^1.2.0"
viz.js@^1.8.1:
version "1.8.1"
resolved "https://registry.yarnpkg.com/viz.js/-/viz.js-1.8.1.tgz#277ab3cf4367c608a95b281a7472083c3e2ee6cf"
vlq@^0.2.1, vlq@^0.2.2:
version "0.2.3"
resolved "https://registry.yarnpkg.com/vlq/-/vlq-0.2.3.tgz#8f3e4328cf63b1540c0d67e1b2778386f8975b26"