node-dep-resolver 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/bin/cli.js +55 -0
  2. package/package.json +39 -0
  3. package/src/base/candidate/CandidateClass.js +20 -0
  4. package/src/base/candidate/methods/generateCandidate.js +73 -0
  5. package/src/base/conflicts/ConflictClass.js +12 -0
  6. package/src/base/constraints/ConstraintClass.js +27 -0
  7. package/src/base/constraints/methods/addConstraint.js +69 -0
  8. package/src/base/dependencies/DependencyClass.js +10 -0
  9. package/src/base/graph/GraphClass.js +20 -0
  10. package/src/base/graph/method/buildGraph.js +80 -0
  11. package/src/base/graph/method/printGraph.js +33 -0
  12. package/src/base/registry/RegistryClass.js +33 -0
  13. package/src/base/registry/methods/getPackageMetadata.js +18 -0
  14. package/src/base/registry/methods/getPackageVersion.js +13 -0
  15. package/src/base/registry/methods/getVersions.js +5 -0
  16. package/src/base/registry/methods/resolveVersion.js +17 -0
  17. package/src/base/resolver/ResolverClass.js +87 -0
  18. package/src/base/resolver/methods/addConstraint.js +12 -0
  19. package/src/base/resolver/methods/buildInitialState.js +11 -0
  20. package/src/base/resolver/methods/checkConflict.js +20 -0
  21. package/src/base/resolver/methods/createSnapshot.js +12 -0
  22. package/src/base/resolver/methods/getFirstConflict.js +10 -0
  23. package/src/base/resolver/methods/getValidVersions.js +9 -0
  24. package/src/base/resolver/methods/resolve.js +13 -0
  25. package/src/base/resolver/methods/restoreSnapshot.js +10 -0
  26. package/src/base/resolver/methods/search.js +39 -0
  27. package/src/base/resolver/methods/selectPackage.js +19 -0
  28. package/src/index.js +19 -0
  29. package/src/rudimentary/candidates.js +121 -0
  30. package/src/rudimentary/canditate.js +121 -0
  31. package/src/rudimentary/constraint.js +73 -0
  32. package/src/rudimentary/graph.js +124 -0
  33. package/src/rudimentary/registry.js +70 -0
  34. package/src/rudimentary/resolver.js +361 -0
