You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

index.js 3.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. 'use strict';
  2. const fs = require('fs');
  3. const path = require('path');
  4. const url = require('url');
  5. const pify = require('pify');
  6. const importLazy = require('import-lazy')(require);
  7. const binCheck = importLazy('bin-check');
  8. const binVersionCheck = importLazy('bin-version-check');
  9. const download = importLazy('download');
  10. const osFilterObj = importLazy('os-filter-obj');
  11. const statAsync = pify(fs.stat);
  12. const chmodAsync = pify(fs.chmod);
  13. /**
  14. * Initialize a new `BinWrapper`
  15. *
  16. * @param {Object} options
  17. * @api public
  18. */
  19. module.exports = class BinWrapper {
  20. constructor(options = {}) {
  21. this.options = options;
  22. if (this.options.strip <= 0) {
  23. this.options.strip = 0;
  24. } else if (!this.options.strip) {
  25. this.options.strip = 1;
  26. }
  27. }
  28. /**
  29. * Get or set files to download
  30. *
  31. * @param {String} src
  32. * @param {String} os
  33. * @param {String} arch
  34. * @api public
  35. */
  36. src(src, os, arch) {
  37. if (arguments.length === 0) {
  38. return this._src;
  39. }
  40. this._src = this._src || [];
  41. this._src.push({
  42. url: src,
  43. os,
  44. arch
  45. });
  46. return this;
  47. }
  48. /**
  49. * Get or set the destination
  50. *
  51. * @param {String} dest
  52. * @api public
  53. */
  54. dest(dest) {
  55. if (arguments.length === 0) {
  56. return this._dest;
  57. }
  58. this._dest = dest;
  59. return this;
  60. }
  61. /**
  62. * Get or set the binary
  63. *
  64. * @param {String} bin
  65. * @api public
  66. */
  67. use(bin) {
  68. if (arguments.length === 0) {
  69. return this._use;
  70. }
  71. this._use = bin;
  72. return this;
  73. }
  74. /**
  75. * Get or set a semver range to test the binary against
  76. *
  77. * @param {String} range
  78. * @api public
  79. */
  80. version(range) {
  81. if (arguments.length === 0) {
  82. return this._version;
  83. }
  84. this._version = range;
  85. return this;
  86. }
  87. /**
  88. * Get path to the binary
  89. *
  90. * @api public
  91. */
  92. path() {
  93. return path.join(this.dest(), this.use());
  94. }
  95. /**
  96. * Run
  97. *
  98. * @param {Array} cmd
  99. * @api public
  100. */
  101. run(cmd = ['--version']) {
  102. return this.findExisting().then(() => {
  103. if (this.options.skipCheck) {
  104. return;
  105. }
  106. return this.runCheck(cmd);
  107. });
  108. }
  109. /**
  110. * Run binary check
  111. *
  112. * @param {Array} cmd
  113. * @api private
  114. */
  115. runCheck(cmd) {
  116. return binCheck(this.path(), cmd).then(works => {
  117. if (!works) {
  118. throw new Error(`The \`${this.path()}\` binary doesn't seem to work correctly`);
  119. }
  120. if (this.version()) {
  121. return binVersionCheck(this.path(), this.version());
  122. }
  123. return Promise.resolve();
  124. });
  125. }
  126. /**
  127. * Find existing files
  128. *
  129. * @api private
  130. */
  131. findExisting() {
  132. return statAsync(this.path()).catch(error => {
  133. if (error && error.code === 'ENOENT') {
  134. return this.download();
  135. }
  136. return Promise.reject(error);
  137. });
  138. }
  139. /**
  140. * Download files
  141. *
  142. * @api private
  143. */
  144. download() {
  145. const files = osFilterObj(this.src() || []);
  146. const urls = [];
  147. if (files.length === 0) {
  148. return Promise.reject(new Error('No binary found matching your system. It\'s probably not supported.'));
  149. }
  150. files.forEach(file => urls.push(file.url));
  151. return Promise.all(urls.map(url => download(url, this.dest(), {
  152. extract: true,
  153. strip: this.options.strip
  154. }))).then(result => {
  155. const resultingFiles = flatten(result.map((item, index) => {
  156. if (Array.isArray(item)) {
  157. return item.map(file => file.path);
  158. }
  159. const parsedUrl = url.parse(files[index].url);
  160. const parsedPath = path.parse(parsedUrl.pathname);
  161. return parsedPath.base;
  162. }));
  163. return Promise.all(resultingFiles.map(fileName => {
  164. return chmodAsync(path.join(this.dest(), fileName), 0o755);
  165. }));
  166. });
  167. }
  168. };
  169. function flatten(arr) {
  170. return arr.reduce((acc, elem) => {
  171. if (Array.isArray(elem)) {
  172. acc.push(...elem);
  173. } else {
  174. acc.push(elem);
  175. }
  176. return acc;
  177. }, []);
  178. }