@sentry/cli 1.77.0 → 1.77.1
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.
- package/CHANGELOG.md +1151 -0
- package/README.md +9 -60
- package/bin/sentry-cli +4 -4
- package/checksums.txt +9 -9
- package/js/helper.js +2 -12
- package/js/index.d.ts +36 -37
- package/js/index.js +10 -0
- package/js/install.js +305 -0
- package/js/releases/index.js +18 -15
- package/js/releases/options/uploadSourcemaps.js +0 -12
- package/package.json +37 -22
- package/scripts/build-in-docker.sh +2 -1
- package/scripts/install.js +6 -317
- package/scripts/test-vercel-nft.js +12 -1
- package/scripts/wheels +0 -174
package/README.md
CHANGED
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
|
|
3
|
-
<
|
|
4
|
-
<source srcset="https://sentry-brand.storage.googleapis.com/sentry-logo-white.png" media="(prefers-color-scheme: dark)" />
|
|
5
|
-
<source srcset="https://sentry-brand.storage.googleapis.com/sentry-logo-black.png" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" />
|
|
6
|
-
<img src="https://sentry-brand.storage.googleapis.com/sentry-logo-black.png" alt="Sentry" width="280">
|
|
7
|
-
</picture>
|
|
8
|
-
</a>
|
|
2
|
+
<img src="https://sentry-brand.storage.googleapis.com/sentry-logo-black.png" width="280">
|
|
3
|
+
<br />
|
|
9
4
|
</p>
|
|
10
5
|
|
|
11
6
|
# Official Sentry Command Line Interface
|
|
@@ -17,7 +12,7 @@
|
|
|
17
12
|
|
|
18
13
|
This is a Sentry command line client for some generic tasks. Right now this is
|
|
19
14
|
primarily used to upload debug symbols to Sentry if you are not using the
|
|
20
|
-
|
|
15
|
+
fastlane tools.
|
|
21
16
|
|
|
22
17
|
* Downloads can be found under
|
|
23
18
|
[Releases](https://github.com/getsentry/sentry-cli/releases/)
|
|
@@ -25,34 +20,13 @@ Fastlane tools.
|
|
|
25
20
|
|
|
26
21
|
## Installation
|
|
27
22
|
|
|
28
|
-
|
|
23
|
+
The recommended way to install is with everybody's favorite curl to bash:
|
|
29
24
|
|
|
30
25
|
curl -sL https://sentry.io/get-cli/ | bash
|
|
31
26
|
|
|
32
|
-
We do, however, encourage you to pin the specific version of the CLI, so your builds are always reproducible.
|
|
33
|
-
To do that, you can use the exact same method, with an additional version specifier:
|
|
34
|
-
|
|
35
|
-
curl -sL https://sentry.io/get-cli/ | SENTRY_CLI_VERSION=2.0.4 bash
|
|
36
|
-
|
|
37
|
-
This will automatically download the correct version of `sentry-cli` for your operating system and install it. If necessary, it will prompt for your admin password for `sudo`. For a different installation location or for systems without `sudo` (like Windows), you can `export INSTALL_DIR=/custom/installation/path` before running this command.
|
|
38
|
-
|
|
39
|
-
If you are using `sentry-cli` on Windows environments, [Microsoft Visual C++ Redistributable](https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist) is required.
|
|
40
|
-
|
|
41
|
-
To verify it’s installed correctly you can bring up the help:
|
|
42
|
-
|
|
43
|
-
sentry-cli --help
|
|
44
|
-
|
|
45
|
-
### pip
|
|
46
|
-
|
|
47
|
-
_New in 2.14.3_: `sentry-cli` can also be installed using `pip`:
|
|
48
|
-
|
|
49
|
-
```bash
|
|
50
|
-
pip install sentry-cli
|
|
51
|
-
```
|
|
52
|
-
|
|
53
27
|
### Node
|
|
54
28
|
|
|
55
|
-
Additionally
|
|
29
|
+
Additionally you can also install this binary via npm:
|
|
56
30
|
|
|
57
31
|
npm install @sentry/cli
|
|
58
32
|
|
|
@@ -77,22 +51,14 @@ Or add property into your `.npmrc` file (https://www.npmjs.org/doc/files/npmrc.h
|
|
|
77
51
|
sentrycli_cdnurl=https://mymirror.local/path
|
|
78
52
|
```
|
|
79
53
|
|
|
80
|
-
|
|
54
|
+
Another option is to use the environment variable `SENTRYCLI_CDNURL`.
|
|
81
55
|
|
|
82
|
-
```
|
|
83
|
-
SENTRYCLI_CDNURL
|
|
84
|
-
SENTRYCLI_USE_LOCAL=1 # Use local instance of sentry-cli binary (looked up via $PATH environment)
|
|
85
|
-
SENTRYCLI_SKIP_DOWNLOAD=1 # Skip downloading binary entirely
|
|
86
|
-
SENTRYCLI_NO_PROGRESS_BAR=1 # Do not print the progress bar when downloading binary (default for non-TTY environments like CI)
|
|
87
|
-
SENTRYCLI_LOG_STREAM=<stdout|stderr> # Changes where to redirect install script output
|
|
56
|
+
```sh
|
|
57
|
+
SENTRYCLI_CDNURL=https://mymirror.local/path npm install @sentry/cli
|
|
88
58
|
```
|
|
89
59
|
|
|
90
|
-
When using `sentry-cli` via JavaScript API or any 3rd party plugin that is consuming said API,
|
|
91
|
-
you can also use `SENTRY_BINARY_PATH=<path>` alongside `SENTRYCLI_SKIP_DOWNLOAD=1` to completely
|
|
92
|
-
control what binaries are downloaded and used throughout the whole process.
|
|
93
|
-
|
|
94
60
|
If you're installing the CLI with NPM from behind a proxy, the install script will
|
|
95
|
-
use either NPM's configured HTTPS proxy server or the value from your `HTTPS_PROXY`
|
|
61
|
+
use either NPM's configured HTTPS proxy server, or the value from your `HTTPS_PROXY`
|
|
96
62
|
environment variable.
|
|
97
63
|
|
|
98
64
|
### Homebrew
|
|
@@ -113,23 +79,6 @@ docker pull getsentry/sentry-cli
|
|
|
113
79
|
docker run --rm -v $(pwd):/work getsentry/sentry-cli --help
|
|
114
80
|
```
|
|
115
81
|
|
|
116
|
-
Starting version _`2.8.0`_, in case you see `"error: config value 'safe.directory' was not found;"` message,
|
|
117
|
-
you also need to correctly set UID and GID of mounted volumes like so:
|
|
118
|
-
|
|
119
|
-
```sh
|
|
120
|
-
docker run --rm -u "$(id -u):$(id -g)" -v $(pwd):/work getsentry/sentry-cli --help
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
This is required due to security issue in older `git` implementations. See [here](https://github.blog/2022-04-12-git-security-vulnerability-announced/) for more details.
|
|
124
|
-
|
|
125
|
-
## Update
|
|
126
|
-
|
|
127
|
-
To update sentry-cli to the latest version run:
|
|
128
|
-
|
|
129
|
-
```sh
|
|
130
|
-
sentry-cli update
|
|
131
|
-
```
|
|
132
|
-
|
|
133
82
|
## Compiling
|
|
134
83
|
|
|
135
84
|
In case you want to compile this yourself, you need to install at minimum the
|
package/bin/sentry-cli
CHANGED
|
@@ -3,17 +3,17 @@
|
|
|
3
3
|
'use strict';
|
|
4
4
|
|
|
5
5
|
const childProcess = require('child_process');
|
|
6
|
-
const
|
|
6
|
+
const cli = require('../js');
|
|
7
7
|
|
|
8
8
|
const child = childProcess
|
|
9
|
-
.spawn(
|
|
9
|
+
.spawn(cli.getPath(), process.argv.slice(2), {
|
|
10
10
|
stdio: 'inherit',
|
|
11
11
|
})
|
|
12
|
-
.on('error',
|
|
12
|
+
.on('error', err => {
|
|
13
13
|
console.error(err); // eslint-disable-line no-console
|
|
14
14
|
process.exit(1);
|
|
15
15
|
})
|
|
16
|
-
.on('exit',
|
|
16
|
+
.on('exit', code => process.exit(code));
|
|
17
17
|
|
|
18
18
|
process.on('SIGTERM', () => child.kill('SIGTERM'));
|
|
19
19
|
process.on('SIGINT', () => child.kill('SIGINT'));
|
package/checksums.txt
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
sentry-cli-Darwin-arm64=
|
|
2
|
-
sentry-cli-Darwin-universal=
|
|
3
|
-
sentry-cli-Darwin-x86_64=
|
|
4
|
-
sentry-cli-Linux-aarch64=
|
|
5
|
-
sentry-cli-Linux-armv7=
|
|
6
|
-
sentry-cli-Linux-i686=
|
|
7
|
-
sentry-cli-Linux-x86_64=
|
|
8
|
-
sentry-cli-Windows-i686.exe=
|
|
9
|
-
sentry-cli-Windows-x86_64.exe=
|
|
1
|
+
sentry-cli-Darwin-arm64=10951f615c9a23b1d598887a5114936c41ccdaec85d0ef749ce5ed1be4409203
|
|
2
|
+
sentry-cli-Darwin-universal=1f1abe664a476b4488e018f1db775cb663aed3beb26a7a1a074346759277a935
|
|
3
|
+
sentry-cli-Darwin-x86_64=1cf1d7595d851bed60639a7ad092b8451c0d13bdc1724767f38e2d78af988047
|
|
4
|
+
sentry-cli-Linux-aarch64=e60603e05bad2a3449e6a18f5bc7d38525762faf32c99ed95bd95d7c5e13225e
|
|
5
|
+
sentry-cli-Linux-armv7=5a30a376de811641edbad5bffe1227cd6186c43fedcc5c544269c4bc0f80acff
|
|
6
|
+
sentry-cli-Linux-i686=44b2ea8ceb3bda57b6925316fb738892e8d84578802dfee0d1c481e4f0730826
|
|
7
|
+
sentry-cli-Linux-x86_64=79ca22a21cb4932f12658f687f931899fa994248d3833957870a172350a4a4f0
|
|
8
|
+
sentry-cli-Windows-i686.exe=45f30e37d3ff3fa48ecb77f91517a73c24d9f3fda0f385b38f396fbc674918a5
|
|
9
|
+
sentry-cli-Windows-x86_64.exe=41c1abafc6aef4c02ec14540c60eb8139b9ba5ef22f9804b49b714c64c954b2f
|
package/js/helper.js
CHANGED
|
@@ -12,10 +12,6 @@ const childProcess = require('child_process');
|
|
|
12
12
|
* @returns {string} The path to the sentry-cli binary
|
|
13
13
|
*/
|
|
14
14
|
function getBinaryPath() {
|
|
15
|
-
if (process.env.SENTRY_BINARY_PATH) {
|
|
16
|
-
return process.env.SENTRY_BINARY_PATH;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
15
|
const parts = [];
|
|
20
16
|
parts.push(__dirname);
|
|
21
17
|
parts.push('..');
|
|
@@ -65,7 +61,7 @@ function mockBinaryPath(mockPath) {
|
|
|
65
61
|
function serializeOptions(schema, options) {
|
|
66
62
|
return Object.keys(schema).reduce((newOptions, option) => {
|
|
67
63
|
const paramValue = options[option];
|
|
68
|
-
if (paramValue === undefined
|
|
64
|
+
if (paramValue === undefined) {
|
|
69
65
|
return newOptions;
|
|
70
66
|
}
|
|
71
67
|
|
|
@@ -148,7 +144,7 @@ function getPath() {
|
|
|
148
144
|
* @param {Object} [config] More configuration to pass to the CLI
|
|
149
145
|
* @returns {Promise.<string>} A promise that resolves to the standard output.
|
|
150
146
|
*/
|
|
151
|
-
|
|
147
|
+
function execute(args, live, silent, configFile, config = {}) {
|
|
152
148
|
const env = { ...process.env };
|
|
153
149
|
if (configFile) {
|
|
154
150
|
env.SENTRY_PROPERTIES = configFile;
|
|
@@ -176,12 +172,6 @@ async function execute(args, live, silent, configFile, config = {}) {
|
|
|
176
172
|
}
|
|
177
173
|
if (config.customHeader) {
|
|
178
174
|
env.CUSTOM_HEADER = config.customHeader;
|
|
179
|
-
} else if (config.headers) {
|
|
180
|
-
const headers = Object.entries(config.headers).flatMap(([key, value]) => [
|
|
181
|
-
'--header',
|
|
182
|
-
`${key}:${value}`,
|
|
183
|
-
]);
|
|
184
|
-
args = [...headers, ...args];
|
|
185
175
|
}
|
|
186
176
|
return new Promise((resolve, reject) => {
|
|
187
177
|
if (live === true) {
|
package/js/index.d.ts
CHANGED
|
@@ -38,6 +38,11 @@ declare module '@sentry/cli' {
|
|
|
38
38
|
* This value will update `SENTRY_VCS_REMOTE` env variable.
|
|
39
39
|
*/
|
|
40
40
|
vcsRemote?: string;
|
|
41
|
+
/**
|
|
42
|
+
* Unique identifier for the distribution, used to further segment your release.
|
|
43
|
+
* Usually your build number.
|
|
44
|
+
*/
|
|
45
|
+
dist?: string;
|
|
41
46
|
/**
|
|
42
47
|
* If true, all logs are suppressed.
|
|
43
48
|
*/
|
|
@@ -47,11 +52,6 @@ declare module '@sentry/cli' {
|
|
|
47
52
|
* This value will update `CUSTOM_HEADER` env variable.
|
|
48
53
|
*/
|
|
49
54
|
customHeader?: string;
|
|
50
|
-
/**
|
|
51
|
-
* Headers added to every outgoing network request.
|
|
52
|
-
* This value does not set any env variable, and is overridden by `customHeader`.
|
|
53
|
-
*/
|
|
54
|
-
headers?: Record<string, string>;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
/**
|
|
@@ -59,9 +59,7 @@ declare module '@sentry/cli' {
|
|
|
59
59
|
* case `paths` takes the place of `include` in the options so as to make it
|
|
60
60
|
* clear that this is not recursive.
|
|
61
61
|
*/
|
|
62
|
-
export type SourceMapsPathDescriptor = Omit<SentryCliUploadSourceMapsOptions, 'include'> & {
|
|
63
|
-
paths: string[];
|
|
64
|
-
};
|
|
62
|
+
export type SourceMapsPathDescriptor = Omit<SentryCliUploadSourceMapsOptions, 'include'> & { paths: string[] }
|
|
65
63
|
|
|
66
64
|
export interface SentryCliUploadSourceMapsOptions {
|
|
67
65
|
/**
|
|
@@ -87,15 +85,6 @@ declare module '@sentry/cli' {
|
|
|
87
85
|
* This prevents the automatic detection of sourcemap references.
|
|
88
86
|
*/
|
|
89
87
|
sourceMapReference?: boolean;
|
|
90
|
-
/**
|
|
91
|
-
* Enables files gzip decompression prior to uploading. Defaults to `false`.
|
|
92
|
-
*/
|
|
93
|
-
decompress?: boolean;
|
|
94
|
-
/**
|
|
95
|
-
* Enable artifacts deduplication prior to uploading. This will skip uploading
|
|
96
|
-
* any artifacts that are already present on the server. Defaults to `true`.
|
|
97
|
-
*/
|
|
98
|
-
dedupe?: boolean;
|
|
99
88
|
/**
|
|
100
89
|
* When paired with the rewrite option this will remove a prefix from uploaded files.
|
|
101
90
|
* For instance you can use this to remove a path that is build machine specific.
|
|
@@ -127,15 +116,6 @@ declare module '@sentry/cli' {
|
|
|
127
116
|
* By default the following file extensions are processed: js, map, jsbundle and bundle.
|
|
128
117
|
*/
|
|
129
118
|
ext?: string[];
|
|
130
|
-
/**
|
|
131
|
-
* Unique identifier for the distribution, used to further segment your release.
|
|
132
|
-
* Usually your build number.
|
|
133
|
-
*/
|
|
134
|
-
dist?: string;
|
|
135
|
-
/**
|
|
136
|
-
* Use new Artifact Bundles upload, that enables use of Debug ID for Source Maps discovery.
|
|
137
|
-
*/
|
|
138
|
-
useArtifactBundle?: boolean;
|
|
139
119
|
}
|
|
140
120
|
|
|
141
121
|
export interface SentryCliNewDeployOptions {
|
|
@@ -196,19 +176,31 @@ declare module '@sentry/cli' {
|
|
|
196
176
|
}
|
|
197
177
|
|
|
198
178
|
export interface SentryCliReleases {
|
|
199
|
-
['new'](
|
|
179
|
+
['new'](
|
|
180
|
+
release: string,
|
|
181
|
+
options?: { projects: string[] } | string[]
|
|
182
|
+
): Promise<string>;
|
|
200
183
|
|
|
201
|
-
setCommits(
|
|
184
|
+
setCommits(
|
|
185
|
+
release: string,
|
|
186
|
+
options: SentryCliCommitsOptions
|
|
187
|
+
): Promise<string>;
|
|
202
188
|
|
|
203
|
-
finalize(release: string): Promise<string
|
|
189
|
+
finalize(release: string): Promise<string>
|
|
204
190
|
|
|
205
|
-
proposeVersion(): Promise<string
|
|
191
|
+
proposeVersion(): Promise<string>
|
|
206
192
|
|
|
207
|
-
uploadSourceMaps(
|
|
193
|
+
uploadSourceMaps(
|
|
194
|
+
release: string,
|
|
195
|
+
options: SentryCliUploadSourceMapsOptions
|
|
196
|
+
): Promise<string>
|
|
208
197
|
|
|
209
198
|
listDeploys(release: string): Promise<string>;
|
|
210
199
|
|
|
211
|
-
newDeploy(
|
|
200
|
+
newDeploy(
|
|
201
|
+
release: string,
|
|
202
|
+
options: SentryCliNewDeployOptions
|
|
203
|
+
): Promise<string>
|
|
212
204
|
|
|
213
205
|
execute(args: string[], live: boolean): Promise<string>;
|
|
214
206
|
}
|
|
@@ -222,14 +214,21 @@ declare module '@sentry/cli' {
|
|
|
222
214
|
* This value will update `SENTRY_PROPERTIES` env variable.
|
|
223
215
|
* @param options {@link SentryCliOptions}
|
|
224
216
|
*/
|
|
225
|
-
constructor(configFile?: string | null, options?: SentryCliOptions)
|
|
217
|
+
constructor(configFile?: string | null, options?: SentryCliOptions)
|
|
226
218
|
|
|
227
219
|
public configFile?: string;
|
|
228
220
|
public options?: SentryCliOptions;
|
|
229
|
-
public releases: SentryCliReleases
|
|
221
|
+
public releases: SentryCliReleases
|
|
222
|
+
|
|
223
|
+
public static getVersion(): string
|
|
224
|
+
public static getPath(): string
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Downloads the CLI binary.
|
|
228
|
+
* @returns {Promise<void>}
|
|
229
|
+
*/
|
|
230
|
+
static downloadBinary(logger: { log(...args: unknown[]): void }): Promise<void>;
|
|
230
231
|
|
|
231
|
-
public
|
|
232
|
-
public static getPath(): string;
|
|
233
|
-
public execute(args: string[], live: boolean): Promise<string>;
|
|
232
|
+
public execute(args: string[], live: boolean): Promise<string>
|
|
234
233
|
}
|
|
235
234
|
}
|
package/js/index.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const pkgInfo = require('../package.json');
|
|
4
4
|
const helper = require('./helper');
|
|
5
5
|
const Releases = require('./releases');
|
|
6
|
+
const install = require('./install');
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Interface to and wrapper around the `sentry-cli` executable.
|
|
@@ -54,6 +55,15 @@ class SentryCli {
|
|
|
54
55
|
return helper.getPath();
|
|
55
56
|
}
|
|
56
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Downloads the CLI binary.
|
|
60
|
+
* @param {any} [configFile] Optional logger to log installation information. Defaults to printing to the terminal.
|
|
61
|
+
* @returns {Promise<void>}
|
|
62
|
+
*/
|
|
63
|
+
static downloadBinary(logger) {
|
|
64
|
+
return install.downloadBinary(logger);
|
|
65
|
+
}
|
|
66
|
+
|
|
57
67
|
/**
|
|
58
68
|
* See {helper.execute} docs.
|
|
59
69
|
* @param {string[]} args Command line arguments passed to `sentry-cli`.
|
package/js/install.js
ADDED
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const http = require('http');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const crypto = require('crypto');
|
|
8
|
+
const zlib = require('zlib');
|
|
9
|
+
const stream = require('stream');
|
|
10
|
+
const process = require('process');
|
|
11
|
+
|
|
12
|
+
const HttpsProxyAgent = require('https-proxy-agent');
|
|
13
|
+
const fetch = require('node-fetch');
|
|
14
|
+
const ProgressBar = require('progress');
|
|
15
|
+
const Proxy = require('proxy-from-env');
|
|
16
|
+
// NOTE: Can be dropped in favor of `fs.mkdirSync(path, { recursive: true })` once we stop supporting Node 8.x
|
|
17
|
+
const mkdirp = require('mkdirp');
|
|
18
|
+
const which = require('which');
|
|
19
|
+
|
|
20
|
+
const helper = require('./helper');
|
|
21
|
+
const pkgInfo = require('../package.json');
|
|
22
|
+
const Logger = require('./logger');
|
|
23
|
+
|
|
24
|
+
function getLogStream(defaultStream) {
|
|
25
|
+
const logStream = process.env.SENTRYCLI_LOG_STREAM || defaultStream;
|
|
26
|
+
|
|
27
|
+
if (logStream === 'stdout') {
|
|
28
|
+
return process.stdout;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (logStream === 'stderr') {
|
|
32
|
+
return process.stderr;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
throw new Error(
|
|
36
|
+
`Incorrect SENTRYCLI_LOG_STREAM env variable. Possible values: 'stdout' | 'stderr'`
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const ttyLogger = new Logger(getLogStream('stderr'));
|
|
41
|
+
|
|
42
|
+
const CDN_URL =
|
|
43
|
+
process.env.SENTRYCLI_LOCAL_CDNURL ||
|
|
44
|
+
process.env.npm_config_sentrycli_cdnurl ||
|
|
45
|
+
process.env.SENTRYCLI_CDNURL ||
|
|
46
|
+
'https://downloads.sentry-cdn.com/sentry-cli';
|
|
47
|
+
|
|
48
|
+
function shouldRenderProgressBar() {
|
|
49
|
+
const silentFlag = process.argv.some((v) => v === '--silent');
|
|
50
|
+
const silentConfig = process.env.npm_config_loglevel === 'silent';
|
|
51
|
+
// Leave `SENTRY_NO_PROGRESS_BAR` for backwards compatibility
|
|
52
|
+
const silentEnv = process.env.SENTRYCLI_NO_PROGRESS_BAR || process.env.SENTRY_NO_PROGRESS_BAR;
|
|
53
|
+
const ciEnv = process.env.CI === 'true';
|
|
54
|
+
// If any of possible options is set, skip rendering of progress bar
|
|
55
|
+
return !(silentFlag || silentConfig || silentEnv || ciEnv);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function getDownloadUrl(platform, arch) {
|
|
59
|
+
const releasesUrl = `${CDN_URL}/${pkgInfo.version}/sentry-cli`;
|
|
60
|
+
let archString = '';
|
|
61
|
+
switch (arch) {
|
|
62
|
+
case 'x64':
|
|
63
|
+
archString = 'x86_64';
|
|
64
|
+
break;
|
|
65
|
+
case 'x86':
|
|
66
|
+
case 'ia32':
|
|
67
|
+
archString = 'i686';
|
|
68
|
+
break;
|
|
69
|
+
case 'arm64':
|
|
70
|
+
archString = 'aarch64';
|
|
71
|
+
break;
|
|
72
|
+
case 'arm':
|
|
73
|
+
archString = 'armv7';
|
|
74
|
+
break;
|
|
75
|
+
default:
|
|
76
|
+
archString = arch;
|
|
77
|
+
}
|
|
78
|
+
switch (platform) {
|
|
79
|
+
case 'darwin':
|
|
80
|
+
return `${releasesUrl}-Darwin-universal`;
|
|
81
|
+
case 'win32':
|
|
82
|
+
return `${releasesUrl}-Windows-${archString}.exe`;
|
|
83
|
+
case 'linux':
|
|
84
|
+
case 'freebsd':
|
|
85
|
+
return `${releasesUrl}-Linux-${archString}`;
|
|
86
|
+
default:
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function createProgressBar(name, total) {
|
|
92
|
+
const incorrectTotal = typeof total !== 'number' || Number.isNaN(total);
|
|
93
|
+
|
|
94
|
+
if (incorrectTotal || !shouldRenderProgressBar()) {
|
|
95
|
+
return {
|
|
96
|
+
tick: () => {},
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const logStream = getLogStream('stdout');
|
|
101
|
+
|
|
102
|
+
if (logStream.isTTY) {
|
|
103
|
+
return new ProgressBar(`fetching ${name} :bar :percent :etas`, {
|
|
104
|
+
complete: '█',
|
|
105
|
+
incomplete: '░',
|
|
106
|
+
width: 20,
|
|
107
|
+
total,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
let pct = null;
|
|
112
|
+
let current = 0;
|
|
113
|
+
return {
|
|
114
|
+
tick: (length) => {
|
|
115
|
+
current += length;
|
|
116
|
+
const next = Math.round((current / total) * 100);
|
|
117
|
+
if (next > pct) {
|
|
118
|
+
pct = next;
|
|
119
|
+
logStream.write(`fetching ${name} ${pct}%\n`);
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function npmCache() {
|
|
126
|
+
const env = process.env;
|
|
127
|
+
return (
|
|
128
|
+
env.npm_config_cache ||
|
|
129
|
+
env.npm_config_cache_folder ||
|
|
130
|
+
env.npm_config_yarn_offline_mirror ||
|
|
131
|
+
(env.APPDATA ? path.join(env.APPDATA, 'npm-cache') : path.join(os.homedir(), '.npm'))
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function getCachedPath(url) {
|
|
136
|
+
const digest = crypto.createHash('md5').update(url).digest('hex').slice(0, 6);
|
|
137
|
+
|
|
138
|
+
return path.join(
|
|
139
|
+
npmCache(),
|
|
140
|
+
'sentry-cli',
|
|
141
|
+
`${digest}-${path.basename(url).replace(/[^a-zA-Z0-9.]+/g, '-')}`
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function getTempFile(cached) {
|
|
146
|
+
return `${cached}.${process.pid}-${Math.random().toString(16).slice(2)}.tmp`;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function validateChecksum(tempPath, name, logger) {
|
|
150
|
+
let storedHash;
|
|
151
|
+
try {
|
|
152
|
+
const checksums = fs.readFileSync(path.join(__dirname, '../checksums.txt'), 'utf8');
|
|
153
|
+
const entries = checksums.split('\n');
|
|
154
|
+
for (let i = 0; i < entries.length; i++) {
|
|
155
|
+
const [key, value] = entries[i].split('=');
|
|
156
|
+
if (key === name) {
|
|
157
|
+
storedHash = value;
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
} catch (e) {
|
|
162
|
+
logger.log(
|
|
163
|
+
'Checksums are generated when the package is published to npm. They are not available directly in the source repository. Skipping validation.'
|
|
164
|
+
);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (!storedHash) {
|
|
169
|
+
logger.log(`Checksum for ${name} not found, skipping validation.`);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const currentHash = crypto.createHash('sha256').update(fs.readFileSync(tempPath)).digest('hex');
|
|
174
|
+
|
|
175
|
+
if (storedHash !== currentHash) {
|
|
176
|
+
fs.unlinkSync(tempPath);
|
|
177
|
+
throw new Error(
|
|
178
|
+
`Checksum validation for ${name} failed.\nExpected: ${storedHash}\nReceived: ${currentHash}`
|
|
179
|
+
);
|
|
180
|
+
} else {
|
|
181
|
+
logger.log('Checksum validation passed.');
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function checkVersion() {
|
|
186
|
+
return helper.execute(['--version']).then((output) => {
|
|
187
|
+
const version = output.replace('sentry-cli ', '').trim();
|
|
188
|
+
const expected = process.env.SENTRYCLI_LOCAL_CDNURL ? 'DEV' : pkgInfo.version;
|
|
189
|
+
if (version !== expected) {
|
|
190
|
+
throw new Error(`Unexpected sentry-cli version "${version}", expected "${expected}"`);
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function downloadBinary(logger = ttyLogger) {
|
|
196
|
+
if (process.env.SENTRYCLI_SKIP_DOWNLOAD === '1') {
|
|
197
|
+
logger.log(`Skipping download because SENTRYCLI_SKIP_DOWNLOAD=1 detected.`);
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const arch = os.arch();
|
|
202
|
+
const platform = os.platform();
|
|
203
|
+
const outputPath = helper.getPath();
|
|
204
|
+
|
|
205
|
+
if (process.env.SENTRYCLI_USE_LOCAL === '1') {
|
|
206
|
+
try {
|
|
207
|
+
const binPath = which.sync('sentry-cli');
|
|
208
|
+
logger.log(`Using local binary: ${binPath}`);
|
|
209
|
+
fs.copyFileSync(binPath, outputPath);
|
|
210
|
+
return Promise.resolve();
|
|
211
|
+
} catch (e) {
|
|
212
|
+
throw new Error(
|
|
213
|
+
'Configured installation of local binary, but it was not found.' +
|
|
214
|
+
'Make sure that `sentry-cli` executable is available in your $PATH or disable SENTRYCLI_USE_LOCAL env variable.'
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const downloadUrl = getDownloadUrl(platform, arch);
|
|
220
|
+
if (!downloadUrl) {
|
|
221
|
+
return Promise.reject(new Error(`Unsupported target ${platform}-${arch}`));
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const cachedPath = getCachedPath(downloadUrl);
|
|
225
|
+
if (fs.existsSync(cachedPath)) {
|
|
226
|
+
logger.log(`Using cached binary: ${cachedPath}`);
|
|
227
|
+
fs.copyFileSync(cachedPath, outputPath);
|
|
228
|
+
return Promise.resolve();
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const proxyUrl = Proxy.getProxyForUrl(downloadUrl);
|
|
232
|
+
const agent = proxyUrl ? new HttpsProxyAgent(proxyUrl) : null;
|
|
233
|
+
|
|
234
|
+
logger.log(`Downloading from ${downloadUrl}`);
|
|
235
|
+
|
|
236
|
+
if (proxyUrl) {
|
|
237
|
+
logger.log(`Using proxy URL: ${proxyUrl}`);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return fetch(downloadUrl, {
|
|
241
|
+
agent,
|
|
242
|
+
compress: false,
|
|
243
|
+
headers: {
|
|
244
|
+
'accept-encoding': 'gzip, deflate, br',
|
|
245
|
+
},
|
|
246
|
+
redirect: 'follow',
|
|
247
|
+
})
|
|
248
|
+
.then((response) => {
|
|
249
|
+
if (!response.ok) {
|
|
250
|
+
throw new Error(
|
|
251
|
+
`Unable to download sentry-cli binary from ${downloadUrl}.\nServer returned ${response.status}: ${response.statusText}.`
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const contentEncoding = response.headers.get('content-encoding');
|
|
256
|
+
let decompressor;
|
|
257
|
+
if (/\bgzip\b/.test(contentEncoding)) {
|
|
258
|
+
decompressor = zlib.createGunzip();
|
|
259
|
+
} else if (/\bdeflate\b/.test(contentEncoding)) {
|
|
260
|
+
decompressor = zlib.createInflate();
|
|
261
|
+
} else if (/\bbr\b/.test(contentEncoding)) {
|
|
262
|
+
decompressor = zlib.createBrotliDecompress();
|
|
263
|
+
} else {
|
|
264
|
+
decompressor = new stream.PassThrough();
|
|
265
|
+
}
|
|
266
|
+
const name = downloadUrl.match(/.*\/(.*?)$/)[1];
|
|
267
|
+
const total = parseInt(response.headers.get('content-length'), 10);
|
|
268
|
+
const progressBar = createProgressBar(name, total);
|
|
269
|
+
const tempPath = getTempFile(cachedPath);
|
|
270
|
+
mkdirp.sync(path.dirname(tempPath));
|
|
271
|
+
|
|
272
|
+
return new Promise((resolve, reject) => {
|
|
273
|
+
response.body
|
|
274
|
+
.on('error', (e) => reject(e))
|
|
275
|
+
.on('data', (chunk) => progressBar.tick(chunk.length))
|
|
276
|
+
.pipe(decompressor)
|
|
277
|
+
.pipe(fs.createWriteStream(tempPath, { mode: '0755' }))
|
|
278
|
+
.on('error', (e) => reject(e))
|
|
279
|
+
.on('close', () => resolve());
|
|
280
|
+
}).then(() => {
|
|
281
|
+
if (process.env.SENTRYCLI_SKIP_CHECKSUM_VALIDATION !== '1') {
|
|
282
|
+
validateChecksum(tempPath, name, logger);
|
|
283
|
+
}
|
|
284
|
+
fs.copyFileSync(tempPath, cachedPath);
|
|
285
|
+
fs.copyFileSync(tempPath, outputPath);
|
|
286
|
+
fs.unlinkSync(tempPath);
|
|
287
|
+
});
|
|
288
|
+
})
|
|
289
|
+
.then(() => {
|
|
290
|
+
return checkVersion();
|
|
291
|
+
})
|
|
292
|
+
.catch((error) => {
|
|
293
|
+
if (error instanceof fetch.FetchError) {
|
|
294
|
+
throw new Error(
|
|
295
|
+
`Unable to download sentry-cli binary from ${downloadUrl}.\nError code: ${error.code}`
|
|
296
|
+
);
|
|
297
|
+
} else {
|
|
298
|
+
throw error;
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
module.exports = {
|
|
304
|
+
downloadBinary,
|
|
305
|
+
};
|