@sentry/cli 1.76.0 → 1.77.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.
@@ -11,18 +11,24 @@ const zlib = require('zlib');
11
11
  const stream = require('stream');
12
12
  const process = require('process');
13
13
 
14
- const HttpsProxyAgent = require('https-proxy-agent');
15
14
  const fetch = require('node-fetch');
15
+ const HttpsProxyAgent = require('https-proxy-agent');
16
16
  const ProgressBar = require('progress');
17
17
  const Proxy = require('proxy-from-env');
18
- // NOTE: Can be dropped in favor of `fs.mkdirSync(path, { recursive: true })` once we stop supporting Node 8.x
19
- const mkdirp = require('mkdirp');
20
18
  const which = require('which');
21
19
 
22
20
  const helper = require('../js/helper');
23
21
  const pkgInfo = require('../package.json');
24
22
  const Logger = require('../js/logger');
25
23
 
24
+ const logger = new Logger(getLogStream('stderr'));
25
+
26
+ const CDN_URL =
27
+ process.env.SENTRYCLI_LOCAL_CDNURL ||
28
+ process.env.npm_config_sentrycli_cdnurl ||
29
+ process.env.SENTRYCLI_CDNURL ||
30
+ 'https://downloads.sentry-cdn.com/sentry-cli';
31
+
26
32
  function getLogStream(defaultStream) {
27
33
  const logStream = process.env.SENTRYCLI_LOG_STREAM || defaultStream;
28
34
 
@@ -39,22 +45,14 @@ function getLogStream(defaultStream) {
39
45
  );
40
46
  }
41
47
 
42
- const logger = new Logger(getLogStream('stderr'));
43
-
44
- const CDN_URL =
45
- process.env.SENTRYCLI_LOCAL_CDNURL ||
46
- process.env.npm_config_sentrycli_cdnurl ||
47
- process.env.SENTRYCLI_CDNURL ||
48
- 'https://downloads.sentry-cdn.com/sentry-cli';
49
-
50
48
  function shouldRenderProgressBar() {
51
- const silentFlag = process.argv.some(v => v === '--silent');
49
+ const silentFlag = process.argv.some((v) => v === '--silent');
52
50
  const silentConfig = process.env.npm_config_loglevel === 'silent';
53
- // Leave `SENTRY_NO_PROGRESS_BAR` for backwards compatibility
54
- const silentEnv = process.env.SENTRYCLI_NO_PROGRESS_BAR || process.env.SENTRY_NO_PROGRESS_BAR;
55
- const ciEnv = process.env.CI === 'true';
51
+ const silentEnv = process.env.SENTRYCLI_NO_PROGRESS_BAR;
52
+ const ciEnv = process.env.CI === 'true' || process.env.CI === '1';
53
+ const notTTY = !process.stdout.isTTY;
56
54
  // If any of possible options is set, skip rendering of progress bar
57
- return !(silentFlag || silentConfig || silentEnv || ciEnv);
55
+ return !(silentFlag || silentConfig || silentEnv || ciEnv || notTTY);
58
56
  }
59
57
 
