-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(northbrook): build a dependency graph for more correct tooling
Northbrook will now traverse all of your packages dependencies, building a graph of inter-dependencies between *only* packages being managed by Northbrook. This is done to sort your packages in a more correct order that will allow tools that do building or bundling of some kind (maybe others!) that deal with these inter-dependencies directly or indirectly. In the case of circular dependencies being detected an error will be thrown informing you to add configuration to eliminate the errors you are having. The package identified in the error message will from then on be excluded from `northbrook link` and continue to use NPM installed packages (or however you chose to setup your projects) to avoid impossible Cycles 🔥 AFFECTS: northbrook
- Loading branch information
Showing
26 changed files
with
240 additions
and
63 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
{ | ||
"name": "a-testpackage", | ||
"version": "0.0.0", | ||
"dependencies": {} | ||
} |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
{ | ||
"name": "b-testpackage", | ||
"version": "0.0.0", | ||
"dependencies": { | ||
"c-testpackage": "latest" | ||
} | ||
} |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
{ | ||
"name": "c-testpackage", | ||
"version": "0.0.0", | ||
"dependencies": { | ||
"a-testpackage": "latest" | ||
} | ||
} |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
{ | ||
"name": "d-testpackage", | ||
"version": "0.0.0", | ||
"dependencies": { | ||
"c-testpackage": "latest" | ||
} | ||
} |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
{ | ||
"name": "e-testpackage", | ||
"version": "0.0.0", | ||
"dependencies": { | ||
"f-testpackage": "latest" | ||
} | ||
} |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
{ | ||
"name": "f-testpackage", | ||
"version": "0.0.0", | ||
"dependencies": { | ||
"e-testpackage": "latest" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import * as assert from 'assert'; | ||
import { join } from 'path'; | ||
import { buildDependencyGraph } from './buildDependencyGraph'; | ||
|
||
const pkg = (name: string) => join(__dirname, '__test__/' + name); | ||
|
||
const packageA = pkg('a'); | ||
const packageB = pkg('b'); | ||
const packageC = pkg('c'); | ||
const packageD = pkg('d'); | ||
const packageE = pkg('e'); | ||
const packageF = pkg('f'); | ||
|
||
describe('buildDependencyGraph', () => { | ||
describe('given packages as input', () => { | ||
it('builds a dependency graph', () => { | ||
const packages = [ packageA, packageB, packageC, packageD ]; | ||
|
||
const graph = buildDependencyGraph(packages); | ||
|
||
assert.deepEqual(graph.paths(), [packageA, packageC, packageB, packageD]); | ||
}); | ||
|
||
it('throws an error when there is a circular dependency', () => { | ||
const packages = [ packageE, packageF ]; | ||
const graph = buildDependencyGraph(packages); | ||
|
||
assert.throws(() => { | ||
graph.paths(); | ||
}); | ||
}); | ||
|
||
it('does not throw an error when given circular configuration', () => { | ||
const packages = [ packageE, packageF ]; | ||
const graph = buildDependencyGraph(packages, [ 'e-testpackage' ]); | ||
|
||
const paths = graph.paths(); | ||
|
||
assert.deepEqual(paths, [packageF, packageE]); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
import { join } from 'path'; | ||
import { EOL } from 'os'; | ||
import { union, clone } from 'ramda'; | ||
import { Pkg } from '../../types'; | ||
|
||
const DependencyGraph = require('dependency-graph').DepGraph; | ||
|
||
export function buildDependencyGraph(packagePaths: Array<string>, circular: Array<string> = []) { | ||
const configs: { [packageName: string]: Pkg } = {}; | ||
const graph: { [pacakgename: string]: Array<Pkg> } = {}; | ||
|
||
packagePaths.forEach(packageFinder(configs)); | ||
|
||
const depGraph = new DependencyGraph(); | ||
const packages = Object.keys(configs).map(key => configs[key]); | ||
const packageNames: Array<string> = packages.map(pkg => pkg.name); | ||
|
||
packages.forEach(({ name, config }) => { | ||
const devDependencies: Array<string> = | ||
findDependencies(config.devDependencies, packageNames); | ||
|
||
const peerDependencies: Array<string> = | ||
findDependencies(config.peerDependencies, packageNames); | ||
|
||
const dependencies: Array <string> = | ||
findDependencies(config.dependencies, packageNames); | ||
|
||
const allDependencies: Array<string> = | ||
union(union(devDependencies, peerDependencies), dependencies); | ||
|
||
depGraph.addNode(name, configs[name]); | ||
|
||
graph[name] = allDependencies.map(depName => configs[depName]); | ||
}); | ||
|
||
packageNames.forEach(name => { | ||
if (circular.indexOf(name) === -1) { | ||
const deps = graph[name]; | ||
|
||
deps.forEach(dep => { | ||
if (circular.indexOf(dep.name) === -1) | ||
depGraph.addDependency(name, dep.name); | ||
}); | ||
} | ||
}); | ||
|
||
return new DepGraph(depGraph, configs, circular); | ||
} | ||
|
||
export class DepGraph { | ||
constructor ( | ||
private depGraph: any, | ||
private configs: { [key: string]: Pkg }, | ||
private circular: Array<string>) {} | ||
|
||
public dependenciesOf (packageName: string) { | ||
return this.depGraph.dependenciesOf(packageName); | ||
} | ||
|
||
public configOf(packageName: string): Pkg { | ||
return clone(this.depGraph.getNodeData(packageName)); | ||
} | ||
|
||
public paths(): Array<string> { | ||
return this.packages().map(pkg => pkg.path); | ||
} | ||
|
||
public packages(): Array<Pkg> { | ||
const configs = this.configs; | ||
|
||
return this.packageNames().map(name => configs[name]); | ||
} | ||
|
||
public packageNames(): Array<string> { | ||
const { depGraph, circular } = this; | ||
|
||
try { | ||
return depGraph.overallOrder() | ||
.filter((name: string) => circular.indexOf(name) === -1) | ||
.concat(circular); | ||
} catch (e) { | ||
const circularPackage = e.message.split(':')[1].split('->')[0].trim(); | ||
|
||
throw new Error(EOL + EOL + `${e.message}` + EOL + EOL + | ||
`Circular dependencies are an advanced use-case and must be handled explicitly.` + EOL + | ||
`To handle circular dependencies it is required to create a ` + EOL + | ||
`Northbrook configuration file` + `containing at least:` + EOL + EOL + | ||
` module.exports = {` + EOL + | ||
` circular: [ '${circularPackage}' ]` + EOL + | ||
` }` + EOL); | ||
} | ||
} | ||
} | ||
|
||
function packageFinder(configs: any) { | ||
return function (path: string) { | ||
const pkgJson = clone(require(join(path, 'package.json'))); | ||
|
||
configs[pkgJson.name] = { path, name: pkgJson.name, config: pkgJson }; | ||
}; | ||
} | ||
|
||
function findDependencies(dependencies: any, packageNames: Array<String>): Array<string> { | ||
if (!dependencies) return []; | ||
|
||
return Object.keys(dependencies) | ||
.filter(name => packageNames.indexOf(name) > -1); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
export * from './northbrook'; | ||
export * from './buildDependencyGraph'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.