Skip to content

Commit 9d70f89

Browse files
committed
feat: resolve full dependency tree
1 parent 465966d commit 9d70f89

File tree

2 files changed

+48
-9
lines changed

2 files changed

+48
-9
lines changed

src/domain/getPackageDependencies.ts

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
PackageVersion,
77
} from "./types";
88
import { maxSatisfying } from "semver";
9+
import axios from "axios";
910

1011
/**
1112
* A function which resolves the direct dependencies of a
@@ -25,7 +26,7 @@ export async function getPackageDependencies(
2526
packageName: PackageName,
2627
packageVersion: PackageVersion,
2728
getPackage: PackageGetter,
28-
): Promise<Record<PackageName, PackageVersion>> {
29+
): Promise<Record<PackageName, Package>> {
2930
const npmPackage: NPMPackage = await getPackage(packageName);
3031

3132
const packageForVersion = npmPackage.versions[packageVersion];
@@ -34,15 +35,48 @@ export async function getPackageDependencies(
3435
}
3536

3637
const dependenciesWithRange = packageForVersion?.dependencies ?? {};
37-
const dependencies: Record<PackageName, PackageVersion> = {};
38+
const dependencies: Record<string, Package> = {};
3839

3940
for (const [dependencyName, range] of Object.entries(dependenciesWithRange)) {
40-
const dependency: NPMPackage = await getPackage(dependencyName);
41-
42-
const maxSatisfyingVersion =
43-
maxSatisfying(Object.keys(dependency.versions), range) ?? range;
44-
dependencies[dependencyName] = maxSatisfyingVersion;
41+
dependencies[dependencyName] = await resolveDependenciesRecursively(
42+
dependencyName,
43+
range,
44+
);
4545
}
4646

4747
return dependencies;
4848
}
49+
50+
async function resolveDependenciesRecursively(
51+
name: string,
52+
range: string,
53+
): Promise<Package> {
54+
const npmPackage = await (
55+
await axios.get(`https://registry.npmjs.org/${name}`)
56+
).data;
57+
58+
const maxSatisfyingVersion = maxSatisfying(
59+
Object.keys(npmPackage.versions),
60+
range,
61+
) as string;
62+
63+
if (!npmPackage.versions[maxSatisfyingVersion]) {
64+
throw new Error();
65+
}
66+
67+
const dependencies: Record<string, Package> = {};
68+
for (const [name, range] of Object.entries(
69+
npmPackage.versions[maxSatisfyingVersion].dependencies ?? {},
70+
)) {
71+
// eslint-disable-next-line
72+
// @ts-ignore
73+
dependencies[name] = await resolveDependenciesRecursively(name, range);
74+
}
75+
76+
return { version: maxSatisfyingVersion, dependencies };
77+
}
78+
79+
interface Package {
80+
version: string;
81+
dependencies: Record<PackageName, Package>;
82+
}

tests/routes/getPackage.integration.test.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ describe("/package/:packageName/:packageVersion endpoint", () => {
3333
server.close();
3434
});
3535

36-
it("responds with list of dependencies", async () => {
36+
it("responds with a tree of dependencies", async () => {
3737
const packageName = "react";
3838
const version = "16.3.0";
3939
const address = `http://localhost:${port}/package/${packageName}/${version}`;
@@ -42,7 +42,12 @@ describe("/package/:packageName/:packageVersion endpoint", () => {
4242
const expectedResponse = {
4343
name: "react",
4444
version: "16.3.0",
45-
dependencies: { "loose-envify": "1.1.0" },
45+
dependencies: {
46+
"loose-envify": {
47+
version: "1.1.0",
48+
dependencies: { "js-tokens": { version: "1.0.3", dependencies: {} } },
49+
},
50+
},
4651
};
4752

4853
expect(response.data).toEqual(expectedResponse);

0 commit comments

Comments
 (0)