Skip to content

Commit

Permalink
Check for duplicate packages in PNPM lockfile (#75)
Browse files Browse the repository at this point in the history
* Create check-pnpm-duplicates CLI command

* Add missing changeset
  • Loading branch information
mbeckem authored Oct 21, 2024
1 parent b3a815c commit 235ed72
Show file tree
Hide file tree
Showing 263 changed files with 24,365 additions and 4 deletions.
5 changes: 5 additions & 0 deletions .changeset/wise-hats-sit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@open-pioneer/check-pnpm-duplicates": minor
---

Initial release.
3 changes: 3 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,6 @@ trim_trailing_whitespace = false

[*.yml]
indent_size = 2

[*.yaml]
indent_size = 2
2 changes: 1 addition & 1 deletion .prettierrc
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"printWidth": 100,
"overrides": [
{
"files": "**/*.yml",
"files": ["**/*.yaml", "**/*.yml"],
"options": {
"tabWidth": 2
}
Expand Down
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "cli-tests",
"name": "build-package-cli-tests",
"private": true,
"devDependencies": {
"@chakra-ui/icons": "2.1.1",
Expand Down
File renamed without changes.
File renamed without changes.
7 changes: 7 additions & 0 deletions integration-tests/check-pnpm-duplicates-tests/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# cli-tests

Tests in this package use the `check-pnpm-duplicates-tests` CLI to check for duplicates in a lockfile.

Before running the tests in this directory, make sure that the other packages have been built first,
e.g. by running `pnpm build` or `pnpm dev` and that the CLI is locally installed (`pnpm install`).
Otherwise, the tests will test old code or fail.
159 changes: 159 additions & 0 deletions integration-tests/check-pnpm-duplicates-tests/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
// SPDX-FileCopyrightText: 2023 Open Pioneer project (https://github.com/open-pioneer)
// SPDX-License-Identifier: Apache-2.0
import { resolve } from "node:path";
import { it } from "vitest";
import { TEST_DATA_DIR } from "./paths";
import { runCli } from "./runCli";
import { expect } from "vitest";

const LOCKFILE_DIR = resolve(TEST_DATA_DIR, "simple-dups-lockfile");

it("reports all duplicates by default", async () => {
const result = await runCli(LOCKFILE_DIR, undefined);
expect(result).toMatchInlineSnapshot(`
{
"exitCode": 1,
"output": "Found unexpected duplicate packages:
- "@changesets/types" # (versions 4.1.0, 6.0.0)
- "@pnpm/dependency-path" # (versions 5.1.3, 5.1.6)
- "@pnpm/resolver-base" # (versions 13.0.1, 13.0.4)
- "@pnpm/types" # (versions 11.1.0, 12.2.0)
- "@rollup/pluginutils" # (versions 4.2.1, 5.1.0)
- "@types/node" # (versions 12.20.55, 18.19.41)
- "agent-base" # (versions 6.0.2, 7.1.1)
- "ansi-regex" # (versions 5.0.1, 6.0.1)
- "ansi-styles" # (versions 3.2.1, 4.3.0, 5.2.0, 6.2.1)
- "argparse" # (versions 1.0.10, 2.0.1)
- "aria-query" # (versions 5.1.3, 5.3.0)
- "brace-expansion" # (versions 1.1.11, 2.0.1)
- "chalk" # (versions 2.4.2, 3.0.0, 4.1.2, 5.3.0)
- "color-convert" # (versions 1.9.3, 2.0.1)
- "color-name" # (versions 1.1.3, 1.1.4, 2.0.0)
- "cross-spawn" # (versions 5.1.0, 7.0.3)
- "debug" # (versions 3.2.7, 4.3.5)
- "doctrine" # (versions 2.1.0, 3.0.0)
- "dom-accessibility-api" # (versions 0.5.16, 0.6.3)
- "emoji-regex" # (versions 10.3.0, 8.0.0, 9.2.2)
- "escape-string-regexp" # (versions 1.0.5, 4.0.0)
- "estree-walker" # (versions 2.0.2, 3.0.3)
- "execa" # (versions 5.1.1, 8.0.1)
- "extend-shallow" # (versions 2.0.1, 3.0.2)
- "find-up" # (versions 4.1.0, 5.0.0)
- "fs-extra" # (versions 11.2.0, 7.0.1, 8.1.0)
- "get-stream" # (versions 6.0.1, 8.0.1)
- "glob" # (versions 10.4.5, 7.2.3)
- "glob-parent" # (versions 5.1.2, 6.0.2)
- "globals" # (versions 11.12.0, 13.24.0)
- "has-flag" # (versions 3.0.0, 4.0.0)
- "http-proxy-agent" # (versions 5.0.0, 7.0.2)
- "https-proxy-agent" # (versions 5.0.1, 7.0.5)
- "human-signals" # (versions 2.1.0, 5.0.0)
- "iconv-lite" # (versions 0.4.24, 0.6.3)
- "is-extendable" # (versions 0.1.1, 1.0.1)
- "is-fullwidth-code-point" # (versions 3.0.0, 4.0.0, 5.0.0)
- "is-stream" # (versions 2.0.1, 3.0.0)
- "js-yaml" # (versions 3.14.1, 4.1.0)
- "jsonfile" # (versions 4.0.0, 6.1.0)
- "locate-path" # (versions 5.0.0, 6.0.0)
- "lru-cache" # (versions 10.4.3, 4.1.5, 6.0.0, 7.10.1)
- "mimic-fn" # (versions 2.1.0, 4.0.0)
- "minimatch" # (versions 3.1.2, 9.0.5)
- "ms" # (versions 2.1.2, 2.1.3)
- "npm-run-path" # (versions 4.0.1, 5.3.0)
- "onetime" # (versions 5.1.2, 6.0.0)
- "p-limit" # (versions 2.3.0, 3.1.0)
- "p-locate" # (versions 4.1.0, 5.0.0)
- "path-key" # (versions 3.1.1, 4.0.0)
- "pify" # (versions 2.3.0, 4.0.1)
- "prettier" # (versions 2.8.8, 3.3.3)
- "react-is" # (versions 16.13.1, 17.0.2)
- "regenerator-runtime" # (versions 0.13.11, 0.14.1)
- "resolve" # (versions 1.22.8, 2.0.0-next.5)
- "resolve-from" # (versions 4.0.0, 5.0.0)
- "rimraf" # (versions 3.0.2, 5.0.9)
- "rrweb-cssom" # (versions 0.6.0, 0.7.1)
- "safe-execa" # (versions 0.1.2, 0.1.4)
- "shebang-command" # (versions 1.2.0, 2.0.0)
- "shebang-regex" # (versions 1.0.0, 3.0.0)
- "signal-exit" # (versions 3.0.7, 4.1.0)
- "slice-ansi" # (versions 5.0.0, 7.1.0)
- "source-map" # (versions 0.5.6, 0.5.7, 0.6.1)
- "sprintf-js" # (versions 1.0.3, 1.1.3)
- "string-width" # (versions 4.2.3, 5.1.2, 7.2.0)
- "strip-ansi" # (versions 6.0.1, 7.1.0)
- "strip-bom" # (versions 3.0.0, 4.0.0)
- "strip-final-newline" # (versions 2.0.0, 3.0.0)
- "stylis" # (versions 4.2.0, 4.3.2)
- "supports-color" # (versions 5.5.0, 7.2.0)
- "tslib" # (versions 2.4.0, 2.7.0)
- "universalify" # (versions 0.1.2, 0.2.0, 2.0.1)
- "whatwg-mimetype" # (versions 3.0.0, 4.0.0)
- "which" # (versions 1.3.1, 2.0.2)
- "wrap-ansi" # (versions 7.0.0, 8.1.0, 9.0.0)
- "yallist" # (versions 2.1.2, 4.0.0)
- "yaml" # (versions 1.10.2, 2.4.5)
To resolve these issues, consider taking one of the following steps:
- Run 'pnpm dedupe'
- Investigate why the package is duplicated (try running 'pnpm why -r <package>') and try to resolve the duplication.
- If the duplication is not a problem, add the package to the allowed list in the configuration file.
",
}
`);
});

it("supports filtering dev dependencies", async () => {
const result = await runCli(LOCKFILE_DIR, resolve(LOCKFILE_DIR, "only-prod-dups.yaml"));
expect(result).toMatchInlineSnapshot(`
{
"exitCode": 1,
"output": "Found unexpected duplicate packages:
- "ansi-styles" # (versions 3.2.1, 4.3.0, 5.2.0)
- "chalk" # (versions 2.4.2, 4.1.2)
- "color-convert" # (versions 1.9.3, 2.0.1)
- "color-name" # (versions 1.1.3, 1.1.4, 2.0.0)
- "escape-string-regexp" # (versions 1.0.5, 4.0.0)
- "extend-shallow" # (versions 2.0.1, 3.0.2)
- "has-flag" # (versions 3.0.0, 4.0.0)
- "is-extendable" # (versions 0.1.1, 1.0.1)
- "react-is" # (versions 16.13.1, 17.0.2)
- "regenerator-runtime" # (versions 0.13.11, 0.14.1)
- "source-map" # (versions 0.5.6, 0.5.7, 0.6.1)
- "stylis" # (versions 4.2.0, 4.3.2)
- "supports-color" # (versions 5.5.0, 7.2.0)
- "tslib" # (versions 2.4.0, 2.7.0)
To resolve these issues, consider taking one of the following steps:
- Run 'pnpm dedupe'
- Investigate why the package is duplicated (try running 'pnpm why -r <package>') and try to resolve the duplication.
- If the duplication is not a problem, add the package to the allowed list in the configuration file.
",
}
`);
});

it("supports allowing duplicates", async () => {
const result = await runCli(LOCKFILE_DIR, resolve(LOCKFILE_DIR, "allow-dups.yaml"));
expect(result).toMatchInlineSnapshot(`
{
"exitCode": 0,
"output": "No unexpected duplicate packages found.
",
}
`);
});

it("warns when rules are redundant", async () => {
const result = await runCli(LOCKFILE_DIR, resolve(LOCKFILE_DIR, "redundant-rules.yaml"));
expect(result).toMatchInlineSnapshot(`
{
"exitCode": 0,
"output": "No unexpected duplicate packages found.
The following rules did not match any packages. They can be removed from the configuration file:
- not_needed1
- not_needed2
",
}
`);
});
11 changes: 11 additions & 0 deletions integration-tests/check-pnpm-duplicates-tests/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"name": "check-pnpm-duplicates-tests",
"private": true,
"scripts": {
"test": "vitest"
},
"devDependencies": {
"@open-pioneer/check-pnpm-duplicates": "workspace:^",
"zx": "^8.1.9"
}
}
13 changes: 13 additions & 0 deletions integration-tests/check-pnpm-duplicates-tests/paths.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// SPDX-FileCopyrightText: 2023 Open Pioneer project (https://github.com/open-pioneer)
// SPDX-License-Identifier: Apache-2.0
import { existsSync } from "node:fs";
import { resolve } from "node:path";
import { fileURLToPath } from "node:url";

export const PACKAGE_DIR = resolve(fileURLToPath(import.meta.url), "..");
export const TEST_DATA_DIR = resolve(PACKAGE_DIR, "test-data");

const PACKAGE_JSON_FILE = resolve(PACKAGE_DIR, "package.json");
if (!existsSync(PACKAGE_JSON_FILE)) {
throw new Error(`No package.json in current directory. Fix path.`);
}
31 changes: 31 additions & 0 deletions integration-tests/check-pnpm-duplicates-tests/runCli.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// SPDX-FileCopyrightText: 2023 Open Pioneer project (https://github.com/open-pioneer)
// SPDX-License-Identifier: Apache-2.0
import { resolve } from "node:path";
import { $, ProcessOutput } from "zx";
import { PACKAGE_DIR } from "./paths";

export interface RunResult {
exitCode: number;
output: string;
}

export async function runCli(workdir: string, configPath: string | undefined): Promise<RunResult> {
const cli = resolve(PACKAGE_DIR, "node_modules/.bin/check-pnpm-duplicates");
const flags: string[] = ["--debug"];
if (configPath) {
flags.push("-c", configPath);
}

try {
const result = await $({ cwd: workdir })`${cli} ${flags}`.quiet();
return { exitCode: 0, output: result.toString() };
} catch (e) {
if (e instanceof ProcessOutput) {
return {
exitCode: e.exitCode ?? -1,
output: e.toString()
};
}
throw new Error("unexpected error from CLI");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# This is the configuration file for the check-pnpm-duplicates CLI.
# See <https://www.npmjs.com/package/@open-pioneer/check-pnpm-duplicates> for more details.

# Ignore any duplicates in devDependencies.
skipDevDependencies: true

# List of packages that are allowed to have duplicates.
#
# You can list packages here that do not cause issues when they are present multiple times in your project.
# You SHOULD NOT list central dependencies here, such as react or any trails packages.
allowed:
- "ansi-styles"
- "chalk"
- "color-convert"
- "color-name"
- "escape-string-regexp"
- "extend-shallow"
- "has-flag"
- "is-extendable"
- "react-is"
- "regenerator-runtime"
- "source-map"
- "stylis"
- "supports-color"
- "tslib"
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# This is the configuration file for the check-pnpm-duplicates CLI.
# See <https://www.npmjs.com/package/@open-pioneer/check-pnpm-duplicates> for more details.

# Ignore any duplicates in devDependencies.
skipDevDependencies: true
Loading

0 comments on commit 235ed72

Please sign in to comment.