Skip to content

Commit

Permalink
feat: NPM multi type parser (#63)
Browse files Browse the repository at this point in the history
Allow someone to use all different versions of npm to their hearts desire: shrinkwrap, package-lock, and yarn!
  • Loading branch information
DarthHater authored Dec 9, 2019
1 parent 5966515 commit 1e8aa37
Show file tree
Hide file tree
Showing 5 changed files with 191 additions and 26 deletions.
20 changes: 20 additions & 0 deletions ext-src/packages/PackageDependenciesHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,24 @@ export class PackageDependenciesHelper {
}
return false;
}

public static checkIfValidWithArray(manifests: Array<string>, format: string): string {
let result: string = "";
manifests.forEach((element) => {
if (this.doesPathExist(this.getWorkspaceRoot(), element)) {
console.debug(`Valid for ${format}`);
result = element;
}
})

return result;
}

private static valueOrEmpty(map: Map<string, string>, key: string): string {
if (map.has(key)) {
let val = map.get(key);
return val === undefined ? "" : val;
}
return "";
}
}
7 changes: 5 additions & 2 deletions ext-src/packages/npm/NpmDependencies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { RequestService } from "../../services/RequestService";
import { NpmUtils } from './NpmUtils';
import { ScanType } from "../../types/ScanType";
import { ComponentEntry } from "../../models/ComponentEntry";
import { NpmScanType } from "./NpmScanType";

