@sentry/cli 1.77.2 → 1.77.3

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.
@@ -2,348 +2,30 @@
2
2
 
3
3
  'use strict';
4
4
 
5
+ const http = require('http');
5
6
  const fs = require('fs');
6
- const os = require('os');
7
7
  const path = require('path');
8
- const crypto = require('crypto');
9
- const zlib = require('zlib');
10
- const stream = require('stream');
11
- const process = require('process');
12
-
13
- const fetch = require('node-fetch');
14
- const HttpsProxyAgent = require('https-proxy-agent');
15
- const ProgressBar = require('progress');
16
- const Proxy = require('proxy-from-env');
17
- const which = require('which');
18
-
19
- const helper = require('../js/helper');
20
- const pkgInfo = require('../package.json');
21
- const Logger = require('../js/logger');
22
-
23
- const logger = new Logger(getLogStream('stderr'));
24
-
25
- const CDN_URL =
26
- process.env.SENTRYCLI_LOCAL_CDNURL ||
27
- process.env.npm_config_sentrycli_cdnurl ||
28
- process.env.SENTRYCLI_CDNURL ||
29
- 'https://downloads.sentry-cdn.com/sentry-cli';
30
-
31
- function getLogStream(defaultStream) {
32
- const logStream = process.env.SENTRYCLI_LOG_STREAM || defaultStream;
33
-
34
- if (logStream === 'stdout') {
35
- return process.stdout;
36
- }
37
-
38
- if (logStream === 'stderr') {
39
- return process.stderr;
40
- }
41
-
42
- throw new Error(
43
- `Incorrect SENTRYCLI_LOG_STREAM env variable. Possible values: 'stdout' | 'stderr'`
44
- );
45
- }
46
-
47
- function shouldRenderProgressBar() {
48
- const silentFlag = process.argv.some((v) => v === '--silent');
49
- const silentConfig = process.env.npm_config_loglevel === 'silent';
50
- const silentEnv = process.env.SENTRYCLI_NO_PROGRESS_BAR;
51
- const ciEnv = process.env.CI === 'true' || process.env.CI === '1';
52
- const notTTY = !process.stdout.isTTY;
53
- // If any of possible options is set, skip rendering of progress bar
54
- return !(silentFlag || silentConfig || silentEnv || ciEnv || notTTY);
55
- }
56
-
57
- function getDownloadUrl(platform, arch) {
58
- const releasesUrl = `${CDN_URL}/${pkgInfo.version}/sentry-cli`;
59
- let archString = '';
60
- switch (arch) {
61
- case 'x64':
62
- archString = 'x86_64';
63
- break;
64
- case 'x86':
65
- case 'ia32':
66
- archString = 'i686';
67
- break;
68
- case 'arm64':
69
- archString = 'aarch64';
70
- break;
71
- case 'arm':
72
- archString = 'armv7';
73
- break;
74
- default:
75
- archString = arch;
76
- }
77
- switch (platform) {
78
- case 'darwin':
79
- return `${releasesUrl}-Darwin-universal`;
80
- case 'win32':
81
- // Windows arm machines can run x64 binaries
82
- if (arch === 'arm64') {
83
- archString = 'x86_64';
84
- }
85
- return `${releasesUrl}-Windows-${archString}.exe`;
86
- case 'linux':
87
- case 'freebsd':
88
- return `${releasesUrl}-Linux-${archString}`;
89
- default:
90
- return null;
91
- }
92
- }
93
-
94
- function createProgressBar(name, total) {
95
- const incorrectTotal = typeof total !== 'number' || Number.isNaN(total);
96
-
97
- if (incorrectTotal || !shouldRenderProgressBar()) {
98
- return {
99
- tick: () => {},
100
- };
101
- }
102
-
103
- const logStream = getLogStream('stdout');
104
-
105
- if (logStream.isTTY) {
106
- return new ProgressBar(`fetching ${name} :bar :percent :etas`, {
107
- complete: '█',
108
- incomplete: '░',
109
- width: 20,
110
- total,
8
+ const { downloadBinary } = require('../js/install');
9
+
10
+ if (process.env.SENTRYCLI_LOCAL_CDNURL) {
11
+ // For testing, mock the CDN by spawning a local server
12
+ const server = http.createServer((request, response) => {
13
+ const contents = fs.readFileSync(path.join(__dirname, '../js/__mocks__/sentry-cli'));
14
+ response.writeHead(200, {
15
+ 'Content-Type': 'application/octet-stream',
16
+ 'Content-Length': String(contents.byteLength),
111
17
  });
112
- }
113
-
114
- let pct = null;
115
- let current = 0;
116
- return {
117
- tick: (length) => {
118
- current += length;
119
- const next = Math.round((current / total) * 100);
120
- if (next > pct) {
121
- pct = next;
122
- logStream.write(`fetching ${name} ${pct}%\n`);
123
- }
124
- },
125
- };
126
- }
127
-
128
- function npmCache() {
129
- const keys = ['npm_config_cache', 'npm_config_cache_folder', 'npm_config_yarn_offline_mirror'];
130
-
131
- for (let key of [...keys, ...keys.map((k) => k.toUpperCase())]) {
132
- if (process.env[key]) return process.env[key];
133
- }
134
-
135
- if (process.env.APPDATA) {
136
- return path.join(process.env.APPDATA, 'npm-cache');
137
- }
138
-
139
- return path.join(os.homedir(), '.npm');
140
- }
141
-
142
- function getCachedPath(url) {
143
- const digest = crypto.createHash('md5').update(url).digest('hex').slice(0, 6);
144
-
145
- return path.join(
146
- npmCache(),
147
- 'sentry-cli',
148
- `${digest}-${path.basename(url).replace(/[^a-zA-Z0-9.]+/g, '-')}`
149
- );
150
- }
151
-
152
- function getTempFile(cached) {
153
- return `${cached}.${process.pid}-${Math.random().toString(16).slice(2)}.tmp`;
154
- }
155
-
156
- function validateChecksum(tempPath, name) {
157
- let storedHash;
158
- try {
159
- const checksums = fs.readFileSync(path.join(__dirname, '../checksums.txt'), 'utf8');
160
- const entries = checksums.split('\n');
161
- for (let i = 0; i < entries.length; i++) {
162
- const [key, value] = entries[i].split('=');
163
- if (key === name) {
164
- storedHash = value;
165
- break;
166
- }
167
- }
168
- } catch (e) {
169
- logger.log(
170
- 'Checksums are generated when the package is published to npm. They are not available directly in the source repository. Skipping validation.'
171
- );
172
- return;
173
- }
174
-
175
- if (!storedHash) {
176
- logger.log(`Checksum for ${name} not found, skipping validation.`);
177
- return;
178
- }
179
-
180
- const currentHash = crypto.createHash('sha256').update(fs.readFileSync(tempPath)).digest('hex');
181
-
182
- if (storedHash !== currentHash) {
183
- fs.unlinkSync(tempPath);
184
- throw new Error(
185
- `Checksum validation for ${name} failed.\nExpected: ${storedHash}\nReceived: ${currentHash}`
186
- );
187
- } else {
188
- logger.log('Checksum validation passed.');
189
- }
190
- }
191
-
192
- async function downloadBinary() {
193
- const arch = os.arch();
194
- const platform = os.platform();
195
- const outputPath = helper.getFallbackBinaryPath();
196
-
197
- if (process.env.SENTRYCLI_USE_LOCAL === '1') {
198
- try {
199
- const binPaths = which.sync('sentry-cli', { all: true });
200
- if (!binPaths.length) throw new Error('Binary not found');
201
- const binPath = binPaths[binPaths.length - 1];
202
- logger.log(`Using local binary: ${binPath}`);
203
- fs.copyFileSync(binPath, outputPath);
204
- return Promise.resolve();
205
- } catch (e) {
206
- throw new Error(
207
- 'Configured installation of local binary, but it was not found.' +
208
- 'Make sure that `sentry-cli` executable is available in your $PATH or disable SENTRYCLI_USE_LOCAL env variable.'
209
- );
210
- }
211
- }
212
-
213
- const downloadUrl = getDownloadUrl(platform, arch);
214
- if (!downloadUrl) {
215
- throw new Error(`Unsupported target ${platform}-${arch}`);
216
- }
217
-
218
- const cachedPath = getCachedPath(downloadUrl);
219
- if (fs.existsSync(cachedPath)) {
220
- logger.log(`Using cached binary: ${cachedPath}`);
221
- fs.copyFileSync(cachedPath, outputPath);
222
- return;
223
- }
224
-
225
- const proxyUrl = Proxy.getProxyForUrl(downloadUrl);
226
- const agent = proxyUrl ? new HttpsProxyAgent(proxyUrl) : null;
227
-
228
- logger.log(`Downloading from ${downloadUrl}`);
229
-
230
- if (proxyUrl) {
231
- logger.log(`Using proxy URL: ${proxyUrl}`);
232
- }
233
-
234
- let response;
235
- try {
236
- response = await fetch(downloadUrl, {
237
- agent,
238
- compress: false,
239
- headers: {
240
- 'accept-encoding': 'gzip, deflate, br',
241
- },
242
- redirect: 'follow',
243
- });
244
- } catch (error) {
245
- let errorMsg = `Unable to download sentry-cli binary from ${downloadUrl}.\nError message: ${error.message}`;
246
- if (error.code) {
247
- errorMsg += `\nError code: ${error.code}`;
248
- }
249
- throw new Error(errorMsg);
250
- }
251
-
252
- if (!response.ok) {
253
- let errorMsg = `Unable to download sentry-cli binary from ${downloadUrl}.\nServer returned: ${response.status}`;
254
- if (response.statusText) {
255
- errorMsg += ` - ${response.statusText}`;
256
- }
257
- throw new Error(errorMsg);
258
- }
259
-
260
- const contentEncoding = response.headers.get('content-encoding');
261
- let decompressor;
262
- if (/\bgzip\b/.test(contentEncoding)) {
263
- decompressor = zlib.createGunzip();
264
- } else if (/\bdeflate\b/.test(contentEncoding)) {
265
- decompressor = zlib.createInflate();
266
- } else if (/\bbr\b/.test(contentEncoding)) {
267
- decompressor = zlib.createBrotliDecompress();
268
- } else {
269
- decompressor = new stream.PassThrough();
270
- }
271
- const name = downloadUrl.match(/.*\/(.*?)$/)[1];
272
- let downloadedBytes = 0;
273
- const totalBytes = parseInt(response.headers.get('content-length'), 10);
274
- const progressBar = createProgressBar(name, totalBytes);
275
- const tempPath = getTempFile(cachedPath);
276
- fs.mkdirSync(path.dirname(tempPath), { recursive: true });
277
-
278
- await new Promise((resolve, reject) => {
279
- response.body
280
- .on('error', (e) => reject(e))
281
- .on('data', (chunk) => {
282
- downloadedBytes += chunk.length;
283
- progressBar.tick(chunk.length);
284
- })
285
- .pipe(decompressor)
286
- .pipe(fs.createWriteStream(tempPath, { mode: '0755' }))
287
- .on('error', (e) => reject(e))
288
- .on('close', () => {
289
- if (downloadedBytes >= totalBytes) {
290
- resolve();
291
- } else {
292
- reject(new Error('connection interrupted'));
293
- }
294
- });
18
+ response.end(contents);
295
19
  });
296
20
 
297
- if (process.env.SENTRYCLI_SKIP_CHECKSUM_VALIDATION !== '1') {
298
- validateChecksum(tempPath, name);
299
- }
300
-
301
- fs.copyFileSync(tempPath, cachedPath);
302
- fs.copyFileSync(tempPath, outputPath);
303
- fs.unlinkSync(tempPath);
304
- }
305
-
306
- async function checkVersion() {
307
- const output = await helper.execute(['--version']);
308
- const version = output.replace('sentry-cli ', '').trim();
309
- const expected = pkgInfo.version;
310
- if (version !== expected) {
311
- throw new Error(`Unexpected sentry-cli version "${version}", expected "${expected}"`);
312
- }
313
- }
314
-
315
- if (process.env.SENTRYCLI_SKIP_DOWNLOAD === '1') {
316
- logger.log(`Skipping download because SENTRYCLI_SKIP_DOWNLOAD=1 detected.`);
317
- process.exit(0);
318
- }
319
-
320
- const { packageName: distributionPackageName, subpath: distributionSubpath } =
321
- helper.getDistributionForThisPlatform();
322
-
323
- if (distributionPackageName === undefined) {
324
- helper.throwUnsupportedPlatformError();
21
+ server.listen(8999);
22
+ process.on('exit', () => server.close());
325
23
  }
326
24
 
327
- try {
328
- require.resolve(`${distributionPackageName}/${distributionSubpath}`);
329
- // If the `resolve` call succeeds it means a binary was installed successfully via optional dependencies so we can skip the manual postinstall download.
330
- process.exit(0);
331
- } catch (e) {
332
- // Optional dependencies likely didn't get installed - proceed with fallback downloading manually
333
- // Log message inspired by esbuild: https://github.com/evanw/esbuild/blob/914f6080c77cfe32a54888caa51ca6ea13873ce9/lib/npm/node-install.ts#L253
334
- logger.log(
335
- `Sentry CLI failed to locate the "${distributionPackageName}" package after installation!
336
-
337
- This can happen if you use an option to disable optional dependencies during installation, like "--no-optional", "--ignore-optional", or "--omit=optional". Sentry CLI uses the "optionalDependencies" package.json feature to install the correct binary for your platform and operating system. This post-install script will now try to work around this by manually downloading the Sentry CLI binary from the Sentry CDN. If this fails, you need to remove the "--no-optional", "--ignore-optional", and "--omit=optional" flags for Sentry CLI to work.`
338
- );
339
-
340
- downloadBinary()
341
- .then(() => checkVersion())
342
- .then(() => {
343
- process.exit(0);
344
- })
345
- .catch((e) => {
346
- console.error(e);
347
- process.exit(1);
348
- });
349
- }
25
+ downloadBinary()
26
+ .then(() => process.exit(0))
27
+ .catch(e => {
28
+ // eslint-disable-next-line no-console
29
+ console.error(e.toString());
30
+ process.exit(1);
31
+ });
@@ -1,16 +1,27 @@
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
1
9
  const { nodeFileTrace } = require('@vercel/nft');
2
10
 
3
11
  const entryPoint = require.resolve('..');
4
12
 
5
13
  // Trace the module entrypoint
6
- nodeFileTrace([entryPoint]).then((result) => {
14
+ nodeFileTrace([entryPoint]).then(result => {
15
+ // eslint-disable-next-line no-console
7
16
  console.log('@vercel/nft traced dependencies:', Array.from(result.fileList));
8
17
 
9
18
  // If either binary is picked up, fail the test
10
19
  if (result.fileList.has('sentry-cli') || result.fileList.has('sentry-cli.exe')) {
20
+ // eslint-disable-next-line no-console
11
21
  console.error('ERROR: The sentry-cli binary should not be found by @vercel/nft');
12
22
  process.exit(-1);
13
23
  } else {
24
+ // eslint-disable-next-line no-console
14
25
  console.log('The sentry-cli binary was not traced by @vercel/nft');
15
26
  }
16
27
  });
package/scripts/wheels DELETED
@@ -1,174 +0,0 @@
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())