feat(nx): support to generate visualization graph
`npm run dep-graph` outputs a visual dependency graph
This commit is contained in:
parent
eca52599d9
commit
49525efe3e
@ -286,4 +286,136 @@ describe('Command line', () => {
|
|||||||
},
|
},
|
||||||
1000000
|
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->mylib</title>');
|
||||||
|
expect(fileContents).toContain('<title>myapp->mylib2</title>');
|
||||||
|
expect(fileContents).toContain('<title>mylib->mylib2</title>');
|
||||||
|
},
|
||||||
|
1000000
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -14,8 +14,7 @@
|
|||||||
"copy": "./scripts/copy.sh",
|
"copy": "./scripts/copy.sh",
|
||||||
"test:schematics": "yarn linknpm fast && ./scripts/test_schematics.sh",
|
"test:schematics": "yarn linknpm fast && ./scripts/test_schematics.sh",
|
||||||
"test:nx": "yarn linknpm fast && ./scripts/test_nx.sh",
|
"test:nx": "yarn linknpm fast && ./scripts/test_nx.sh",
|
||||||
"test":
|
"test": "yarn linknpm fast && ./scripts/test_nx.sh && ./scripts/test_schematics.sh",
|
||||||
"yarn linknpm fast && ./scripts/test_nx.sh && ./scripts/test_schematics.sh",
|
|
||||||
"checkformat": "prettier \"{packages,e2e}/**/*.ts\" --list-different",
|
"checkformat": "prettier \"{packages,e2e}/**/*.ts\" --list-different",
|
||||||
"publish_npm": "./scripts/publish.sh"
|
"publish_npm": "./scripts/publish.sh"
|
||||||
},
|
},
|
||||||
@ -44,6 +43,7 @@
|
|||||||
"angular": "1.6.6",
|
"angular": "1.6.6",
|
||||||
"app-root-path": "^2.0.1",
|
"app-root-path": "^2.0.1",
|
||||||
"cosmiconfig": "^4.0.0",
|
"cosmiconfig": "^4.0.0",
|
||||||
|
"graphviz": "^0.0.8",
|
||||||
"husky": "^0.14.3",
|
"husky": "^0.14.3",
|
||||||
"jasmine-core": "~2.8.0",
|
"jasmine-core": "~2.8.0",
|
||||||
"jasmine-spec-reporter": "~4.2.1",
|
"jasmine-spec-reporter": "~4.2.1",
|
||||||
@ -52,8 +52,9 @@
|
|||||||
"karma-chrome-launcher": "~2.2.0",
|
"karma-chrome-launcher": "~2.2.0",
|
||||||
"karma-jasmine": "~1.1.0",
|
"karma-jasmine": "~1.1.0",
|
||||||
"karma-webpack": "2.0.4",
|
"karma-webpack": "2.0.4",
|
||||||
"npm-run-all": "4.1.2",
|
|
||||||
"ng-packagr": "2.2.0",
|
"ng-packagr": "2.2.0",
|
||||||
|
"npm-run-all": "4.1.2",
|
||||||
|
"opn": "^5.3.0",
|
||||||
"precise-commits": "1.0.0",
|
"precise-commits": "1.0.0",
|
||||||
"prettier": "1.10.2",
|
"prettier": "1.10.2",
|
||||||
"rxjs": "^5.5.6",
|
"rxjs": "^5.5.6",
|
||||||
@ -62,6 +63,7 @@
|
|||||||
"tmp": "0.0.33",
|
"tmp": "0.0.33",
|
||||||
"tslint": "5.9.1",
|
"tslint": "5.9.1",
|
||||||
"typescript": "2.6.2",
|
"typescript": "2.6.2",
|
||||||
|
"viz.js": "^1.8.1",
|
||||||
"yargs-parser": "9.0.2",
|
"yargs-parser": "9.0.2",
|
||||||
"zone.js": "^0.8.19",
|
"zone.js": "^0.8.19",
|
||||||
"fs-extra": "5.0.0"
|
"fs-extra": "5.0.0"
|
||||||
|
|||||||
@ -12,12 +12,14 @@
|
|||||||
"affected:apps": "./node_modules/.bin/nx affected apps",
|
"affected:apps": "./node_modules/.bin/nx affected apps",
|
||||||
"affected:build": "./node_modules/.bin/nx affected build",
|
"affected:build": "./node_modules/.bin/nx affected build",
|
||||||
"affected:e2e": "./node_modules/.bin/nx affected e2e",
|
"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": "./node_modules/.bin/nx format write",
|
||||||
"format:write": "./node_modules/.bin/nx format write",
|
"format:write": "./node_modules/.bin/nx format write",
|
||||||
"format:check": "./node_modules/.bin/nx format check",
|
"format:check": "./node_modules/.bin/nx format check",
|
||||||
"update": "./node_modules/.bin/nx update",
|
"update": "./node_modules/.bin/nx update",
|
||||||
"update:check": "./node_modules/.bin/nx update check",
|
"update:check": "./node_modules/.bin/nx update check",
|
||||||
"update:skip": "./node_modules/.bin/nx update skip",
|
"update:skip": "./node_modules/.bin/nx update skip",
|
||||||
|
"dep-graph": "./node_modules/.bin/nx dep-graph",
|
||||||
"postinstall": "ngc -p ngc.tsconfig.json"
|
"postinstall": "ngc -p ngc.tsconfig.json"
|
||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
|
|||||||
@ -1,6 +1,10 @@
|
|||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
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
|
* 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) {
|
export function updateJsonFile(path: string, callback: (a: any) => any) {
|
||||||
const json = readJsonFile(path);
|
const json = readJsonFile(path);
|
||||||
callback(json);
|
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[] {
|
export function addApp(apps: any[] | undefined, newApp: any): any[] {
|
||||||
@ -59,3 +63,17 @@ export function copyFile(file: string, target: string) {
|
|||||||
source.pipe(dest);
|
source.pipe(dest);
|
||||||
source.on('error', e => console.error(e));
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
14
packages/schematics/migrations/20180404-adding-dep-graph.ts
Normal file
14
packages/schematics/migrations/20180404-adding-dep-graph.ts
Normal 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'
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -20,7 +20,7 @@
|
|||||||
},
|
},
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"types": "index.d.js",
|
"types": "index.d.js",
|
||||||
"peerDependencies": { },
|
"peerDependencies": {},
|
||||||
"author": "Victor Savkin",
|
"author": "Victor Savkin",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
@ -32,9 +32,12 @@
|
|||||||
"@ngrx/schematics": "5.2.0",
|
"@ngrx/schematics": "5.2.0",
|
||||||
"@schematics/angular": "0.4.6",
|
"@schematics/angular": "0.4.6",
|
||||||
"app-root-path": "^2.0.1",
|
"app-root-path": "^2.0.1",
|
||||||
|
"graphviz": "0.0.8",
|
||||||
"npm-run-all": "4.1.2",
|
"npm-run-all": "4.1.2",
|
||||||
|
"opn": "^5.3.0",
|
||||||
"semver": "5.4.1",
|
"semver": "5.4.1",
|
||||||
"tmp": "0.0.33",
|
"tmp": "0.0.33",
|
||||||
|
"viz.js": "^1.8.1",
|
||||||
"yargs-parser": "9.0.2"
|
"yargs-parser": "9.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@ -9,20 +9,18 @@
|
|||||||
"test": "ng test",
|
"test": "ng test",
|
||||||
"lint": "./node_modules/.bin/nx lint && ng lint",
|
"lint": "./node_modules/.bin/nx lint && ng lint",
|
||||||
"e2e": "ng e2e",
|
"e2e": "ng e2e",
|
||||||
|
|
||||||
"affected:apps": "./node_modules/.bin/nx affected apps",
|
"affected:apps": "./node_modules/.bin/nx affected apps",
|
||||||
"affected:build": "./node_modules/.bin/nx affected build",
|
"affected:build": "./node_modules/.bin/nx affected build",
|
||||||
"affected:e2e": "./node_modules/.bin/nx affected e2e",
|
"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": "./node_modules/.bin/nx format write",
|
||||||
"format:write": "./node_modules/.bin/nx format write",
|
"format:write": "./node_modules/.bin/nx format write",
|
||||||
"format:check": "./node_modules/.bin/nx format check",
|
"format:check": "./node_modules/.bin/nx format check",
|
||||||
|
|
||||||
"update": "./node_modules/.bin/nx update",
|
"update": "./node_modules/.bin/nx update",
|
||||||
"update:check": "./node_modules/.bin/nx update check",
|
"update:check": "./node_modules/.bin/nx update check",
|
||||||
"update:skip": "./node_modules/.bin/nx update skip",
|
"update:skip": "./node_modules/.bin/nx update skip",
|
||||||
|
|
||||||
"workspace-schematic": "./node_modules/.bin/nx workspace-schematic",
|
"workspace-schematic": "./node_modules/.bin/nx workspace-schematic",
|
||||||
|
"dep-graph": "./node_modules/.bin/nx dep-graph",
|
||||||
"postinstall": "./node_modules/.bin/nx postinstall"
|
"postinstall": "./node_modules/.bin/nx postinstall"
|
||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
@ -43,7 +41,8 @@
|
|||||||
"@ngrx/router-store": "<%= routerStoreVersion %>",
|
"@ngrx/router-store": "<%= routerStoreVersion %>",
|
||||||
"@ngrx/store": "<%= ngrxVersion %>",
|
"@ngrx/store": "<%= ngrxVersion %>",
|
||||||
"@ngrx/store-devtools": "<%= ngrxVersion %>",
|
"@ngrx/store-devtools": "<%= ngrxVersion %>",
|
||||||
"ngrx-store-freeze": "<%= ngrxStoreFreezeVersion %>"
|
"ngrx-store-freeze": "<%= ngrxStoreFreezeVersion %>",
|
||||||
|
"viz": "^0.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@angular/cli": "<%= angularCliVersion %>",
|
"@angular/cli": "<%= angularCliVersion %>",
|
||||||
@ -64,7 +63,8 @@
|
|||||||
"karma-jasmine-html-reporter": "^0.2.2",
|
"karma-jasmine-html-reporter": "^0.2.2",
|
||||||
"protractor": "~5.1.2",
|
"protractor": "~5.1.2",
|
||||||
"ts-node": "~4.1.0",
|
"ts-node": "~4.1.0",
|
||||||
"tslint": "~5.9.1",<% } %>
|
"tslint": "~5.9.1",<%
|
||||||
|
} %>
|
||||||
"typescript": "<%= typescriptVersion %>",
|
"typescript": "<%= typescriptVersion %>",
|
||||||
"prettier": "<%= prettierVersion %>"
|
"prettier": "<%= prettierVersion %>"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -85,6 +85,9 @@ function updatePackageJson() {
|
|||||||
'./node_modules/.bin/nx affected build';
|
'./node_modules/.bin/nx affected build';
|
||||||
packageJson.scripts['affected:e2e'] = './node_modules/.bin/nx affected e2e';
|
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'] = './node_modules/.bin/nx format write';
|
||||||
packageJson.scripts['format:write'] = './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';
|
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['update:skip'] = './node_modules/.bin/nx update skip';
|
||||||
|
|
||||||
packageJson.scripts['lint'] = './node_modules/.bin/nx lint && ng lint';
|
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['postinstall'] = './node_modules/.bin/nx postinstall';
|
||||||
packageJson.scripts['workspace-schematic'] =
|
packageJson.scripts['workspace-schematic'] =
|
||||||
'./node_modules/.bin/nx workspace-schematic';
|
'./node_modules/.bin/nx workspace-schematic';
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
affectedApps,
|
affectedAppNames,
|
||||||
dependencies,
|
dependencies,
|
||||||
DependencyType,
|
DependencyType,
|
||||||
ProjectType,
|
ProjectType,
|
||||||
@ -254,9 +254,9 @@ describe('Calculates Dependencies Between Apps and Libs', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('affectedApps', () => {
|
describe('affectedAppNames', () => {
|
||||||
it('should return the list of affected files', () => {
|
it('should return the list of affected files', () => {
|
||||||
const affected = affectedApps(
|
const affected = affectedAppNames(
|
||||||
'nrwl',
|
'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', () => {
|
it('should return app app names if a touched file is not part of a project', () => {
|
||||||
const affected = affectedApps(
|
const affected = affectedAppNames(
|
||||||
'nrwl',
|
'nrwl',
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
@ -353,7 +353,7 @@ describe('Calculates Dependencies Between Apps and Libs', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should handle slashes', () => {
|
it('should handle slashes', () => {
|
||||||
const affected = affectedApps(
|
const affected = affectedAppNames(
|
||||||
'nrwl',
|
'nrwl',
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
@ -379,7 +379,7 @@ describe('Calculates Dependencies Between Apps and Libs', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should handle circular dependencies', () => {
|
it('should handle circular dependencies', () => {
|
||||||
const affected = affectedApps(
|
const affected = affectedAppNames(
|
||||||
'nrwl',
|
'nrwl',
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
|
|||||||
@ -8,7 +8,8 @@ export enum ProjectType {
|
|||||||
|
|
||||||
export enum DependencyType {
|
export enum DependencyType {
|
||||||
es6Import = 'es6Import',
|
es6Import = 'es6Import',
|
||||||
loadChildren = 'loadChildren'
|
loadChildren = 'loadChildren',
|
||||||
|
implicit = 'implicit'
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProjectNode = {
|
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,
|
npmScope: string,
|
||||||
projects: ProjectNode[],
|
projects: ProjectNode[],
|
||||||
fileRead: (s: string) => string,
|
fileRead: (s: string) => string,
|
||||||
touchedFiles: string[]
|
touchedFiles: string[]
|
||||||
): string[] {
|
): string[] {
|
||||||
projects = normalizeProjects(projects);
|
return affectedProjects(npmScope, projects, fileRead, touchedFiles)
|
||||||
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)
|
.filter(p => p.type === ProjectType.app)
|
||||||
.map(p => p.name)
|
.map(p => p.name);
|
||||||
.filter(name => hasDependencyOnTouchedProjects(name, tp, deps, []));
|
}
|
||||||
}
|
|
||||||
|
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(
|
function hasDependencyOnTouchedProjects(
|
||||||
@ -86,16 +115,18 @@ function normalizeFiles(files: string[]): string[] {
|
|||||||
return files.map(f => f.replace(/[\\\/]+/g, '/'));
|
return files.map(f => f.replace(/[\\\/]+/g, '/'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type Deps = { [projectName: string]: Dependency[] };
|
||||||
|
|
||||||
export function dependencies(
|
export function dependencies(
|
||||||
npmScope: string,
|
npmScope: string,
|
||||||
projects: ProjectNode[],
|
projects: ProjectNode[],
|
||||||
fileRead: (s: string) => string
|
fileRead: (s: string) => string
|
||||||
): { [projectName: string]: Dependency[] } {
|
): Deps {
|
||||||
return new Deps(npmScope, projects, fileRead).calculateDeps();
|
return new DepsCalculator(npmScope, projects, fileRead).calculateDeps();
|
||||||
}
|
}
|
||||||
|
|
||||||
class Deps {
|
class DepsCalculator {
|
||||||
private deps: { [projectName: string]: Dependency[] };
|
private deps: Deps;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private npmScope: string,
|
private npmScope: string,
|
||||||
|
|||||||
@ -1,18 +1,21 @@
|
|||||||
import { execSync } from 'child_process';
|
import { execSync } from 'child_process';
|
||||||
import { getAffectedApps, parseFiles } from './shared';
|
import { getAffectedApps, getAffectedProjects, parseFiles } from './shared';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as resolve from 'resolve';
|
import * as resolve from 'resolve';
|
||||||
import * as runAll from 'npm-run-all';
|
import * as runAll from 'npm-run-all';
|
||||||
import * as yargsParser from 'yargs-parser';
|
import * as yargsParser from 'yargs-parser';
|
||||||
|
import { generateGraph } from './dep-graph';
|
||||||
|
|
||||||
export function affected(args: string[]): void {
|
export function affected(args: string[]): void {
|
||||||
const command = args[0];
|
const command = args[0];
|
||||||
let apps: string[];
|
let apps: string[];
|
||||||
|
let projects: string[];
|
||||||
let rest: string[];
|
let rest: string[];
|
||||||
try {
|
try {
|
||||||
const p = parseFiles(args.slice(1));
|
const p = parseFiles(args.slice(1));
|
||||||
rest = p.rest;
|
rest = p.rest;
|
||||||
apps = getAffectedApps(p.files);
|
apps = getAffectedApps(p.files);
|
||||||
|
projects = getAffectedProjects(p.files);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
printError(command, e);
|
printError(command, e);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
@ -28,6 +31,9 @@ export function affected(args: string[]): void {
|
|||||||
case 'e2e':
|
case 'e2e':
|
||||||
e2e(apps, rest);
|
e2e(apps, rest);
|
||||||
break;
|
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".`
|
`Or pass the list of files, as follows: npm run affected:${command} -- --files="libs/mylib/index.ts,libs/mylib2/index.ts".`
|
||||||
);
|
);
|
||||||
console.error(
|
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);
|
console.error(e.message);
|
||||||
}
|
}
|
||||||
|
|||||||
5
packages/schematics/src/command-line/dep-graph.spec.ts
Normal file
5
packages/schematics/src/command-line/dep-graph.spec.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
describe('dep-graph', () => {
|
||||||
|
describe('getNodeProps', () => {
|
||||||
|
it('should get highlighted props for critical path', () => {});
|
||||||
|
});
|
||||||
|
});
|
||||||
346
packages/schematics/src/command-line/dep-graph.ts
Normal file
346
packages/schematics/src/command-line/dep-graph.ts
Normal 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
|
||||||
|
});
|
||||||
|
}
|
||||||
@ -7,6 +7,7 @@ import { update } from './update';
|
|||||||
import { patchNg } from './patch-ng';
|
import { patchNg } from './patch-ng';
|
||||||
import { lint } from './lint';
|
import { lint } from './lint';
|
||||||
import { workspaceSchematic } from './workspace-schematic';
|
import { workspaceSchematic } from './workspace-schematic';
|
||||||
|
import { generateGraph } from './dep-graph';
|
||||||
|
|
||||||
const processedArgs = yargsParser(process.argv, {
|
const processedArgs = yargsParser(process.argv, {
|
||||||
alias: {
|
alias: {
|
||||||
@ -21,6 +22,9 @@ switch (command) {
|
|||||||
case 'affected':
|
case 'affected':
|
||||||
affected(args);
|
affected(args);
|
||||||
break;
|
break;
|
||||||
|
case 'dep-graph':
|
||||||
|
generateGraph(yargsParser(args));
|
||||||
|
break;
|
||||||
case 'format':
|
case 'format':
|
||||||
format(args);
|
format(args);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@ -1,16 +1,16 @@
|
|||||||
import { execSync } from 'child_process';
|
import { execSync } from 'child_process';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import {
|
import {
|
||||||
affectedApps,
|
AffectedFetcher,
|
||||||
|
affectedAppNames,
|
||||||
ProjectNode,
|
ProjectNode,
|
||||||
ProjectType,
|
ProjectType,
|
||||||
touchedProjects
|
touchedProjects,
|
||||||
|
dependencies,
|
||||||
|
Dependency,
|
||||||
|
affectedProjectNames
|
||||||
} from './affected-apps';
|
} from './affected-apps';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import {
|
|
||||||
dependencies,
|
|
||||||
Dependency
|
|
||||||
} from '@nrwl/schematics/src/command-line/affected-apps';
|
|
||||||
import { statSync } from 'fs';
|
import { statSync } from 'fs';
|
||||||
import * as appRoot from 'app-root-path';
|
import * as appRoot from 'app-root-path';
|
||||||
import { readJsonFile } from '../utils/fileutils';
|
import { readJsonFile } from '../utils/fileutils';
|
||||||
@ -110,17 +110,22 @@ export function readCliConfig(): any {
|
|||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getAffectedApps(touchedFiles: string[]): string[] {
|
export const getAffected = (affectedNamesFetcher: AffectedFetcher) => (
|
||||||
|
touchedFiles: string[]
|
||||||
|
): string[] => {
|
||||||
const config = readCliConfig();
|
const config = readCliConfig();
|
||||||
const projects = getProjectNodes(config);
|
const projects = getProjectNodes(config);
|
||||||
|
|
||||||
return affectedApps(
|
return affectedNamesFetcher(
|
||||||
config.project.npmScope,
|
config.project.npmScope,
|
||||||
projects,
|
projects,
|
||||||
f => fs.readFileSync(`${appRoot.path}/${f}`, 'utf-8'),
|
f => fs.readFileSync(`${appRoot.path}/${f}`, 'utf-8'),
|
||||||
touchedFiles
|
touchedFiles
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export const getAffectedApps = getAffected(affectedAppNames);
|
||||||
|
export const getAffectedProjects = getAffected(affectedProjectNames);
|
||||||
|
|
||||||
export function getTouchedProjects(touchedFiles: string[]): string[] {
|
export function getTouchedProjects(touchedFiles: string[]): string[] {
|
||||||
return touchedProjects(getProjectNodes(readCliConfig()), touchedFiles).filter(
|
return touchedProjects(getProjectNodes(readCliConfig()), touchedFiles).filter(
|
||||||
|
|||||||
@ -1,6 +1,10 @@
|
|||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
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
|
* 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) {
|
export function updateJsonFile(path: string, callback: (a: any) => any) {
|
||||||
const json = readJsonFile(path);
|
const json = readJsonFile(path);
|
||||||
callback(json);
|
callback(json);
|
||||||
fs.writeFileSync(path, serializeJson(json));
|
writeToFile(path, serializeJson(json));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addApp(apps: any[] | undefined, newApp: any): any[] {
|
export function addApp(apps: any[] | undefined, newApp: any): any[] {
|
||||||
@ -58,3 +62,17 @@ export function copyFile(file: string, target: string) {
|
|||||||
source.pipe(dest);
|
source.pipe(dest);
|
||||||
source.on('error', e => console.error(e));
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
npm run build
|
|
||||||
./scripts/link.sh
|
./scripts/link.sh
|
||||||
rm -rf tmp
|
rm -rf tmp
|
||||||
mkdir tmp
|
mkdir tmp
|
||||||
|
|||||||
16
yarn.lock
16
yarn.lock
@ -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"
|
version "4.1.11"
|
||||||
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658"
|
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:
|
growly@^1.3.0:
|
||||||
version "1.3.0"
|
version "1.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081"
|
resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081"
|
||||||
@ -5263,7 +5269,7 @@ onetime@^2.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
mimic-fn "^1.0.0"
|
mimic-fn "^1.0.0"
|
||||||
|
|
||||||
opn@^5.1.0:
|
opn@^5.1.0, opn@^5.3.0:
|
||||||
version "5.3.0"
|
version "5.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/opn/-/opn-5.3.0.tgz#64871565c863875f052cfdf53d3e3cb5adb53b1c"
|
resolved "https://registry.yarnpkg.com/opn/-/opn-5.3.0.tgz#64871565c863875f052cfdf53d3e3cb5adb53b1c"
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -7153,6 +7159,10 @@ tar@^2.0.0, tar@^2.2.1:
|
|||||||
fstream "^1.0.2"
|
fstream "^1.0.2"
|
||||||
inherits "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:
|
term-size@^1.2.0:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/term-size/-/term-size-1.2.0.tgz#458b83887f288fc56d6fffbfad262e26638efa69"
|
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"
|
core-util-is "1.0.2"
|
||||||
extsprintf "^1.2.0"
|
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:
|
vlq@^0.2.1, vlq@^0.2.2:
|
||||||
version "0.2.3"
|
version "0.2.3"
|
||||||
resolved "https://registry.yarnpkg.com/vlq/-/vlq-0.2.3.tgz#8f3e4328cf63b1540c0d67e1b2778386f8975b26"
|
resolved "https://registry.yarnpkg.com/vlq/-/vlq-0.2.3.tgz#8f3e4328cf63b1540c0d67e1b2778386f8975b26"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user