export class NpmDependencies implements PackageDependencies {
Dependencies: Array<NpmPackage> = [];
Expand All @@ -31,6 +32,7 @@ export class NpmDependencies implements PackageDependencies {
ComponentEntry
>();
RequestService: RequestService;
private scanType: string = "";

constructor(private requestService: RequestService) {
this.RequestService = this.requestService;
Expand All @@ -39,7 +41,7 @@ export class NpmDependencies implements PackageDependencies {
public async packageForIq(): Promise<any> {
try {
let npmUtils = new NpmUtils();
this.Dependencies = await npmUtils.getDependencyArray();
this.Dependencies = await npmUtils.getDependencyArray(this.scanType);
Promise.resolve();
}
catch (e) {
Expand All @@ -48,7 +50,8 @@ export class NpmDependencies implements PackageDependencies {
}

public CheckIfValid(): boolean {
return PackageDependenciesHelper.checkIfValid("package.json", "npm");
this.scanType = PackageDependenciesHelper.checkIfValidWithArray(NpmScanType, "npm");
return this.scanType === "" ? false : true;
}

public ConvertToComponentEntry(resultEntry: any): string {
Expand Down
9 changes: 6 additions & 3 deletions ext-src/packages/npm/NpmLiteDependencies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,26 @@ import { LitePackageDependencies } from "../LitePackageDependencies";
import { PackageDependenciesHelper } from "../PackageDependenciesHelper";
import { NpmPackage } from "./NpmPackage";
import { NpmUtils } from "./NpmUtils";
import { NpmScanType } from "./NpmScanType";

export class NpmLiteDependencies implements LitePackageDependencies {
dependencies: Array<NpmPackage> = [];
format: string = "npm";
manifestName: string = "package.json"
manifestName: string = "package.json";
private scanType: string = "";

constructor() {
}

public checkIfValid(): boolean {
return PackageDependenciesHelper.checkIfValid(this.manifestName, this.format);
this.scanType = PackageDependenciesHelper.checkIfValidWithArray(NpmScanType, this.format);
return this.scanType === "" ? false : true;
}

public async packageForService(): Promise<any> {
try {
let npmUtils = new NpmUtils();
this.dependencies = await npmUtils.getDependencyArray();
this.dependencies = await npmUtils.getDependencyArray(this.scanType);

Promise.resolve();
}
Expand Down
23 changes: 23 additions & 0 deletions ext-src/packages/npm/NpmScanType.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright (c) 2019-present Sonatype, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

const YARN_LOCK = "yarn.lock";
const PACKAGE_LOCK_JSON = "package-lock.json";
const NPM_SHRINKWRAP_JSON = "npm-shrinkwrap.json";

const NpmScanType: Array<string> = [NPM_SHRINKWRAP_JSON, PACKAGE_LOCK_JSON, YARN_LOCK];

export { NpmScanType, YARN_LOCK, PACKAGE_LOCK_JSON, NPM_SHRINKWRAP_JSON };
158 changes: 137 additions & 21 deletions ext-src/packages/npm/NpmUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,47 +13,163 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as path from "path";
import * as path from 'path';
import * as fs from "fs";
import * as _ from "lodash";

import { exec } from "../../utils/exec";
import { PackageDependenciesHelper } from "../PackageDependenciesHelper";
import { NpmPackage } from "./NpmPackage";
import { NPM_SHRINKWRAP_JSON, YARN_LOCK, PACKAGE_LOCK_JSON } from "./NpmScanType";

const NPM_SHRINKWRAP_COMMAND = 'npm shrinkwrap';
const YARN_LIST_COMMAND = 'yarn list';
const NPM_LIST_COMMAND = 'npm list';

export class NpmUtils {
public async getDependencyArray(): Promise<Array<NpmPackage>> {
public async getDependencyArray(manifestType: string): Promise<Array<NpmPackage>> {
try {
const npmShrinkwrapFilename = path.join(
PackageDependenciesHelper.getWorkspaceRoot(),
"npm-shrinkwrap.json"
);
if (!fs.existsSync(npmShrinkwrapFilename)) {
let { stdout, stderr } = await exec("npm shrinkwrap", {
if (manifestType === YARN_LOCK) {
let {stdout, stderr} = await exec(YARN_LIST_COMMAND, {
cwd: PackageDependenciesHelper.getWorkspaceRoot()
});

if (stdout != "" && stderr == "") {
return Promise.resolve(this.parseYarnList(stdout));
}
} else if (manifestType === NPM_SHRINKWRAP_JSON) {
let { stdout, stderr } = await exec(NPM_SHRINKWRAP_COMMAND, {
cwd: PackageDependenciesHelper.getWorkspaceRoot()
});
let npmShrinkWrapFile = "npm-shrinkwrap.json";
let npmShrinkWrapFile = NPM_SHRINKWRAP_JSON;
let shrinkWrapSucceeded =
stdout || stderr.search(npmShrinkWrapFile) > -1;
if (!shrinkWrapSucceeded) {
return Promise.reject("Unable to run npm shrinkwrap");
return Promise.reject(`Unable to run ${NPM_SHRINKWRAP_COMMAND}`);
}
let obj = JSON.parse(fs.readFileSync(path.join(PackageDependenciesHelper.getWorkspaceRoot(), NPM_SHRINKWRAP_JSON), "utf8"));

return Promise.resolve(this.flattenAndUniqDependencies(obj));
} else if (manifestType === PACKAGE_LOCK_JSON) {
let {stdout, stderr} = await exec(NPM_LIST_COMMAND, {
cwd: PackageDependenciesHelper.getWorkspaceRoot()
});

if (stdout != "" && stderr == "") {
return Promise.resolve(this.parseNpmList(stdout));
}
} else {
return Promise.reject(`No valid command supplied, have you implemented it? Manifest type supplied: ${manifestType}`);
}
//read npm-shrinkwrap.json
let obj = JSON.parse(fs.readFileSync(npmShrinkwrapFilename, "utf8"));

return Promise.resolve(this.flattenAndUniqDependencies(obj));
} catch (e) {
return Promise.reject(
"npm shrinkwrap failed, try running it manually to see what went wrong:" +
e.message
);
return Promise.reject(`${manifestType} read failed, try running it manually to see what went wrong: ${e.stderr}`);
}
return Promise.reject();
}

private parseYarnList(output: string) {
let dependencyList: NpmPackage[] = [];
let results = output.split("\n");

results.forEach((dep, index) => {
if (index == 0) {
console.debug("Skipping line");
} else {
let splitParts = dep.trim().split(" ");
if (!this.isRegularVersion(splitParts[splitParts.length - 1])) {
console.debug("Skipping since version range");
} else {
try {
dependencyList.push(this.setAndReturnNpmPackage(splitParts));
}
catch (e) {
console.debug(e.stderr);
}
}
}
});

return this.sortDependencyList(dependencyList);
}

private setAndReturnNpmPackage(splitParts: string[]): NpmPackage {
let newName = this.removeScopeSymbolFromName(splitParts[splitParts.length - 1]);
let newSplit = newName.split("@");
const name = newSplit[0];
const version = newSplit[1];
if (name != "" && version != undefined) {
return new NpmPackage(name, version, "");
}
else {
throw new Error(`No valid information, skipping dependency: ${newName}`);
}
}

private sortDependencyList(list: NpmPackage[]): NpmPackage[] {
return list.sort((a, b) => {
if (a.Name > b.Name) { return 1; }
if (a.Name < b.Name) { return -1; }
return 0;
});
}

private isRegularVersion(version: string): boolean {
if (version.includes("^")) {
return false;
}
if (version.includes(">=")) {
return false;
}
if (version.includes("<=")) {
return false;
}
if (version.includes("~")) {
return false;
}
if (version.includes("<")) {
return false;
}
if (version.includes(">")) {
return false;
}
return true;
}

private parseNpmList(output: string) {
let dependencyList: NpmPackage[] = [];
let results = output.split("\n");

results.forEach((dep, index) => {
if (index == 0) {
console.debug("Skipping first line");
} else {
let splitParts = dep.trim().split(" ");

if (splitParts[splitParts.length - 1] === "deduped") {
console.debug("Skipping");
} else {
try {
dependencyList.push(this.setAndReturnNpmPackage(splitParts));
}
catch (e) {
console.debug(e.stderr);
}
}
}
});

return this.sortDependencyList(dependencyList);
}

private removeScopeSymbolFromName(name: string): string {
if (name.substr(0, 1) === "@") {
return "%40" + name.substr(1, name.length);
} else {
return name;
}
}

private flattenAndUniqDependencies(
npmShrinkwrapContents: any
): Array<NpmPackage> {
private flattenAndUniqDependencies(npmShrinkwrapContents: any): Array<NpmPackage> {
console.debug("flattenAndUniqDependencies");
//first level in npm-shrinkwrap is our project package, we go a level deeper not to include it in the results
// TODO: handle case where npmShrinkwrapContents does not have a 'dependencies' element defined (eg: simple projects)
Expand Down

0 comments on commit 1e8aa37

Please sign in to comment.