60
58
  function getDownloadUrl(platform, arch) {
@@ -113,7 +111,7 @@ function createProgressBar(name, total) {
113
111
  let pct = null;
114
112
  let current = 0;
115
113
  return {
116
- tick: length => {
114
+ tick: (length) => {
117
115
  current += length;
118
116
  const next = Math.round((current / total) * 100);
119
117
  if (next > pct) {
@@ -125,21 +123,21 @@ function createProgressBar(name, total) {
125
123
  }
126
124
 
127
125
  function npmCache() {
128
- const env = process.env;
129
- return (
130
- env.npm_config_cache ||
131
- env.npm_config_cache_folder ||
132
- env.npm_config_yarn_offline_mirror ||
133
- (env.APPDATA ? path.join(env.APPDATA, 'npm-cache') : path.join(os.homedir(), '.npm'))
134
- );
126
+ const keys = ['npm_config_cache', 'npm_config_cache_folder', 'npm_config_yarn_offline_mirror'];
127
+
128
+ for (let key of [...keys, ...keys.map((k) => k.toUpperCase())]) {
129
+ if (process.env[key]) return process.env[key];
130
+ }
131
+
132
+ if (process.env.APPDATA) {
133
+ return path.join(process.env.APPDATA, 'npm-cache');
134
+ }
135
+
136
+ return path.join(os.homedir(), '.npm');
135
137
  }
136
138
 
137
139
  function getCachedPath(url) {
138
- const digest = crypto
139
- .createHash('md5')
140
- .update(url)
141
- .digest('hex')
142
- .slice(0, 6);
140
+ const digest = crypto.createHash('md5').update(url).digest('hex').slice(0, 6);
143
141
 
144
142
  return path.join(
145
143
  npmCache(),
@@ -149,9 +147,7 @@ function getCachedPath(url) {
149
147
  }
150
148
 
151
149
  function getTempFile(cached) {
152
- return `${cached}.${process.pid}-${Math.random()
153
- .toString(16)
154
- .slice(2)}.tmp`;
150
+ return `${cached}.${process.pid}-${Math.random().toString(16).slice(2)}.tmp`;
155
151
  }
156
152
 
157
153
  function validateChecksum(tempPath, name) {
@@ -178,10 +174,7 @@ function validateChecksum(tempPath, name) {
178
174
  return;
179
175
  }
180
176
 
181
- const currentHash = crypto
182
- .createHash('sha256')
183
- .update(fs.readFileSync(tempPath))
184
- .digest('hex');
177
+ const currentHash = crypto.createHash('sha256').update(fs.readFileSync(tempPath)).digest('hex');
185
178
 
186
179
  if (storedHash !== currentHash) {
187
180
  fs.unlinkSync(tempPath);
@@ -193,14 +186,16 @@ function validateChecksum(tempPath, name) {
193
186
  }
194
187
  }
195
188
 
196
- function downloadBinary() {
189
+ async function downloadBinary() {
197
190
  const arch = os.arch();
198
191
  const platform = os.platform();
199
192
  const outputPath = helper.getPath();
200
193
 
201
194
  if (process.env.SENTRYCLI_USE_LOCAL === '1') {
202
195
  try {
203
- const binPath = which.sync('sentry-cli');
196
+ const binPaths = which.sync('sentry-cli', { all: true });
197
+ if (!binPaths.length) throw new Error('Binary not found');
198
+ const binPath = binPaths[binPaths.length - 1];
204
199
  logger.log(`Using local binary: ${binPath}`);
205
200
  fs.copyFileSync(binPath, outputPath);
206
201
  return Promise.resolve();
@@ -214,14 +209,14 @@ function downloadBinary() {
214
209
 
215
210
  const downloadUrl = getDownloadUrl(platform, arch);
216
211
  if (!downloadUrl) {
217
- return Promise.reject(new Error(`Unsupported target ${platform}-${arch}`));
212
+ throw new Error(`Unsupported target ${platform}-${arch}`);
218
213
  }
219
214
 
220
215
  const cachedPath = getCachedPath(downloadUrl);
221
216
  if (fs.existsSync(cachedPath)) {
222
217
  logger.log(`Using cached binary: ${cachedPath}`);
223
218
  fs.copyFileSync(cachedPath, outputPath);
224
- return Promise.resolve();
219
+ return;
225
220
  }
226
221
 
227
222
  const proxyUrl = Proxy.getProxyForUrl(downloadUrl);
@@ -233,74 +228,85 @@ function downloadBinary() {
233
228
  logger.log(`Using proxy URL: ${proxyUrl}`);
234
229
  }
235
230
 
236
- return fetch(downloadUrl, {
237
- agent,
238
- compress: false,
239
- headers: {
240
- 'accept-encoding': 'gzip, deflate, br',
241
- },
242
- redirect: 'follow',
243
- })
244
- .then(response => {
245
- if (!response.ok) {
246
- throw new Error(
247
- `Unable to download sentry-cli binary from ${downloadUrl}.\nServer returned ${response.status}: ${response.statusText}.`
248
- );
249
- }
231
+ let response;
232
+ try {
233
+ response = await fetch(downloadUrl, {
234
+ agent,
235
+ compress: false,
236
+ headers: {
237
+ 'accept-encoding': 'gzip, deflate, br',
238
+ },
239
+ redirect: 'follow',
240
+ });
241
+ } catch (error) {
242
+ let errorMsg = `Unable to download sentry-cli binary from ${downloadUrl}.\nError message: ${error.message}`;
243
+ if (error.code) {
244
+ errorMsg += `\nError code: ${error.code}`;
245
+ }
246
+ throw new Error(errorMsg);
247
+ }
250
248
 
251
- const contentEncoding = response.headers.get('content-encoding');
252
- let decompressor;
253
- if (/\bgzip\b/.test(contentEncoding)) {
254
- decompressor = zlib.createGunzip();
255
- } else if (/\bdeflate\b/.test(contentEncoding)) {
256
- decompressor = zlib.createInflate();
257
- } else if (/\bbr\b/.test(contentEncoding)) {
258
- decompressor = zlib.createBrotliDecompress();
259
- } else {
260
- decompressor = new stream.PassThrough();
261
- }
262
- const name = downloadUrl.match(/.*\/(.*?)$/)[1];
263
- const total = parseInt(response.headers.get('content-length'), 10);
264
- const progressBar = createProgressBar(name, total);
265
- const tempPath = getTempFile(cachedPath);
266
- mkdirp.sync(path.dirname(tempPath));
267
-
268
- return new Promise((resolve, reject) => {
269
- response.body
270
- .on('error', e => reject(e))
271
- .on('data', chunk => progressBar.tick(chunk.length))
272
- .pipe(decompressor)
273
- .pipe(fs.createWriteStream(tempPath, { mode: '0755' }))
274
- .on('error', e => reject(e))
275
- .on('close', () => resolve());
276
- }).then(() => {
277
- if (process.env.SENTRYCLI_SKIP_CHECKSUM_VALIDATION !== '1') {
278
- validateChecksum(tempPath, name);
249
+ if (!response.ok) {
250
+ let errorMsg = `Unable to download sentry-cli binary from ${downloadUrl}.\nServer returned: ${response.status}`;
251
+ if (response.statusText) {
252
+ errorMsg += ` - ${response.statusText}`;
253
+ }
254
+ throw new Error(errorMsg);
255
+ }
256
+
257
+ const contentEncoding = response.headers.get('content-encoding');
258
+ let decompressor;
259
+ if (/\bgzip\b/.test(contentEncoding)) {
260
+ decompressor = zlib.createGunzip();
261
+ } else if (/\bdeflate\b/.test(contentEncoding)) {
262
+ decompressor = zlib.createInflate();
263
+ } else if (/\bbr\b/.test(contentEncoding)) {
264
+ decompressor = zlib.createBrotliDecompress();
265
+ } else {
266
+ decompressor = new stream.PassThrough();
267
+ }
268
+ const name = downloadUrl.match(/.*\/(.*?)$/)[1];
269
+ let downloadedBytes = 0;
270
+ const totalBytes = parseInt(response.headers.get('content-length'), 10);
271
+ const progressBar = createProgressBar(name, totalBytes);
272
+ const tempPath = getTempFile(cachedPath);
273
+ fs.mkdirSync(path.dirname(tempPath), { recursive: true });
274
+
275
+ await new Promise((resolve, reject) => {
276
+ response.body
277
+ .on('error', (e) => reject(e))
278
+ .on('data', (chunk) => {
279
+ downloadedBytes += chunk.length;
280
+ progressBar.tick(chunk.length);
281
+ })
282
+ .pipe(decompressor)
283
+ .pipe(fs.createWriteStream(tempPath, { mode: '0755' }))
284
+ .on('error', (e) => reject(e))
285
+ .on('close', () => {
286
+ if (downloadedBytes >= totalBytes) {
287
+ resolve();
288
+ } else {
289
+ reject(new Error('connection interrupted'));
279
290
  }
280
- fs.copyFileSync(tempPath, cachedPath);
281
- fs.copyFileSync(tempPath, outputPath);
282
- fs.unlinkSync(tempPath);
283
291
  });
284
- })
285
- .catch(error => {
286
- if (error instanceof fetch.FetchError) {
287
- throw new Error(
288
- `Unable to download sentry-cli binary from ${downloadUrl}.\nError code: ${error.code}`
289
- );
290
- } else {
291
- throw error;
292
- }
293
- });
292
+ });
293
+
294
+ if (process.env.SENTRYCLI_SKIP_CHECKSUM_VALIDATION !== '1') {
295
+ validateChecksum(tempPath, name);
296
+ }
297
+
298
+ fs.copyFileSync(tempPath, cachedPath);
299
+ fs.copyFileSync(tempPath, outputPath);
300
+ fs.unlinkSync(tempPath);
294
301
  }
295
302
 
296
- function checkVersion() {
297
- return helper.execute(['--version']).then(output => {
298
- const version = output.replace('sentry-cli ', '').trim();
299
- const expected = process.env.SENTRYCLI_LOCAL_CDNURL ? 'DEV' : pkgInfo.version;
300
- if (version !== expected) {
301
- throw new Error(`Unexpected sentry-cli version "${version}", expected "${expected}"`);
302
- }
303
- });
303
+ async function checkVersion() {
304
+ const output = await helper.execute(['--version']);
305
+ const version = output.replace('sentry-cli ', '').trim();
306
+ const expected = process.env.SENTRYCLI_LOCAL_CDNURL ? 'DEV' : pkgInfo.version;
307
+ if (version !== expected) {
308
+ throw new Error(`Unexpected sentry-cli version "${version}", expected "${expected}"`);
309
+ }
304
310
  }
305
311
 
306
312
  if (process.env.SENTRYCLI_LOCAL_CDNURL) {
@@ -323,11 +329,14 @@ if (process.env.SENTRYCLI_SKIP_DOWNLOAD === '1') {
323
329
  process.exit(0);
324
330
  }
325
331
 
326
- downloadBinary()
327
- .then(() => checkVersion())
328
- .then(() => process.exit(0))
329
- .catch(e => {
332
+ (async () => {
333
+ try {
334
+ await downloadBinary();
335
+ await checkVersion();
336
+ process.exit(0);
337
+ } catch (e) {
330
338
  // eslint-disable-next-line no-console
331
339
  console.error(e.toString());
332
340
  process.exit(1);
333
- });
341
+ }
342
+ })();
@@ -1,27 +1,16 @@
1
- const major = process.versions.node.split('.')[0];
2
-
3
- // @vercel/nft doe not support Node.js v8
4
- if (major < 10) {
5
- process.exit(0);
6
- }
7
-
8
- // eslint-disable-next-line import/no-extraneous-dependencies
9
1
  const { nodeFileTrace } = require('@vercel/nft');
10
2
 
11
3
  const entryPoint = require.resolve('..');
12
4
 
13
5
  // Trace the module entrypoint
14
- nodeFileTrace([entryPoint]).then(result => {
15
- // eslint-disable-next-line no-console
6
+ nodeFileTrace([entryPoint]).then((result) => {
16
7
  console.log('@vercel/nft traced dependencies:', Array.from(result.fileList));
17
8
 
18
9
  // If either binary is picked up, fail the test
19
10
  if (result.fileList.has('sentry-cli') || result.fileList.has('sentry-cli.exe')) {
20
- // eslint-disable-next-line no-console
21
11
  console.error('ERROR: The sentry-cli binary should not be found by @vercel/nft');
22
12
  process.exit(-1);
23
13
  } else {
24
- // eslint-disable-next-line no-console
25
14
  console.log('The sentry-cli binary was not traced by @vercel/nft');
26
15
  }
27
16
  });
package/scripts/wheels ADDED
@@ -0,0 +1,174 @@
1
+ #!/usr/bin/env python3
2
+ import argparse
3
+ import base64
4
+ import hashlib
5
+ import os.path
6
+ import shutil
7
+ import tempfile
8
+ import zipfile
9
+ from typing import NamedTuple
10
+
11
+
12
+ class Wheel(NamedTuple):
13
+ src: str
14
+ plat: str
15
+ exe: str = 'sentry-cli'
16
+
17
+
18
+ WHEELS = (
19
+ Wheel(
20
+ src='sentry-cli-Darwin-arm64',
21
+ plat='macosx_11_0_arm64',
22
+ ),
23
+ Wheel(
24
+ src='sentry-cli-Darwin-universal',
25
+ plat='macosx_11_0_universal2',
26
+ ),
27
+ Wheel(
28
+ src='sentry-cli-Darwin-x86_64',
29
+ plat='macosx_10_15_x86_64',
30
+ ),
31
+ Wheel(
32
+ src='sentry-cli-Linux-aarch64',
33
+ plat='manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_2_aarch64',
34
+ ),
35
+ Wheel(
36
+ src='sentry-cli-Linux-armv7',
37
+ plat='manylinux_2_17_armv7l.manylinux2014_armv7l.musllinux_1_2_armv7l',
38
+ ),
39
+ Wheel(
40
+ src='sentry-cli-Linux-i686',
41
+ plat='manylinux_2_17_i686.manylinux2014_i686.musllinux_1_2_i686',
42
+ ),
43
+ Wheel(
44
+ src='sentry-cli-Linux-x86_64',
45
+ plat='manylinux_2_17_x86_64.manylinux2014_x86_64.musllinux_1_2_x86_64',
46
+ ),
47
+ Wheel(
48
+ src='sentry-cli-Windows-i686.exe',
49
+ plat='win32',
50
+ exe='sentry-cli.exe',
51
+ ),
52
+ Wheel(
53
+ src='sentry-cli-Windows-x86_64.exe',
54
+ plat='win_amd64',
55
+ exe='sentry-cli.exe',
56
+ ),
57
+ )
58
+
59
+
60
+ def main() -> int:
61
+ parser = argparse.ArgumentParser()
62
+ parser.add_argument('--binaries', required=True)
63
+ parser.add_argument('--base', required=True)
64
+ parser.add_argument('--dest', required=True)
65
+ args = parser.parse_args()
66
+
67
+ expected = {wheel.src for wheel in WHEELS}
68
+ received = set(os.listdir(args.binaries))
69
+ if expected < received:
70
+ raise SystemExit(
71
+ f'Unexpected binaries:\n\n'
72
+ f'- extra: {", ".join(sorted(received - expected))}\n'
73
+ f'- missing: {", ".join(sorted(expected - received))}'
74
+ )
75
+
76
+ sdist_path = wheel_path = None
77
+ for fname in os.listdir(args.base):
78
+ if fname.endswith('.tar.gz'):
79
+ sdist_path = os.path.join(args.base, fname)
80
+ elif fname.endswith('.whl'):
81
+ wheel_path = os.path.join(args.base, fname)
82
+ else:
83
+ raise SystemExit(f'unexpected file in `--base`: {fname}')
84
+
85
+ if sdist_path is None or wheel_path is None:
86
+ raise SystemExit('expected wheel and sdist in `--base`')
87
+
88
+ os.makedirs(args.dest, exist_ok=True)
89
+ shutil.copy(sdist_path, args.dest)
90
+
91
+ for wheel in WHEELS:
92
+ binary_src = os.path.join(args.binaries, wheel.src)
93
+ binary_size = os.stat(binary_src).st_size
94
+ with open(binary_src, 'rb') as bf:
95
+ digest = hashlib.sha256(bf.read()).digest()
96
+ digest_b64 = base64.urlsafe_b64encode(digest).rstrip(b'=').decode()
97
+
98
+ basename = os.path.basename(wheel_path)
99
+ wheelname, _ = os.path.splitext(basename)
100
+ name, version, py, abi, plat = wheelname.split('-')
101
+
102
+ with tempfile.TemporaryDirectory() as tmp:
103
+ with zipfile.ZipFile(wheel_path) as zipf:
104
+ zipf.extractall(tmp)
105
+
106
+ distinfo = os.path.join(tmp, f'{name}-{version}.dist-info')
107
+ scripts = os.path.join(tmp, f'{name}-{version}.data', 'scripts')
108
+
109
+ # replace the script binary with our copy
110
+ os.remove(os.path.join(scripts, 'sentry-cli'))
111
+ shutil.copy(binary_src, os.path.join(scripts, wheel.exe))
112
+
113
+ # rewrite RECORD to include the new file
114
+ record_fname = os.path.join(distinfo, 'RECORD')
115
+ with open(record_fname) as f:
116
+ record_lines = list(f)
117
+
118
+ record = f'{name}-{version}.data/scripts/sentry-cli,'
119
+ for i, line in enumerate(record_lines):
120
+ if line.startswith(record):
121
+ record_lines[i] = (
122
+ f'{name}-{version}.data/scripts/{wheel.exe},'
123
+ f'sha256={digest_b64},'
124
+ f'{binary_size}\n'
125
+ )
126
+ break
127
+ else:
128
+ raise SystemExit(f'could not find {record!r} in RECORD')
129
+
130
+ with open(record_fname, 'w') as f:
131
+ f.writelines(record_lines)
132
+
133
+ # rewrite WHEEL to have the new tags
134
+ wheel_fname = os.path.join(distinfo, 'WHEEL')
135
+ with open(wheel_fname) as f:
136
+ wheel_lines = list(f)
137
+
138
+ for i, line in enumerate(wheel_lines):
139
+ if line.startswith('Tag: '):
140
+ wheel_lines[i:i + 1] = [
141
+ f'Tag: {py}-{abi}-{plat}\n'
142
+ for plat in wheel.plat.split('.')
143
+ ]
144
+ break
145
+ else:
146
+ raise SystemExit("could not find 'Tag: ' in WHEEL")
147
+
148
+ with open(wheel_fname, 'w') as f:
149
+ f.writelines(wheel_lines)
150
+
151
+ # write out the final zip
152
+ new_basename = f'{name}-{version}-{py}-{abi}-{wheel.plat}.whl'
153
+ tmp_new_wheel = os.path.join(tmp, new_basename)
154
+ fnames = sorted(
155
+ os.path.join(root, fname)
156
+ for root, _, fnames in os.walk(tmp)
157
+ for fname in fnames
158
+ )
159
+ with zipfile.ZipFile(tmp_new_wheel, 'w') as zipf:
160
+ for fname in fnames:
161
+ zinfo = zipfile.ZipInfo(os.path.relpath(fname, tmp))
162
+ if '/scripts/' in zinfo.filename:
163
+ zinfo.external_attr = 0o100755 << 16
164
+ with open(fname, 'rb') as fb:
165
+ zipf.writestr(zinfo, fb.read())
166
+
167
+ # move into dest
168
+ shutil.move(tmp_new_wheel, args.dest)
169
+
170
+ return 0
171
+
172
+
173
+ if __name__ == '__main__':
174
+ raise SystemExit(main())