@xhmikosr/bin-wrapper 14.2.4 → 14.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/index.js +102 -69
  2. package/package.json +2 -3
  3. package/readme.md +4 -0
package/index.js CHANGED
@@ -2,34 +2,42 @@ import {promises as fs} from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import binCheck from '@xhmikosr/bin-check';
4
4
  import binaryVersionCheck from 'binary-version-check';
5
- import download from '@xhmikosr/downloader';
5
+ import downloader from '@xhmikosr/downloader';
6
6
  import osFilterObject from '@xhmikosr/os-filter-obj';
7
7
 
8
8
  /**
9
- * Initialize a new `BinWrapper`
10
- *
11
- * @param {Object} options
12
- * @api public
9
+ * @typedef {Object} BinWrapperOptions
10
+ * @property {number} [strip=1] - Number of leading paths to strip from the archive.
11
+ * @property {boolean} [skipCheck=false] - Skip binary checks.
13
12
  */
13
+
14
+ /**
15
+ * @typedef {Object} SourceFile
16
+ * @property {string} url - The URL of the file.
17
+ * @property {string} [os] - The operating system the file is for.
18
+ * @property {string} [arch] - The architecture the file is for.
19
+ */
20
+
14
21
  export default class BinWrapper {
22
+ /**
23
+ * @param {BinWrapperOptions} [options]
24
+ */
15
25
  constructor(options = {}) {
16
- this.options = options;
26
+ const {strip = 1, skipCheck = false} = options;
17
27
 
18
- if (this.options.strip <= 0) {
19
- this.options.strip = 0;
20
- // eslint-disable-next-line logical-assignment-operators
21
- } else if (!this.options.strip) {
22
- this.options.strip = 1;
23
- }
28
+ this.options = {
29
+ strip: Math.max(0, strip),
30
+ skipCheck,
31
+ };
24
32
  }
25
33
 
26
34
  /**
27
35
  * Get or set files to download
28
36
  *
29
- * @param {String} src
30
- * @param {String} os
31
- * @param {String} arch
32
- * @api public
37
+ * @param {string} [src] - The source URL of the file.
38
+ * @param {string} [os] - The operating system the file is for.
39
+ * @param {string} [arch] - The architecture the file is for.
40
+ * @returns {SourceFile[]|undefined|this} - Returns the source files if no arguments are provided, otherwise returns `this`.
33
41
  */
34
42
  src(src, os, arch) {
35
43
  if (arguments.length === 0) {
@@ -45,8 +53,8 @@ export default class BinWrapper {
45
53
  /**
46
54
  * Get or set the destination
47
55
  *
48
- * @param {String} dest
49
- * @api public
56
+ * @param {string} [dest] - The destination path.
57
+ * @returns {string|undefined|this} - Returns the destination if no arguments are provided, otherwise returns `this`.
50
58
  */
51
59
  dest(dest) {
52
60
  if (arguments.length === 0) {
@@ -54,14 +62,15 @@ export default class BinWrapper {
54
62
  }
55
63
 
56
64
  this._dest = dest;
65
+
57
66
  return this;
58
67
  }
59
68
 
60
69
  /**
61
70
  * Get or set the binary
62
71
  *
63
- * @param {String} bin
64
- * @api public
72
+ * @param {string} [bin] - The binary name.
73
+ * @returns {string|undefined|this} - Returns the binary name if no arguments are provided, otherwise returns `this`.
65
74
  */
66
75
  use(bin) {
67
76
  if (arguments.length === 0) {
@@ -69,14 +78,15 @@ export default class BinWrapper {
69
78
  }
70
79
 
71
80
  this._use = bin;
81
+
72
82
  return this;
73
83
  }
74
84
 
75
85
  /**
76
86
  * Get or set a semver range to test the binary against
77
87
  *
78
- * @param {String} range
79
- * @api public
88
+ * @param {string} [range] - The semver range.
89
+ * @returns {string|undefined|this} - Returns the semver range if no arguments are provided, otherwise returns `this`.
80
90
  */
81
91
  version(range) {
82
92
  if (arguments.length === 0) {
@@ -84,100 +94,123 @@ export default class BinWrapper {
84
94
  }
85
95
 
86
96
  this._version = range;
97
+
87
98
  return this;
88
99
  }
89
100
 
90
101
  /**
91
102
  * Get path to the binary
92
103
  *
93
- * @api public
104
+ * @returns {string} - The full path to the binary.
94
105
  */
95
106
  path() {
96
107
  return path.join(this.dest(), this.use());
97
108
  }
98
109
 
99
110
  /**
100
- * Run
111
+ * Get the source URLs matching the current OS and arch
101
112
  *
102
- * @param {Array} cmd
103
- * @api public
113
+ * @returns {string[]}
104
114
  */
105
- run(cmd = ['--version']) {
106
- return this.findExisting().then(() => {
107
- if (this.options.skipCheck) {
108
- return;
109
- }
115
+ resolvedUrls() {
116
+ return osFilterObject(this.src() || []).map(file => file.url);
117
+ }
110
118
 
111
- return this.runCheck(cmd);
112
- });
119
+ /**
120
+ * Check for the binary and download it if missing, then optionally verify it works.
121
+ *
122
+ * @param {string[]} [cmd=['--version']] - Arguments passed to the binary when checking it.
123
+ * @returns {Promise<void>}
124
+ */
125
+ async run(cmd = ['--version']) {
126
+ await this.findExisting();
127
+
128
+ if (this.options.skipCheck) {
129
+ return;
130
+ }
131
+
132
+ await this.runCheck(cmd);
113
133
  }
114
134
 
115
135
  /**
116
136
  * Run binary check
117
137
  *
118
- * @param {Array} cmd
138
+ * @param {string[]} cmd - Arguments to pass to the binary.
139
+ * @returns {Promise<void>}
119
140
  * @api private
120
141
  */
121
- runCheck(cmd) {
122
- return binCheck(this.path(), cmd).then(works => {
123
- if (!works) {
124
- throw new Error(`The "${this.path()}" binary doesn't seem to work correctly`);
125
- }
142
+ async runCheck(cmd) {
143
+ const works = await binCheck(this.path(), cmd);
144
+ if (!works) {
145
+ throw new Error(`The "${this.path()}" binary doesn't seem to work correctly`);
146
+ }
126
147
 
127
- if (this.version()) {
128
- return binaryVersionCheck(this.path(), this.version());
129
- }
130
- });
148
+ if (this.version()) {
149
+ await binaryVersionCheck(this.path(), this.version());
150
+ }
131
151
  }
132
152
 
133
153
  /**
134
- * Find existing files
154
+ * Check whether the binary exists; download it if not.
135
155
  *
156
+ * @returns {Promise<void>}
136
157
  * @api private
137
158
  */
138
- findExisting() {
139
- return fs.stat(this.path()).catch(error => {
159
+ async findExisting() {
160
+ try {
161
+ await fs.access(this.path());
162
+ } catch (error) {
140
163
  if (error?.code === 'ENOENT') {
141
- return this.download();
164
+ await this.download();
165
+ } else {
166
+ throw error;
142
167
  }
143
-
144
- throw error;
145
- });
168
+ }
146
169
  }
147
170
 
148
171
  /**
149
- * Download files
172
+ * Download files matching the current OS/arch and make them executable.
150
173
  *
174
+ * @returns {Promise<void>}
151
175
  * @api private
152
176
  */
153
- download() {
154
- const files = osFilterObject(this.src() || []);
177
+ async download() {
178
+ const urls = this.resolvedUrls();
155
179
 
156
- if (files.length === 0) {
157
- return Promise.reject(new Error('No binary found matching your system. It\'s probably not supported.'));
180
+ if (urls.length === 0) {
181
+ throw new Error('No binary found matching your system. It\'s probably not supported.');
158
182
  }
159
183
 
160
- const urls = files.map(file => file.url);
161
-
162
- return Promise.all(urls.map(url =>
163
- download(url, this.dest(), {
184
+ const results = await Promise.all(urls.map(url =>
185
+ downloader(url, this.dest(), {
164
186
  extract: true,
165
187
  decompress: {
166
188
  strip: this.options.strip,
167
189
  },
168
- }))).then(result => {
169
- const resultFiles = result.flatMap((item, index) => {
170
- if (Array.isArray(item)) {
171
- return item.map(file => file.path);
172
- }
190
+ })));
173
191
 
174
- const parsedUrl = new URL(files[index].url);
175
- const parsedPath = path.parse(parsedUrl.pathname);
192
+ const resultFiles = results.flatMap((item, index) => {
193
+ if (Array.isArray(item)) {
194
+ return item.map(file => file.path);
195
+ }
176
196
 
177
- return parsedPath.base;
178
- });
197
+ const parsedUrl = new URL(urls[index]);
179
198
 
180
- return Promise.all(resultFiles.map(file => fs.chmod(path.join(this.dest(), file), 0o755)));
199
+ return path.parse(parsedUrl.pathname).base;
181
200
  });
201
+
202
+ await Promise.all(resultFiles
203
+ .filter(Boolean)
204
+ .map(async file => {
205
+ try {
206
+ await fs.chmod(path.join(this.dest(), file), 0o755);
207
+ } catch (error) {
208
+ // We guess the saved name from the URL, but the downloader may
209
+ // have used a different one, so skip a missing file.
210
+ if (error?.code !== 'ENOENT') {
211
+ throw error;
212
+ }
213
+ }
214
+ }));
182
215
  }
183
216
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xhmikosr/bin-wrapper",
3
- "version": "14.2.4",
3
+ "version": "14.3.0",
4
4
  "description": "Binary wrapper that makes your programs seamlessly available as local dependencies",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -42,7 +42,7 @@
42
42
  ],
43
43
  "dependencies": {
44
44
  "@xhmikosr/bin-check": "^8.2.1",
45
- "@xhmikosr/downloader": "^16.1.2",
45
+ "@xhmikosr/downloader": "^16.1.3",
46
46
  "@xhmikosr/os-filter-obj": "^4.1.0",
47
47
  "binary-version-check": "^6.1.0"
48
48
  },
@@ -56,7 +56,6 @@
56
56
  },
57
57
  "xo": {
58
58
  "rules": {
59
- "promise/prefer-await-to-then": "off",
60
59
  "unicorn/prevent-abbreviations": "off"
61
60
  }
62
61
  }
package/readme.md CHANGED
@@ -105,6 +105,10 @@ Define which file to use as the binary.
105
105
 
106
106
  Returns the full path to your binary.
107
107
 
108
+ ### .resolvedUrls()
109
+
110
+ Returns an array of the source URLs matching the current OS and arch.
111
+
108
112
  ### .version(range)
109
113
 
110
114
  #### range