@@ -0,0 +1,10 @@
1
+ export default function getFirstConflict() {
2
+ for (const packageName of this.conflicts) {
3
+ return {
4
+ packageName,
5
+ constraints: this.constraints.get(packageName)
6
+ };
7
+ }
8
+
9
+ return null;
10
+ }
@@ -0,0 +1,9 @@
1
+ import semver from "semver";
2
+
3
+ export default async function getValidVersions(name, range) {
4
+ const versions = await this.registry.getVersions(name);
5
+
6
+ return versions
7
+ .filter(version => semver.satisfies(version, range))
8
+ .sort(semver.rcompare);
9
+ }
@@ -0,0 +1,13 @@
1
+ export default async function resolve() {
2
+ console.log("\nStarting resolution...\n");
3
+
4
+ await this.buildInitialState();
5
+
6
+ const result = await this.search();
7
+
8
+ if (!result) {
9
+ throw new Error("Could not find a valid dependency tree.");
10
+ }
11
+
12
+ return result;
13
+ }
@@ -0,0 +1,10 @@
1
+ export default function restoreSnapshot(snapshot) {
2
+ this.selected = new Map(snapshot.selected);
3
+ this.constraints = new Map(
4
+ [...snapshot.constraints].map(([name, values]) => [
5
+ name,
6
+ values.map(value => ({ ...value }))
7
+ ])
8
+ );
9
+ this.conflicts = new Set(snapshot.conflicts);
10
+ }
@@ -0,0 +1,39 @@
1
+ export default async function search() {
2
+ const conflict = this.getFirstConflict();
3
+
4
+ if (!conflict) {
5
+ console.log("\n[SUCCESS] Valid dependency tree found.");
6
+ return new Map(this.selected);
7
+ }
8
+
9
+ console.log(`\n[CONFLICT] ${conflict.packageName}`);
10
+
11
+ const candidates = await this.candidates.generateCandidates(conflict);
12
+
13
+ for (const candidate of candidates) {
14
+ console.log(`Trying ${candidate.packageName}@${candidate.newVersion}`);
15
+
16
+ const snapshot = this.createSnapshot();
17
+
18
+ try {
19
+ await this.selectPackage(
20
+ candidate.packageName,
21
+ candidate.newVersion,
22
+ "RESOLUTION"
23
+ );
24
+
25
+ const result = await this.search();
26
+
27
+ if (result) {
28
+ return result;
29
+ }
30
+ } catch (error) {
31
+ console.log(error.message);
32
+ }
33
+
34
+ this.restoreSnapshot(snapshot);
35
+ console.log("[BACKTRACKING] Backtracking...");
36
+ }
37
+
38
+ return null;
39
+ }
@@ -0,0 +1,19 @@
1
+ export default async function selectPackage(name, version, requestedBy) {
2
+ this.selected.set(name, {
3
+ name,
4
+ version,
5
+ requestedBy
6
+ });
7
+
8
+ const packageData = await this.registry.getPackageVersion(name, version);
9
+
10
+ const dependencies = packageData.dependencies || {};
11
+
12
+ for (const [dependencyName, dependencyRange] of Object.entries(dependencies)) {
13
+ this.addConstraint(
14
+ dependencyName,
15
+ dependencyRange,
16
+ `${name}@${version}`
17
+ );
18
+ }
19
+ }
package/src/index.js ADDED
@@ -0,0 +1,19 @@
1
+ import Resolver from "./base/resolver/ResolverClass.js";
2
+ import Registry from "./base/registry/RegistryClass.js";
3
+ import Graph from "./base/graph/GraphClass.js";
4
+ import Constraint from "./base/constraints/ConstraintClass.js";
5
+ import Candidates from "./base/candidate/CandidateClass.js";
6
+ import Dependency from "./base/dependencies/DependencyClass.js";
7
+ import Conflict from "./base/conflicts/ConflictClass.js";
8
+
9
+ export {
10
+ Resolver,
11
+ Registry,
12
+ Graph,
13
+ Constraint,
14
+ Candidates,
15
+ Dependency,
16
+ Conflict
17
+ };
18
+
19
+ export default Resolver;
@@ -0,0 +1,121 @@
1
+ import semver from "semver";
2
+ import {
3
+ getVersions,
4
+ getPackageVersion
5
+ } from "./registry.js";
6
+
7
+ export async function generateCandidates(
8
+ conflict
9
+ ) {
10
+ const candidates = [];
11
+
12
+ const packageName =
13
+ conflict.packageName;
14
+
15
+ for (
16
+ const requirement
17
+ of conflict.constraints
18
+ ) {
19
+
20
+ const requester =
21
+ requirement.requester;
22
+
23
+ const requesterParts =
24
+ requester.split("@");
25
+
26
+ const requesterName =
27
+ requesterParts[0];
28
+
29
+ const currentVersion =
30
+ requesterParts.slice(1).join("@");
31
+
32
+ console.log(
33
+ `\nLooking for alternatives for ${requester}`
34
+ );
35
+
36
+ const versions =
37
+ await getVersions(requesterName);
38
+
39
+ for (const version of versions) {
40
+
41
+ // Don't try the version we're already using
42
+ if (version === currentVersion) {
43
+ continue;
44
+ }
45
+
46
+ // Get this candidate's metadata
47
+ const packageData =
48
+ await getPackageVersion(
49
+ requesterName,
50
+ version
51
+ );
52
+
53
+ const dependencies =
54
+ packageData.dependencies || {};
55
+
56
+ // Does this version still depend on
57
+ // the conflicting package?
58
+ if (!dependencies[packageName]) {
59
+ candidates.push({
60
+ packageName: requesterName,
61
+ oldVersion: currentVersion,
62
+ newVersion: version,
63
+ reason: `No longer depends on ${packageName}`
64
+ });
65
+
66
+ continue;
67
+ }
68
+
69
+ const newRange =
70
+ dependencies[packageName];
71
+
72
+ // Does this new dependency range
73
+ // overlap with the other constraints?
74
+ let compatible = true;
75
+
76
+ for (
77
+ const other
78
+ of conflict.constraints
79
+ ) {
80
+
81
+ if (
82
+ other.requester === requester
83
+ ) {
84
+ continue;
85
+ }
86
+
87
+ if (
88
+ !semver.intersects(
89
+ newRange,
90
+ other.range
91
+ )
92
+ ) {
93
+ compatible = false;
94
+ break;
95
+ }
96
+ }
97
+
98
+ if (compatible) {
99
+ candidates.push({
100
+ packageName: requesterName,
101
+ oldVersion: currentVersion,
102
+ newVersion: version,
103
+ reason:
104
+ `${requesterName}@${version} requires ` +
105
+ `${packageName} ${newRange}`
106
+ });
107
+ }
108
+ }
109
+ }
110
+
111
+ // Prefer smaller version changes for now
112
+ candidates.sort((a, b) => {
113
+
114
+ return semver.compare(
115
+ a.newVersion,
116
+ a.oldVersion
117
+ );
118
+ });
119
+
120
+ return candidates;
121
+ }
@@ -0,0 +1,121 @@
1
+ import semver from "semver";
2
+ import {
3
+ getVersions,
4
+ getPackageVersion
5
+ } from "./registry.js";
6
+
7
+ export async function generateCandidates(
8
+ conflict
9
+ ) {
10
+ const candidates = [];
11
+
12
+ const packageName =
13
+ conflict.packageName;
14
+
15
+ for (
16
+ const requirement
17
+ of conflict.constraints
18
+ ) {
19
+
20
+ const requester =
21
+ requirement.requester;
22
+
23
+ const requesterParts =
24
+ requester.split("@");
25
+
26
+ const requesterName =
27
+ requesterParts[0];
28
+
29
+ const currentVersion =
30
+ requesterParts.slice(1).join("@");
31
+
32
+ console.log(
33
+ `\nLooking for alternatives for ${requester}`
34
+ );
35
+
36
+ const versions =
37
+ await getVersions(requesterName);
38
+
39
+ for (const version of versions) {
40
+
41
+ // Don't try the version we're already using
42
+ if (version === currentVersion) {
43
+ continue;
44
+ }
45
+
46
+ // Get this candidate's metadata
47
+ const packageData =
48
+ await getPackageVersion(
49
+ requesterName,
50
+ version
51
+ );
52
+
53
+ const dependencies =
54
+ packageData.dependencies || {};
55
+
56
+ // Does this version still depend on
57
+ // the conflicting package?
58
+ if (!dependencies[packageName]) {
59
+ candidates.push({
60
+ packageName: requesterName,
61
+ oldVersion: currentVersion,
62
+ newVersion: version,
63
+ reason: `No longer depends on ${packageName}`
64
+ });
65
+
66
+ continue;
67
+ }
68
+
69
+ const newRange =
70
+ dependencies[packageName];
71
+
72
+ // Does this new dependency range
73
+ // overlap with the other constraints?
74
+ let compatible = true;
75
+
76
+ for (
77
+ const other
78
+ of conflict.constraints
79
+ ) {
80
+
81
+ if (
82
+ other.requester === requester
83
+ ) {
84
+ continue;
85
+ }
86
+
87
+ if (
88
+ !semver.intersects(
89
+ newRange,
90
+ other.range
91
+ )
92
+ ) {
93
+ compatible = false;
94
+ break;
95
+ }
96
+ }
97
+
98
+ if (compatible) {
99
+ candidates.push({
100
+ packageName: requesterName,
101
+ oldVersion: currentVersion,
102
+ newVersion: version,
103
+ reason:
104
+ `${requesterName}@${version} requires ` +
105
+ `${packageName} ${newRange}`
106
+ });
107
+ }
108
+ }
109
+ }
110
+
111
+ // Prefer smaller version changes for now
112
+ candidates.sort((a, b) => {
113
+
114
+ return semver.compare(
115
+ a.newVersion,
116
+ a.oldVersion
117
+ );
118
+ });
119
+
120
+ return candidates;
121
+ }
@@ -0,0 +1,73 @@
1
+ import semver from "semver";
2
+
3
+ const constraints = new Map();
4
+
5
+ const conflicts = new Set();
6
+
7
+ export function addConstraint(
8
+ dependencyName,
9
+ dependencyRange,
10
+ requestedBy
11
+ ) {
12
+
13
+ if (!constraints.has(dependencyName)) {
14
+
15
+ constraints.set(
16
+ dependencyName,
17
+ {
18
+ constraints: [],
19
+ conflict: false
20
+ }
21
+ );
22
+ }
23
+
24
+ const state =
25
+ constraints.get(dependencyName);
26
+
27
+ // Avoid duplicate constraints
28
+ const alreadyExists =
29
+ state.constraints.some(
30
+ item =>
31
+ item.requester === requestedBy &&
32
+ item.range === dependencyRange
33
+ );
34
+
35
+ if (alreadyExists) {
36
+ return;
37
+ }
38
+
39
+ // Check against existing constraints
40
+ for (
41
+ const existing
42
+ of state.constraints
43
+ ) {
44
+
45
+ const compatible =
46
+ semver.intersects(
47
+ existing.range,
48
+ dependencyRange
49
+ );
50
+
51
+ if (!compatible) {
52
+
53
+ state.conflict = true;
54
+
55
+ conflicts.add(
56
+ dependencyName
57
+ );
58
+ }
59
+ }
60
+
61
+ state.constraints.push({
62
+ requester: requestedBy,
63
+ range: dependencyRange
64
+ });
65
+ }
66
+
67
+ export function getConstraints() {
68
+ return constraints;
69
+ }
70
+
71
+ export function getConflicts() {
72
+ return conflicts;
73
+ }
@@ -0,0 +1,124 @@
1
+ import {
2
+ resolveVersion,
3
+ getPackageVersion
4
+ } from "./registry.js";
5
+
6
+ import {
7
+ addConstraint,
8
+ getConstraints,
9
+ getConflicts
10
+ } from "./constraint.js";
11
+
12
+ export const buildGraph = async (rootDependencies) => {
13
+
14
+ const graph = new Map();
15
+
16
+ async function visit(
17
+ name,
18
+ range,
19
+ requestedBy
20
+ ) {
21
+
22
+ console.log(
23
+ `Resolving ${name} ${range}...`
24
+ );
25
+
26
+ // Record constraint immediately
27
+ addConstraint(
28
+ name,
29
+ range,
30
+ requestedBy
31
+ );
32
+
33
+ const version =
34
+ await resolveVersion(
35
+ name,
36
+ range
37
+ );
38
+
39
+ const key = `${name}@${version}`;
40
+
41
+ // Already processed?
42
+ if (graph.has(key)) {
43
+ return;
44
+ }
45
+
46
+ const packageData =
47
+ await getPackageVersion(
48
+ name,
49
+ version
50
+ );
51
+
52
+ const node = {
53
+ name,
54
+ version,
55
+ dependencies:
56
+ packageData.dependencies || {}
57
+ };
58
+
59
+ graph.set(key, node);
60
+
61
+ // Visit dependencies
62
+ for (
63
+ const [
64
+ dependencyName,
65
+ dependencyRange
66
+ ]
67
+ of Object.entries(node.dependencies)
68
+ ) {
69
+
70
+ await visit(
71
+ dependencyName,
72
+ dependencyRange,
73
+ `${name}@${version}`
74
+ );
75
+ }
76
+ }
77
+
78
+ // Root dependencies
79
+ for (
80
+ const [
81
+ name,
82
+ range
83
+ ]
84
+ of Object.entries(rootDependencies)
85
+ ) {
86
+
87
+ await visit(
88
+ name,
89
+ range,
90
+ "ROOT"
91
+ );
92
+ }
93
+
94
+ return graph;
95
+ }
96
+
97
+ export const printGraph = (graph) => {
98
+ for (const [key, node] of graph) {
99
+ console.log(key);
100
+ for (const [name, range] of Object.entries(node.dependencies)) {
101
+ console.log(` └── ${name} ${range}`);
102
+ }
103
+ console.log();
104
+ }
105
+
106
+ const constraints = getConstraints();
107
+ const conflicts = getConflicts();
108
+
109
+ if (constraints.size > 0) {
110
+ console.log("========== CONSTRAINTS & CONFLICTS ==========\n");
111
+ for (const [depName, state] of constraints) {
112
+ const status = state.conflict ? "CONFLICT" : "OK";
113
+ console.log(`${depName} [${status}]`);
114
+ for (const r of state.constraints) {
115
+ console.log(` └── requested ${r.range} by ${r.requester}`);
116
+ }
117
+ console.log();
118
+ }
119
+
120
+ if (conflicts.size > 0) {
121
+ console.log(`[WARNING] Conflicts detected in ${conflicts.size} package(s): ${Array.from(conflicts).join(", ")}\n`);
122
+ }
123
+ }
124
+ }
@@ -0,0 +1,70 @@
1
+ import axios from "axios";
2
+ import semver from "semver";
3
+ //done
4
+ const REGISTRY_URL = "https://registry.npmjs.org";
5
+ //done
6
+ const metadataCache = new Map();
7
+
8
+ export async function getPackageMetadata(name) {
9
+ if (metadataCache.has(name)) {
10
+ return metadataCache.get(name);
11
+ }
12
+
13
+ const url =
14
+ `${REGISTRY_URL}/${encodeURIComponent(name)}`;
15
+
16
+ const response = await axios.get(url);
17
+
18
+ metadataCache.set(name, response.data);
19
+
20
+ return response.data;
21
+ }
22
+
23
+ export async function resolveVersion(name, range) {
24
+ const metadata =
25
+ await getPackageMetadata(name);
26
+
27
+ const versions =
28
+ Object.keys(metadata.versions);
29
+
30
+ const validVersions =
31
+ versions.filter(version =>
32
+ semver.satisfies(version, range)
33
+ );
34
+
35
+ if (validVersions.length === 0) {
36
+ throw new Error(
37
+ `No version of ${name} satisfies ${range}`
38
+ );
39
+ }
40
+
41
+ validVersions.sort(semver.rcompare);
42
+
43
+ return validVersions[0];
44
+ }
45
+
46
+ export async function getPackageVersion(
47
+ name,
48
+ version
49
+ ) {
50
+ const metadata =
51
+ await getPackageMetadata(name);
52
+
53
+ const packageData =
54
+ metadata.versions[version];
55
+
56
+ if (!packageData) {
57
+ throw new Error(
58
+ `${name}@${version} does not exist`
59
+ );
60
+ }
61
+
62
+ return packageData;
63
+ }
64
+
65
+ export async function getVersions(name) {
66
+ const metadata =
67
+ await getPackageMetadata(name);
68
+
69
+ return Object.keys(metadata.versions);
70
+ }