forked from anvaka/npmgraphbuilder
-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.js
138 lines (111 loc) · 3.51 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
var npa = require('npm-package-arg');
var guessVersion = require('./lib/guessVersion.js');
var Promise = require('bluebird');
module.exports = buildGraph;
module.exports.isRemote = isRemote;
function buildGraph(http, url) {
url = url || 'http://registry.npmjs.org/';
if (url[url.length - 1] !== '/') {
throw new Error('registry url is supposed to end with /');
}
var progress;
var cache = Object.create(null);
return {
createNpmDependenciesGraph: createNpmDependenciesGraph,
notifyProgress: function (cb) {
progress = cb;
}
};
function createNpmDependenciesGraph(packageName, graph, version) {
if (!packageName) throw new Error('Initial package name is required');
if (!graph) throw new Error('Graph data structure is required');
if (!version) version = 'latest';
var queue = [];
var processed = Object.create(null);
queue.push({
name: packageName,
version: version,
parent: null
});
return processQueue(graph);
function processQueue(graph) {
if (typeof progress === 'function') {
progress(queue.length);
}
var work = queue.pop();
var cached = cache[getCacheKey(work)];
if (cached) {
return new Promise(function(resolve) {
resolve(processRegistryResponse(cached));
});
}
if (isRemote(work.version)) {
// TODO: This will not download remote dependnecies (e.g. git-based)
return new Promise(function(resolve) {
resolve(processRegistryResponse({data: {}}));
});
}
var escapedName = npa(work.name).escapedName;
if (!escapedName) {
throw new Error('TODO: Escaped name is missing for ' + work.name);
}
return http(url + escapedName).then(processRegistryResponse);
function processRegistryResponse(res) {
cache[getCacheKey(work)] = res;
traverseDependencies(work, res.data);
if (queue.length) {
// continue building the graph
return processQueue(graph);
}
return graph;
}
}
function getCacheKey(work) {
var packageIsRemote = isRemote(work.version);
var cacheKey = work.name;
return packageIsRemote ? cacheKey + work.version : cacheKey
}
function traverseDependencies(work, packageJson) {
var version, pkg, id;
if (isRemote(work.version)) {
version = '';
pkg = packageJson;
id = work.version;
} else {
version = guessVersion(work.version, packageJson);
pkg = packageJson.versions[version];
id = pkg._id;
}
// TODO: here is a good place to address https://github.com/anvaka/npmgraph.an/issues/4
var dependencies = pkg.dependencies;
graph.beginUpdate();
graph.addNode(id, pkg);
if (work.parent && !graph.hasLink(work.parent, id)) {
graph.addLink(work.parent, id);
}
graph.endUpdate();
if (processed[id]) {
// no need to enqueue this package again - we already downladed it before
return;
}
processed[id] = true;
if (dependencies) {
Object.keys(dependencies).forEach(addToQueue);
}
function addToQueue(name) {
queue.push({
name: name,
version: dependencies[name],
parent: id
})
}
}
}
}
function isRemote(version) {
return typeof version === 'string' && (
(version.indexOf('git') === 0) ||
(version.indexOf('http') === 0) ||
(version.indexOf('file') === 0)
);
}