Skip to content

Commit

Permalink
feat: add basic getPeerDependenciesRecursive
Browse files Browse the repository at this point in the history
  • Loading branch information
motea927 committed Feb 15, 2024
1 parent e0250bc commit b44c363
Show file tree
Hide file tree
Showing 2 changed files with 106 additions and 9 deletions.
41 changes: 34 additions & 7 deletions src/peerDependencies.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ import path from "node:path";
import { fileURLToPath } from "node:url";
import { describe, it, expect } from "vitest";
import fse from "fs-extra";
import { getPeerDependencies } from "./peerDependencies";
import {
getPeerDependencies,
getPeerDependenciesRecursive,
} from "./peerDependencies";

const { writeJsonSync } = fse;

Expand All @@ -28,11 +31,9 @@ describe("getPeerDependencies", () => {
writeJsonSync(path.join(tempDir, "package.json"), packageJSON);
const result = getPeerDependencies(tempDir, true);
fse.removeSync(tempDir);
expect(result).toStrictEqual({
...peerDependencies,
...devDependencies,
...dependencies,
});
expect(result.dependencies).toStrictEqual(dependencies);
expect(result.devDependencies).toStrictEqual(devDependencies);
expect(result.peerDependencies).toStrictEqual(peerDependencies);
});

it("if is not root, should return peerDependencies", () => {
Expand All @@ -56,6 +57,32 @@ describe("getPeerDependencies", () => {
writeJsonSync(path.join(tempDir, "package.json"), packageJSON);
const result = getPeerDependencies(tempDir, false);
fse.removeSync(tempDir);
expect(result).toStrictEqual(peerDependencies);
expect(result.peerDependencies).toStrictEqual(peerDependencies);
expect(result.devDependencies).toBeUndefined();
expect(result.dependencies).toBeUndefined();
});
});

describe("getPeerDependenciesRecursive", () => {
it("should return all peer dependencies", () => {
const packageJSONDir = fileURLToPath(
new URL("../node_modules/vitest", import.meta.url),
);

const allPeerDependencies = getPeerDependenciesRecursive(packageJSONDir);
// from pnpm lock file
// '@edge-runtime/vm': '*'
// @types/node': ^18.0.0 || >=20.0.0
// '@vitest/browser': ^1.0.0
// '@vitest/ui': ^1.0.0
// happy-dom: '*'
// jsdom: '*'

expect(allPeerDependencies["@edge-runtime/vm"]).toBeDefined();
expect(allPeerDependencies["@types/node"]).toBeDefined();
expect(allPeerDependencies["@vitest/browser"]).toBeDefined();
expect(allPeerDependencies["@vitest/ui"]).toBeDefined();
expect(allPeerDependencies["happy-dom"]).toBeDefined();
expect(allPeerDependencies.jsdom).toBeDefined();
});
});
74 changes: 72 additions & 2 deletions src/peerDependencies.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,87 @@
import path from "node:path";
import fse from "fs-extra";
import resolve from "resolve";

const { readJsonSync } = fse;

export function getPeerDependencies(
packageJSONDir: string,
isRoot: boolean,
): Record<string, string> | undefined {
): {
peerDependencies: Record<string, string> | undefined;
devDependencies: Record<string, string> | undefined;
dependencies: Record<string, string> | undefined;
} {
const packageJSONPath = path.resolve(packageJSONDir, "package.json");
const packageJSON = readJsonSync(packageJSONPath);
const peerDependencies = packageJSON.peerDependencies;
const devDependencies = isRoot ? packageJSON.devDependencies : undefined;
const dependencies = isRoot ? packageJSON.dependencies : undefined;

return { ...peerDependencies, ...devDependencies, ...dependencies };
return { peerDependencies, devDependencies, dependencies };
}

export function getPeerDependenciesRecursive(
packageJSONDir: string,
allDeps: Record<string, string> = {},
visitedDirs = new Set<string>(),
): Record<string, string> {
const isRoot = visitedDirs.size === 0;

// If we've already visited this directory, return
if (visitedDirs.has(packageJSONDir)) {
return allDeps;
}
visitedDirs.add(packageJSONDir);

// devDependencies, dependencies, peerDependencies
// devDependencies, dependencies only exist in root package.json, otherwise undefined
const allDependencies = getPeerDependencies(packageJSONDir, isRoot);
if (Object.values(allDependencies).every((deps) => !deps)) {
return allDeps;
}

for (const dependencies of Object.values(allDependencies).filter(Boolean)) {
if (!dependencies) {
throw new Error("dependencies should not be undefined");
}

for (const [peerDependencyName, _peerDependencyVersion] of Object.entries(

Check warning on line 49 in src/peerDependencies.ts

View workflow job for this annotation

GitHub Actions / ci

'_peerDependencyVersion' is assigned a value but never used

Check warning on line 49 in src/peerDependencies.ts

View workflow job for this annotation

GitHub Actions / autofix

'_peerDependencyVersion' is assigned a value but never used
dependencies,
)) {
const resolvedSubDirPath = resolvePackageDir(
packageJSONDir,
peerDependencyName,
);

if (resolvedSubDirPath) {
getPeerDependenciesRecursive(resolvedSubDirPath, allDeps);
}
}
}

Object.assign(allDeps, allDependencies.peerDependencies);

return allDeps;
}

export function resolvePackageDir(basedir: string, packageName: string) {
let packagePath: string | undefined;

function packageFilter(pkg: Record<string, any>, pkgdir: string) {
if (!packagePath || pkg.version) {
packagePath = pkgdir;
}
return pkg;
}

try {
resolve.sync(packageName, { basedir, packageFilter });
} catch {
// resolve.sync throws if no main: is present
// Some packages (such as @types/*) do not have a main
// As long as we have a packagePath, it's fine
}

return packagePath;
}

0 comments on commit b44c363

Please sign in to comment.