Skip to content

Commit

Permalink
refactor: changed npm ls to arborist actual, updated tests, added --p…
Browse files Browse the repository at this point in the history
…roduction snapshot tests
  • Loading branch information
Lukasz-pluszczewski committed Dec 26, 2024
1 parent 890df38 commit dbcefd1
Show file tree
Hide file tree
Showing 10 changed files with 230 additions and 170 deletions.
3 changes: 2 additions & 1 deletion biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
},
"files": {
"ignoreUnknown": false,
"ignore": ["dist", "build"]
"ignore": ["dist", "build"],
"maxSize": 1000000000000
},
"formatter": {
"enabled": true,
Expand Down
4 changes: 1 addition & 3 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,7 @@
"types": "./dist/types/index.d.ts"
}
},
"files": [
"dist"
],
"files": ["dist"],
"scripts": {
"build": "tsup-node",
"dev": "tsc --watch",
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/dependency-finder/find-dependencies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ function findExternalDependencies({
switch (packageManager) {
case "npm":
case "yarn":
return findNpmDependencies(projectRoot, production, verbose);
return findNpmDependencies(projectRoot, production);
case "pnpm":
return findPnpmDependencies(projectRoot, production, verbose);
case "yarn-classic":
Expand Down
89 changes: 27 additions & 62 deletions packages/core/src/dependency-finder/npm.ts
Original file line number Diff line number Diff line change
@@ -1,73 +1,38 @@
import type { DependenciesResult } from "@license-auditor/data";
import { ExecCommandException } from "../exceptions/index.js";
import { execCommand } from "./exec-command.js";
import Arborist, { type Node } from "@npmcli/arborist";

export const findNpmDepsCommand = "npm ls --all -p";
export const findNpmProdDepsCommand = "npm ls --all -p --omit=dev";
const flattenDependenciesTree = (
tree: Node,
production?: boolean,
): string[] => {
const dependencies: string[] = [];
for (const [, node] of tree.children) {
if (!(production && node.dev)) {
const packagePath = node.path;
if (!packagePath) {
throw new Error("Package found by arborist is missing a path");
}
dependencies.push(packagePath);
}
dependencies.push(...flattenDependenciesTree(node, production));
}

return dependencies;
};

export async function findNpmDependencies(
projectRoot: string,
production?: boolean | undefined,
verbose?: boolean | undefined,
): Promise<DependenciesResult> {
const { output, warning } = await (async () => {
try {
return {
output: await execCommand(
production ? findNpmProdDepsCommand : findNpmDepsCommand,
projectRoot,
verbose,
),
};
} catch (error) {
if (error instanceof ExecCommandException) {
if (/missing:.+required by/.test(error.stderr)) {
return {
output: error.stdout,
warning: `Results incomplete because of an error. This is most likely caused by peer dependencies. Try turning legacy-peer-deps off and resolve peer dependency conflicts. Original error:\n${error.stderr}`,
};
}
if (/ELSPROBLEMS/.test(error.stderr)) {
throw new ExecCommandException(
[
"",
"Unable to resolve project dependencies.",
error.message,
"",
"Potential causes:",
" - Incompatible or inconsistent version specifications in package.json.",
" - Conflicts in peer dependencies.",
" - Cached or outdated data in node_modules or npm cache.",
"",
"Suggested actions:",
" 1. Inspect and resolve version conflicts in package.json.",
" 2. Check and address peer dependency conflicts.",
" 3. Clear the node_modules folder and reinstall dependencies with a clean state:",
" remove node_modules and run npm install",
" 4. Clear the npm cache to ensure no outdated or corrupted data is used:",
" npm cache clean --force",
].join("\n"),
{
stdout: error.stdout,
stderr: error.stderr,
originalError: error.originalError,
},
);
}
}
throw error;
}
})();

// Remove the first line, as npm always prints the project root first
const lines = output.split("\n").slice(1);
try {
const arborist = new Arborist({ path: projectRoot });
const tree = await arborist.loadActual();

const dependencies = lines
.filter((line) => line.trim() !== "")
.map((line) => line.trim());
const dependencies: string[] = flattenDependenciesTree(tree, production);

if (warning) {
return { dependencies, warning };
return { dependencies };
} catch (error) {
console.log("arborist error", error);
throw error;
}
return { dependencies };
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,16 @@ export function retrieveLicenseFromLicenseFileContent(content: string): {
// threshold selected empirically based on our tests
const foundLicense = licenseMap.get(detectedLicense.licenseId);
if (!foundLicense) {
throw new Error(`License detected but not found in license map: ${detectedLicense.licenseId}`);
throw new Error(
`License detected but not found in license map: ${detectedLicense.licenseId}`,
);
}

return {
licenses: addLicenseSource([LicenseSchema.parse(foundLicense)], LICENSE_SOURCE.licenseFileContent),
licenses: addLicenseSource(
[LicenseSchema.parse(foundLicense)],
LICENSE_SOURCE.licenseFileContent,
),
};
}

Expand Down
6 changes: 3 additions & 3 deletions test/test/npm.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ describe("license-auditor", () => {
});

expect(errorCode).toBe(0);
expect(output).toContain("Results incomplete because of an error.");
expect(output).toContain("170 licenses are compliant");
});

describe("parse license files", () => {
Expand Down Expand Up @@ -792,8 +792,8 @@ describe("license-auditor", () => {
cwd: testDirectory,
});

expect(errorCode).toBe(1);
expect(output).toContain("Unable to resolve project dependencies.");
expect(errorCode).toBe(0);
expect(output).toContain("7 licenses are compliant");
},
);
});
97 changes: 92 additions & 5 deletions test/test/snapshot.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { getCliPath } from "../utils/get-cli-path";
import { readJsonFile } from "../utils/read-json-file";
import { runCliCommand } from "../utils/run-cli-command";
import "../utils/path-serializer";
import { matchSnapshotRecursive } from '../utils/matchSnapshotRecursive';
import { matchSnapshotRecursive } from "../utils/match-snapshot-recursive";

describe("snapshot testing", () => {
monorepoFixture("monorepo project", async ({ testDirectory, expect }) => {
Expand All @@ -25,8 +25,29 @@ describe("snapshot testing", () => {
const jsonOutput = await readJsonFile(
path.join(testDirectory, "license-auditor.results.json"),
);
matchSnapshotRecursive('./__snapshots__/monorepo.json', jsonOutput, false);
matchSnapshotRecursive("./__snapshots__/monorepo.json", jsonOutput, false);
});
monorepoFixture(
"monorepo project production only",
async ({ testDirectory, expect }) => {
const { errorCode } = await runCliCommand({
command: "npx",
args: [getCliPath(), "--verbose", "--json", "--production"],
cwd: testDirectory,
});

expect(errorCode).toBe(0);

const jsonOutput = await readJsonFile(
path.join(testDirectory, "license-auditor.results.json"),
);
matchSnapshotRecursive(
"./__snapshots__/monorepo-production.json",
jsonOutput,
false,
);
},
);

pnpmFixture("pnpm project", async ({ testDirectory, expect }) => {
const { errorCode } = await runCliCommand({
Expand All @@ -40,9 +61,31 @@ describe("snapshot testing", () => {
const jsonOutput = await readJsonFile(
path.join(testDirectory, "license-auditor.results.json"),
);
matchSnapshotRecursive('./__snapshots__/pnpm.json', jsonOutput, false);
matchSnapshotRecursive("./__snapshots__/pnpm.json", jsonOutput, false);
});

pnpmFixture(
"pnpm project production only",
async ({ testDirectory, expect }) => {
const { errorCode } = await runCliCommand({
command: "npx",
args: [getCliPath(), "--verbose", "--json", "--production"],
cwd: testDirectory,
});

expect(errorCode).toBe(0);

const jsonOutput = await readJsonFile(
path.join(testDirectory, "license-auditor.results.json"),
);
matchSnapshotRecursive(
"./__snapshots__/pnpm-production.json",
jsonOutput,
false,
);
},
);

defaultTest("npm project", async ({ testDirectory, expect }) => {
const { errorCode } = await runCliCommand({
command: "npx",
Expand All @@ -55,9 +98,31 @@ describe("snapshot testing", () => {
const jsonOutput = await readJsonFile(
path.join(testDirectory, "license-auditor.results.json"),
);
matchSnapshotRecursive('./__snapshots__/npm.json', jsonOutput, false);
matchSnapshotRecursive("./__snapshots__/npm.json", jsonOutput, false);
});

defaultTest(
"npm project production only",
async ({ testDirectory, expect }) => {
const { errorCode } = await runCliCommand({
command: "npx",
args: [getCliPath(), "--verbose", "--json", "--production"],
cwd: testDirectory,
});

expect(errorCode).toBe(0);

const jsonOutput = await readJsonFile(
path.join(testDirectory, "license-auditor.results.json"),
);
matchSnapshotRecursive(
"./__snapshots__/npm-production.json",
jsonOutput,
false,
);
},
);

yarnFixture("yarn project", async ({ testDirectory, expect }) => {
const { errorCode } = await runCliCommand({
command: "npx",
Expand All @@ -70,6 +135,28 @@ describe("snapshot testing", () => {
const jsonOutput = await readJsonFile(
path.join(testDirectory, "license-auditor.results.json"),
);
matchSnapshotRecursive('./__snapshots__/yarn.json', jsonOutput, false);
matchSnapshotRecursive("./__snapshots__/yarn.json", jsonOutput, false);
});

yarnFixture(
"yarn project production only",
async ({ testDirectory, expect }) => {
const { errorCode } = await runCliCommand({
command: "npx",
args: [getCliPath(), "--verbose", "--json", "--production"],
cwd: testDirectory,
});

expect(errorCode).toBe(0);

const jsonOutput = await readJsonFile(
path.join(testDirectory, "license-auditor.results.json"),
);
matchSnapshotRecursive(
"./__snapshots__/yarn-production.json",
jsonOutput,
false,
);
},
);
});
Loading

0 comments on commit dbcefd1

Please sign in to comment.