Skip to content

Commit

Permalink
Update logic to allow for opt/req and prod/dev/root
Browse files Browse the repository at this point in the history
  • Loading branch information
MarshallOfSound committed Apr 7, 2018
1 parent 5343385 commit afa7a29
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 130 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,5 @@ const walker = new Walker(modulePath);
Returns `Promise<Module[]>`

Will walk your entire node_modules tree reporting back an array of "modules", each
module has a "path", "name" and "depType". See the typescript definition file
module has a "path", "name" and "relationship". See the typescript definition file
for more information.
34 changes: 18 additions & 16 deletions src/Walker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as debug from 'debug';
import * as fs from 'fs-extra';
import * as path from 'path';

import { DepType, depTypeGreater, childDepType } from './depTypes';
import { DepType, DepRequireState, depRelationshipGreater, childRequired, DepRelationship } from './depTypes';

export type VersionRange = string;
export interface PackageJSON {
Expand All @@ -13,7 +13,7 @@ export interface PackageJSON {
}
export interface Module {
path: string;
depType: DepType;
relationship: DepRelationship;
name: string;
}

Expand Down Expand Up @@ -48,7 +48,7 @@ export class Walker {
return null;
}

private async walkDependenciesForModuleInModule(moduleName: string, modulePath: string, depType: DepType) {
private async walkDependenciesForModuleInModule(moduleName: string, modulePath: string, relationship: DepRelationship) {
let testPath = modulePath;
let discoveredPath: string | null = null;
let lastRelative: string | null = null;
Expand All @@ -65,7 +65,7 @@ export class Walker {
}
}
// If we can't find it the install is probably buggered
if (!discoveredPath && depType !== DepType.OPTIONAL && depType !== DepType.DEV_OPTIONAL) {
if (!discoveredPath && relationship.getRequired() !== DepRequireState.OPTIONAL) {
throw new Error(
`Failed to locate module "${moduleName}" from "${modulePath}"
Expand All @@ -74,22 +74,22 @@ export class Walker {
}
// If we can find it let's do the same thing for that module
if (discoveredPath) {
await this.walkDependenciesForModule(discoveredPath, depType);
await this.walkDependenciesForModule(discoveredPath, relationship);
}
}

private async walkDependenciesForModule(modulePath: string, depType: DepType) {
d('walk reached:', modulePath, ' Type is:', DepType[depType]);
private async walkDependenciesForModule(modulePath: string, relationship: DepRelationship) {
d('walk reached:', modulePath, ' Type is:', relationship.toString());
// We have already traversed this module
if (this.walkHistory.has(modulePath)) {
d('already walked this route');
// Find the existing module reference
const existingModule = this.modules.find(module => module.path === modulePath) as Module;
// If the depType we are traversing with now is higher than the
// last traversal then update it (prod superseeds dev for instance)
if (depTypeGreater(depType, existingModule.depType)) {
d(`existing module has a type of "${existingModule.depType}", new module type would be "${depType}" therefore updating`);
existingModule.depType = depType;
if (depRelationshipGreater(relationship, existingModule.relationship)) {
d(`existing module has a type of "${existingModule.relationship.toString()}", new module type would be "${relationship.toString()}" therefore updating`);
existingModule.relationship = relationship;
}
return;
}
Expand All @@ -105,11 +105,13 @@ export class Walker {
// Record this module as being traversed
this.walkHistory.add(modulePath);
this.modules.push({
depType,
relationship,
path: modulePath,
name: pJ.name,
});

const childDepType = relationship.getType() === DepType.DEV ? DepType.DEV : DepType.PROD;

// For every prod dep
for (const moduleName in pJ.dependencies) {
// npm decides it's a funny thing to put optional dependencies in the "dependencies" section
Expand All @@ -121,18 +123,18 @@ export class Walker {
await this.walkDependenciesForModuleInModule(
moduleName,
modulePath,
childDepType(depType, DepType.PROD),
new DepRelationship(childDepType, childRequired(relationship.getRequired(), DepRequireState.REQUIRED)),
);
}

// For every dev dep, but only if we are in the root module
if (depType === DepType.ROOT) {
if (relationship.getType() === DepType.ROOT) {
d('we\'re still at the beginning, walking down the dev route');
for (const moduleName in pJ.devDependencies) {
await this.walkDependenciesForModuleInModule(
moduleName,
modulePath,
childDepType(depType, DepType.DEV),
new DepRelationship(DepType.DEV, childRequired(relationship.getRequired(), DepRequireState.REQUIRED)),
);
}
}
Expand All @@ -142,7 +144,7 @@ export class Walker {
await this.walkDependenciesForModuleInModule(
moduleName,
modulePath,
childDepType(depType, DepType.OPTIONAL),
new DepRelationship(childDepType, childRequired(relationship.getRequired(), DepRequireState.OPTIONAL)),
);
}
}
Expand All @@ -154,7 +156,7 @@ export class Walker {
this.cache = new Promise<Module[]>(async (resolve, reject) => {
this.modules = [];
try {
await this.walkDependenciesForModule(this.rootModule, DepType.ROOT);
await this.walkDependenciesForModule(this.rootModule, new DepRelationship(DepType.ROOT, DepRequireState.REQUIRED));
} catch (err) {
reject(err);
return;
Expand Down
82 changes: 36 additions & 46 deletions src/depTypes.ts
Original file line number Diff line number Diff line change
@@ -1,43 +1,41 @@
export enum DepType {
PROD,
DEV,
ROOT,
}

export enum DepRequireState {
OPTIONAL,
DEV_OPTIONAL,
ROOT
REQUIRED,
}

export class DepRelationship {
constructor(private type: DepType, private required: DepRequireState) {}

public getType() { return this.type; }
public getRequired() { return this.required; }
public toString() {
return `${DepType[this.getType()]}_${DepRequireState[this.getRequired()]}`;
}
}

export const depRequireStateGreater = (newState: DepRequireState, existing: DepRequireState) => {
if (existing === DepRequireState.REQUIRED) {
return false;
} else if (newState === DepRequireState.REQUIRED) {
return true;
}
return false;
}

export const depTypeGreater = (newType: DepType, existing: DepType) => {
switch (existing) {
case DepType.DEV:
switch (newType) {
case DepType.OPTIONAL:
case DepType.PROD:
case DepType.ROOT:
return true;
case DepType.DEV:
case DepType.DEV_OPTIONAL:
default:
return false;
}
case DepType.DEV_OPTIONAL:
switch (newType) {
case DepType.OPTIONAL:
case DepType.PROD:
case DepType.ROOT:
case DepType.DEV:
return true;
case DepType.DEV_OPTIONAL:
default:
return false;
}
case DepType.OPTIONAL:
switch (newType) {
case DepType.PROD:
case DepType.ROOT:
return true;
case DepType.OPTIONAL:
case DepType.DEV:
case DepType.DEV_OPTIONAL:
default:
return false;
}
Expand All @@ -46,19 +44,15 @@ export const depTypeGreater = (newType: DepType, existing: DepType) => {
case DepType.ROOT:
return true;
case DepType.PROD:
case DepType.OPTIONAL:
case DepType.DEV:
case DepType.DEV_OPTIONAL:
default:
return false;
}
case DepType.ROOT:
switch (newType) {
case DepType.ROOT:
case DepType.PROD:
case DepType.OPTIONAL:
case DepType.DEV:
case DepType.DEV_OPTIONAL:
default:
return false;
}
Expand All @@ -67,22 +61,18 @@ export const depTypeGreater = (newType: DepType, existing: DepType) => {
}
}

export const childDepType = (parentType: DepType, childType: DepType) => {
if (childType === DepType.ROOT) {
throw new Error('Something went wrong, a child dependency can\'t be marked as the ROOT');
export const depRelationshipGreater = (newRelationship: DepRelationship, existingRelationship: DepRelationship) => {
if (depRequireStateGreater(newRelationship.getRequired(), existingRelationship.getRequired())) {
return true;
}
switch (parentType) {
case DepType.ROOT:
return childType;
case DepType.PROD:
if (childType === DepType.OPTIONAL) return DepType.OPTIONAL;
return DepType.PROD;
case DepType.OPTIONAL:
return DepType.OPTIONAL;
case DepType.DEV_OPTIONAL:
return DepType.DEV_OPTIONAL;
case DepType.DEV:
if (childType === DepType.OPTIONAL) return DepType.DEV_OPTIONAL;
return DepType.DEV;
return depTypeGreater(newRelationship.getType(), existingRelationship.getType());
}

export const childRequired = (parent: DepRequireState, child: DepRequireState) => {
switch (parent) {
case DepRequireState.OPTIONAL:
return DepRequireState.OPTIONAL;
default:
return child;
}
}
27 changes: 16 additions & 11 deletions test/Walker_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as path from 'path';
import { expect } from 'chai';

import { Module, Walker } from '../src/Walker';
import { DepType } from '../src/depTypes';
import { DepType, DepRequireState } from '../src/depTypes';

describe('Walker', () => {
let walker: Walker;
Expand All @@ -18,26 +18,31 @@ describe('Walker', () => {
expect(walker.getRootModule()).to.equal(path.resolve(__dirname, '..'));
});

it('should locate top level prod deps as prod deps', () => {
expect(dep('fs-extra')).to.have.property('depType', DepType.PROD);
it('should locate top level prod deps as required prod deps', () => {
expect(dep('fs-extra').relationship.getType()).equal(DepType.PROD);
expect(dep('fs-extra').relationship.getRequired()).equal(DepRequireState.REQUIRED);
});

it('should locate top level dev deps as dev deps', () => {
expect(dep('mocha')).to.have.property('depType', DepType.DEV);
it('should locate top level dev deps as required dev deps', () => {
expect(dep('mocha').relationship.getType()).equal(DepType.DEV);
expect(dep('mocha').relationship.getRequired()).equal(DepRequireState.REQUIRED);
});

it('should locate a dep of a dev dep as a dev dep', () => {
expect(dep('commander')).to.have.property('depType', DepType.DEV);
it('should locate a dep of a dev dep as a required dev dep', () => {
expect(dep('commander').relationship.getType()).equal(DepType.DEV);
expect(dep('commander').relationship.getRequired()).equal(DepRequireState.REQUIRED);
});

it('should locate a dep of a dev dep that is also a top level prod dep as a prod dep', () => {
expect(dep('debug')).to.have.property('depType', DepType.PROD);
it('should locate a dep of a dev dep that is also a top level prod dep as a required prod dep', () => {
expect(dep('debug').relationship.getType()).equal(DepType.PROD);
expect(dep('debug').relationship.getRequired()).equal(DepRequireState.REQUIRED);
});

it('should locate a dep of a dev dep that is optional as a dev_optional dep', function () {
it('should locate a dep of a dev dep that is optional as an optional dev dep', function () {
if (process.platform !== 'darwin') {
this.skip();
}
expect(dep('fsevents')).to.have.property('depType', DepType.DEV_OPTIONAL);
expect(dep('fsevents').relationship.getType()).to.equal(DepType.DEV);
expect(dep('fsevents').relationship.getRequired()).to.equal(DepRequireState.OPTIONAL);
});
});
Loading

0 comments on commit afa7a29

Please sign in to comment.