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 39b2d1e
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 66 deletions.
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
86 changes: 24 additions & 62 deletions packages/core/src/dependency-finder/npm.ts
Original file line number Diff line number Diff line change
@@ -1,73 +1,35 @@
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) => {
const dependencies: string[] = [];
tree.children.forEach((node) => {
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 };
}
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");
},
);
});
61 changes: 61 additions & 0 deletions test/test/snapshot.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,20 @@ describe("snapshot testing", () => {
);
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 @@ -43,6 +57,21 @@ describe("snapshot testing", () => {
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 @@ -58,6 +87,23 @@ describe("snapshot testing", () => {
matchSnapshotRecursive('./__snapshots__/npm.json', jsonOutput, false);
});

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

console.log(output);

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 @@ -72,4 +118,19 @@ describe("snapshot testing", () => {
);
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);
});
});

0 comments on commit 39b2d1e

Please sign in to comment.