@sentry/cli 2.1.0 → 2.1.4
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/README.md +19 -0
- package/checksums.txt +9 -9
- package/js/helper.js +24 -10
- package/js/index.d.ts +33 -29
- package/js/logger.js +14 -0
- package/js/releases/index.js +3 -1
- package/js/releases/options/uploadSourcemaps.js +12 -0
- package/package.json +11 -6
- package/scripts/install.js +39 -24
- package/scripts/test-vercel-nft.js +16 -0
- package/scripts/wheels +174 -0
package/README.md
CHANGED
|
@@ -36,10 +36,20 @@ To do that, you can use the exact same method, with an additional version specif
|
|
|
36
36
|
|
|
37
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
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
|
+
|
|
39
41
|
To verify it’s installed correctly you can bring up the help:
|
|
40
42
|
|
|
41
43
|
sentry-cli --help
|
|
42
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
|
+
|
|
43
53
|
### Node
|
|
44
54
|
|
|
45
55
|
Additionally you can also install this binary via npm:
|
|
@@ -95,6 +105,15 @@ docker pull getsentry/sentry-cli
|
|
|
95
105
|
docker run --rm -v $(pwd):/work getsentry/sentry-cli --help
|
|
96
106
|
```
|
|
97
107
|
|
|
108
|
+
Starting version _`2.8.0`_, in case you see `"error: config value 'safe.directory' was not found;"` message,
|
|
109
|
+
you also need to correctly set UID and GID of mounted volumes like so:
|
|
110
|
+
|
|
111
|
+
```sh
|
|
112
|
+
docker run --rm -u "$(id -u):$(id -g)" -v $(pwd):/work getsentry/sentry-cli --help
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
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.
|
|
116
|
+
|
|
98
117
|
## Compiling
|
|
99
118
|
|
|
100
119
|
In case you want to compile this yourself, you need to install at minimum the
|
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=9bfaca2efe775f0ae4f3683d325fa98ce19a46acbeafd137674f3b21822564ab
|
|
2
|
+
sentry-cli-Darwin-universal=94e1249b692301e7287ce059bbff83a68d9568e134bcb196bee79a20813b0c69
|
|
3
|
+
sentry-cli-Darwin-x86_64=c0356f589eb28c9dcd530b6cdbadefac9e4ee7ddafb74ceae0754582fbead60e
|
|
4
|
+
sentry-cli-Linux-aarch64=0e04e810af16520b39a4edb3dd163edd5991de4ea479e55834cf4ac7e36bfa90
|
|
5
|
+
sentry-cli-Linux-armv7=1b97cce44b32ef10f992dbf627e75632ad72cdc00d9b06e9d75b7676f632ae86
|
|
6
|
+
sentry-cli-Linux-i686=2003f137a2038c431723508680c01d058e35eea2db789f0ffc22d667c2c3f9be
|
|
7
|
+
sentry-cli-Linux-x86_64=272fefd13e7076cec6aa1896820f4edf7d1e4478781a2c3538cfb7a62dee1214
|
|
8
|
+
sentry-cli-Windows-i686.exe=379c2cf9434a670529aa51d1c9ff381a30106f29639852484252cdd017da0d06
|
|
9
|
+
sentry-cli-Windows-x86_64.exe=a33e783bfdf70276aaa85cad6db38b994406f58119269c3f30976ba498ed86a6
|
package/js/helper.js
CHANGED
|
@@ -1,21 +1,29 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const path = require('path');
|
|
3
4
|
const childProcess = require('child_process');
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
|
-
*
|
|
7
|
-
* @
|
|
7
|
+
* This convoluted function resolves the path to the `sentry-cli` binary in a
|
|
8
|
+
* way that can't be analysed by @vercel/nft.
|
|
9
|
+
*
|
|
10
|
+
* Without this, the binary can be detected as an asset and included by bundlers
|
|
11
|
+
* that use @vercel/nft.
|
|
12
|
+
* @returns {string} The path to the sentry-cli binary
|
|
8
13
|
*/
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
);
|
|
14
|
+
function getBinaryPath() {
|
|
15
|
+
const parts = [];
|
|
16
|
+
parts.push(__dirname);
|
|
17
|
+
parts.push('..');
|
|
18
|
+
parts.push(`sentry-cli${process.platform === 'win32' ? '.exe' : ''}`);
|
|
19
|
+
return path.resolve(...parts);
|
|
20
|
+
}
|
|
12
21
|
|
|
13
22
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* ref: https://github.com/getsentry/sentry-javascript/issues/3865
|
|
17
|
-
* ref: https://github.com/vercel/nft/issues/203
|
|
23
|
+
* Absolute path to the sentry-cli binary (platform dependent).
|
|
24
|
+
* @type {string}
|
|
18
25
|
*/
|
|
26
|
+
let binaryPath = getBinaryPath();
|
|
19
27
|
|
|
20
28
|
/**
|
|
21
29
|
* Overrides the default binary path with a mock value, useful for testing.
|
|
@@ -53,7 +61,7 @@ function mockBinaryPath(mockPath) {
|
|
|
53
61
|
function serializeOptions(schema, options) {
|
|
54
62
|
return Object.keys(schema).reduce((newOptions, option) => {
|
|
55
63
|
const paramValue = options[option];
|
|
56
|
-
if (paramValue === undefined) {
|
|
64
|
+
if (paramValue === undefined || paramValue === null) {
|
|
57
65
|
return newOptions;
|
|
58
66
|
}
|
|
59
67
|
|
|
@@ -164,6 +172,12 @@ async function execute(args, live, silent, configFile, config = {}) {
|
|
|
164
172
|
}
|
|
165
173
|
if (config.customHeader) {
|
|
166
174
|
env.CUSTOM_HEADER = config.customHeader;
|
|
175
|
+
} else if (config.headers) {
|
|
176
|
+
const headers = Object.entries(config.headers).flatMap(([key, value]) => [
|
|
177
|
+
'--header',
|
|
178
|
+
`${key}:${value}`,
|
|
179
|
+
]);
|
|
180
|
+
args = [...headers, ...args];
|
|
167
181
|
}
|
|
168
182
|
return new Promise((resolve, reject) => {
|
|
169
183
|
if (live === true) {
|
package/js/index.d.ts
CHANGED
|
@@ -38,11 +38,6 @@ 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;
|
|
46
41
|
/**
|
|
47
42
|
* If true, all logs are suppressed.
|
|
48
43
|
*/
|
|
@@ -52,6 +47,11 @@ declare module '@sentry/cli' {
|
|
|
52
47
|
* This value will update `CUSTOM_HEADER` env variable.
|
|
53
48
|
*/
|
|
54
49
|
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,7 +59,9 @@ 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'> & {
|
|
62
|
+
export type SourceMapsPathDescriptor = Omit<SentryCliUploadSourceMapsOptions, 'include'> & {
|
|
63
|
+
paths: string[];
|
|
64
|
+
};
|
|
63
65
|
|
|
64
66
|
export interface SentryCliUploadSourceMapsOptions {
|
|
65
67
|
/**
|
|
@@ -85,6 +87,11 @@ declare module '@sentry/cli' {
|
|
|
85
87
|
* This prevents the automatic detection of sourcemap references.
|
|
86
88
|
*/
|
|
87
89
|
sourceMapReference?: boolean;
|
|
90
|
+
/**
|
|
91
|
+
* Enable artifacts deduplication prior to uploading. This will skip uploading
|
|
92
|
+
* any artifacts that are already present on the server. Defaults to `true`.
|
|
93
|
+
*/
|
|
94
|
+
dedupe?: boolean;
|
|
88
95
|
/**
|
|
89
96
|
* When paired with the rewrite option this will remove a prefix from uploaded files.
|
|
90
97
|
* For instance you can use this to remove a path that is build machine specific.
|
|
@@ -116,6 +123,15 @@ declare module '@sentry/cli' {
|
|
|
116
123
|
* By default the following file extensions are processed: js, map, jsbundle and bundle.
|
|
117
124
|
*/
|
|
118
125
|
ext?: string[];
|
|
126
|
+
/**
|
|
127
|
+
* Unique identifier for the distribution, used to further segment your release.
|
|
128
|
+
* Usually your build number.
|
|
129
|
+
*/
|
|
130
|
+
dist?: string;
|
|
131
|
+
/**
|
|
132
|
+
* Use new Artifact Bundles upload, that enables use of Debug ID for Source Maps discovery.
|
|
133
|
+
*/
|
|
134
|
+
useArtifactBundle?: boolean;
|
|
119
135
|
}
|
|
120
136
|
|
|
121
137
|
export interface SentryCliNewDeployOptions {
|
|
@@ -176,31 +192,19 @@ declare module '@sentry/cli' {
|
|
|
176
192
|
}
|
|
177
193
|
|
|
178
194
|
export interface SentryCliReleases {
|
|
179
|
-
['new'](
|
|
180
|
-
release: string,
|
|
181
|
-
options?: { projects: string[] } | string[]
|
|
182
|
-
): Promise<string>;
|
|
195
|
+
['new'](release: string, options?: { projects: string[] } | string[]): Promise<string>;
|
|
183
196
|
|
|
184
|
-
setCommits(
|
|
185
|
-
release: string,
|
|
186
|
-
options: SentryCliCommitsOptions
|
|
187
|
-
): Promise<string>;
|
|
197
|
+
setCommits(release: string, options: SentryCliCommitsOptions): Promise<string>;
|
|
188
198
|
|
|
189
|
-
finalize(release: string): Promise<string
|
|
199
|
+
finalize(release: string): Promise<string>;
|
|
190
200
|
|
|
191
|
-
proposeVersion(): Promise<string
|
|
201
|
+
proposeVersion(): Promise<string>;
|
|
192
202
|
|
|
193
|
-
uploadSourceMaps(
|
|
194
|
-
release: string,
|
|
195
|
-
options: SentryCliUploadSourceMapsOptions
|
|
196
|
-
): Promise<string>
|
|
203
|
+
uploadSourceMaps(release: string, options: SentryCliUploadSourceMapsOptions): Promise<string>;
|
|
197
204
|
|
|
198
205
|
listDeploys(release: string): Promise<string>;
|
|
199
206
|
|
|
200
|
-
newDeploy(
|
|
201
|
-
release: string,
|
|
202
|
-
options: SentryCliNewDeployOptions
|
|
203
|
-
): Promise<string>
|
|
207
|
+
newDeploy(release: string, options: SentryCliNewDeployOptions): Promise<string>;
|
|
204
208
|
|
|
205
209
|
execute(args: string[], live: boolean): Promise<string>;
|
|
206
210
|
}
|
|
@@ -214,14 +218,14 @@ declare module '@sentry/cli' {
|
|
|
214
218
|
* This value will update `SENTRY_PROPERTIES` env variable.
|
|
215
219
|
* @param options {@link SentryCliOptions}
|
|
216
220
|
*/
|
|
217
|
-
constructor(configFile?: string | null, options?: SentryCliOptions)
|
|
221
|
+
constructor(configFile?: string | null, options?: SentryCliOptions);
|
|
218
222
|
|
|
219
223
|
public configFile?: string;
|
|
220
224
|
public options?: SentryCliOptions;
|
|
221
|
-
public releases: SentryCliReleases
|
|
225
|
+
public releases: SentryCliReleases;
|
|
222
226
|
|
|
223
|
-
public static getVersion(): string
|
|
224
|
-
public static getPath(): string
|
|
225
|
-
public execute(args: string[], live: boolean): Promise<string
|
|
227
|
+
public static getVersion(): string;
|
|
228
|
+
public static getPath(): string;
|
|
229
|
+
public execute(args: string[], live: boolean): Promise<string>;
|
|
226
230
|
}
|
|
227
231
|
}
|
package/js/logger.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const format = require('util').format;
|
|
4
|
+
|
|
5
|
+
module.exports = class Logger {
|
|
6
|
+
constructor(stream) {
|
|
7
|
+
this.stream = stream;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
log() {
|
|
11
|
+
const message = format(...arguments);
|
|
12
|
+
this.stream.write(`[sentry-cli] ${message}\n`);
|
|
13
|
+
}
|
|
14
|
+
};
|
package/js/releases/index.js
CHANGED
|
@@ -140,13 +140,15 @@ class Releases {
|
|
|
140
140
|
* ignoreFile: null, // path to a file with ignore rules
|
|
141
141
|
* rewrite: false, // preprocess sourcemaps before uploading
|
|
142
142
|
* sourceMapReference: true, // add a source map reference to source files
|
|
143
|
+
* dedupe: true, // deduplicate already uploaded files
|
|
143
144
|
* stripPrefix: [], // remove certain prefices from filenames
|
|
144
145
|
* stripCommonPrefix: false, // guess common prefices to remove from filenames
|
|
145
146
|
* validate: false, // validate source maps and cancel the upload on error
|
|
146
147
|
* urlPrefix: '', // add a prefix source map urls after stripping them
|
|
147
148
|
* urlSuffix: '', // add a suffix source map urls after stripping them
|
|
148
149
|
* ext: ['js', 'map', 'jsbundle', 'bundle'], // override file extensions to scan for
|
|
149
|
-
* projects: ['node'] // provide a list of projects
|
|
150
|
+
* projects: ['node'], // provide a list of projects
|
|
151
|
+
* decompress: false // decompress gzip files before uploading
|
|
150
152
|
* });
|
|
151
153
|
*
|
|
152
154
|
* @param {string} release Unique name of the release.
|
|
@@ -11,6 +11,10 @@ module.exports = {
|
|
|
11
11
|
param: '--dist',
|
|
12
12
|
type: 'string',
|
|
13
13
|
},
|
|
14
|
+
decompress: {
|
|
15
|
+
param: '--decompress',
|
|
16
|
+
type: 'boolean',
|
|
17
|
+
},
|
|
14
18
|
rewrite: {
|
|
15
19
|
param: '--rewrite',
|
|
16
20
|
invertedParam: '--no-rewrite',
|
|
@@ -20,6 +24,10 @@ module.exports = {
|
|
|
20
24
|
invertedParam: '--no-sourcemap-reference',
|
|
21
25
|
type: 'boolean',
|
|
22
26
|
},
|
|
27
|
+
dedupe: {
|
|
28
|
+
invertedParam: '--no-dedupe',
|
|
29
|
+
type: 'boolean',
|
|
30
|
+
},
|
|
23
31
|
stripPrefix: {
|
|
24
32
|
param: '--strip-prefix',
|
|
25
33
|
type: 'array',
|
|
@@ -44,4 +52,8 @@ module.exports = {
|
|
|
44
52
|
param: '--ext',
|
|
45
53
|
type: 'array',
|
|
46
54
|
},
|
|
55
|
+
useArtifactBundle: {
|
|
56
|
+
param: '--use-artifact-bundle',
|
|
57
|
+
type: 'boolean',
|
|
58
|
+
},
|
|
47
59
|
};
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sentry/cli",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.4",
|
|
4
4
|
"description": "A command line utility to work with Sentry. https://docs.sentry.io/hosted/learn/cli/",
|
|
5
5
|
"repository": "git://github.com/getsentry/sentry-cli.git",
|
|
6
6
|
"homepage": "https://docs.sentry.io/hosted/learn/cli/",
|
|
7
7
|
"author": "Sentry",
|
|
8
8
|
"license": "BSD-3-Clause",
|
|
9
9
|
"engines": {
|
|
10
|
-
"node": ">=
|
|
10
|
+
"node": ">= 10"
|
|
11
11
|
},
|
|
12
12
|
"main": "js/index.js",
|
|
13
13
|
"types": "js/index.d.ts",
|
|
@@ -17,13 +17,13 @@
|
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"https-proxy-agent": "^5.0.0",
|
|
19
19
|
"node-fetch": "^2.6.7",
|
|
20
|
-
"npmlog": "^6.0.1",
|
|
21
20
|
"progress": "^2.0.3",
|
|
22
21
|
"proxy-from-env": "^1.1.0",
|
|
23
22
|
"which": "^2.0.2"
|
|
24
23
|
},
|
|
25
24
|
"devDependencies": {
|
|
26
|
-
"
|
|
25
|
+
"@vercel/nft": "^0.22.1",
|
|
26
|
+
"eslint": "^7.32.0",
|
|
27
27
|
"eslint-config-prettier": "^8.5.0",
|
|
28
28
|
"jest": "^27.5.1",
|
|
29
29
|
"npm-run-all": "^4.1.5",
|
|
@@ -34,11 +34,12 @@
|
|
|
34
34
|
"fix": "npm-run-all fix:eslint fix:prettier",
|
|
35
35
|
"fix:eslint": "eslint --fix bin/* scripts/**/*.js js/**/*.js",
|
|
36
36
|
"fix:prettier": "prettier --write bin/* scripts/**/*.js js/**/*.js",
|
|
37
|
-
"test": "npm-run-all test:jest test:eslint test:prettier",
|
|
37
|
+
"test": "npm-run-all test:jest test:eslint test:prettier test:vercel-nft",
|
|
38
38
|
"test:jest": "jest",
|
|
39
39
|
"test:watch": "jest --watch --notify",
|
|
40
40
|
"test:eslint": "eslint bin/* scripts/**/*.js js/**/*.js",
|
|
41
|
-
"test:prettier": "prettier --check bin/* scripts/**/*.js js/**/*.js"
|
|
41
|
+
"test:prettier": "prettier --check bin/* scripts/**/*.js js/**/*.js",
|
|
42
|
+
"test:vercel-nft": "node scripts/test-vercel-nft.js"
|
|
42
43
|
},
|
|
43
44
|
"jest": {
|
|
44
45
|
"collectCoverage": true,
|
|
@@ -46,5 +47,9 @@
|
|
|
46
47
|
"testPathIgnorePatterns": [
|
|
47
48
|
"<rootDir>/src"
|
|
48
49
|
]
|
|
50
|
+
},
|
|
51
|
+
"volta": {
|
|
52
|
+
"node": "10.24.1",
|
|
53
|
+
"yarn": "1.22.19"
|
|
49
54
|
}
|
|
50
55
|
}
|
package/scripts/install.js
CHANGED
|
@@ -15,11 +15,13 @@ const fetch = require('node-fetch');
|
|
|
15
15
|
const HttpsProxyAgent = require('https-proxy-agent');
|
|
16
16
|
const ProgressBar = require('progress');
|
|
17
17
|
const Proxy = require('proxy-from-env');
|
|
18
|
-
const npmLog = require('npmlog');
|
|
19
18
|
const which = require('which');
|
|
20
19
|
|
|
21
20
|
const helper = require('../js/helper');
|
|
22
21
|
const pkgInfo = require('../package.json');
|
|
22
|
+
const Logger = require('../js/logger');
|
|
23
|
+
|
|
24
|
+
const logger = new Logger(getLogStream('stderr'));
|
|
23
25
|
|
|
24
26
|
const CDN_URL =
|
|
25
27
|
process.env.SENTRYCLI_LOCAL_CDNURL ||
|
|
@@ -47,9 +49,10 @@ function shouldRenderProgressBar() {
|
|
|
47
49
|
const silentFlag = process.argv.some((v) => v === '--silent');
|
|
48
50
|
const silentConfig = process.env.npm_config_loglevel === 'silent';
|
|
49
51
|
const silentEnv = process.env.SENTRYCLI_NO_PROGRESS_BAR;
|
|
50
|
-
const ciEnv = process.env.CI === 'true';
|
|
52
|
+
const ciEnv = process.env.CI === 'true' || process.env.CI === '1';
|
|
53
|
+
const notTTY = !process.stdout.isTTY;
|
|
51
54
|
// If any of possible options is set, skip rendering of progress bar
|
|
52
|
-
return !(silentFlag || silentConfig || silentEnv || ciEnv);
|
|
55
|
+
return !(silentFlag || silentConfig || silentEnv || ciEnv || notTTY);
|
|
53
56
|
}
|
|
54
57
|
|
|
55
58
|
function getDownloadUrl(platform, arch) {
|
|
@@ -120,13 +123,17 @@ function createProgressBar(name, total) {
|
|
|
120
123
|
}
|
|
121
124
|
|
|
122
125
|
function npmCache() {
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
env.
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
)
|
|
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');
|
|
130
137
|
}
|
|
131
138
|
|
|
132
139
|
function getCachedPath(url) {
|
|
@@ -156,14 +163,14 @@ function validateChecksum(tempPath, name) {
|
|
|
156
163
|
}
|
|
157
164
|
}
|
|
158
165
|
} catch (e) {
|
|
159
|
-
|
|
166
|
+
logger.log(
|
|
160
167
|
'Checksums are generated when the package is published to npm. They are not available directly in the source repository. Skipping validation.'
|
|
161
168
|
);
|
|
162
169
|
return;
|
|
163
170
|
}
|
|
164
171
|
|
|
165
172
|
if (!storedHash) {
|
|
166
|
-
|
|
173
|
+
logger.log(`Checksum for ${name} not found, skipping validation.`);
|
|
167
174
|
return;
|
|
168
175
|
}
|
|
169
176
|
|
|
@@ -175,7 +182,7 @@ function validateChecksum(tempPath, name) {
|
|
|
175
182
|
`Checksum validation for ${name} failed.\nExpected: ${storedHash}\nReceived: ${currentHash}`
|
|
176
183
|
);
|
|
177
184
|
} else {
|
|
178
|
-
|
|
185
|
+
logger.log('Checksum validation passed.');
|
|
179
186
|
}
|
|
180
187
|
}
|
|
181
188
|
|
|
@@ -187,7 +194,7 @@ async function downloadBinary() {
|
|
|
187
194
|
if (process.env.SENTRYCLI_USE_LOCAL === '1') {
|
|
188
195
|
try {
|
|
189
196
|
const binPath = which.sync('sentry-cli');
|
|
190
|
-
|
|
197
|
+
logger.log(`Using local binary: ${binPath}`);
|
|
191
198
|
fs.copyFileSync(binPath, outputPath);
|
|
192
199
|
return Promise.resolve();
|
|
193
200
|
} catch (e) {
|
|
@@ -205,7 +212,7 @@ async function downloadBinary() {
|
|
|
205
212
|
|
|
206
213
|
const cachedPath = getCachedPath(downloadUrl);
|
|
207
214
|
if (fs.existsSync(cachedPath)) {
|
|
208
|
-
|
|
215
|
+
logger.log(`Using cached binary: ${cachedPath}`);
|
|
209
216
|
fs.copyFileSync(cachedPath, outputPath);
|
|
210
217
|
return;
|
|
211
218
|
}
|
|
@@ -213,10 +220,10 @@ async function downloadBinary() {
|
|
|
213
220
|
const proxyUrl = Proxy.getProxyForUrl(downloadUrl);
|
|
214
221
|
const agent = proxyUrl ? new HttpsProxyAgent(proxyUrl) : null;
|
|
215
222
|
|
|
216
|
-
|
|
223
|
+
logger.log(`Downloading from ${downloadUrl}`);
|
|
217
224
|
|
|
218
225
|
if (proxyUrl) {
|
|
219
|
-
|
|
226
|
+
logger.log(`Using proxy URL: ${proxyUrl}`);
|
|
220
227
|
}
|
|
221
228
|
|
|
222
229
|
let response;
|
|
@@ -257,19 +264,29 @@ async function downloadBinary() {
|
|
|
257
264
|
decompressor = new stream.PassThrough();
|
|
258
265
|
}
|
|
259
266
|
const name = downloadUrl.match(/.*\/(.*?)$/)[1];
|
|
260
|
-
|
|
261
|
-
const
|
|
267
|
+
let downloadedBytes = 0;
|
|
268
|
+
const totalBytes = parseInt(response.headers.get('content-length'), 10);
|
|
269
|
+
const progressBar = createProgressBar(name, totalBytes);
|
|
262
270
|
const tempPath = getTempFile(cachedPath);
|
|
263
271
|
fs.mkdirSync(path.dirname(tempPath), { recursive: true });
|
|
264
272
|
|
|
265
273
|
await new Promise((resolve, reject) => {
|
|
266
274
|
response.body
|
|
267
275
|
.on('error', (e) => reject(e))
|
|
268
|
-
.on('data', (chunk) =>
|
|
276
|
+
.on('data', (chunk) => {
|
|
277
|
+
downloadedBytes += chunk.length;
|
|
278
|
+
progressBar.tick(chunk.length);
|
|
279
|
+
})
|
|
269
280
|
.pipe(decompressor)
|
|
270
281
|
.pipe(fs.createWriteStream(tempPath, { mode: '0755' }))
|
|
271
282
|
.on('error', (e) => reject(e))
|
|
272
|
-
.on('close', () =>
|
|
283
|
+
.on('close', () => {
|
|
284
|
+
if (downloadedBytes >= totalBytes) {
|
|
285
|
+
resolve();
|
|
286
|
+
} else {
|
|
287
|
+
reject(new Error('connection interrupted'));
|
|
288
|
+
}
|
|
289
|
+
});
|
|
273
290
|
});
|
|
274
291
|
|
|
275
292
|
if (process.env.SENTRYCLI_SKIP_CHECKSUM_VALIDATION !== '1') {
|
|
@@ -305,10 +322,8 @@ if (process.env.SENTRYCLI_LOCAL_CDNURL) {
|
|
|
305
322
|
process.on('exit', () => server.close());
|
|
306
323
|
}
|
|
307
324
|
|
|
308
|
-
npmLog.stream = getLogStream('stderr');
|
|
309
|
-
|
|
310
325
|
if (process.env.SENTRYCLI_SKIP_DOWNLOAD === '1') {
|
|
311
|
-
|
|
326
|
+
logger.log(`Skipping download because SENTRYCLI_SKIP_DOWNLOAD=1 detected.`);
|
|
312
327
|
process.exit(0);
|
|
313
328
|
}
|
|
314
329
|
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const { nodeFileTrace } = require('@vercel/nft');
|
|
2
|
+
|
|
3
|
+
const entryPoint = require.resolve('..');
|
|
4
|
+
|
|
5
|
+
// Trace the module entrypoint
|
|
6
|
+
nodeFileTrace([entryPoint]).then((result) => {
|
|
7
|
+
console.log('@vercel/nft traced dependencies:', Array.from(result.fileList));
|
|
8
|
+
|
|
9
|
+
// If either binary is picked up, fail the test
|
|
10
|
+
if (result.fileList.has('sentry-cli') || result.fileList.has('sentry-cli.exe')) {
|
|
11
|
+
console.error('ERROR: The sentry-cli binary should not be found by @vercel/nft');
|
|
12
|
+
process.exit(-1);
|
|
13
|
+
} else {
|
|
14
|
+
console.log('The sentry-cli binary was not traced by @vercel/nft');
|
|
15
|
+
}
|
|
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())
|