index.js 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. var path = require('path'),
  2. fs = require('fs'),
  3. f = require('util').format,
  4. resolveFrom = require('resolve-from'),
  5. semver = require('semver');
  6. var exists = fs.existsSync || path.existsSync;
  7. // Find the location of a package.json file near or above the given location
  8. var find_package_json = function(location) {
  9. var found = false;
  10. while(!found) {
  11. if (exists(location + '/package.json')) {
  12. found = location;
  13. } else if (location !== '/') {
  14. location = path.dirname(location);
  15. } else {
  16. return false;
  17. }
  18. }
  19. return location;
  20. }
  21. // Find the package.json object of the module closest up the module call tree that contains name in that module's peerOptionalDependencies
  22. var find_package_json_with_name = function(name) {
  23. // Walk up the module call tree until we find a module containing name in its peerOptionalDependencies
  24. var currentModule = module;
  25. var found = false;
  26. while (currentModule) {
  27. // Check currentModule has a package.json
  28. location = currentModule.filename;
  29. var location = find_package_json(location)
  30. if (!location) {
  31. currentModule = currentModule.parent;
  32. continue;
  33. }
  34. // Read the package.json file
  35. var object = JSON.parse(fs.readFileSync(f('%s/package.json', location)));
  36. // Is the name defined by interal file references
  37. var parts = name.split(/\//);
  38. // Check whether this package.json contains peerOptionalDependencies containing the name we're searching for
  39. if (!object.peerOptionalDependencies || (object.peerOptionalDependencies && !object.peerOptionalDependencies[parts[0]])) {
  40. currentModule = currentModule.parent;
  41. continue;
  42. }
  43. found = true;
  44. break;
  45. }
  46. // Check whether name has been found in currentModule's peerOptionalDependencies
  47. if (!found) {
  48. throw new Error(f('no optional dependency [%s] defined in peerOptionalDependencies in any package.json', parts[0]));
  49. }
  50. return {
  51. object: object,
  52. parts: parts
  53. }
  54. }
  55. var require_optional = function(name, options) {
  56. options = options || {};
  57. options.strict = typeof options.strict == 'boolean' ? options.strict : true;
  58. var res = find_package_json_with_name(name)
  59. var object = res.object;
  60. var parts = res.parts;
  61. // Unpack the expected version
  62. var expectedVersions = object.peerOptionalDependencies[parts[0]];
  63. // The resolved package
  64. var moduleEntry = undefined;
  65. // Module file
  66. var moduleEntryFile = name;
  67. try {
  68. // Validate if it's possible to read the module
  69. moduleEntry = require(moduleEntryFile);
  70. } catch(err) {
  71. // Attempt to resolve in top level package
  72. try {
  73. // Get the module entry file
  74. moduleEntryFile = resolveFrom(process.cwd(), name);
  75. if(moduleEntryFile == null) return undefined;
  76. // Attempt to resolve the module
  77. moduleEntry = require(moduleEntryFile);
  78. } catch(err) {
  79. if(err.code === 'MODULE_NOT_FOUND') return undefined;
  80. }
  81. }
  82. // Resolve the location of the module's package.json file
  83. var location = find_package_json(require.resolve(moduleEntryFile));
  84. if(!location) {
  85. throw new Error('package.json can not be located');
  86. }
  87. // Read the module file
  88. var dependentOnModule = JSON.parse(fs.readFileSync(f('%s/package.json', location)));
  89. // Get the version
  90. var version = dependentOnModule.version;
  91. // Validate if the found module satisfies the version id
  92. if(semver.satisfies(version, expectedVersions) == false
  93. && options.strict) {
  94. var error = new Error(f('optional dependency [%s] found but version [%s] did not satisfy constraint [%s]', parts[0], version, expectedVersions));
  95. error.code = 'OPTIONAL_MODULE_NOT_FOUND';
  96. throw error;
  97. }
  98. // Satifies the module requirement
  99. return moduleEntry;
  100. }
  101. require_optional.exists = function(name) {
  102. try {
  103. var m = require_optional(name);
  104. if(m === undefined) return false;
  105. return true;
  106. } catch(err) {
  107. return false;
  108. }
  109. }
  110. module.exports = require_optional;