Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds support for only checking prod deps #92

Merged
merged 1 commit into from
May 31, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/bin/rotten-deps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ yargs
boolean: true,
requiresArg: false,
})
.option('ignore-dev', {
description: 'ignores developer dependencies',
boolean: true,
requiresArg: false,
default: false,
})
.parseAsync()
.then(argv => {
if (argv.help) yargs.showHelp();
Expand Down Expand Up @@ -147,12 +153,14 @@ yargs

const configPath = argv['config-path'];
const defaultExpiration = argv['default-expiration'];
const ignoreDev = argv['ignore-dev'];

const configParser = (raw: string): Promise<Config> =>
new Promise(
(resolve) => {
const parsed = JSON.parse(raw);
if (defaultExpiration) parsed.defaultExpiration = defaultExpiration;
if (ignoreDev) parsed.ignoreDevDependencies = ignoreDev;
resolve(parsed);
},
);
Expand All @@ -175,6 +183,7 @@ yargs
};

if (defaultExpiration) config.defaultExpiration = defaultExpiration;
if (ignoreDev) config.ignoreDevDependencies = ignoreDev;

maestro(config);
}
Expand Down
1 change: 1 addition & 0 deletions src/lib/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ interface Rule {

export interface Config {
defaultExpiration?: number;
ignoreDevDependencies?: boolean,
readonly kind?: 'config';
readonly rules: Rule[];
}
Expand Down
39 changes: 37 additions & 2 deletions src/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@
*/

import { createFileReader, createConfig } from './config';
import { createOutdatedRequest, createDetailsRequest, PackageDetails } from './npm-interactions';
import {
createOutdatedRequest,
createDetailsRequest,
PackageDetails,
createListRequest,
} from './npm-interactions';

import type { Config } from './config';
import type { OutdatedPackage, OutdatedData } from './npm-interactions';
Expand Down Expand Up @@ -79,6 +84,34 @@ const getIndividualPackageDetails = async (outdated: OutdatedData): Promise<Comb
}
};


/**
* Compares a set of outdated dependencies and compares it to the list of installed prod dependencies
* and drops any value from the outdated dependencies that doesn't appear in the set of installed
* dependencies
*/
const filterOutdated = async (c: Config, outdated: OutdatedData): Promise<OutdatedData> => {
if (!c.ignoreDevDependencies) return outdated;

const listRequest = createListRequest(true);
const maybeModules = await listRequest();

if (maybeModules instanceof Error) throw maybeModules;
const setOfInstalledDependencies = new Set(maybeModules.getListOfInstalledDependencies());
const setOfOutdatedDependencies = new Set(Object.entries(outdated));

const filtered: OutdatedData = {};

setOfOutdatedDependencies.forEach((dep) => {
const [name, data] = dep;
if (!setOfInstalledDependencies.has(name)) setOfOutdatedDependencies.delete(dep);
filtered[name] = data;
});

return filtered;
};


/**
* Compares the details on each dependency flagged as outdated in order to
* determine how stale a version actually is.
Expand All @@ -96,10 +129,12 @@ export const generateReport = async (c: Config, r?: Reporter): Promise<ReportRes

if (outdated instanceof Error) return outdated;

const filteredOutdated = await filterOutdated(c, outdated);

const reportData: ReportData[] = [];
let hasPreinstallWarning = false;

const individualDetails = await getIndividualPackageDetails(outdated);
const individualDetails = await getIndividualPackageDetails(filteredOutdated);

if (individualDetails instanceof Error) return individualDetails;

Expand Down
51 changes: 51 additions & 0 deletions src/lib/npm-interactions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,26 @@ interface DetailsRequest {
(): Promise<PackageDetails|Error>;
}

interface ListDependency {
version: string;
resolved: string;
}

interface ListResponse {
version: string,
name: string,
dependencies: Record<string, ListDependency>;
}

interface ListWithHelpers {
data: ListResponse;
getListOfInstalledDependencies: () => Array<string>;
}

interface ListRequest {
(): Promise<ListWithHelpers | Error>;
}


/**
* Creates a function for running `npm outdated`
Expand Down Expand Up @@ -77,7 +97,38 @@ export const createDetailsRequest = (dependencyName: string): DetailsRequest =>
};


const isDependencyList = (result: ListResponse | any): result is ListResponse =>
(result as ListResponse).version !== undefined
&& (result as ListResponse).name !== undefined
&& (result as ListResponse).dependencies !== undefined;


/**
* Uses the `npm ls` command to get a list of installed dependencies of a project.
* @param prod if this is set to true dev dependencies will be ignored
*/
export const createListRequest = (prod = false): ListRequest => {
const command = process.platform === 'win32' ? 'npm.cmd' : 'npm';
const args = ['ls', '--json'];

if (prod) args.push('--prod');

return () => new Promise(resolve => {
proc.execFile(command, args, { encoding: 'utf8' }, (err, stdout) => {
const results = JSON.parse(stdout);
if (!isDependencyList(results)) resolve(new Error('unexpected list response'));

resolve({
data: results,
getListOfInstalledDependencies: () => Object.getOwnPropertyNames(results.dependencies),
});
});
});
};


