index.js 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. 'use strict';
  2. const {URL} = require('url');
  3. const {Agent: HttpAgent} = require('http');
  4. const {Agent: HttpsAgent} = require('https');
  5. const got = require('got');
  6. const registryUrl = require('registry-url');
  7. const registryAuthToken = require('registry-auth-token');
  8. const semver = require('semver');
  9. // These agent options are chosen to match the npm client defaults and help with performance
  10. // See: `npm config get maxsockets` and #50
  11. const agentOptions = {
  12. keepAlive: true,
  13. maxSockets: 50
  14. };
  15. const httpAgent = new HttpAgent(agentOptions);
  16. const httpsAgent = new HttpsAgent(agentOptions);
  17. class PackageNotFoundError extends Error {
  18. constructor(packageName) {
  19. super(`Package \`${packageName}\` could not be found`);
  20. this.name = 'PackageNotFoundError';
  21. }
  22. }
  23. class VersionNotFoundError extends Error {
  24. constructor(packageName, version) {
  25. super(`Version \`${version}\` for package \`${packageName}\` could not be found`);
  26. this.name = 'VersionNotFoundError';
  27. }
  28. }
  29. const packageJson = async (packageName, options) => {
  30. options = {
  31. version: 'latest',
  32. ...options
  33. };
  34. const scope = packageName.split('/')[0];
  35. const registryUrl_ = options.registryUrl || registryUrl(scope);
  36. const packageUrl = new URL(encodeURIComponent(packageName).replace(/^%40/, '@'), registryUrl_);
  37. const authInfo = registryAuthToken(registryUrl_.toString(), {recursive: true});
  38. const headers = {
  39. accept: 'application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8, */*'
  40. };
  41. if (options.fullMetadata) {
  42. delete headers.accept;
  43. }
  44. if (authInfo) {
  45. headers.authorization = `${authInfo.type} ${authInfo.token}`;
  46. }
  47. const gotOptions = {
  48. json: true,
  49. headers,
  50. agent: {
  51. http: httpAgent,
  52. https: httpsAgent
  53. }
  54. };
  55. if (options.agent) {
  56. gotOptions.agent = options.agent;
  57. }
  58. let response;
  59. try {
  60. response = await got(packageUrl, gotOptions);
  61. } catch (error) {
  62. if (error.statusCode === 404) {
  63. throw new PackageNotFoundError(packageName);
  64. }
  65. throw error;
  66. }
  67. let data = response.body;
  68. if (options.allVersions) {
  69. return data;
  70. }
  71. let {version} = options;
  72. const versionError = new VersionNotFoundError(packageName, version);
  73. if (data['dist-tags'][version]) {
  74. data = data.versions[data['dist-tags'][version]];
  75. } else if (version) {
  76. if (!data.versions[version]) {
  77. const versions = Object.keys(data.versions);
  78. version = semver.maxSatisfying(versions, version);
  79. if (!version) {
  80. throw versionError;
  81. }
  82. }
  83. data = data.versions[version];
  84. if (!data) {
  85. throw versionError;
  86. }
  87. }
  88. return data;
  89. };
  90. module.exports = packageJson;
  91. // TODO: remove this in the next major version
  92. module.exports.default = packageJson;
  93. module.exports.PackageNotFoundError = PackageNotFoundError;
  94. module.exports.VersionNotFoundError = VersionNotFoundError;