context.js 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. var assert = require("assert");
  2. var path = require("path");
  3. var Q = require("q");
  4. var util = require("./util");
  5. var spawn = require("child_process").spawn;
  6. var ReadFileCache = require("./cache").ReadFileCache;
  7. var grepP = require("./grep");
  8. var glob = require("glob");
  9. var env = process.env;
  10. function BuildContext(options, readFileCache) {
  11. var self = this;
  12. assert.ok(self instanceof BuildContext);
  13. assert.ok(readFileCache instanceof ReadFileCache);
  14. if (options) {
  15. assert.strictEqual(typeof options, "object");
  16. } else {
  17. options = {};
  18. }
  19. Object.freeze(options);
  20. Object.defineProperties(self, {
  21. readFileCache: { value: readFileCache },
  22. config: { value: options.config },
  23. options: { value: options },
  24. optionsHash: { value: util.deepHash(options) }
  25. });
  26. }
  27. var BCp = BuildContext.prototype;
  28. BCp.makePromise = function(callback, context) {
  29. return util.makePromise(callback, context);
  30. };
  31. BCp.spawnP = function(command, args, kwargs) {
  32. args = args || [];
  33. kwargs = kwargs || {};
  34. var deferred = Q.defer();
  35. var outs = [];
  36. var errs = [];
  37. var options = {
  38. stdio: "pipe",
  39. env: env
  40. };
  41. if (kwargs.cwd) {
  42. options.cwd = kwargs.cwd;
  43. }
  44. var child = spawn(command, args, options);
  45. child.stdout.on("data", function(data) {
  46. outs.push(data);
  47. });
  48. child.stderr.on("data", function(data) {
  49. errs.push(data);
  50. });
  51. child.on("close", function(code) {
  52. if (errs.length > 0 || code !== 0) {
  53. var err = {
  54. code: code,
  55. text: errs.join("")
  56. };
  57. }
  58. deferred.resolve([err, outs.join("")]);
  59. });
  60. var stdin = kwargs && kwargs.stdin;
  61. if (stdin) {
  62. child.stdin.end(stdin);
  63. }
  64. return deferred.promise;
  65. };
  66. BCp.setIgnoreDependencies = function(value) {
  67. Object.defineProperty(this, "ignoreDependencies", {
  68. value: !!value
  69. });
  70. };
  71. // This default can be overridden by individual BuildContext instances.
  72. BCp.setIgnoreDependencies(false);
  73. BCp.setRelativize = function(value) {
  74. Object.defineProperty(this, "relativize", {
  75. value: !!value
  76. });
  77. };
  78. // This default can be overridden by individual BuildContext instances.
  79. BCp.setRelativize(false);
  80. BCp.setUseProvidesModule = function(value) {
  81. Object.defineProperty(this, "useProvidesModule", {
  82. value: !!value
  83. });
  84. };
  85. // This default can be overridden by individual BuildContext instances.
  86. BCp.setUseProvidesModule(false);
  87. BCp.setCacheDirectory = function(dir) {
  88. if (!dir) {
  89. // Disable the cache directory.
  90. } else {
  91. assert.strictEqual(typeof dir, "string");
  92. }
  93. Object.defineProperty(this, "cacheDir", {
  94. value: dir || null
  95. });
  96. };
  97. // This default can be overridden by individual BuildContext instances.
  98. BCp.setCacheDirectory(null);
  99. function PreferredFileExtension(ext) {
  100. assert.strictEqual(typeof ext, "string");
  101. assert.ok(this instanceof PreferredFileExtension);
  102. Object.defineProperty(this, "extension", {
  103. value: ext.toLowerCase()
  104. });
  105. }
  106. var PFEp = PreferredFileExtension.prototype;
  107. PFEp.check = function(file) {
  108. return file.split(".").pop().toLowerCase() === this.extension;
  109. };
  110. PFEp.trim = function(file) {
  111. if (this.check(file)) {
  112. var len = file.length;
  113. var extLen = 1 + this.extension.length;
  114. file = file.slice(0, len - extLen);
  115. }
  116. return file;
  117. };
  118. PFEp.glob = function() {
  119. return "**/*." + this.extension;
  120. };
  121. exports.PreferredFileExtension = PreferredFileExtension;
  122. BCp.setPreferredFileExtension = function(pfe) {
  123. assert.ok(pfe instanceof PreferredFileExtension);
  124. Object.defineProperty(this, "preferredFileExtension", { value: pfe });
  125. };
  126. BCp.setPreferredFileExtension(new PreferredFileExtension("js"));
  127. BCp.expandIdsOrGlobsP = function(idsOrGlobs) {
  128. var context = this;
  129. return Q.all(
  130. idsOrGlobs.map(this.expandSingleIdOrGlobP, this)
  131. ).then(function(listOfListsOfIDs) {
  132. var result = [];
  133. var seen = {};
  134. util.flatten(listOfListsOfIDs).forEach(function(id) {
  135. if (!seen.hasOwnProperty(id)) {
  136. seen[id] = true;
  137. if (util.isValidModuleId(id))
  138. result.push(id);
  139. }
  140. });
  141. return result;
  142. });
  143. };
  144. BCp.expandSingleIdOrGlobP = function(idOrGlob) {
  145. var context = this;
  146. return util.makePromise(function(callback) {
  147. // If idOrGlob already looks like an acceptable identifier, don't
  148. // try to expand it.
  149. if (util.isValidModuleId(idOrGlob)) {
  150. callback(null, [idOrGlob]);
  151. return;
  152. }
  153. glob(idOrGlob, {
  154. cwd: context.readFileCache.sourceDir
  155. }, function(err, files) {
  156. if (err) {
  157. callback(err);
  158. } else {
  159. callback(null, files.filter(function(file) {
  160. return !context.isHiddenFile(file);
  161. }).map(function(file) {
  162. return context.preferredFileExtension.trim(file);
  163. }));
  164. }
  165. });
  166. });
  167. };
  168. BCp.readModuleP = function(id) {
  169. return this.readFileCache.readFileP(
  170. id + "." + this.preferredFileExtension.extension
  171. );
  172. };
  173. BCp.readFileP = function(file) {
  174. return this.readFileCache.readFileP(file);
  175. };
  176. // Text editors such as VIM and Emacs often create temporary swap files
  177. // that should be ignored.
  178. var hiddenExp = /^\.|~$/;
  179. BCp.isHiddenFile = function(file) {
  180. return hiddenExp.test(path.basename(file));
  181. };
  182. BCp.getProvidedP = util.cachedMethod(function() {
  183. var context = this;
  184. var pattern = "@providesModule\\s+\\S+";
  185. return grepP(
  186. pattern,
  187. context.readFileCache.sourceDir
  188. ).then(function(pathToMatch) {
  189. var idToPath = {};
  190. Object.keys(pathToMatch).sort().forEach(function(path) {
  191. if (context.isHiddenFile(path))
  192. return;
  193. var id = pathToMatch[path].split(/\s+/).pop();
  194. // If we're about to overwrite an existing module identifier,
  195. // make sure the corresponding path ends with the preferred
  196. // file extension. This allows @providesModule directives in
  197. // .coffee files, for example, but prevents .js~ temporary
  198. // files from taking precedence over actual .js files.
  199. if (!idToPath.hasOwnProperty(id) ||
  200. context.preferredFileExtension.check(path))
  201. idToPath[id] = path;
  202. });
  203. return idToPath;
  204. });
  205. });
  206. var providesExp = /@providesModule[ ]+(\S+)/;
  207. BCp.getProvidedId = function(source) {
  208. var match = providesExp.exec(source);
  209. return match && match[1];
  210. };
  211. exports.BuildContext = BuildContext;