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