export default {
createOutdatedRequest,
createDetailsRequest,
createListRequest,
};
14 changes: 1 addition & 13 deletions test/api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,11 @@ import { assert } from 'chai';
import { spy } from 'sinon';
import { generateReport } from '../src/lib/index';
import { createOutdatedRequest } from '../src/lib/npm-interactions';


const defaultDir = process.cwd();
const changeDir = (directory: string) => () => process.chdir(directory);
const restoreDir = changeDir(defaultDir);
const sampleAppDir = changeDir(
join(__dirname, './dummies/sample-app'),
);
const noInstallDir = changeDir(
join(__dirname, './dummies/sample-app-no-install'),
);

import { restoreDir, sampleAppDir, noInstallDir } from './helpers/test-directory-helpers';

const sampleConfigDir = join(__dirname, './dummies/');
const getTestConfig = (configID: string) => JSON.parse(readFileSync(`${sampleConfigDir}/${configID}.json`, { encoding: 'utf8' }));


describe('API integrations', () => {
afterEach(restoreDir);

Expand Down
18 changes: 18 additions & 0 deletions test/helpers/test-directory-helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { join } from 'path';

const defaultDir = process.cwd();
const changeDir = (directory: string) => () => process.chdir(directory);

export const restoreDir = changeDir(defaultDir);
export const sampleAppDir = changeDir(
join(__dirname, '../dummies/sample-app'),
);
export const noInstallDir = changeDir(
join(__dirname, '../dummies/sample-app-no-install'),
);

export default {
restoreDir,
sampleAppDir,
noInstallDir,
}
23 changes: 21 additions & 2 deletions test/npm-interactions.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { assert } from 'chai';
import npmLib from '../src/lib/npm-interactions';
import { restoreDir, sampleAppDir } from './helpers/test-directory-helpers';


/**
Expand All @@ -16,23 +17,27 @@ const setPlatformWindows = setProcessPlatform('win32');
const setPlatformDarwin = setProcessPlatform('darwin');

describe('NPM Interaction Library', () => {
it('Should create a function for outdated and view requests for windows machines', () => {
it('Should create functions for interactions on windows machines', () => {
setPlatformWindows();
const getOutdatedRequest = npmLib.createOutdatedRequest();
const getDetailsRequest = npmLib.createDetailsRequest('banana');
const getListRequest = npmLib.createListRequest();

assert(typeof getOutdatedRequest === 'function');
assert(typeof getDetailsRequest === 'function');
assert(typeof getListRequest === 'function');
restorePlatform();
});

it('Should create a function for outdated and view requests for non-windows machines', () => {
it('Should create functions for interactions on non-windows machines', () => {
setPlatformDarwin();
const getOutdatedRequest = npmLib.createOutdatedRequest();
const getDetailsRequest = npmLib.createDetailsRequest('banana');
const getListRequest = npmLib.createListRequest();

assert(typeof getOutdatedRequest === 'function');
assert(typeof getDetailsRequest === 'function');
assert(typeof getListRequest === 'function');
restorePlatform();
});

Expand All @@ -47,5 +52,19 @@ describe('NPM Interaction Library', () => {
const response = await getDetailsRequest();
assert.equal(response.name, 'express');
}).timeout(8000);

it('Should get a list of installed dependencies', async () => {
sampleAppDir();
const getListRequest = npmLib.createListRequest();
const response = await getListRequest();

assert(!(response instanceof Error));

const listOfInstalledDependencies = response.getListOfInstalledDependencies();
assert(listOfInstalledDependencies.includes('mocha'));
restoreDir();
}).timeout(8000);

it('Should get a list of installed prod dependencies');
});

1 change: 1 addition & 0 deletions wallaby.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ module.exports = () => ({
'src/lib/*.ts',
{ pattern: 'src/bin/rotten-deps.ts', instrument: false },
{ pattern: 'test/dummies/**/*', instrument: false },
{ pattern: 'test/helpers/**/*', instrument: false },
],
tests: [
'test/*.test.js',
Expand Down