Skip to content

Commit

Permalink
Merge pull request #378 from x3cion/schematic-runner-pathfinding
Browse files Browse the repository at this point in the history
refactor() make schematic runner more robust
  • Loading branch information
kamilmysliwiec committed Sep 25, 2019
2 parents 79dabdd + b0510b2 commit 93e1cb7
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 76 deletions.
38 changes: 13 additions & 25 deletions lib/runners/schematic.runner.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,25 @@
import { existsSync } from 'fs';
import { join, sep } from 'path';
import { join, resolve } from 'path';
import { AbstractRunner } from './abstract.runner';

export class SchematicRunner extends AbstractRunner {
constructor() {
super(`"${SchematicRunner.findClosestSchematicsBinary(__dirname)}"`);
super(`"${SchematicRunner.findClosestSchematicsBinary()}"`);
}

public static findClosestSchematicsBinary(path: string): string {
const segments = path.split(sep);
const binaryPath = ['node_modules', '.bin', 'schematics'];

const combineSegments = (pkgLastIndex: number) => [
sep,
...segments.slice(0, pkgLastIndex),
...binaryPath,
];
const globalBinPathSegments = combineSegments(
segments.lastIndexOf('cli') + 1,
);
const schematicsGlobalPath = join(...globalBinPathSegments);
if (existsSync(schematicsGlobalPath)) {
return schematicsGlobalPath;
}
public static getModulePaths() {
return module.paths;
}

const localBinPathSegments = combineSegments(
segments.lastIndexOf('node_modules'),
);
const schematicsLocalPath = join(...localBinPathSegments);
if (existsSync(schematicsLocalPath)) {
return schematicsLocalPath;
public static findClosestSchematicsBinary(): string {
const subPath = join('.bin', 'schematics');
for (const path of this.getModulePaths()) {
const binaryPath = resolve(path, subPath);
if (existsSync(binaryPath)) {
return binaryPath;
}
}

return join(__dirname, '../..', 'node_modules/.bin/schematics');
throw new Error("'schematics' binary path could not be found!");
}
}
115 changes: 64 additions & 51 deletions test/lib/runners/schematic.runner.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,80 +2,93 @@ import * as fs from 'fs';
import * as path from 'path';
import { SchematicRunner } from '../../../lib/runners/schematic.runner';

const withSep = (route: string) => route.replace('/', path.sep);
const withSep = (route: string) => path.resolve(route.split('/').join(path.sep));

const existsSyncTrueForPathMock = (pathToExist: string) => {
pathToExist = withSep(pathToExist);
return (pathToCheck: string) => pathToCheck === pathToExist;
};

const getModulePathsMock = (fullPath: string) => {
const moduleLoaderPaths: string[] = [];
const fullPathBits = fullPath.split(path.sep);

while (--fullPathBits.length !== 0) {
if (fullPathBits[fullPathBits.length - 1] === 'node_modules') {
continue;
}

const modulePath = [...fullPathBits, 'node_modules'].join(path.sep);

moduleLoaderPaths.push(modulePath);
}

return () => moduleLoaderPaths;
};

describe('SchematicRunner', () => {
describe('Mocking Checks', () => {
describe('existsSyncTrueForPathMock', () => {
it('it should return true only for one specific path', () => {
const truePath = withSep('/this/path/exists');
const wrongPath = withSep('/this/path/doesnt/exist');
const existsSync = existsSyncTrueForPathMock(truePath);

expect(existsSync(truePath)).toBe(true);
expect(existsSync(wrongPath)).toBe(false);
});
});

describe('getModulePathsMock', () => {
it('it should return the same array as the module.paths module-local field', () => {
const realModulePath = module.paths;
const mockedModulePath = getModulePathsMock(__filename)();

expect(mockedModulePath).toEqual(realModulePath);
});
});
});

describe('findClosestSchematicsBinary', () => {
it('it should return the correct path when called from globally installed package', () => {
(fs as any).existsSync = jest.fn();
(fs.existsSync as jest.Mock)
.mockReturnValueOnce(true)
.mockReturnValueOnce(false);
const existingPath = withSep('/home/test/.nvm/versions/node/v8.12.0/lib/node_modules/@nestjs/cli/node_modules/.bin/schematics');

const globalRunnersDirname = withSep(
'/home/test/.nvm/versions/node/v8.12.0/lib/node_modules/@nestjs/cli/lib/runners',
);

const resolvedPath = SchematicRunner.findClosestSchematicsBinary(
globalRunnersDirname,
);
(fs as any).existsSync = jest.fn(existsSyncTrueForPathMock(existingPath));
(SchematicRunner as any).prototype.constructor.getModulePaths = getModulePathsMock(globalRunnersDirname);

expect(resolvedPath).toEqual(
withSep(
'/home/test/.nvm/versions/node/v8.12.0/lib/node_modules/@nestjs/cli/node_modules/.bin/schematics',
),
);
const resolvedPath = SchematicRunner.findClosestSchematicsBinary();

expect(resolvedPath).toEqual(existingPath);
});

it('should return the correct path when called from locally installed package', () => {
(fs as any).existsSync = jest.fn();
(fs.existsSync as jest.Mock)
.mockReturnValueOnce(false)
.mockReturnValueOnce(true);
const existingPath = withSep('/home/test/project/node_modules/.bin/schematics');

const localRunnersDirname = withSep(
'/home/test/project/node_modules/@nestjs/cli/lib/runners',
);

const resolvedPath = SchematicRunner.findClosestSchematicsBinary(
localRunnersDirname,
);
(fs as any).existsSync = jest.fn(existsSyncTrueForPathMock(existingPath));
(SchematicRunner as any).prototype.constructor.getModulePaths = getModulePathsMock(localRunnersDirname);

expect(resolvedPath).toEqual(
withSep('/home/test/project/node_modules/.bin/schematics'),
);
});
const resolvedPath = SchematicRunner.findClosestSchematicsBinary();

xit('should return the fallback path when neither expected global nor local paths are found', () => {
// Need to mock __dirname in order to get this test to work.
(fs as any).existsSync = jest.fn();
(fs.existsSync as jest.Mock)
.mockReturnValueOnce(false)
.mockReturnValueOnce(false);

__dirname = path.join(
path.sep,
'home',
'test',
'node_modules',
'@nestjs',
'cli',
'lib',
'runners',
);
expect(resolvedPath).toEqual(existingPath);
});

const localRunnersDirname = withSep(
'/home/test/project/node_modules/@nestjs/cli/lib/runners',
it('should throw when no path is found', () => {
const globalRunnersDirname = withSep(
'/home/test/.nvm/versions/node/v8.12.0/lib/node_modules/@nestjs/cli/lib/runners',
);

const resolvedPath = SchematicRunner.findClosestSchematicsBinary(
localRunnersDirname,
);
(fs as any).existsSync = jest.fn().mockReturnValue(false);
(SchematicRunner as any).prototype.constructor.getModulePaths = getModulePathsMock(globalRunnersDirname);

expect(resolvedPath).toEqual(
withSep(
'/home/test/node_modules/@nestjs/cli/node_modules/.bin/schematics',
),
);
expect(SchematicRunner.findClosestSchematicsBinary).toThrow();
});
});
});

0 comments on commit 93e1cb7

Please sign in to comment.