Skip to content

Commit

Permalink
fix(packager): introduce unit tests for packagers, fix bugs found on …
Browse files Browse the repository at this point in the history
…testing
  • Loading branch information
floydspace committed Nov 1, 2020
1 parent 23854b7 commit b33be2f
Show file tree
Hide file tree
Showing 8 changed files with 600 additions and 61 deletions.
2 changes: 1 addition & 1 deletion examples/complete/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import validateIsin from 'isin-validator';

export function handler(event: any) {
export function handler(event: string) {
const isInvalid = validateIsin(event);

return {
Expand Down
2 changes: 1 addition & 1 deletion src/packagers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export function get(cwd: string, packagerId?: keyof typeof registeredPackagers):
const pkger = findPackager(cwd, packagerId);

if (!(pkger in registeredPackagers)) {
const message = `Could not find packager '${packagerId}'`;
const message = `Could not find packager '${pkger}'`;
console.log(`ERROR: ${message}`);
throw new Error(message);
}
Expand Down
58 changes: 28 additions & 30 deletions src/packagers/npm.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { any, isEmpty, replace } from 'ramda';
import { any, isEmpty } from 'ramda';

import { JSONObject } from '../types';
import { SpawnError, spawnProcess } from '../utils';
Expand Down Expand Up @@ -32,7 +32,7 @@ export class NPM implements Packager {
}
}

getProdDependencies(cwd: string, depth: number) {
getProdDependencies(cwd: string, depth?: number) {
// Get first level dependency graph
const command = /^win/.test(process.platform) ? 'npm.cmd' : 'npm';
const args = [
Expand All @@ -48,32 +48,34 @@ export class NPM implements Packager {
{ npmError: 'peer dep missing', log: true }
];

let processOutput;
try {
const processOutput = spawnProcess(command, args, { cwd });
const depJson = processOutput.stdout;

return JSON.parse(depJson);
processOutput = spawnProcess(command, args, { cwd });
} catch (err) {
if (err instanceof SpawnError) {
// Only exit with an error if we have critical npm errors for 2nd level inside
const errors = err.stderr?.split('\n') ?? [];
const failed = errors.reduce((f, error) => {
if (f) {
return true;
}
return (
!isEmpty(error) &&
!any(ignoredError => error.startsWith(`npm ERR! ${ignoredError.npmError}`), ignoredNpmErrors)
);
}, false);

if (!failed && !isEmpty(err.stdout)) {
return { stdout: err.stdout };
if (!(err instanceof SpawnError)) {
throw err;
}

// Only exit with an error if we have critical npm errors for 2nd level inside
const errors = err.stderr?.split('\n') ?? [];
const failed = errors.reduce((f, error) => {
if (f) {
return true;
}
return (
!isEmpty(error) &&
!any(ignoredError => error.startsWith(`npm ERR! ${ignoredError.npmError}`), ignoredNpmErrors)
);
}, false);

if (failed || isEmpty(err.stdout)) {
throw err;
}

throw err;
processOutput = { stdout: err.stdout };
}

return JSON.parse(processOutput.stdout);
}

/**
Expand All @@ -90,7 +92,7 @@ export class NPM implements Packager {

if (lockfile.dependencies) {
for (const lockedDependency in lockfile.dependencies) {
this.rebaseLockfile(pathToPackageRoot, lockedDependency);
this.rebaseLockfile(pathToPackageRoot, lockfile.dependencies[lockedDependency]);
}
}

Expand All @@ -114,17 +116,13 @@ export class NPM implements Packager {
runScripts(cwd, scriptNames) {
const command = /^win/.test(process.platform) ? 'npm.cmd' : 'npm';

scriptNames.forEach(scriptName => {
const args = ['run', scriptName];

spawnProcess(command, args, { cwd });
});
scriptNames.forEach(scriptName => spawnProcess(command, ['run', scriptName], { cwd }));
}

private rebaseFileReferences(pathToPackageRoot: string, moduleVersion: string) {
if (/^file:[^/]{2}/.test(moduleVersion)) {
const filePath = replace(/^file:/, '', moduleVersion);
return replace(/\\/g, '/', `file:${pathToPackageRoot}/${filePath}`);
const filePath = moduleVersion.replace(/^file:/, '');
return `file:${pathToPackageRoot}/${filePath}`.replace(/\\/g, '/');
}

return moduleVersion;
Expand Down
67 changes: 38 additions & 29 deletions src/packagers/yarn.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { any, head, isEmpty, join, pathOr, reduce, replace, split, startsWith, tail } from 'ramda';
import { any, head, isEmpty, join, pathOr, split, tail } from 'ramda';

import { JSONObject } from '../types';
import { SpawnError, spawnProcess } from '../utils';
import { safeJsonParse, SpawnError, spawnProcess, splitLines } from '../utils';
import { Packager } from './packager';

/**
Expand Down Expand Up @@ -36,7 +36,7 @@ export class Yarn implements Packager {
}
}

getProdDependencies(cwd, depth) {
getProdDependencies(cwd: string, depth?: number) {
const command = /^win/.test(process.platform) ? 'yarn.cmd' : 'yarn';
const args = ['list', `--depth=${depth || 1}`, '--json', '--production'];

Expand All @@ -47,33 +47,36 @@ export class Yarn implements Packager {
try {
processOutput = spawnProcess(command, args, { cwd });
} catch (err) {
if (err instanceof SpawnError) {
// Only exit with an error if we have critical npm errors for 2nd level inside
const errors = err.stderr?.split('\n') ?? [];
const failed = errors.reduce((f, error) => {
if (f) {
return true;
}
return (
!isEmpty(error) &&
!any(ignoredError => error.startsWith(`npm ERR! ${ignoredError.npmError}`), ignoredYarnErrors)
);
}, false);

if (!failed && !isEmpty(err.stdout)) {
return { stdout: err.stdout };
if (!(err instanceof SpawnError)) {
throw err;
}

// Only exit with an error if we have critical npm errors for 2nd level inside
const errors = err.stderr?.split('\n') ?? [];
const failed = errors.reduce((f, error) => {
if (f) {
return true;
}
return (
!isEmpty(error) &&
!any(ignoredError => error.startsWith(`npm ERR! ${ignoredError.npmError}`), ignoredYarnErrors)
);
}, false);

if (failed || isEmpty(err.stdout)) {
throw err;
}

throw err;
processOutput = { stdout: err.stdout };
}

const depJson = processOutput.stdout;
const parsedTree = JSON.parse(depJson);
const convertTrees = reduce((__, tree: JSONObject) => {
const lines = splitLines(processOutput.stdout);
const parsedLines = lines.map(safeJsonParse);
const parsedTree = parsedLines.find(line => line && line.type === 'tree');
const convertTrees = ts => ts.reduce((__, tree: JSONObject) => {
const splitModule = split('@', tree.name);
// If we have a scoped module we have to re-add the @
if (startsWith('@', tree.name)) {
if (tree.name.startsWith('@')) {
splitModule.splice(0, 1);
splitModule[0] = '@' + splitModule[0];
}
Expand Down Expand Up @@ -101,28 +104,34 @@ export class Yarn implements Packager {
while ((match = fileVersionMatcher.exec(lockfile)) !== null) {
replacements.push({
oldRef: match[1],
newRef: replace(/\\/g, '/', `${pathToPackageRoot}/${match[1]}`)
newRef: `${pathToPackageRoot}/${match[1]}`.replace(/\\/g, '/')
});
}

// Replace all lines in lockfile
return reduce((__, replacement) => replace(__, replacement.oldRef, replacement.newRef), lockfile, replacements);
return replacements.reduce((__, replacement) => __.replace(replacement.oldRef, replacement.newRef), lockfile);
}

install(cwd) {
install(cwd: string, packagerOptions?) {
const command = /^win/.test(process.platform) ? 'yarn.cmd' : 'yarn';
const args = ['install', '--frozen-lockfile', '--non-interactive'];
const args = [ 'install', '--frozen-lockfile', '--non-interactive' ];

// Convert supported packagerOptions
if (packagerOptions.ignoreScripts) {
args.push('--ignore-scripts');
}

spawnProcess(command, args, { cwd });
}

// "Yarn install" prunes automatically
prune(cwd) {
return this.install(cwd);
prune(cwd: string, packagerOptions?) {
return this.install(cwd, packagerOptions);
}

runScripts(cwd, scriptNames: string[]) {
const command = /^win/.test(process.platform) ? 'yarn.cmd' : 'yarn';

scriptNames.forEach(scriptName => spawnProcess(command, ['run', scriptName], { cwd }));
}
}
12 changes: 12 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,18 @@ export function spawnProcess(command: string, args: string[], options: childProc
return { stdout, stderr };
}

export function safeJsonParse(str: string) {
try {
return JSON.parse(str);
} catch (e) {
return null;
}
}

export function splitLines(str: string) {
return str.split(/\r?\n/);
}

/**
* Extracts the file name from handler string.
*/
Expand Down
21 changes: 21 additions & 0 deletions tests/packagers/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* Unit tests for packagers/index
*/

import { get } from '../../src/packagers';
import { NPM } from '../../src/packagers/npm';
import * as Utils from '../../src/utils';

const getCurrentPackager = jest.spyOn(Utils, 'getCurrentPackager');

describe('packagers factory', () => {
it('should throw on unknown packagers', () => {
getCurrentPackager.mockReset().mockReturnValue('unknown' as never);
expect(() => get('.')).toThrowError(/Could not find packager 'unknown'/);
});

it('should return npm packager', () => {
const npm = get('.', 'npm');
expect(npm).toBeInstanceOf(NPM);
});
});
Loading

0 comments on commit b33be2f

Please sign in to comment.