1 | ;
|
2 |
|
3 | var resolvePackagePath = require('./resolve-package-path');
|
4 | var cacheKey = require('./cache-key');
|
5 | var tryRequire = require('./try-require');
|
6 | var crypto = require('crypto');
|
7 |
|
8 | /*
|
9 | * Entries in 'MODULE_ENTRY' cache. The idea is to minimize the information needed for
|
10 | * each entry (don't store the entire package.json object), but keep
|
11 | * information around for all parts of computing a hash for the dependency
|
12 | * tree of the original module named by 'name' and 'dir' in hashForDep.
|
13 | * The ModuleEntry is created at the time the package.json file is
|
14 | * read in (so that's only done once), but some properties may not be initialized
|
15 | * until later (this then supports creating cycles in the dependencies list,
|
16 | * which is supported by Node).
|
17 | */
|
18 | module.exports = ModuleEntry;
|
19 |
|
20 | function ModuleEntry(name, version, rootDir, sourceHash) {
|
21 |
|
22 | // The name of the module, from its package.json file.
|
23 | this.name = name;
|
24 |
|
25 | // The version of the module, from its package.json file.
|
26 | this.version = version;
|
27 |
|
28 | // The resolved real file path for the directory containing the package.json file.
|
29 | // The string has no trailing path separator(s), as path.resolve() removes them.
|
30 | this.rootDir = rootDir;
|
31 |
|
32 | // The computed hash for only the module's source files (under
|
33 | // the root dir, but specifically NOT any node_modules dependencies).
|
34 | // See locate() for initialization of this value.
|
35 | this._sourceHash = sourceHash;
|
36 |
|
37 | // The computed hash for the module's source files plus
|
38 | // the source hashes of the dependencies, recursively to the
|
39 | // leaves of the dependency tree.
|
40 | this._hash = null;
|
41 |
|
42 | // References to other ModuleEntry objects that correspond
|
43 | // to the dependencies listed in the package.json file.
|
44 | // See locate() for initialization of this value.
|
45 | this._dependencies = Object.create(null);
|
46 | }
|
47 |
|
48 | ModuleEntry.prototype.addDependency = function(dependency, moduleEntry) {
|
49 | this._dependencies[dependency] = moduleEntry;
|
50 | };
|
51 |
|
52 | /*
|
53 | * Starting from an initial ModuleEntry, traverse the entry's dependencies
|
54 | * ("almost" ModuleEntry objects) recursively to the leaves of the dependency
|
55 | * tree. Return an object containing all the ModuleEntry objects,
|
56 | * keyed by each entry's rootDir (real path to the module's root directory.)
|
57 | * NOTE: for speed, the initial caller of _gatherDependencies must pass
|
58 | * in a result object, so we aren't constantly checking that it is set.
|
59 | */
|
60 | ModuleEntry.prototype._gatherDependencies = function(dependencies) {
|
61 |
|
62 | var moduleEntry = this;
|
63 |
|
64 | if (dependencies[moduleEntry.rootDir] !== undefined) {
|
65 | // we already hit this path during the dependencies somewhere earlier
|
66 | // in the dependency tree, so avoid cycling.
|
67 | return;
|
68 | }
|
69 |
|
70 | dependencies[moduleEntry.rootDir] = moduleEntry;
|
71 |
|
72 | Object.keys(moduleEntry._dependencies).forEach(function(dep) {
|
73 | moduleEntry._dependencies[dep]._gatherDependencies(dependencies);
|
74 | });
|
75 |
|
76 | return dependencies;
|
77 | };
|
78 |
|
79 | /*
|
80 | * Compute/return the 'hash' field for the given moduleEntry.
|
81 | * The hash actually consists only of the _sourceHash values of all the
|
82 | * dependencies (ModuleEntry objects) concatenated with '\x00' between
|
83 | * entries. The '_sourceHash' values are computed at the time the
|
84 | * ModuleEntry is created, so don't need to be done again.
|
85 | */
|
86 | ModuleEntry.prototype.getHash = function(heimdallNode) {
|
87 | if (this._hash !== null) {
|
88 | return this._hash;
|
89 | }
|
90 |
|
91 | // We haven't computed the hash for this entry yet, though the
|
92 | // ModuleEntry entries are all present. It is possible that we
|
93 | // have the hash for some dependencies already.
|
94 |
|
95 | // Compute the full transitive dependency list. There are no duplicates,
|
96 | // because we check during _gatherDependencies before inserting in the result.
|
97 | // The keys of the returned object are the package.json baseDir values (the
|
98 | // moduleEntry.rootDir). The values are the moduleEntry objects.
|
99 | var dependencies = this._gatherDependencies(Object.create(null));
|
100 |
|
101 | // For repeatability between runs, sort the dependencies list keys
|
102 | // (rootDir values) before assembling into a final hash value.
|
103 | var dependencyRootDirs = Object.keys(dependencies).sort();
|
104 |
|
105 | if (heimdallNode) {
|
106 | heimdallNode.stats.paths += dependencyRootDirs.length;
|
107 | }
|
108 |
|
109 | var sourceHashes = dependencyRootDirs.map(function(rootDir) {
|
110 | return dependencies[rootDir]._sourceHash;
|
111 | }).join('\x00');
|
112 |
|
113 | var hash = crypto.createHash('sha1').update(sourceHashes).digest('hex');
|
114 |
|
115 | return (this._hash = hash);
|
116 | };
|
117 |
|
118 |
|
119 | /*
|
120 | * Compute and return the ModuleEntry for a given name/dir pair. This is done
|
121 | * recursively so a whole tree can be computed all at once, independent of the
|
122 | * hashing functions. We also compute the '_sourceHash' value (i.e. the hash of
|
123 | * just the package source files, excluding the node_modules dependencies) at
|
124 | * the same time to make it easier to establish the 'hash' value (includes
|
125 | * dependency _sourceHash values) later.
|
126 | * Note this is a class function, not an instance function.
|
127 | */
|
128 | ModuleEntry.locate = function(caches, name, dir, hashTreeFn) {
|
129 | var Constructor = this;
|
130 |
|
131 | var nameDirKey = cacheKey(name, dir);
|
132 | var realPathKey;
|
133 |
|
134 | // It's possible that for a given name/dir pair, there is no package.
|
135 | // Record a null entry in CACHES.PATH anyway so we don't need to
|
136 | // redo that search each time.
|
137 | if (caches.PATH.has(nameDirKey)) {
|
138 | realPathKey = caches.PATH.get(nameDirKey);
|
139 | if (realPathKey !== null) {
|
140 | return caches.MODULE_ENTRY.get(realPathKey);
|
141 | } else {
|
142 | return null;
|
143 | }
|
144 | }
|
145 |
|
146 | // There is no caches.PATH entry. Try to get a real package.json path. If there
|
147 | // isn't a real path, the name+dir reference is invalid, so note that in caches.PATH.
|
148 | // If there is a real path, check if it is already in the caches.MODULE_ENTRY.
|
149 | // If not, create it and insert it.
|
150 | var realPath = resolvePackagePath(caches, name, dir);
|
151 |
|
152 | if (realPath === null) {
|
153 | caches.PATH.set(nameDirKey, null);
|
154 | return null;
|
155 | }
|
156 |
|
157 | // We have a path to a file that supposedly is package.json. We need to be sure
|
158 | // that either we already have a caches.MODULE_ENTRY entry (in which case we can
|
159 | // just create a caches.PATH entry to point to it) or that we can read the
|
160 | // package.json file, at which point we can create a caches.MODULE_ENTRY entry,
|
161 | // then finally the new caches.PATH ENTRY.
|
162 |
|
163 | // Generate the cache key for a given real file path. This key is then
|
164 | // used as the key for entries in the CacheGroup.MODULE_ENTRY cache
|
165 | // and values in the CacheGroup.PATH cache.
|
166 | realPathKey = cacheKey('hashed:' + realPath, '');
|
167 |
|
168 | if (caches.MODULE_ENTRY.has(realPathKey)) {
|
169 | caches.PATH.set(nameDirKey, realPathKey);
|
170 | return caches.MODULE_ENTRY.get(realPathKey);
|
171 | }
|
172 |
|
173 | // Require the package. If we get a 'module-not-found' error it will return null
|
174 | // and we can insert an entry in the cache saying we couldn't find the package
|
175 | // in case it's requested later. Any other more serious error we specifically
|
176 | // don't try to catch here.
|
177 | var thePackage = tryRequire(realPath);
|
178 |
|
179 | // if the package was not found, do as above to create a caches.PATH entry
|
180 | // that refers to no path, so we don't waste time doing it again later.
|
181 | if (thePackage === null) {
|
182 | caches.PATH.set(nameDirKey, null);
|
183 | return thePackage;
|
184 | }
|
185 |
|
186 | // We have the package object and the relevant keys.
|
187 |
|
188 | // Compute the dir containing the package.json,
|
189 | var rootDir = realPath.slice(0, realPath.length - 13); // length('/package.json') === 13
|
190 |
|
191 | // Create and insert the new ModuleEntry into the cache.MODULE_ENTRY
|
192 | // now so we know to stop when dealing with cycles.
|
193 | var moduleEntry = new Constructor(thePackage.name, thePackage.version, rootDir, hashTreeFn(rootDir));
|
194 |
|
195 | caches.MODULE_ENTRY.set(realPathKey, moduleEntry);
|
196 | caches.PATH.set(nameDirKey, realPathKey);
|
197 |
|
198 | // compute the dependencies here so the ModuleEntry doesn't need to know
|
199 | // about caches or the hashTreeFn or the package and we don't need to
|
200 | // guard against accidental reinitialization of dependencies.
|
201 | if (thePackage.dependencies) {
|
202 | // Recursively locate the references to other moduleEntry objects for the module's
|
203 | // dependencies. Initially we just do the 'local' dependencies (i.e. the ones referenced
|
204 | // in the package.json's 'dependencies' field.) Later, moduleEntry._gatherDependencies
|
205 | // is called to compute the complete list including transitive dependencies, and that
|
206 | // is then used for the final moduleEntry._hash calculation.
|
207 | Object.keys(thePackage.dependencies).sort().forEach(function(dep) {
|
208 | var dependencyModuleEntry = Constructor.locate(caches, dep, rootDir, hashTreeFn);
|
209 |
|
210 | // there's not really a good reason to include a failed resolution
|
211 | // of a package in the dependencies list.
|
212 | if (dependencyModuleEntry !== null) {
|
213 | moduleEntry.addDependency(dep, dependencyModuleEntry);
|
214 | }
|
215 | });
|
216 | }
|
217 |
|
218 | return moduleEntry;
|
219 | };
|